Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,23 @@ git open --issue
git open -i
# If this branch is named like issue/#123, this will open the corresponding
# issue in the repo website
# Supports multiple formats: issue/123, bugfix-123, #123, feature/123, fix-123, etc.

git open --pull-request
git open -r
# Open the pull requests / merge requests page for the repository
# Supports GitHub, GitLab, Bitbucket, Azure DevOps, and cnb.cool

git open --path
# Open the current directory path in the repo website
# Useful when you want to browse the current folder in the browser

git open --print
git open -p
# Only print the url at the terminal, but don't open it
```

(`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)
(`git open` works with these [hosted repo providers](#supported-remote-repositories), `git open --issue` currently only works with GitHub, GitLab, Visual Studio Team Services and Team Foundation Server)

### Examples

Expand All @@ -39,14 +49,24 @@ $ git open someremote somebranch
# opens https://github.com/PROVIDED_REMOTE_USER/CURRENT_REPO/tree/PROVIDED_BRANCH

$ git open --issue
# If branches use naming convention of issues/#123,
# If branches use naming convention of issues/#123, bugfix-456, feature-789, etc.
# opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/issues/123

$ git open --pull-request
# opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/pulls (GitHub)
# or https://gitlab.example.com/user/repo/-/merge_requests (GitLab)

$ git open --path
# opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/tree/CURRENT_BRANCH/current/directory

$ git open --print
# prints https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/tree/CURRENT_BRANCH

$ git open --suffix pulls
# opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/pulls

$ git open --file README.md
# opens https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/tree/CURRENT_BRANCH/README.md
```

## Installation
Expand Down
107 changes: 92 additions & 15 deletions git-open
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ git open [remote] [branch]
Available options are
c,commit! open current commit
i,issue! open issues page
r,pull-request! open pull request page
s,suffix= append this suffix
f,file= append this file
path! open current directory path
p,print! just print the url
"

Expand All @@ -30,17 +32,22 @@ SUBDIRECTORY_OK='Yes' . "$(git --exec-path)/git-sh-setup"
# Defaults
is_commit=0
is_issue=0
is_pull_request=0
protocol="https"
print_only=0
suffix_flag=""
file_flag=""
path_flag=""

while test $# != 0; do
case "$1" in
--commit) is_commit=1;;
--issue) is_issue=1;;
--pull-request) is_pull_request=1;;
-r) is_pull_request=1;;
--suffix=*) suffix_flag="$1";;
--file=*) file_flag="$1";;
--path) path_flag=1;;
--print) print_only=1;;
--) shift; break ;;
esac
Expand All @@ -52,16 +59,28 @@ IFS='=' read -ra suffix_flag <<< "$suffix_flag"
function join_by { local IFS="$1"; shift; echo "$*"; }
suffix=$(join_by "=" "${suffix_flag[@]:1}")

# parse file from file=value
IFS='=' read -ra file_flag <<< "$file_flag"
file=$(join_by "=" "${file_flag[@]:1}")

# are we in a git repo?
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
echo "Not a git repository." 1>&2
exit 1
fi

# parse file from file=value
IFS='=' read -ra file_flag <<< "$file_flag"
file=$(join_by "=" "${file_flag[@]:1}")

# handle --path: use current directory relative to repo root
path_suffix=""
if [[ "$path_flag" == "1" ]]; then
repo_root=$(git rev-parse --show-toplevel)
current_dir=$(pwd)
if [[ "$current_dir" != "$repo_root" ]]; then
# Get relative path from repo root
rel_path="${current_dir#"$repo_root"/}"
path_suffix="$rel_path"
fi
fi

# choose remote. priority to: provided argument, default in config, detected tracked remote, 'origin'
branch=${2:-$(git symbolic-ref -q --short HEAD)}
upstream_branch_full_name=$(git config "branch.$branch.merge")
Expand Down Expand Up @@ -183,10 +202,47 @@ remote_ref=${upstream_branch:-${branch:-$(git describe --tags --exact-match 2>/d
# Split arguments on '/'
IFS='/' read -r -a pathargs <<<"$urlpath"

# Extract issue number from branch name
# Supports formats: issue/123, bugfix-123, #123, issues/123, feature/123, fix-123, etc.
function extract_issue_number() {
local branch_name="$1"
local issue_num=""

# Try various patterns: issue/123, bugfix-123, #123, etc.
if [[ "$branch_name" =~ (^|[-/])(issues?|bugfix|feature|fix|hotfix)?[-/]?[#]?([0-9]+) ]]; then
issue_num="${BASH_REMATCH[3]}"
elif [[ "$branch_name" =~ ^#?([0-9]+)$ ]]; then
# Just a number like #123 or 123
issue_num="${BASH_REMATCH[1]}"
fi

echo "$issue_num"
}

if (( is_issue )); then
# For issues, take the numbers and preprend 'issues/'
[[ $remote_ref =~ [0-9]+ ]]
providerBranchRef="/issues/${BASH_REMATCH[0]}"
issue_number=$(extract_issue_number "$remote_ref")
if [[ -z "$issue_number" ]]; then
echo "Could not extract issue number from branch name: $remote_ref" 1>&2
echo "Supported formats: issue/123, bugfix-123, #123, 123, etc." 1>&2
exit 1
fi
providerBranchRef="/issues/$issue_number"
elif (( is_pull_request )); then
# Generate pull request URL based on platform
if [[ "$domain" == 'github.com' || "$domain" == *'github'* ]]; then
providerBranchRef="/pulls"
elif [[ "$domain" == 'gitlab.com' || "$domain" == *'gitlab'* || "$forge" == 'gitlab' ]]; then
providerBranchRef="/-/merge_requests"
elif [[ "$domain" == 'bitbucket.org' || "$domain" == *'bitbucket'* ]]; then
providerBranchRef="/pull-requests"
elif [[ "$domain" == *'visualstudio.com'* || "$domain" == *'azure.com'* || "$domain" == *'dev.azure.com'* ]]; then
providerBranchRef="/_git/pullrequests"
elif [[ "$domain" =~ cnb\.cool$ ]]; then
providerBranchRef="/-/merge_requests"
else
# Default to GitHub-style /pulls
providerBranchRef="/pulls"
fi
else
# Make # and % characters url friendly
# github.com/paulirish/git-open/pull/24
Expand All @@ -198,7 +254,11 @@ else
fi
fi

if [[ "$domain" == 'bitbucket.org' ]]; then
# Skip provider-specific branch ref modifications for pull-request only
# (issue handling is provider-specific and needs to be processed below)
if (( is_pull_request )); then
: # Do nothing, keep the providerBranchRef set earlier
elif [[ "$domain" == 'bitbucket.org' ]]; then
providerBranchRef="/src/$remote_ref"
elif [[ "${#pathargs[@]}" -ge 3 && ${pathargs[${#pathargs[@]} - 3]} == 'scm' ]]; then
# Bitbucket server always has /scm/ as the third to last segment in the url path, e.g. /scm/ppp/test-repo.git
Expand Down Expand Up @@ -257,20 +317,32 @@ elif [[ "$domain" =~ amazonaws\.com$ ]]; then
providerBranchRef="${providerBranchRef##*/}/--/"
elif [[ "$domain" =~ cnb\.cool$ ]]; then
# cnb.cool
# Replace URL path.
# Ex. repos/example -> repos/example/-
urlpath="$urlpath/-"
if [[ $remote_ref = "master" ]]; then
# repos/example/tree/master -> repos/example/-/tree/master
urlpath="$urlpath$providerBranchRef"
if (( is_issue || is_pull_request )); then
# For issue and pull-request, add the path prefix to urlpath
# providerBranchRef already starts with /issues/ or /-/merge_requests
urlpath="$urlpath/-"
else
# Replace URL path.
# Ex: repos/example -> repos/example/-
urlpath="$urlpath/-"
if [[ $remote_ref = "master" ]]; then
# repos/example/tree/master -> repos/example/-/tree/master
urlpath="$urlpath$providerBranchRef"
fi
fi
fi
openurl="$protocol://$domain/$urlpath"

if (( is_commit )); then
sha=$(git rev-parse HEAD)
openurl="$openurl/commit/$sha"
elif [[ $remote_ref != "master" || "$file" ]]; then
elif (( is_pull_request )); then
# For pull requests, just add the PR path
openurl="$openurl$providerBranchRef"
elif (( is_issue )); then
# For issues, just add the issue path
openurl="$openurl$providerBranchRef"
elif [[ $remote_ref != "master" || "$file" || "$path_suffix" ]]; then
# simplify URL for master
openurl="$openurl$providerBranchRef"
fi
Expand All @@ -284,6 +356,11 @@ if [ "$file" ]; then
openurl="$openurl/$absfile"
fi

# Add path_suffix from --path option
if [ "$path_suffix" ]; then
openurl="$openurl/$path_suffix"
fi

if [ "$suffix" ]; then
openurl="$openurl/$suffix"
fi
Expand Down
55 changes: 50 additions & 5 deletions git-open.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

## SYNOPSIS

`git open` [--issue] [--commit] [--suffix some_suffix] [remote-name] [branch-name]
`git open` [--issue] [--commit] [--pull-request] [--path] [--suffix some_suffix] [--file some_file] [remote-name] [branch-name]


## DESCRIPTION
Expand All @@ -15,14 +15,40 @@ git hosting services are supported.
## OPTIONS

`-c`, `--commit`
Open the current commit. See `EXAMPLES` for more information.
Open the current commit. See `EXAMPLES` for more information.
Only tested with GitHub & GitLab.

`-i`, `--issue`
Open the current issue. When the name of the current branch matches the right pattern,
it will open the webpage with that issue. See `EXAMPLES` for more information.
Open the current issue. When the name of the current branch matches the right pattern,
it will open the webpage with that issue. See `EXAMPLES` for more information.
This only works on GitHub, GitLab, Visual Studio Team Services and Team Foundation Server at the moment.

Supported branch name formats:
- `issue/123`, `issues/123`
- `bugfix-123`, `bugfix/123`
- `feature-123`, `feature/123`
- `fix-123`, `fix/123`
- `hotfix-123`, `hotfix/123`
- `#123` (just the issue number with hash)

`-r`, `--pull-request`
Open the pull requests / merge requests page for the repository.
The URL format is automatically determined based on the git hosting service:
- GitHub: `/pulls`
- GitLab: `/-/merge_requests`
- Bitbucket: `/pull-requests`
- Azure DevOps: `/_git/pullrequests`
- cnb.cool: `/-/merge_requests`

`--path`
Open the current directory path in the repo website. This uses the current
working directory relative to the repository root to construct the URL.
Useful when you want to browse the current folder in the browser.

`-f`, `--file` some_file
Append the given file path to the URL. The file path is relative to the
repository root. Use `--path` to automatically use the current directory.

`-s`, `--suffix` some_suffix
Append the given suffix to the url

Expand Down Expand Up @@ -56,9 +82,28 @@ It opens https://github.com/PROVIDED_REMOTE_USER/CURRENT_REPO/tree/PROVIDED_BRAN
git open --issue
```

If branches use naming convention of `issues/#123`, it opens
If branches use naming convention of `issues/#123`, `bugfix-123`, `feature/456`, etc., it opens
https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/issues/123

```sh
git open --pull-request
```

It opens the URL https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/pulls (GitHub)
or https://gitlab.example.com/user/repo/-/merge_requests (GitLab)

```sh
git open --path
```

It opens the URL https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/tree/CURRENT_BRANCH/current/directory

```sh
git open --file README.md
```

It opens the URL https://github.com/TRACKED_REMOTE_USER/CURRENT_REPO/tree/CURRENT_BRANCH/README.md

```sh
git open --suffix pulls
```
Expand Down
Loading