This commit is contained in:
maooc 2026-06-07 22:42:50 -03:00 committed by GitHub
commit 102a907a66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 298 additions and 22 deletions

View file

@ -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
@ -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

107
git-open
View file

@ -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
"
@ -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
@ -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")
@ -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
@ -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
@ -257,12 +317,18 @@ 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"
@ -270,7 +336,13 @@ 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
@ -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

View file

@ -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
@ -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
@ -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
```

View file

@ -666,6 +666,140 @@ setup() {
assert_output "https://cnb.cool/repos/repo/-/issues/10"
}
##
## Enhanced --issue with multiple branch name formats
##
@test "gh: git open --issue with bugfix- prefix" {
git remote set-url origin "github.com:paulirish/git-open.git"
git checkout -B "bugfix-123"
run ../git-open "--issue"
assert_output "https://github.com/paulirish/git-open/issues/123"
}
@test "gh: git open --issue with feature/ prefix" {
git remote set-url origin "github.com:paulirish/git-open.git"
git checkout -B "feature/456"
run ../git-open "--issue"
assert_output "https://github.com/paulirish/git-open/issues/456"
}
@test "gh: git open --issue with fix- prefix" {
git remote set-url origin "github.com:paulirish/git-open.git"
git checkout -B "fix-789"
run ../git-open "--issue"
assert_output "https://github.com/paulirish/git-open/issues/789"
}
@test "gh: git open --issue with hotfix- prefix" {
git remote set-url origin "github.com:paulirish/git-open.git"
git checkout -B "hotfix-999"
run ../git-open "--issue"
assert_output "https://github.com/paulirish/git-open/issues/999"
}
@test "gh: git open --issue with # prefix" {
git remote set-url origin "github.com:paulirish/git-open.git"
git checkout -B "#42"
run ../git-open "--issue"
assert_output "https://github.com/paulirish/git-open/issues/42"
}
@test "gh: git open --issue error when no issue number found" {
git remote set-url origin "github.com:paulirish/git-open.git"
git checkout -B "some-random-branch"
run ../git-open "--issue"
[ "$status" -eq 1 ]
assert_output --partial "Could not extract issue number from branch name"
assert_output --partial "Supported formats: issue/123, bugfix-123, #123, 123, etc."
}
##
## --pull-request tests
##
@test "gh: git open --pull-request" {
git remote set-url origin "git@github.com:user/repo.git"
run ../git-open "--pull-request"
assert_output "https://github.com/user/repo/pulls"
}
@test "gh: git open -r (short option)" {
git remote set-url origin "git@github.com:user/repo.git"
run ../git-open "-r"
assert_output "https://github.com/user/repo/pulls"
}
@test "gitlab: git open --pull-request" {
git remote set-url origin "git@gitlab.com:user/repo.git"
run ../git-open "--pull-request"
assert_output "https://gitlab.com/user/repo/-/merge_requests"
}
@test "gitlab: git open -r (short option)" {
git remote set-url origin "git@gitlab.com:user/repo.git"
run ../git-open "-r"
assert_output "https://gitlab.com/user/repo/-/merge_requests"
}
@test "bitbucket: git open --pull-request" {
git remote set-url origin "git@bitbucket.org:user/repo.git"
run ../git-open "--pull-request"
assert_output "https://bitbucket.org/user/repo/pull-requests"
}
@test "bitbucket: git open -r (short option)" {
git remote set-url origin "git@bitbucket.org:user/repo.git"
run ../git-open "-r"
assert_output "https://bitbucket.org/user/repo/pull-requests"
}
@test "cnb: git open --pull-request" {
git remote set-url origin "https://cnb.cool/repos/repo"
run ../git-open "--pull-request"
assert_output "https://cnb.cool/repos/repo/-/merge_requests"
}
@test "cnb: git open -r (short option)" {
git remote set-url origin "https://cnb.cool/repos/repo"
run ../git-open "-r"
assert_output "https://cnb.cool/repos/repo/-/merge_requests"
}
##
## --path tests
##
@test "gh: git open --path in root directory" {
git remote set-url origin "git@github.com:user/repo.git"
run ../git-open "--path"
assert_output "https://github.com/user/repo"
}
@test "gh: git open --path in subdirectory" {
git remote set-url origin "git@github.com:user/repo.git"
mkdir -p subdir/nested
# Create a file in the directory so git tracks it
touch subdir/nested/.gitkeep
git add subdir
git commit -m "add subdir" -q
# Run from the subdirectory with explicit cd
run bash -c 'cd subdir/nested && "$0" --path' "$BATS_TEST_DIRNAME/../git-open"
assert_output "https://github.com/user/repo/tree/master/subdir/nested"
}
@test "gh: git open --path combined with branch" {
git remote set-url origin "git@github.com:user/repo.git"
git checkout -B "feature-branch"
mkdir -p src/components
touch src/components/.gitkeep
git add src
git commit -m "add src dir" -q
# Run from the subdirectory with explicit cd
run bash -c 'cd src/components && "$0" --path' "$BATS_TEST_DIRNAME/../git-open"
assert_output "https://github.com/user/repo/tree/feature-branch/src/components"
}
teardown() {
cd ..
rm -rf "$foldername"