From 3af1ee88a5b67b62151fffee51a3909721adffd0 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Mon, 24 Aug 2026 13:26:27 -0700 Subject: [PATCH] fix(pull): name the head repository in default compare links (#39075) (#39079) Backport #39075 by @4RH1T3CT0R7 The "New Pull Request" buttons and the `/pulls/new/{branch}` redirect build their compare link as `{owner}:{branch}`. If a fork and its parent share an owner, through ALLOW_FORK_INTO_SAME_OWNER, or after a transfer, that head resolves back to the base repo, so the link compares the base against itself and 404s on a branch that only exists in the fork. Switching to `{owner}/{repo}:{branch}` names the head repo unambiguously, and it's what the compare page's own links already use. Also clears the 404 in #37649; the archived-parent half of that report is separate. Co-authored-by: Artem Lytkin <146867384+4RH1T3CT0R7@users.noreply.github.com> --- routers/web/repo/pull.go | 2 +- services/context/repo.go | 8 ++++++-- services/context/repo_test.go | 25 +++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 services/context/repo_test.go diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 62b956d06cd..e6a464f2895 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1316,7 +1316,7 @@ func PullsNewRedirect(ctx *context.Context) { return } redirectRepo = repo.BaseRepo - branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) + branch = context.CompareHeadRef(repo, branch) } ctx.Redirect(fmt.Sprintf("%s/compare/%s...%s?expand=1", redirectRepo.Link(), util.PathEscapeSegments(redirectRepo.DefaultBranch), util.PathEscapeSegments(branch))) } diff --git a/services/context/repo.go b/services/context/repo.go index 81df87a67f4..f2a6db06604 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -63,11 +63,15 @@ func (prc *PullRequestContext) CanCreateNewPull() bool { return can } +// CompareHeadRef formats the head side of a compare link, "owner/repo:branch" is only needed when a fork can share its base repo's owner +func CompareHeadRef(headRepo *repo_model.Repository, headBranch string) string { + return util.Iif(setting.Repository.AllowForkIntoSameOwner, headRepo.FullName(), headRepo.OwnerName) + ":" + headBranch +} + func (prc *PullRequestContext) MakeDefaultCompareLink(headBranch string) string { return prc.baseRepo.Link() + "/compare/" + util.PathEscapeSegments(prc.DefaultTargetBranch()) + "..." + - util.Iif(prc.SameRepo(), "", util.PathEscapeSegments(prc.headRepo.OwnerName)+":") + - util.PathEscapeSegments(headBranch) + util.PathEscapeSegments(util.Iif(prc.SameRepo(), headBranch, CompareHeadRef(prc.headRepo, headBranch))) } func (prc *PullRequestContext) DefaultTargetBranch() string { diff --git a/services/context/repo_test.go b/services/context/repo_test.go new file mode 100644 index 00000000000..868ce7783d3 --- /dev/null +++ b/services/context/repo_test.go @@ -0,0 +1,25 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package context + +import ( + "testing" + + repo_model "gitea.dev/models/repo" + "gitea.dev/modules/setting" + "gitea.dev/modules/test" + + "github.com/stretchr/testify/assert" +) + +func TestCompareHeadRef(t *testing.T) { + defer test.MockVariableValue(&setting.Repository.AllowForkIntoSameOwner)() + headRepo := &repo_model.Repository{OwnerName: "user", Name: "fork"} + + setting.Repository.AllowForkIntoSameOwner = false + assert.Equal(t, "user:my-branch", CompareHeadRef(headRepo, "my-branch")) + + setting.Repository.AllowForkIntoSameOwner = true + assert.Equal(t, "user/fork:my-branch", CompareHeadRef(headRepo, "my-branch")) +}