From 4c38988ba9c9b174477ef34affa1538cbc4b619a Mon Sep 17 00:00:00 2001 From: Vijay Misal Date: Thu, 27 Aug 2026 11:29:00 +0530 Subject: [PATCH] feat(mr): support Forgejo/Codeberg pull request URLs (#1267) * feat(mr): support Forgejo/Codeberg pull request URLs Codeberg and other Forgejo instances expose pull requests under refs/pull//head instead of GitLab's refs/merge-requests//head, so passing a Codeberg pull request URL to git mr fetched the wrong ref. Recognize the /pulls/ URL shape and switch to the matching ref for that case, leaving the GitLab URL and bare numeric id behavior unchanged. Closes #1213 * test(git-mr): cover Forgejo/Codeberg URLs, regenerate man docs Add a bats test that exercises the pulls URL branch via a local insteadOf remote rewrite (no real HTTP endpoint needed), and bring git-mr.1/git-mr.html in line with the git-mr.md changes. --------- Co-authored-by: vjymisal0 --- Commands.md | 13 +++++++-- bin/git-mr | 10 ++++++- man/git-mr.1 | 18 ++++++++++++- man/git-mr.html | 12 ++++++++- man/git-mr.md | 11 +++++++- tests/git-mr.bats | 68 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 tests/git-mr.bats diff --git a/Commands.md b/Commands.md index 0c6ddbf..4cc2eee 100644 --- a/Commands.md +++ b/Commands.md @@ -1428,8 +1428,8 @@ $ git reset-file .htaccess dc82b19 ## git mr -Checks out a merge request from GitLab. Usage: `git mr [REMOTE]`. -Default remote is `origin`. +Checks out a merge request from GitLab, or a pull request from Forgejo/Codeberg. +Usage: `git mr [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. diff --git a/bin/git-mr b/bin/git-mr index 9a359d3..5cac8df 100755 --- a/bin/git-mr +++ b/bin/git-mr @@ -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" diff --git a/man/git-mr.1 b/man/git-mr.1 index d5483b4..65af4b8 100644 --- a/man/git-mr.1 +++ b/man/git-mr.1 @@ -28,7 +28,7 @@ The name of the remote to fetch from\. Defaults to \fBorigin\fR\. . .P -GitLab merge request URL in the format \fBhttps://gitlab\.tld/owner/repository/merge_requests/453\fR\. +GitLab merge request URL in the format \fBhttps://gitlab\.tld/owner/repository/merge_requests/453\fR, or a Forgejo/Codeberg pull request URL in the format \fBhttps://codeberg\.tld/owner/repository/pulls/453\fR\. . .SH "EXAMPLES" This checks out merge request \fB!51\fR from remote \fBorigin\fR to branch \fBmr/51\fR\. @@ -46,6 +46,22 @@ Switched to branch \'mr/51\' . .IP "" 0 . +.P +This checks out pull request \fB#51\fR from a Forgejo/Codeberg URL to branch \fBmr/51\fR\. +. +.IP "" 4 +. +.nf + +$ 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\' +. +.fi +. +.IP "" 0 +. .SH "AUTHOR" Written by Étienne BERSAC \fIbersace03@gmail\.com\fR from git\-pr(1)\. . diff --git a/man/git-mr.html b/man/git-mr.html index cb90daf..87bc1c8 100644 --- a/man/git-mr.html +++ b/man/git-mr.html @@ -94,7 +94,9 @@

<url>

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

@@ -106,6 +108,14 @@ From gitlab.com:owner/repository 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).

diff --git a/man/git-mr.md b/man/git-mr.md index 03a62fd..4f9b2b8 100644 --- a/man/git-mr.md +++ b/man/git-mr.md @@ -21,7 +21,9 @@ git-mr(1) -- Checks out a merge request locally <url> 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 from git-pr(1). diff --git a/tests/git-mr.bats b/tests/git-mr.bats new file mode 100644 index 0000000..651c0a6 --- /dev/null +++ b/tests/git-mr.bats @@ -0,0 +1,68 @@ +# 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' +} + +@test "checks out a Forgejo/Codeberg pull request by URL" { + # git-mr only recognizes http(s) URLs, so rewrite the fake Codeberg URL + # to our local bare "remote" via insteadOf rather than needing a real + # HTTP(S) endpoint. + git config --local url."$remote_dir".insteadOf "https://codeberg.org/owner/repository.git" + + run git mr "https://codeberg.org/owner/repository/pulls/51" + assert_success + assert_output --partial "refs/pull/51/head" + + run git rev-parse --abbrev-ref HEAD + assert_output 'mr/51' + + run git config --get branch.mr/51.merge + assert_output 'refs/pull/51/head' + + run git config --get branch.mr/51.remote + assert_output 'https://codeberg.org/owner/repository.git' +}