├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── LICENSE.md ├── README.md ├── changelog.md ├── git-open ├── git-open.1.md ├── git-open.plugin.sh ├── git-open.plugin.zsh ├── issue_template.md ├── markdownlint.json ├── package-lock.json ├── package.json └── test └── git-open.bats /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: # run on all PRs, not just PRs to a particular branch 7 | 8 | jobs: 9 | basics: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: git clone 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 100 17 | 18 | - uses: actions/setup-node@v3 19 | with: 20 | node-version: latest 21 | 22 | - run: npm ci 23 | 24 | - name: Setup BATS 25 | uses: mig4/setup-bats@v1 26 | with: 27 | bats-version: 0.4.0 # This is the version i have locally. looks like an upgrade is in order, though… 28 | - run: git submodule update --init 29 | name: pull in bats assertion libs 30 | 31 | - run: npm run lint 32 | - run: npm run shellcheck 33 | - run: npm run unit 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | sandboxrepo 3 | git-open.1 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/test_helper/bats-assert"] 2 | path = test/test_helper/bats-assert 3 | url = https://github.com/ztombol/bats-assert 4 | [submodule "test/test_helper/bats-support"] 5 | path = test/test_helper/bats-support 6 | url = https://github.com/ztombol/bats-support 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | sandboxrepo 3 | git-open.1 4 | bats-assert 5 | bats-support -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | Copyright (c) 2013-2017 Jason McCreary, Paul Irish and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-open [![Build Status](https://img.shields.io/travis/paulirish/git-open/master.svg)](https://travis-ci.org/paulirish/git-open) 2 | 3 | Type `git open` to open the repo website (GitHub, GitLab, Bitbucket) in your browser. 4 | 5 | ![Demo of git open in action](https://user-images.githubusercontent.com/39191/33507513-f60041ae-d6a9-11e7-985c-ab296d6a5b0f.gif) 6 | 7 | ## Usage 8 | 9 | ```sh 10 | git open [remote-name] [branch-name] 11 | # Open the page for this branch on the repo website 12 | 13 | git open --commit 14 | git open -c 15 | # Open the current commit in the repo website 16 | 17 | git open --issue 18 | git open -i 19 | # If this branch is named like issue/#123, this will open the corresponding 20 | # issue in the repo website 21 | 22 | git open --print 23 | git open -p 24 | # Only print the url at the terminal, but don't open it 25 | ``` 26 | 27 | (`git open` works with these [hosted repo providers](#supported-remote-repositories), `git open --issue` currently only works with GitHub, Visual Studio Team Services and Team Foundation Server) 28 | 29 | ### Examples 30 | 31 | ```sh 32 | $ git open 33 | # opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/tree/CURRENT_BRANCH 34 | 35 | $ git open someremote 36 | # opens https://github.com/PROVIDED_REMOTE_USER/CURRENT_REPO/tree/CURRENT_BRANCH 37 | 38 | $ git open someremote somebranch 39 | # opens https://github.com/PROVIDED_REMOTE_USER/CURRENT_REPO/tree/PROVIDED_BRANCH 40 | 41 | $ git open --issue 42 | # If branches use naming convention of issues/#123, 43 | # opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/issues/123 44 | 45 | $ git open --print 46 | # prints https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/tree/CURRENT_BRANCH 47 | 48 | $ git open --suffix pulls 49 | # opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/pulls 50 | ``` 51 | 52 | ## Installation 53 | 54 | ### Basic install 55 | 56 | The preferred way of installation is to simply add the `git-open` script 57 | somewhere into your path (e.g. add the directory to your `PATH` environment 58 | or copy `git-open` into an existing included path like `/usr/local/bin`). 59 | 60 | ### Install via NPM: 61 | 62 | ```sh 63 | npm install --global git-open 64 | ``` 65 | 66 | ### Windows Powershell 67 | 68 | Save git-open anywhere, say as ~/Documents/Scripts/git-open.sh and define 69 | a function in your Powershell profile (see ~/Documents/WindowsPowerShell/profile.ps1) like this: 70 | 71 | ```sh 72 | function git-open { cmd /c "C:\Program Files\Git\usr\bin\bash.exe" "~/Documents/Scripts/git-open.sh" } 73 | Set-Alias -Name gop -Value git-open 74 | ``` 75 | 76 | ### Windows with `cmd` terminal 77 | 78 | Save the `git-open` script in any place accessible via your `%PATH%` environment var. 79 | 80 | ### ZSH 81 | 82 | #### [Antigen](https://github.com/zsh-users/antigen) 83 | 84 | Add `antigen bundle paulirish/git-open` to your `.zshrc` with your other bundle 85 | commands. 86 | 87 | Antigen will handle cloning the plugin for you automatically the next time you 88 | start zsh, and periodically checking for updates to the git repository. You can 89 | also add the plugin to a running zsh with `antigen bundle paulirish/git-open` 90 | for testing before adding it to your `.zshrc`. 91 | 92 | #### [Oh-My-Bash](https://github.com/ohmybash/oh-my-bash) 93 | 94 | 1. `git clone https://github.com/paulirish/git-open.git $OSH_CUSTOM/plugins/git-open` 95 | 2. Add `git-open` to your plugin list - edit `~/.bashrc` and change 96 | `plugins=(...)` to `plugins=(... git-open)` 97 | 3. `source ~/.bashrc` 98 | 99 | #### [Oh-My-Zsh](http://ohmyz.sh/) 100 | 101 | 1. `git clone https://github.com/paulirish/git-open.git $ZSH_CUSTOM/plugins/git-open` 102 | 2. Add `git-open` to your plugin list - edit `~/.zshrc` and change 103 | `plugins=(...)` to `plugins=(... git-open)` 104 | 3. `source ~/.zshrc` 105 | 106 | #### [Zgen](https://github.com/tarjoilija/zgen) 107 | 108 | Add `zgen load paulirish/git-open` to your .zshrc file in the same function 109 | you're doing your other `zgen load` calls in. ZGen will take care of cloning 110 | the repository the next time you run `zgen save`, and will also periodically 111 | check for updates to the git repository. 112 | 113 | #### [zplug](https://github.com/zplug/zplug) 114 | 115 | `zplug "paulirish/git-open", as:plugin` 116 | 117 | ## Supported remote repositories 118 | 119 | git-open can automatically guess the corresponding repository page for remotes 120 | (default looks for `origin`) on the following hosts: 121 | 122 | - github.com 123 | - gist.github.com 124 | - gitlab.com 125 | - GitLab custom hosted (see below) 126 | - bitbucket.org 127 | - Atlassian Bitbucket Server (formerly _Atlassian Stash_) 128 | - Visual Studio Team Services 129 | - Team Foundation Server (on-premises) 130 | - AWS Code Commit 131 | - cnb.cool 132 | 133 | ## Configuration 134 | 135 | See the [man page](git-open.1.md) for more information on how to configure `git-open`. 136 | 137 | ## Alternative projects 138 | 139 | See [hub](https://github.com/github/hub) for complete GitHub opening support. 140 | It's the official GitHub project and provides `hub browse`. 141 | 142 | [Homebrew has an alternate git-open](https://github.com/jeffreyiacono/git-open) 143 | that only works with GitHub but can open user profile pages, too. 144 | 145 | @[gerep has an alternate git-open](https://github.com/gerep/git-open) that 146 | works with a few providers. Of note, it opens the default view for BitBucket 147 | instead of the source view. 148 | 149 | And, of course, [jasonmccreary's original gh](https://github.com/jasonmccreary/gh) 150 | from which this plugin was forked. 151 | 152 | ## Thanks 153 | 154 | [jasonmccreary](https://github.com/jasonmccreary/) did [the initial hard work](https://github.com/jasonmccreary/gh). Since then, [many contributors](https://github.com/paulirish/git-open/graphs/contributors) have submitted great PRs. 155 | 156 | ## Contributing & Development 157 | 158 | Please provide examples of the URLs you are parsing with each PR. 159 | 160 | ### Testing: 161 | 162 | You'll need to install [bats](https://github.com/sstephenson/bats#installing-bats-from-source), the Bash automated testing system. It's also available as `brew install bats` 163 | 164 | ```sh 165 | git submodule update --init # pull in the assertion libraries 166 | 167 | # Run the test suite once: 168 | bats test # or `npm run unit` 169 | 170 | # Run it on every change with `entr` 171 | brew install entr 172 | npm run watch 173 | ``` 174 | 175 | ## Related projects 176 | 177 | - [`git recent`](https://github.com/paulirish/git-recent) - View your most recent git branches 178 | - [`diff-so-fancy`](https://github.com/so-fancy/diff-so-fancy/) - Making the output of `git diff` so fancy 179 | 180 | ## License 181 | 182 | Copyright Jason McCreary & Paul Irish. Licensed under MIT. 183 | 184 | 185 | ## Changelog 186 | 187 | 188 | - **2018-12-03** - [2.1.0 shipped](https://github.com/paulirish/git-open/releases/tag/v2.1.0). 189 | - **2017-12-01** - [2.0 shipped](https://github.com/paulirish/git-open/releases/tag/v2.0.0). Breaking change: [Gitlab configuration](https://github.com/paulirish/git-open#configuration) handled differently. 190 | - **2017-12-01** - Configuration for custom remote added 191 | - **2017-11-30** - Support for VSTS Added 192 | - **2017-10-31** - `--issue` and `-h` added 193 | - **2017-10-30** - Configuration for custom domains added 194 | - **2017-10-30** - WSL support added 195 | - **2017-06-16** - Introduced a test suite in BATS 196 | - **2017-06-15** - Entire script rewritten and simplified by @dermagia 197 | - **2016-07-23** - Readme: fix oh-my-zsh install instructions 198 | - **2016-07-22** - 1.1.0 shipped. update and add linters for package.json, readme. 199 | - **2016-07-11** - Readme formatting and installation instructions updated. Changelog started 200 | 201 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## [Version 2.1.0](https://github.com/paulirish/git-open/releases/tag/v2.1.0) (2018-12-3) 2 | [Full changelog](https://github.com/paulirish/git-open/compare/v2.0.0...v2.1.0) 3 | 4 | ### New features 5 | 6 | - Support opening current tag / commit: [#130](https://github.com/paulirish/git-open/pull/130) 7 | - Add man page: [#110](https://github.com/paulirish/git-open/pull/110) 8 | - Add support for sshconfig expansion: [#109](https://github.com/paulirish/git-open/pull/109) 9 | - Add support for Bitbucket Server with different root context: [#113](https://github.com/paulirish/git-open/pull/113) 10 | - Add support for AWS Code commit.: [#128](https://github.com/paulirish/git-open/pull/128) 11 | 12 | ### Bug fixes 13 | 14 | - Fix bug with open -i on default VSTS project repo: [`67b72ac`](https://github.com/paulirish/git-open/commit/67b72ac), [`aea6e3e`](https://github.com/paulirish/git-open/commit/aea6e3e) 15 | - Use ls-remote --get-url to get remote url: [`740222b`](https://github.com/paulirish/git-open/commit/740222b) 16 | - Fix shellcheck: [`775361b`](https://github.com/paulirish/git-open/commit/775361b) 17 | - Fix incompatability with bash < 4: [`895240f`](https://github.com/paulirish/git-open/commit/895240f) 18 | - Swap uppercase/lowercase for ssh feature: [`995e915`](https://github.com/paulirish/git-open/commit/995e915) ([#123](https://github.com/paulirish/git-open/issues/123)) 19 | - Remove openopt, allow spaces in $BROWSER: [`96c80ff`](https://github.com/paulirish/git-open/commit/96c80ff) 20 | - readme: One liner install for oh-my-zsh: [`29434cd`](https://github.com/paulirish/git-open/commit/29434cd) 21 | - deps: change markdownlint to markdownlint-cli: [`702b8a6`](https://github.com/paulirish/git-open/commit/702b8a6), [`5b815cb`](https://github.com/paulirish/git-open/commit/5b815cb) 22 | 23 | 24 | --- 25 | 26 | Generated by [changelog.md](https://github.com/egoist/changelog.md) 27 | -------------------------------------------------------------------------------- /git-open: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Use git-sh-setup, similar to git-rebase 4 | # https://www.kernel.org/pub/software/scm/git/docs/git-sh-setup.html 5 | # https://github.com/git/git/blob/master/git-rebase.sh 6 | # shellcheck disable=SC2034 7 | OPTIONS_STUCKLONG=t 8 | # shellcheck disable=SC2034 9 | OPTIONS_KEEPDASHDASH= 10 | # shellcheck disable=SC2034 11 | OPTIONS_SPEC="\ 12 | git open [options] 13 | git open [remote] [branch] 14 | -- 15 | Opens the GitHub page for a repo/branch in your browser. 16 | https://github.com/paulirish/git-open/ 17 | 18 | Available options are 19 | c,commit! open current commit 20 | i,issue! open issues page 21 | s,suffix= append this suffix 22 | f,file= append this file 23 | p,print! just print the url 24 | " 25 | 26 | # https://github.com/koalaman/shellcheck/wiki/SC1090 27 | # shellcheck source=/dev/null 28 | SUBDIRECTORY_OK='Yes' . "$(git --exec-path)/git-sh-setup" 29 | 30 | # Defaults 31 | is_commit=0 32 | is_issue=0 33 | protocol="https" 34 | print_only=0 35 | suffix_flag="" 36 | file_flag="" 37 | 38 | while test $# != 0; do 39 | case "$1" in 40 | --commit) is_commit=1;; 41 | --issue) is_issue=1;; 42 | --suffix=*) suffix_flag="$1";; 43 | --file=*) file_flag="$1";; 44 | --print) print_only=1;; 45 | --) shift; break ;; 46 | esac 47 | shift 48 | done 49 | 50 | # parse suffix from suffix=value 51 | IFS='=' read -ra suffix_flag <<< "$suffix_flag" 52 | function join_by { local IFS="$1"; shift; echo "$*"; } 53 | suffix=$(join_by "=" "${suffix_flag[@]:1}") 54 | 55 | # parse file from file=value 56 | IFS='=' read -ra file_flag <<< "$file_flag" 57 | file=$(join_by "=" "${file_flag[@]:1}") 58 | 59 | # are we in a git repo? 60 | if ! git rev-parse --is-inside-work-tree &>/dev/null; then 61 | echo "Not a git repository." 1>&2 62 | exit 1 63 | fi 64 | 65 | # choose remote. priority to: provided argument, default in config, detected tracked remote, 'origin' 66 | branch=${2:-$(git symbolic-ref -q --short HEAD)} 67 | upstream_branch_full_name=$(git config "branch.$branch.merge") 68 | upstream_branch=${upstream_branch_full_name#"refs/heads/"} 69 | tracked_remote=$(git config "branch.$branch.remote") 70 | default_remote=$(git config open.default.remote) 71 | remote=${1:-$default_remote} 72 | remote=${remote:-$tracked_remote} 73 | remote=${remote:-"origin"} 74 | 75 | giturl=$(git ls-remote --get-url "$remote") 76 | 77 | if [[ -z "$giturl" ]]; then 78 | echo "Git remote is not set for $remote" 1>&2 79 | exit 1 80 | fi 81 | 82 | ssh_config=${ssh_config:-"$HOME/.ssh/config"} 83 | # Resolves an ssh alias defined in ssh_config to it's corresponding hostname 84 | # echos out result, should be used within subshell $( ssh_resolve $host ) 85 | # echos out nothing if alias could not be resolved 86 | function ssh_resolve() { 87 | domain="$1" 88 | ssh_found=true 89 | # Filter to only ssh_config lines that start with "Host" or "HostName" 90 | resolved=$(while read -r ssh_line; do 91 | # Split each line by spaces, of the form: 92 | # Host alias [alias...] 93 | # Host regex 94 | # HostName resolved.domain.com 95 | read -r -a ssh_array <<<"${ssh_line}" 96 | ssh_optcode="$(echo "${ssh_array[0]}" | tr '[:lower:]' '[:upper:]')" 97 | if [[ $ssh_optcode == HOST ]]; then 98 | # Host 99 | ssh_found=false 100 | # Iterate through aliases looking for a match 101 | for ssh_index in $(seq 1 $((${#ssh_array[@]} - 1))); do 102 | ssh_host=${ssh_array[$ssh_index]} 103 | # shellcheck disable=SC2053 104 | if [[ $domain == $ssh_host ]]; then 105 | # Found a match, next HostName entry will be returned while matched 106 | ssh_found=true 107 | break 108 | fi 109 | done 110 | elif $ssh_found && [[ $ssh_optcode == HOSTNAME ]]; then 111 | # HostName, but only if ssh_found is true (the last Host entry matched) 112 | # Replace all instances of %h with the Host alias 113 | echo "${ssh_array[1]//%h/$domain}" 114 | fi 115 | done < <(grep -iE "^\\s*Host(Name)?\\s+" "$ssh_config")) 116 | # Take only the last resolved hostname (multiple are overridden) 117 | tail -1 <<<"$resolved" 118 | } 119 | 120 | # From git-fetch(5), native protocols: 121 | # ssh://[user@]host.xz[:port]/path/to/repo.git/ 122 | # git://host.xz[:port]/path/to/repo.git/ 123 | # http[s]://host.xz[:port]/path/to/repo.git/ 124 | # ftp[s]://host.xz[:port]/path/to/repo.git/ 125 | # [user@]host.xz:path/to/repo.git/ - scp-like but is an alternative to ssh. 126 | # [user@]hostalias:path/to/repo.git/ - handles host aliases defined in ssh_config(5) 127 | 128 | # Determine whether this is a url (https, ssh, git+ssh...) or an scp-style path 129 | if [[ "$giturl" =~ ^[a-z\+]+://.* ]]; then 130 | # Trim URL scheme and possible username 131 | gitprotocol=${giturl%%://*} 132 | uri=${giturl#*://} 133 | uri=${uri#*@} 134 | 135 | # Split on first '/ to get server name and path 136 | domain=${uri%%/*} 137 | urlpath=${uri#*/} 138 | 139 | # Remove port number from non-http/https protocols (ie, ssh) 140 | if [[ $gitprotocol != 'https' && $gitprotocol != 'http' ]]; then 141 | domain=${domain%:*} 142 | fi 143 | else 144 | # Trim possible username from SSH path 145 | uri=${giturl##*@} 146 | 147 | # Split on first ':' to get server name and path 148 | domain=${uri%%:*} 149 | urlpath=${uri#*:} 150 | 151 | # Resolve sshconfig aliases 152 | if [[ -e "$ssh_config" ]]; then 153 | domain_resolv=$(ssh_resolve "$domain") 154 | if [[ -n "$domain_resolv" ]]; then 155 | domain="$domain_resolv" 156 | fi 157 | fi 158 | fi 159 | 160 | # Trim "/" from beginning of URL; "/" and ".git" from end of URL 161 | urlpath=${urlpath#/} urlpath=${urlpath%/} urlpath=${urlpath%.git} 162 | 163 | # If the URL is provided as "http", preserve that 164 | if [[ $gitprotocol == 'http' ]]; then 165 | protocol='http' 166 | fi 167 | 168 | # Allow config options to replace the server or the protocol 169 | openurl="$protocol://$domain" 170 | 171 | function getConfig() { 172 | config=$(git config --get-urlmatch "open.$1" "$openurl") 173 | echo "${config:-${!1}}" 174 | } 175 | 176 | domain=$(getConfig "domain") 177 | protocol=$(getConfig "protocol") 178 | forge=$(getConfig "forge") 179 | 180 | # Remote ref to open 181 | remote_ref=${upstream_branch:-${branch:-$(git describe --tags --exact-match 2>/dev/null || git rev-parse HEAD)}} 182 | 183 | # Split arguments on '/' 184 | IFS='/' read -r -a pathargs <<<"$urlpath" 185 | 186 | if (( is_issue )); then 187 | # For issues, take the numbers and preprend 'issues/' 188 | [[ $remote_ref =~ [0-9]+ ]] 189 | providerBranchRef="/issues/${BASH_REMATCH[0]}" 190 | else 191 | # Make # and % characters url friendly 192 | # github.com/paulirish/git-open/pull/24 193 | remote_ref=${remote_ref//%/%25} remote_ref=${remote_ref//#/%23} 194 | if [[ $forge == 'gitea' ]]; then 195 | providerBranchRef="/src/branch/$remote_ref" 196 | else 197 | providerBranchRef="/tree/$remote_ref" 198 | fi 199 | fi 200 | 201 | if [[ "$domain" == 'bitbucket.org' ]]; then 202 | providerBranchRef="/src/$remote_ref" 203 | elif [[ "${#pathargs[@]}" -ge 3 && ${pathargs[${#pathargs[@]} - 3]} == 'scm' ]]; then 204 | # Bitbucket server always has /scm/ as the third to last segment in the url path, e.g. /scm/ppp/test-repo.git 205 | # Anything before the 'scm' is part of the server's root context 206 | 207 | # If there are other context parts, add them, up to (but not including) the found 'scm' 208 | pathPref=("${pathargs[*]:0:${#pathargs[@]} - 3}") 209 | 210 | # Replace the 'scm' element, with 'projects'. Keep the first argument, the string 'repos', and finally the rest of the arguments. 211 | # shellcheck disable=SC2206 212 | pathargs=(${pathPref[@]} 'projects' ${pathargs[${#pathargs[@]} - 2]} 'repos' "${pathargs[@]:${#pathargs[@]} - 1}") 213 | IFS='/' urlpath="${pathargs[*]}" 214 | providerBranchRef="/browse?at=$remote_ref" 215 | elif [[ "${#pathargs[@]}" -ge '2' && ${pathargs[${#pathargs[@]} - 2]} == '_git' ]]; then 216 | # Visual Studio Team Services and Team Foundation Server always have /_git/ as the second to last segment in the url path 217 | if (( is_issue )); then 218 | # Switch to workitems, provide work item id if specified 219 | urlpath="${urlpath%%/_git/*}/_workitems" 220 | # Handle case for the default repository url 221 | urlpath="${urlpath#_git\/*}" 222 | providerBranchRef="?id=${remote_ref//[^0-9]/}" 223 | else 224 | # Keep project and repository name, append branch selector. 225 | providerBranchRef="?version=GB$remote_ref" 226 | fi 227 | elif [[ "$domain" =~ amazonaws\.com$ ]]; then 228 | # AWS Code Commit 229 | if (( is_issue )); then 230 | echo "Issue feature does not supported on AWS Code Commit." 1>&2 231 | exit 1 232 | fi 233 | 234 | # Take region name from domain. 235 | region=${domain#*.} 236 | region=${region%%.*} 237 | 238 | # Replace domain. 239 | # Ex. git-codecommit.us-east-1.amazonaws.com -> us-east-1.console.aws.amazon.com 240 | domain="${region}.console.aws.amazon.com" 241 | 242 | # Replace URL path. 243 | # Ex. v1/repos/example -> codecommit/home?region=us-east-1#/repository/example/browse/ 244 | urlpath="codecommit/home?region=${region}#/repository/${urlpath##*/}/browse/" 245 | 246 | # Replace branch ref. 247 | # Ex. /tree/foobar -> foobar/--/ 248 | providerBranchRef="${providerBranchRef##*/}/--/" 249 | elif [[ "$domain" =~ cnb\.cool$ ]]; then 250 | # cnb.cool 251 | # Replace URL path. 252 | # Ex. repos/example -> repos/example/- 253 | urlpath="$urlpath/-" 254 | if [[ $remote_ref = "master" ]]; then 255 | # repos/example/tree/master -> repos/example/-/tree/master 256 | urlpath="$urlpath$providerBranchRef" 257 | fi 258 | fi 259 | openurl="$protocol://$domain/$urlpath" 260 | 261 | if (( is_commit )); then 262 | sha=$(git rev-parse HEAD) 263 | openurl="$openurl/commit/$sha" 264 | elif [[ $remote_ref != "master" || "$file" ]]; then 265 | # simplify URL for master 266 | openurl="$openurl$providerBranchRef" 267 | fi 268 | 269 | if [ "$file" ]; then 270 | absfile=$(git ls-tree --full-name --name-only "$branch" "$file") 271 | if [[ -z "$absfile" ]]; then 272 | echo "File $file is not in repository" 1>&2 273 | exit 1 274 | fi 275 | openurl="$openurl/$absfile" 276 | fi 277 | 278 | if [ "$suffix" ]; then 279 | openurl="$openurl/$suffix" 280 | fi 281 | 282 | # get current open browser command 283 | case $( uname -s ) in 284 | Darwin) open='open';; 285 | MINGW*) open='start';; 286 | MSYS*) open='start';; 287 | CYGWIN*) open='cygstart';; 288 | *) # Try to detect WSL (Windows Subsystem for Linux) 289 | if uname -r | grep -q -i Microsoft; then 290 | open='/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoProfile Start' 291 | else 292 | open='xdg-open' 293 | fi;; 294 | esac 295 | 296 | if (( print_only )); then 297 | BROWSER="echo" 298 | fi 299 | 300 | # open it in a browser 301 | ${BROWSER:-$open} "$openurl" 302 | -------------------------------------------------------------------------------- /git-open.1.md: -------------------------------------------------------------------------------- 1 | # git-open - Open the repository's website in your browser. 2 | 3 | 4 | ## SYNOPSIS 5 | 6 | `git open` [--issue] [--commit] [--suffix some_suffix] [remote-name] [branch-name] 7 | 8 | 9 | ## DESCRIPTION 10 | 11 | `git open` opens the repository's website in your browser. The major well known 12 | git hosting services are supported. 13 | 14 | 15 | ## OPTIONS 16 | 17 | `-c`, `--commit` 18 | Open the current commit. See `EXAMPLES` for more information. 19 | Only tested with GitHub & GitLab. 20 | 21 | `-i`, `--issue` 22 | Open the current issue. When the name of the current branch matches the right pattern, 23 | it will open the webpage with that issue. See `EXAMPLES` for more information. 24 | This only works on GitHub, GitLab, Visual Studio Team Services and Team Foundation Server at the moment. 25 | 26 | `-s`, `--suffix` some_suffix 27 | Append the given suffix to the url 28 | 29 | `-p`, `--print` 30 | Just print the URL. Do not open it in browser. 31 | 32 | `-h` 33 | Show a short help text. 34 | 35 | ## EXAMPLES 36 | 37 | ```sh 38 | git open 39 | ``` 40 | 41 | It opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/ 42 | 43 | ```sh 44 | git open someremote 45 | ``` 46 | 47 | It opens https://github.com/PROVIDED_REMOTE_USER/CURRENT_REPO/ 48 | 49 | ```sh 50 | git open someremote somebranch 51 | ``` 52 | 53 | It opens https://github.com/PROVIDED_REMOTE_USER/CURRENT_REPO/tree/PROVIDED_BRANCH 54 | 55 | ```sh 56 | git open --issue 57 | ``` 58 | 59 | If branches use naming convention of `issues/#123`, it opens 60 | https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/issues/123 61 | 62 | ```sh 63 | git open --suffix pulls 64 | ``` 65 | 66 | It opens the URL https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/pulls 67 | 68 | ```sh 69 | git open --print 70 | ``` 71 | 72 | It prints the URL https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/ 73 | 74 | ```sh 75 | git open --commit 76 | ``` 77 | 78 | Supposing that the current sha is `2ddc8d4548d0cee3d714dcf0068dbec5b168a9b2`, it opens 79 | https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/commit/2ddc8d4548d0cee3d714dcf0068dbec5b168a9b2 80 | 81 | 82 | ## SUPPORTED GIT HOSTING SERVICES 83 | 84 | git-open can automatically guess the corresponding repository page for remotes 85 | on the following git hosting services: 86 | 87 | - github.com 88 | - gist.github.com 89 | - gitlab.com 90 | - GitLab CE/EE (self hosted GitLab, see `CONFIGURATION`) 91 | - bitbucket.org 92 | - Atlassian Bitbucket Server (formerly _Atlassian Stash_) 93 | - Visual Studio Team Services 94 | - Team Foundation Server (on-premises) 95 | 96 | 97 | ## CONFIGURATION 98 | 99 | To configure git-open you may need to set some `git config` options. 100 | You can use `--global` to set across all repos, instead of just the current repo. 101 | 102 | ```sh 103 | git config [--global] option value 104 | ``` 105 | 106 | ### Configuring which remote to open 107 | 108 | By default, `git open` opens the remote named `origin`. However, if your current branch is remotely-tracking a different remote, that tracked remote will be used. 109 | 110 | In some instances, you may want to override this behavior. When you fork a project 111 | and add a remote named `upstream` you often want that upstream to be opened 112 | rather than your fork. To accomplish this, you can set the `open.default.remote` within your project: 113 | 114 | ```sh 115 | git config open.default.remote upstream 116 | ``` 117 | 118 | This is equivalent to always typing `git open upstream`. 119 | 120 | 121 | ### Gitea options 122 | 123 | To configure Gitea support you need to set the following option. 124 | 125 | `open.[gitdomain].forge` 126 | The git forge present at the git domain. This only needs to be set for Gitea because it uses another branch URL format. 127 | 128 | **Example** 129 | 130 | ```sh 131 | git config [--global] "open.https://gitea.internal.biz.forge" "gitea" 132 | ``` 133 | 134 | 135 | ### GitLab options 136 | 137 | To configure GitLab support (or other unique hosting situations) you may need to set some options. 138 | 139 | `open.[gitdomain].domain` 140 | The (web) domain to open based on the provided git repo domain. 141 | 142 | `open.[gitdomain].protocol` 143 | The (web) protocol to open based on the provided git repo domain. Defaults to `https`. 144 | 145 | ```sh 146 | git config [--global] open.[gitdomain].domain [value] 147 | git config [--global] open.[gitdomain].protocol [value] 148 | ``` 149 | 150 | **Example** 151 | 152 | - Your git remote is at `ssh://git@git.internal.biz:7000/XXX/YYY.git` 153 | - Your hosted gitlab is `http://repo.intranet/subpath/XXX/YYY` 154 | 155 | ```sh 156 | git config [--global] "open.https://git.internal.biz.domain" "repo.intranet/subpath" 157 | git config [--global] "open.https://git.internal.biz.protocol" "http" 158 | ``` 159 | 160 | 161 | ## DEBUGGING 162 | 163 | You can run `git-open` in `echo` mode, which doesn't open your browser, but just prints the URL to stdout: 164 | 165 | ```sh 166 | env BROWSER='echo' ./git-open 167 | ``` 168 | 169 | 170 | ## AUTHORS 171 | 172 | Jason McCreary did the initial hard work. Paul Irish based his project on his work. 173 | Since then many contributors have submitted great PRs. 174 | 175 | 176 | ## SEE ALSO 177 | 178 | git(1), git-remote(1), git-config(1), [Project page](https://github.com/paulirish/git-open) 179 | -------------------------------------------------------------------------------- /git-open.plugin.sh: -------------------------------------------------------------------------------- 1 | # This plugin is MIT licensed to match the git-open license. 2 | # 3 | # Make git-open easy to install and keep up to date if you're using a 4 | # Bash framework like oh-my-bash. 5 | 6 | export PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )":${PATH} 7 | -------------------------------------------------------------------------------- /git-open.plugin.zsh: -------------------------------------------------------------------------------- 1 | # This plugin is MIT licensed to match the git-open license. 2 | # 3 | # Make git-open easy to install and keep up to date if you're using a 4 | # ZSH framework like Zgen or Antigen 5 | 6 | export PATH=$(dirname $0):${PATH} 7 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | If an incorrect URL is opened, please provide the following so we can write a test: 2 | 3 | #### Example clone url: 4 | 5 | #### Example branch name: 6 | 7 | #### Expected web URL: 8 | 9 | 10 | -------------------------------------------------------------------------------- /markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD002": false, 4 | "MD003": { "style": "atx" }, 5 | "MD004": { "style": "dash" }, 6 | "MD007": { "indent": 4 }, 7 | "MD009": false, 8 | "MD012": false, 9 | "MD013": false, 10 | "MD026": false, 11 | "MD033": false, 12 | "MD034": false, 13 | "MD036": false, 14 | "MD041": false 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "git-open", 3 | "version": "3.1.0", 4 | "description": "Type `git open` to open the GitHub/GitLab/Bitbucket homepage for a repository.", 5 | "author": "Paul Irish (http://paulirish.com/)", 6 | "license": "MIT", 7 | "homepage": "https://github.com/paulirish/git-open", 8 | "bugs": { 9 | "url": "https://github.com/paulirish/git-open/issues" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/paulirish/git-open.git" 14 | }, 15 | "contributors": [ 16 | "Paul Irish (http://paulirish.com/)", 17 | "Jason McCreary (http://jason.pureconcepts.net/)", 18 | "David O'Trakoun (https://davidosomething.com/)" 19 | ], 20 | "keywords": [ 21 | "git", 22 | "cli" 23 | ], 24 | "engines": { 25 | "node": ">=0.10.3", 26 | "npm": ">=2.0.0" 27 | }, 28 | "preferGlobal": true, 29 | "bin": { 30 | "git-open": "git-open", 31 | "git-home": "git-open" 32 | }, 33 | "man": "./git-open.1", 34 | "scripts": { 35 | "lint:editorconfig": "eclint check ./git-open *.md *.yml", 36 | "lint:readme": "markdownlint --config markdownlint.json README.md", 37 | "lint:man": "markdownlint --config markdownlint.json git-open.1.md", 38 | "man": "marked-man --version \"git-open $npm_package_version\" --manual \"Git manual\" --section 1 git-open.1.md > git-open.1", 39 | "unit": "bats test/", 40 | "shellcheck": "shellcheck ./git-open", 41 | "lint": "npm run lint:readme && npm run lint:man && npm run lint:editorconfig", 42 | "test": "npm run unit && npm run lint", 43 | "watch": "find . -maxdepth 2 -iname '*bats' -o -iname 'git-open' | entr bats test/", 44 | "prepublishOnly": "npm run man && npm run test" 45 | }, 46 | "devDependencies": { 47 | "eclint": "^2.1.0", 48 | "markdownlint-cli": "^0.32.0", 49 | "marked-man": "^0.7.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/git-open.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load "test_helper/bats-support/load" 4 | load "test_helper/bats-assert/load" 5 | 6 | foldername="sandboxrepo" 7 | 8 | setup() { 9 | create_git_sandbox 10 | export BROWSER=echo 11 | } 12 | 13 | ## 14 | ## Test environment 15 | ## 16 | @test "test environment" { 17 | assert_equal "$BROWSER" "echo" 18 | cd .. 19 | assert [ -e "$foldername" ] 20 | assert [ -e "$foldername/.git" ] 21 | } 22 | 23 | ## 24 | ## Help 25 | ## 26 | 27 | @test "help text" { 28 | run ../git-open -h 29 | assert_output --partial "usage: git open" 30 | } 31 | 32 | @test "invalid option" { 33 | run ../git-open --invalid-option 34 | assert_output --partial "error: unknown option \`invalid-option'" 35 | assert_output --partial "usage: git open" 36 | } 37 | 38 | ## 39 | ## Print 40 | ## 41 | 42 | @test "print url" { 43 | git remote set-url origin "git@github.com:user/repo.git" 44 | git checkout -B "master" 45 | BROWSER="assert_failure" run ../git-open -p 46 | assert_output "https://github.com/user/repo" 47 | } 48 | 49 | ## 50 | ## url handling 51 | ## 52 | 53 | @test "url: insteadOf handling" { 54 | git config --local url.http://example.com/.insteadOf ex: 55 | git remote set-url origin ex:example.git 56 | git checkout -B master 57 | run ../git-open 58 | assert_output "http://example.com/example" 59 | } 60 | 61 | ## 62 | ## GitHub 63 | ## 64 | 65 | @test "gh: basic" { 66 | git remote set-url origin "git@github.com:user/repo.git" 67 | git checkout -B "master" 68 | run ../git-open 69 | assert_output "https://github.com/user/repo" 70 | } 71 | 72 | @test "gh: branch" { 73 | git remote set-url origin "git@github.com:user/repo.git" 74 | git checkout -B "mybranch" 75 | run ../git-open 76 | assert_output "https://github.com/user/repo/tree/mybranch" 77 | } 78 | 79 | @test "gh: upstream branch" { 80 | git remote set-url origin "git@github.com:user/repo.git" 81 | git remote add upstreamRemote "git@github.com:user/upstream-repo.git" 82 | git checkout -B "mybranch" 83 | git config --local "branch.mybranch.merge" "refs/heads/myupstream/mybranch" 84 | git config --local "branch.mybranch.remote" "upstreamRemote" 85 | run ../git-open 86 | assert_output "https://github.com/user/upstream-repo/tree/myupstream/mybranch" 87 | } 88 | 89 | @test "gh: upstream branch from param" { 90 | git remote set-url origin "git@github.com:user/repo.git" 91 | git remote add upstreamRemote "git@github.com:user/upstream-repo.git" 92 | git checkout -B "mybranch" 93 | git config --local "branch.mybranch.merge" "refs/heads/upstreamBranch" 94 | git config --local "branch.mybranch.remote" "upstreamRemote" 95 | git checkout master 96 | run ../git-open upstreamRemote mybranch 97 | assert_output "https://github.com/user/upstream-repo/tree/upstreamBranch" 98 | } 99 | 100 | @test "gh: tag" { 101 | git remote set-url origin "git@github.com:user/repo.git" 102 | git tag mytag 103 | echo a > a 104 | git add a 105 | git commit -m a 106 | git checkout mytag 107 | run ../git-open 108 | assert_output "https://github.com/user/repo/tree/mytag" 109 | } 110 | 111 | @test "gh: non-origin remote" { 112 | git remote set-url origin "git@github.com:user/repo.git" 113 | git remote add upstream "git@github.com:upstreamorg/repo.git" 114 | run ../git-open "upstream" 115 | assert_output "https://github.com/upstreamorg/repo" 116 | 117 | git checkout -B "mybranch" 118 | run ../git-open "upstream" "otherbranch" 119 | assert_output "https://github.com/upstreamorg/repo/tree/otherbranch" 120 | } 121 | 122 | @test "gh: without git user" { 123 | # https://github.com/paulirish/git-open/pull/63 124 | git remote set-url origin "github.com:paulirish/git-open.git" 125 | run ../git-open 126 | assert_output "https://github.com/paulirish/git-open" 127 | } 128 | 129 | @test "gh: ssh origin" { 130 | git remote set-url origin "ssh://git@github.com/user/repo" 131 | run ../git-open 132 | assert_output "https://github.com/user/repo" 133 | 134 | # https://github.com/paulirish/git-open/pull/30 135 | git remote set-url origin "ssh://git@github.com/user/repo.git" 136 | run ../git-open 137 | assert_output "https://github.com/user/repo" 138 | } 139 | 140 | @test "gh: git protocol origin" { 141 | git remote set-url origin "git://github.com/user/repo.git" 142 | git checkout -B "master" 143 | run ../git-open 144 | assert_output "https://github.com/user/repo" 145 | } 146 | 147 | @test "gh: git open --issue" { 148 | # https://github.com/paulirish/git-open/pull/46 149 | git remote set-url origin "github.com:paulirish/git-open.git" 150 | git checkout -B "issues/#12" 151 | run ../git-open "--issue" 152 | assert_output "https://github.com/paulirish/git-open/issues/12" 153 | 154 | git checkout -B "fix-issue-37" 155 | run ../git-open "--issue" 156 | assert_output "https://github.com/paulirish/git-open/issues/37" 157 | 158 | git checkout -B "fix-issue-38" 159 | run ../git-open "-i" 160 | assert_output "https://github.com/paulirish/git-open/issues/38" 161 | } 162 | 163 | @test "gh: git open --commit" { 164 | git remote set-url origin "github.com:paulirish/git-open.git" 165 | sha=$(git rev-parse HEAD) 166 | run ../git-open "--commit" 167 | assert_output "https://github.com/paulirish/git-open/commit/${sha}" 168 | } 169 | 170 | @test "gh: git open --suffix anySuffix" { 171 | run ../git-open "--suffix" "anySuffix" 172 | assert_output "https://github.com/paulirish/git-open/anySuffix" 173 | } 174 | 175 | @test "gh: git open --suffix anySuffix?hello=world" { 176 | run ../git-open "--suffix" "anySuffix?hello=world" 177 | assert_output "https://github.com/paulirish/git-open/anySuffix?hello=world" 178 | } 179 | 180 | @test "gh: gist" { 181 | git remote set-url origin "git@gist.github.com:2d84a6db1b41b4020685.git" 182 | run ../git-open 183 | assert_output "https://gist.github.com/2d84a6db1b41b4020685" 184 | } 185 | 186 | @test "gh: --file option" { 187 | run ../git-open -f readme.txt 188 | assert_output "https://github.com/paulirish/git-open/tree/master/readme.txt" 189 | 190 | # and now a new file in a subdirectory. 191 | mkdir -p subdir 192 | touch subdir/howdy.txt 193 | git add subdir 194 | git commit -am add-in-howdy 195 | run ../git-open -f subdir/howdy.txt 196 | assert_output "https://github.com/paulirish/git-open/tree/master/subdir/howdy.txt" 197 | } 198 | 199 | @test "basic: # and % in branch names are URL encoded" { 200 | # https://github.com/paulirish/git-open/pull/24 201 | git checkout -B "issue-#42" 202 | run ../git-open 203 | assert_output "https://github.com/paulirish/git-open/tree/issue-%2342" 204 | 205 | git checkout -B "just-50%" 206 | run ../git-open 207 | assert_output "https://github.com/paulirish/git-open/tree/just-50%25" 208 | } 209 | 210 | @test "basic: tracked remote is default" { 211 | # https://github.com/paulirish/git-open/issues/65 212 | 213 | # create a local git repo I can push to 214 | remote_name="sandboxremote" 215 | remote_url="git@github.com:userfork/git-open.git" 216 | 217 | # ideally we'd set a real upstream branch, but that's not possible without 218 | # pull/push'ing over the network. So we're cheating and just setting the 219 | # branch..remote config 220 | # https://github.com/paulirish/git-open/pull/88#issuecomment-339813145 221 | git remote add $remote_name $remote_url 222 | git config --local --add branch.master.remote $remote_name 223 | 224 | run ../git-open 225 | assert_output "https://github.com/userfork/git-open" 226 | 227 | git config --local --add branch.master.remote origin 228 | run ../git-open 229 | assert_output "https://github.com/paulirish/git-open" 230 | } 231 | 232 | @test "basic: https url can contain port" { 233 | git remote set-url origin "https://github.com:99/user/repo.git" 234 | run ../git-open 235 | assert_output "https://github.com:99/user/repo" 236 | } 237 | 238 | @test "basic: ssh url has port removed from http url" { 239 | git remote set-url origin "ssh://github.com:22/user/repo.git" 240 | run ../git-open 241 | assert_output "https://github.com/user/repo" 242 | } 243 | 244 | @test "basic: http url scheme is preserved" { 245 | git remote set-url origin "http://github.com/user/repo.git" 246 | # ignore any local config like: `url.https://github.com/.insteadof=http://github.com/` 247 | GIT_CONFIG_NOSYSTEM=1 run ../git-open 248 | assert_output "http://github.com/user/repo" 249 | } 250 | 251 | ## 252 | ## SSH config 253 | ## 254 | 255 | @test "sshconfig: basic" { 256 | create_ssh_sandbox 257 | # Basic 258 | git remote set-url origin "basic:user/repo.git" 259 | run ../git-open 260 | assert_output --partial "https://basic.com/user/repo" 261 | # With git user 262 | git remote set-url origin "git@nouser:user/repo.git" 263 | run ../git-open 264 | assert_output "https://no.user/user/repo" 265 | } 266 | 267 | @test "sshconfig: no action on no match" { 268 | create_ssh_sandbox 269 | git remote set-url origin "git@nomatch:user/repo.git" 270 | run ../git-open 271 | assert_output "https://nomatch/user/repo" 272 | # No match due to improper casing 273 | } 274 | 275 | @test "sshconfig: check case sensitivity" { 276 | create_ssh_sandbox 277 | # Host and HostName keywords should be case insensitive 278 | # But output URL will be case sensitive 279 | git remote set-url origin "malformed:user/repo.git" 280 | run ../git-open 281 | assert_output "https://MaL.FoRmEd/user/repo" 282 | # SSH aliases (hosts) are case sensitive, this should not match 283 | git remote set-url origin "git@MALFORMED:user/repo.git" 284 | run ../git-open 285 | refute_output "https://MaL.FoRmEd/user/repo" 286 | } 287 | 288 | @test "sshconfig: multitarget host" { 289 | create_ssh_sandbox 290 | for i in $(seq 1 3); do 291 | git remote set-url origin "multi$i:user/repo.git" 292 | run ../git-open 293 | assert_output "https://multi.com/user/repo" 294 | done 295 | } 296 | 297 | @test "sshconfig: host substitution in hostname" { 298 | create_ssh_sandbox 299 | for i in $(seq 1 3); do 300 | git remote set-url origin "sub$i:user/repo.git" 301 | run ../git-open 302 | assert_output "https://sub$i.multi.com/user/repo" 303 | done 304 | } 305 | 306 | @test "sshconfig: host wildcard * matches zero or more chars" { 307 | create_ssh_sandbox 308 | # Normal * 309 | for str in "" "-prod" "-dev"; do 310 | git remote set-url origin "zero$str:user/repo.git" 311 | run ../git-open 312 | assert_output "https://zero.com/user/repo" 313 | done 314 | # * with substitution 315 | for str in "" "-prod" "-dev"; do 316 | git remote set-url origin "subzero$str:user/repo.git" 317 | run ../git-open 318 | assert_output "https://subzero$str.zero/user/repo" 319 | done 320 | } 321 | 322 | @test "sshconfig: host wildcard ? matches exactly one char" { 323 | create_ssh_sandbox 324 | # Normal ? 325 | for i in $(seq 1 3); do 326 | git remote set-url origin "one$i:user/repo.git" 327 | run ../git-open 328 | assert_output "https://one.com/user/repo" 329 | done 330 | # Refute invalid match on ? 331 | for str in "" "-test"; do 332 | git remote set-url origin "one:user/repo.git" 333 | run ../git-open 334 | refute_output "https://one$str.com/user/repo" 335 | done 336 | 337 | # ? with substitution 338 | for i in $(seq 1 3); do 339 | git remote set-url origin "subone$i:user/repo.git" 340 | run ../git-open 341 | assert_output "https://subone$i.one/user/repo" 342 | done 343 | # Refute invalid match on ? with substitution 344 | for str in "" "-test"; do 345 | git remote set-url origin "subone$str:user/repo.git" 346 | run ../git-open 347 | refute_output "https://subone$str.one/user/repo" 348 | done 349 | # Refute invalid match on ? with substitution 350 | } 351 | 352 | @test "sshconfig: overriding host rules" { 353 | create_ssh_sandbox 354 | git remote set-url origin "zero-override:user/repo.git" 355 | run ../git-open 356 | assert_output "https://override.zero.com/user/repo" 357 | } 358 | 359 | ## 360 | ## Bitbucket 361 | ## 362 | 363 | @test "bitbucket: basic" { 364 | git remote set-url origin "git@bitbucket.org:paulirish/crbug-extension.git" 365 | run ../git-open 366 | assert_output --partial "https://bitbucket.org/paulirish/crbug-extension" 367 | } 368 | 369 | @test "bitbucket: tag" { 370 | git remote set-url origin "git@bitbucket.org:paulirish/crbug-extension.git" 371 | git tag mytag 372 | echo a > a 373 | git add a 374 | git commit -m a 375 | git checkout mytag 376 | run ../git-open 377 | assert_output "https://bitbucket.org/paulirish/crbug-extension/src/mytag" 378 | } 379 | 380 | @test "bitbucket: non-origin remote" { 381 | # https://github.com/paulirish/git-open/pull/4 382 | git remote add bbclone "git@bitbucket.org:rwhitbeck/git-open.git" 383 | run ../git-open "bbclone" 384 | assert_output "https://bitbucket.org/rwhitbeck/git-open" 385 | } 386 | 387 | @test "bitbucket: open source view" { 388 | # https://github.com/paulirish/git-open/pull/26 389 | git remote set-url origin "https://bitbucket.org/kisom/consbri.git" 390 | git checkout -B "devel" 391 | run ../git-open 392 | refute_output --partial "//kisom" 393 | assert_output "https://bitbucket.org/kisom/consbri/src/devel" 394 | } 395 | 396 | @test "bitbucket: open source view with a slash/branch" { 397 | # https://github.com/paulirish/git-open/pull/26 398 | # see https://github.com/paulirish/git-open/issues/80 for feat/branchname issues 399 | git remote set-url origin "https://bitbucket.org/guyzmo/git-repo.git" 400 | git checkout -B "bugfix/conftest_fix" 401 | run ../git-open 402 | assert_output "https://bitbucket.org/guyzmo/git-repo/src/bugfix/conftest_fix" 403 | } 404 | 405 | @test "bitbucket: ssh:// clone urls" { 406 | # https://github.com/paulirish/git-open/pull/36 407 | git remote set-url origin "ssh://git@bitbucket.org/lbesson/bin.git" 408 | run ../git-open 409 | assert_output "https://bitbucket.org/lbesson/bin" 410 | } 411 | 412 | @test "bitbucket: no username@ in final url" { 413 | # https://github.com/paulirish/git-open/pull/69 414 | git remote set-url origin "https://trend_rand@bitbucket.org/trend_rand/test-repo.git" 415 | run ../git-open 416 | refute_output --partial "@" 417 | } 418 | 419 | @test "bitbucket: Bitbucket Server" { 420 | # https://github.com/paulirish/git-open/issues/77#issuecomment-309044010 421 | git remote set-url origin "https://user@mybb.domain.com/scm/ppp/rrr.git" 422 | run ../git-open 423 | 424 | # any of the following are acceptable 425 | assert_output "https://mybb.domain.com/projects/ppp/repos/rrr" || 426 | assert_output "https://mybb.domain.com/projects/ppp/repos/rrr/browse/?at=master" || 427 | assert_output "https://mybb.domain.com/projects/ppp/repos/rrr/browse/?at=refs%2Fheads%2Fmaster" 428 | } 429 | 430 | @test "bitbucket: Bitbucket Server branch" { 431 | # https://github.com/paulirish/git-open/issues/80 432 | git remote set-url origin "https://user@mybb.domain.com/scm/ppp/rrr.git" 433 | git checkout -B "develop" 434 | run ../git-open 435 | 436 | # The following query args work with BB Server: 437 | # at=refs%2Fheads%2Fdevelop, at=develop, at=refs/heads/develop 438 | # However /src/develop does not (unlike bitbucket.org) 439 | assert_output "https://mybb.domain.com/projects/ppp/repos/rrr/browse?at=develop" || 440 | assert_output "https://mybb.domain.com/projects/ppp/repos/rrr/browse?at=refs%2Fheads%2Fdevelop" || 441 | assert_output "https://mybb.domain.com/projects/ppp/repos/rrr/browse?at=refs/heads/develop" 442 | 443 | refute_output --partial "/src/develop" 444 | } 445 | 446 | 447 | @test "bitbucket: Bitbucket Server private user repos" { 448 | # https://github.com/paulirish/git-open/pull/83#issuecomment-309968538 449 | git remote set-url origin "https://mybb.domain.com/scm/~first.last/rrr.git" 450 | git checkout -B "develop" 451 | run ../git-open 452 | assert_output "https://mybb.domain.com/projects/~first.last/repos/rrr/browse?at=develop" || 453 | assert_output "https://mybb.domain.com/projects/~first.last/repos/rrr/browse?at=refs%2Fheads%2Fdevelop" || 454 | assert_output "https://mybb.domain.com/projects/~first.last/repos/rrr/browse?at=refs/heads/develop" 455 | 456 | } 457 | 458 | 459 | @test "bitbucket: Bitbucket Server with different root context" { 460 | # https://github.com/paulirish/git-open/pull/15 461 | git remote set-url origin "https://user@bitbucket.example.com/git/scm/ppp/test-repo.git" 462 | run ../git-open 463 | assert_output "https://bitbucket.example.com/git/projects/ppp/repos/test-repo" || 464 | assert_output "https://bitbucket.example.com/git/projects/ppp/repos/test-repo/?at=master" || 465 | assert_output "https://bitbucket.example.com/git/projects/ppp/repos/test-repo/?at=refs%2Fheads%2Fmaster" 466 | } 467 | 468 | 469 | @test "bitbucket: Bitbucket Server with different root context with multiple parts" { 470 | # https://github.com/paulirish/git-open/pull/15 471 | git remote set-url origin "https://user@bitbucket.example.com/really/long/root/context/scm/ppp/test-repo.git" 472 | run ../git-open 473 | assert_output "https://bitbucket.example.com/really/long/root/context/projects/ppp/repos/test-repo" || 474 | assert_output "https://bitbucket.example.com/really/long/root/context/projects/ppp/repos/test-repo/?at=master" || 475 | assert_output "https://bitbucket.example.com/really/long/root/context/projects/ppp/repos/test-repo/?at=refs%2Fheads%2Fmaster" 476 | } 477 | 478 | 479 | @test "bitbucket: Bitbucket Server private user repos with different root context" { 480 | # https://github.com/paulirish/git-open/pull/83#issuecomment-309968538 481 | git remote set-url origin "https://mybb.domain.com/root/context/scm/~first.last/rrr.git" 482 | git checkout -B "develop" 483 | run ../git-open 484 | assert_output "https://mybb.domain.com/root/context/projects/~first.last/repos/rrr/browse?at=develop" || 485 | assert_output "https://mybb.domain.com/root/context/projects/~first.last/repos/rrr/browse?at=refs%2Fheads%2Fdevelop" || 486 | assert_output "https://mybb.domain.com/root/context/projects/~first.last/repos/rrr/browse?at=refs/heads/develop" 487 | } 488 | 489 | 490 | ## 491 | ## GitLab 492 | ## 493 | 494 | @test "gitlab: default ssh origin style" { 495 | # https://github.com/paulirish/git-open/pull/55 496 | git remote set-url origin "git@gitlab.example.com:user/repo" 497 | run ../git-open 498 | assert_output "https://gitlab.example.com/user/repo" 499 | } 500 | 501 | @test "gitlab: ssh://git@ origin" { 502 | # https://github.com/paulirish/git-open/pull/51 503 | git remote set-url origin "ssh://git@gitlab.domain.com/user/repo" 504 | run ../git-open 505 | assert_output "https://gitlab.domain.com/user/repo" 506 | refute_output --partial "//user" 507 | } 508 | 509 | @test "gitlab: separate domains" { 510 | # https://github.com/paulirish/git-open/pull/56 511 | git remote set-url origin "git@git.example.com:namespace/project.git" 512 | git config --local --add "open.https://git.example.com.domain" "gitlab.example.com" 513 | run ../git-open 514 | assert_output "https://gitlab.example.com/namespace/project" 515 | } 516 | 517 | @test "gitlab: special domain and path" { 518 | git remote set-url origin "ssh://git@git.example.com:7000/XXX/YYY.git" 519 | git config --local --add "open.https://git.example.com.domain" "repo.intranet/subpath" 520 | git config --local --add "open.https://git.example.com.protocol" "http" 521 | 522 | run ../git-open 523 | assert_output "http://repo.intranet/subpath/XXX/YYY" 524 | refute_output --partial "https://" 525 | } 526 | 527 | @test "gitlab: different port" { 528 | # https://github.com/paulirish/git-open/pull/76 529 | git remote set-url origin "ssh://git@git.example.com:7000/XXX/YYY.git" 530 | run ../git-open 531 | assert_output "https://git.example.com/XXX/YYY" 532 | refute_output --partial ":7000" 533 | 534 | git remote set-url origin "https://git.example.com:7000/XXX/YYY.git" 535 | run ../git-open 536 | assert_output "https://git.example.com:7000/XXX/YYY" 537 | } 538 | 539 | @test "gitlab: issue" { 540 | git remote set-url origin "git@gitlab.example.com:user/repo" 541 | git checkout -B "10-bump-up-to-v2.0" 542 | run ../git-open "--issue" 543 | assert_output "https://gitlab.example.com/user/repo/issues/10" 544 | } 545 | 546 | ## 547 | ## Visual Studio Team Services 548 | ## 549 | 550 | @test "vsts: https url" { 551 | git remote set-url origin "https://gitopen.visualstudio.com/Project/_git/Repository" 552 | run ../git-open 553 | assert_output --partial "https://gitopen.visualstudio.com/Project/_git/Repository" 554 | } 555 | 556 | @test "vsts: ssh url" { 557 | git remote add vsts_ssh "ssh://gitopen@gitopen.visualstudio.com:22/Project/_git/Repository" 558 | run ../git-open "vsts_ssh" 559 | assert_output "https://gitopen.visualstudio.com/Project/_git/Repository" 560 | } 561 | 562 | @test "vsts: on-premises tfs http url" { 563 | git remote set-url origin "http://tfs.example.com:8080/Project/_git/Repository" 564 | run ../git-open 565 | assert_output --partial "http://tfs.example.com:8080/Project/_git/Repository" 566 | } 567 | 568 | @test "vsts: branch" { 569 | git remote set-url origin "ssh://gitopen@gitopen.visualstudio.com:22/_git/Repository" 570 | git checkout -B "mybranch" 571 | run ../git-open 572 | assert_output "https://gitopen.visualstudio.com/_git/Repository?version=GBmybranch" 573 | } 574 | 575 | @test "vsts: on-premises tfs branch" { 576 | git remote set-url origin "http://tfs.example.com:8080/Project/Folder/_git/Repository" 577 | git checkout -B "mybranch" 578 | run ../git-open 579 | assert_output "http://tfs.example.com:8080/Project/Folder/_git/Repository?version=GBmybranch" 580 | } 581 | 582 | @test "vsts: issue" { 583 | git remote set-url origin "http://tfs.example.com:8080/Project/Folder/_git/Repository" 584 | git checkout -B "bugfix-36" 585 | run ../git-open "--issue" 586 | assert_output "http://tfs.example.com:8080/Project/Folder/_workitems?id=36" 587 | } 588 | 589 | @test "vsts: default project repository - issue" { 590 | git remote set-url origin "https://gitopen.visualstudio.com/_git/Project" 591 | git checkout -B "bugfix-36" 592 | run ../git-open "--issue" 593 | assert_output "https://gitopen.visualstudio.com/Project/_workitems?id=36" 594 | } 595 | 596 | ## 597 | ## AWS Code Commit 598 | ## 599 | 600 | @test "aws: https url" { 601 | git remote set-url origin "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo" 602 | git checkout -B "master" 603 | run ../git-open 604 | assert_output "https://us-east-1.console.aws.amazon.com/codecommit/home?region=us-east-1#/repository/repo/browse/" 605 | } 606 | 607 | @test "aws: ssh url" { 608 | git remote set-url origin "ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo" 609 | git checkout -B "master" 610 | run ../git-open 611 | assert_output "https://us-east-1.console.aws.amazon.com/codecommit/home?region=us-east-1#/repository/repo/browse/" 612 | } 613 | 614 | @test "aws: branch " { 615 | git remote set-url origin "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo" 616 | git checkout -B "mybranch" 617 | run ../git-open 618 | assert_output "https://us-east-1.console.aws.amazon.com/codecommit/home?region=us-east-1#/repository/repo/browse/mybranch/--/" 619 | } 620 | 621 | @test "aws: issue" { 622 | git remote set-url origin "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo" 623 | git checkout -B "issues/#12" 624 | run ../git-open "--issue" 625 | [ "$status" -eq 1 ] 626 | assert_output "Issue feature does not supported on AWS Code Commit." 627 | } 628 | 629 | ## 630 | ## cnb.cool 631 | ## 632 | 633 | @test "cnb: https url" { 634 | git remote set-url origin "https://cnb.cool/repos/repo" 635 | git checkout -B "master" 636 | run ../git-open 637 | assert_output "https://cnb.cool/repos/repo/-/tree/master" 638 | } 639 | 640 | @test "cnb: branch " { 641 | git remote set-url origin "https://cnb.cool/repos/repo" 642 | git checkout -B "mybranch" 643 | run ../git-open 644 | assert_output "https://cnb.cool/repos/repo/-/tree/mybranch" 645 | } 646 | 647 | @test "cnb: issue" { 648 | git remote set-url origin "https://cnb.cool/repos/repo" 649 | git checkout -B "issues/10" 650 | run ../git-open "--issue" 651 | assert_output "https://cnb.cool/repos/repo/-/issues/10" 652 | } 653 | 654 | teardown() { 655 | cd .. 656 | rm -rf "$foldername" 657 | rm -rf "$ssh_config" 658 | refute [ -e "$ssh_config" ] 659 | unset ssh_config 660 | } 661 | 662 | # helper to create a test git sandbox that won't dirty the real repo 663 | function create_git_sandbox() { 664 | rm -rf "$foldername" 665 | mkdir "$foldername" 666 | cd "$foldername" 667 | # safety check. Don't muck with the git repo if we're not inside the sandbox. 668 | assert_equal $(basename $PWD) "$foldername" 669 | 670 | git init -q 671 | assert [ -e "../$foldername/.git" ] 672 | git config user.email "test@runner.com" && git config user.name "Test Runner" 673 | 674 | # newer git auto-creates the origin remote 675 | if ! git remote add origin "github.com:paulirish/git-open.git"; then 676 | git remote set-url origin "github.com:paulirish/git-open.git" 677 | fi 678 | 679 | git checkout -B "master" 680 | 681 | echo "ok" > readme.txt 682 | git add readme.txt 683 | git commit -m "add file" -q 684 | } 685 | 686 | # helper to create test SSH config file 687 | function create_ssh_sandbox() { 688 | export ssh_config=$(mktemp) 689 | refute [ -z "$ssh_config" ] 690 | 691 | # Populate ssh config with test data 692 | echo "$ssh_testdata" >$ssh_config 693 | assert [ -e "$ssh_config" ] 694 | } 695 | 696 | # Test SSH config data 697 | ssh_testdata=" 698 | # Autogenerated test sshconfig for paulirish/git-open BATS tests 699 | # It is safe to delete this file, a new one will be generated each test 700 | 701 | Host basic 702 | HostName basic.com 703 | User git 704 | 705 | Host nomatch 706 | User git 707 | 708 | Host nouser 709 | HostName no.user 710 | 711 | host malformed 712 | hOsTnAmE MaL.FoRmEd 713 | User other 714 | 715 | # Multiple targets 716 | Host multi1 multi2 multi3 717 | HostName multi.com 718 | User git 719 | 720 | Host sub1 sub2 sub3 721 | HostName %h.multi.com 722 | User git 723 | 724 | # Wildcard * matching (zero or more characters) 725 | Host zero* 726 | HostName zero.com 727 | User git 728 | 729 | Host subzero* 730 | HostName %h.zero 731 | User git 732 | 733 | # Wildcard ? matching (exactly one character) 734 | Host one? 735 | HostName one.com 736 | User git 737 | 738 | Host subone? 739 | HostName %h.one 740 | User git 741 | 742 | # Overrides rule zero* 743 | Host zero-override 744 | HostName override.zero.com 745 | User git 746 | " 747 | --------------------------------------------------------------------------------