feat(mr): support Forgejo/Codeberg pull request URLs

Codeberg and other Forgejo instances expose pull requests under
refs/pull/<id>/head instead of GitLab's refs/merge-requests/<id>/head,
so passing a Codeberg pull request URL to git mr fetched the wrong
ref. Recognize the /pulls/<id> URL shape and switch to the matching
ref for that case, leaving the GitLab URL and bare numeric id
behavior unchanged.

Closes #1213
This commit is contained in:
Vijay Misal 2026-08-19 18:14:30 +05:30
parent dbf68bac59
commit 8cd8741a3f
No known key found for this signature in database
GPG key ID: 19904AA4CFDB27F9
4 changed files with 78 additions and 4 deletions

View file

@ -1428,8 +1428,8 @@ $ git reset-file .htaccess dc82b19
## git mr
Checks out a merge request from GitLab. Usage: `git mr <ID|URL> [REMOTE]`.
Default remote is `origin`.
Checks out a merge request from GitLab, or a pull request from Forgejo/Codeberg.
Usage: `git mr <ID|URL> [REMOTE]`. Default remote is `origin`.
``` bash
$ git mr 51
@ -1447,6 +1447,15 @@ From gitlab.com:owner/repository
Switched to branch 'mr/51'
```
A Forgejo/Codeberg pull request URL is also supported:
``` bash
$ git mr https://codeberg.org/owner/repository/pulls/51
From codeberg.org:owner/repository
* [new ref] refs/pull/51/head -> mr/51
Switched to branch 'mr/51'
```
Just like [git pr](#git-pr), `git mr` accepts a `clean` argument to trash all
`mr/` branches. Ensure current branch is not one.

View file

@ -7,6 +7,8 @@ if [ -z "${1-}" ] ; then
exit 1
fi
remote_ref_kind=merge-requests
if test "$1" = "clean"; then
git for-each-ref refs/heads/mr/* --format='%(refname)' | while read -r ref; do
git branch -D "${ref#refs/heads/}"
@ -15,13 +17,19 @@ if test "$1" = "clean"; then
elif [[ $1 =~ ^(https?://[^/]+/(.+))/merge_requests/([0-9]+).*$ ]]; then
remote=${BASH_REMATCH[1]}.git
id=${BASH_REMATCH[3]}
elif [[ $1 =~ ^(https?://[^/]+/(.+))/pulls/([0-9]+).*$ ]]; then
# Forgejo/Codeberg pull request URL, e.g.
# https://codeberg.org/owner/repository/pulls/453
remote=${BASH_REMATCH[1]}.git
id=${BASH_REMATCH[3]}
remote_ref_kind=pull
else
id=$1
remote=${2:-origin}
fi
branch=mr/$id
remote_ref=refs/merge-requests/$id/head
remote_ref=refs/$remote_ref_kind/$id/head
git fetch -fu "$remote" "$remote_ref:$branch"
git checkout "$branch"
git config --local --replace "branch.$branch.merge" "$remote_ref"

View file

@ -21,7 +21,9 @@ git-mr(1) -- Checks out a merge request locally
&lt;url&gt;
GitLab merge request URL in the format
`https://gitlab.tld/owner/repository/merge_requests/453`.
`https://gitlab.tld/owner/repository/merge_requests/453`, or a
Forgejo/Codeberg pull request URL in the format
`https://codeberg.tld/owner/repository/pulls/453`.
## EXAMPLES
@ -33,6 +35,13 @@ This checks out merge request `!51` from remote `origin` to branch `mr/51`.
* [new ref] refs/merge-requests/51/head -> mr/51
Switched to branch 'mr/51'
This checks out pull request `#51` from a Forgejo/Codeberg URL to branch `mr/51`.
$ git mr https://codeberg.org/owner/repository/pulls/51
From codeberg.org:owner/repository
* [new ref] refs/pull/51/head -> mr/51
Switched to branch 'mr/51'
## AUTHOR
Written by Étienne BERSAC <bersace03@gmail.com> from git-pr(1).

48
tests/git-mr.bats Normal file
View file

@ -0,0 +1,48 @@
# shellcheck shell=bash
source "$BATS_TEST_DIRNAME/test_util.sh"
setup_file() {
test_util.setup_file
PATH="$BATS_TEST_DIRNAME/bin:$PATH"
}
setup() {
test_util.cd_test
test_util.git_init
touch ./tracked
git add ./tracked
git commit -m 'Initial commit'
# Local bare "remote" standing in for a real GitLab/Forgejo instance,
# with a merge-requests ref and a pull ref pointing at the same commit.
remote_dir="$BATS_TEST_TMPDIR/remote.git"
git init --bare --initial-branch main "$remote_dir"
git push "$remote_dir" HEAD:refs/merge-requests/51/head
git push "$remote_dir" HEAD:refs/pull/51/head
git remote add origin "$remote_dir"
}
@test "checks out a merge request by numeric id" {
run git mr 51
assert_success
assert_output --partial "refs/merge-requests/51/head"
run git rev-parse --abbrev-ref HEAD
assert_output 'mr/51'
run git config --get branch.mr/51.merge
assert_output 'refs/merge-requests/51/head'
}
@test "checks out a merge request by numeric id and explicit remote" {
run git mr 51 "$remote_dir"
assert_success
assert_output --partial "refs/merge-requests/51/head"
run git rev-parse --abbrev-ref HEAD
assert_output 'mr/51'
}