From deec2b0929c5a7badd74df8bcb767a8cce51e33f Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 28 Apr 2026 23:03:50 +0200 Subject: [PATCH] Fix compare dropdown for branches without common history (#37470) --- modules/gitrepo/compare_test.go | 32 +++++ modules/gitrepo/merge.go | 6 +- options/locale/locale_en-US.json | 1 + routers/web/repo/compare.go | 197 ++++++++++-------------------- routers/web/repo/pull.go | 31 ++--- services/git/compare.go | 11 +- templates/repo/diff/compare.tmpl | 1 + tests/integration/compare_test.go | 35 ++++++ 8 files changed, 160 insertions(+), 154 deletions(-) diff --git a/modules/gitrepo/compare_test.go b/modules/gitrepo/compare_test.go index 2d2af0934db..91ee32bed54 100644 --- a/modules/gitrepo/compare_test.go +++ b/modules/gitrepo/compare_test.go @@ -4,9 +4,15 @@ package gitrepo import ( + "path/filepath" + "strings" "testing" + "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type mockRepository struct { @@ -17,6 +23,32 @@ func (r *mockRepository) RelativePath() string { return r.path } +func TestMergeBaseNoCommonHistory(t *testing.T) { + repoDir := filepath.Join(t.TempDir(), "repo.git") + require.NoError(t, gitcmd.NewCommand("init").AddDynamicArguments(repoDir).Run(t.Context())) + _, _, runErr := gitcmd.NewCommand("fast-import").WithDir(repoDir).WithStdinBytes([]byte(strings.TrimSpace(` +commit refs/heads/branch1 +committer User 1714310400 +0000 +data 12 +First commit +M 100644 inline file1.txt +data 12 +Hello from 1 + +commit refs/heads/branch2 +committer User 1714310400 +0000 +data 13 +Second commit +M 100644 inline file2.txt +data 12 +Hello from 2 +`))).RunStdString(t.Context()) + require.NoError(t, runErr) + mergeBase, err := MergeBase(t.Context(), &mockRepository{path: repoDir}, "branch1", "branch2") + assert.Empty(t, mergeBase) + assert.ErrorIs(t, err, util.ErrNotExist) +} + func TestRepoGetDivergingCommits(t *testing.T) { repo := &mockRepository{path: "repo1_bare"} do, err := GetDivergingCommits(t.Context(), repo, "master", "branch2") diff --git a/modules/gitrepo/merge.go b/modules/gitrepo/merge.go index 8d58e21c8db..51983929495 100644 --- a/modules/gitrepo/merge.go +++ b/modules/gitrepo/merge.go @@ -9,13 +9,17 @@ import ( "strings" "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/util" ) // MergeBase checks and returns merge base of two commits. func MergeBase(ctx context.Context, repo Repository, baseCommitID, headCommitID string) (string, error) { - mergeBase, _, err := RunCmdString(ctx, repo, gitcmd.NewCommand("merge-base"). + mergeBase, stderr, err := RunCmdString(ctx, repo, gitcmd.NewCommand("merge-base"). AddDashesAndList(baseCommitID, headCommitID)) if err != nil { + if gitcmd.IsErrorExitCode(err, 1) && strings.TrimSpace(stderr) == "" { + return "", util.NewNotExistErrorf("merge-base for %s and %s doesn't exist", baseCommitID, headCommitID) + } return "", fmt.Errorf("get merge-base of %s and %s failed: %w", baseCommitID, headCommitID, err) } return strings.TrimSpace(mergeBase), nil diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 6281ff8f547..2ee983ae183 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -1784,6 +1784,7 @@ "repo.pulls.review_only_possible_for_full_diff": "Review is only possible when viewing the full diff", "repo.pulls.filter_changes_by_commit": "Filter by commit", "repo.pulls.nothing_to_compare": "These branches are equal. There is no need to create a pull request.", + "repo.pulls.no_common_history": "These branches do not share a common merge base. Select a different base or compare branch.", "repo.pulls.nothing_to_compare_have_tag": "The selected branches/tags are equal.", "repo.pulls.nothing_to_compare_and_allow_empty_pr": "These branches are equal. This PR will be empty.", "repo.pulls.has_pull_request": "A pull request between these branches already exists: %[2]s#%[3]d", diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 7598ce561c3..f37c9ef2d17 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -189,8 +189,8 @@ func setCsvCompareContext(ctx *context.Context) { } } -// ParseCompareInfo parse compare info between two commit for preparing comparing references -func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo { +// parseCompareInfo parse compare info between two commit for preparing comparing references +func parseCompareInfo(ctx *context.Context) (*git_service.CompareInfo, error) { baseRepo := ctx.Repo.Repository fileOnly := ctx.FormBool("file-only") @@ -199,47 +199,29 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo { // remove the check when we support compare with carets if compareReq.BaseOriRefSuffix != "" { - ctx.HTTPError(http.StatusBadRequest, "Unsupported comparison syntax: ref with suffix") - return nil + return nil, util.NewInvalidArgumentErrorf("unsupported comparison syntax: ref with suffix") } // 2 get repository and owner for head headOwner, headRepo, err := common.GetHeadOwnerAndRepo(ctx, baseRepo, compareReq) - switch { - case errors.Is(err, util.ErrInvalidArgument): - ctx.HTTPError(http.StatusBadRequest, err.Error()) - return nil - case errors.Is(err, util.ErrNotExist): - ctx.NotFound(nil) - return nil - case err != nil: - ctx.ServerError("GetHeadOwnerAndRepo", err) - return nil + if err != nil { + return nil, err } - isSameRepo := baseRepo.ID == headRepo.ID - // 3 permission check // base repository's code unit read permission check has been done on web.go permBase := ctx.Repo.Permission // If we're not merging from the same repo: + isSameRepo := baseRepo.ID == headRepo.ID if !isSameRepo { // Assert ctx.Doer has permission to read headRepo's codes permHead, err := access_model.GetDoerRepoPermission(ctx, headRepo, ctx.Doer) if err != nil { - ctx.ServerError("GetDoerRepoPermission", err) - return nil + return nil, err } if !permHead.CanRead(unit.TypeCode) { - if log.IsTrace() { - log.Trace("Permission Denied: User: %-v cannot read code in Repo: %-v\nUser in headRepo has Permissions: %-+v", - ctx.Doer, - headRepo, - permHead) - } - ctx.NotFound(nil) - return nil + return nil, util.NewNotExistErrorf("") // permission: no error message for end users } ctx.Data["CanWriteToHeadRepo"] = permHead.CanWrite(unit.TypeCode) } @@ -250,24 +232,17 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo { baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefName) if baseRef == "" { - ctx.NotFound(nil) - return nil + return nil, util.NewNotExistErrorf("no base ref: %s", baseRefName) } - var headGitRepo *git.Repository - if isSameRepo { - headGitRepo = ctx.Repo.GitRepo - } else { - headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo) - if err != nil { - ctx.ServerError("OpenRepository", err) - return nil - } - defer headGitRepo.Close() + headGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, headRepo) + if err != nil { + ctx.ServerError("OpenRepository", err) + return nil, err } + headRef := headGitRepo.UnstableGuessRefByShortName(headRefName) if headRef == "" { - ctx.NotFound(nil) - return nil + return nil, util.NewNotExistErrorf("no head ref: %s", headRefName) } ctx.Data["BaseName"] = baseRepo.OwnerName @@ -291,12 +266,9 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo { var rootRepo *repo_model.Repository if baseRepo.IsFork { err = baseRepo.GetBaseRepo(ctx) - if err != nil { - if !repo_model.IsErrRepoNotExist(err) { - ctx.ServerError("Unable to find root repo", err) - return nil - } - } else { + if err != nil && !repo_model.IsErrRepoNotExist(err) { + return nil, err + } else if err == nil { rootRepo = baseRepo.BaseRepo } } @@ -313,42 +285,10 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo { } } - has := headRepo != nil - // 3. If the base is a forked from "RootRepo" and the owner of - // the "RootRepo" is the :headUser - set headRepo to that - if !has && rootRepo != nil && rootRepo.OwnerID == headOwner.ID { - headRepo = rootRepo - has = true - } - - // 4. If the ctx.Doer has their own fork of the baseRepo and the headUser is the ctx.Doer - // set the headRepo to the ownFork - if !has && ownForkRepo != nil && ownForkRepo.OwnerID == headOwner.ID { - headRepo = ownForkRepo - has = true - } - - // 5. If the headOwner has a fork of the baseRepo - use that - if !has { - headRepo = repo_model.GetForkedRepo(ctx, headOwner.ID, baseRepo.ID) - has = headRepo != nil - } - - // 6. If the baseRepo is a fork and the headUser has a fork of that use that - if !has && baseRepo.IsFork { - headRepo = repo_model.GetForkedRepo(ctx, headOwner.ID, baseRepo.ForkID) - has = headRepo != nil - } - - // 7. Otherwise if we're not the same repo and haven't found a repo give up - if !isSameRepo && !has { - ctx.Data["PageIsComparePull"] = false - } - ctx.Data["HeadRepo"] = headRepo ctx.Data["BaseCompareRepo"] = ctx.Repo.Repository - // If we have a rootRepo and it's different from: + // If we have a rootRepo, and it's different from: // 1. the computed base // 2. the computed head // then get the branches of it @@ -361,17 +301,15 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo { if !fileOnly { branches, tags, err := getBranchesAndTagsForRepo(ctx, rootRepo) if err != nil { - ctx.ServerError("GetBranchesForRepo", err) - return nil + return nil, err } - ctx.Data["RootRepoBranches"] = branches ctx.Data["RootRepoTags"] = tags } } } - // If we have a ownForkRepo and it's different from: + // If we have a ownForkRepo, and it's different from: // 1. The computed base // 2. The computed head // 3. The rootRepo (if we have one) @@ -386,8 +324,7 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo { if !fileOnly { branches, tags, err := getBranchesAndTagsForRepo(ctx, ownForkRepo) if err != nil { - ctx.ServerError("GetBranchesForRepo", err) - return nil + return nil, err } ctx.Data["OwnForkRepoBranches"] = branches ctx.Data["OwnForkRepoTags"] = tags @@ -395,33 +332,21 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo { } } - // Treat as pull request if both references are branches - if ctx.Data["PageIsComparePull"] == nil { - ctx.Data["PageIsComparePull"] = baseRef.IsBranch() && headRef.IsBranch() && permBase.CanReadIssuesOrPulls(true) - } - - if ctx.Data["PageIsComparePull"] == true && !permBase.CanReadIssuesOrPulls(true) { - if log.IsTrace() { - log.Trace("Permission Denied: User: %-v cannot create/read pull requests in Repo: %-v\nUser in baseRepo has Permissions: %-+v", - ctx.Doer, - baseRepo, - permBase) - } - ctx.NotFound(nil) - return nil - } - compareInfo, err := git_service.GetCompareInfo(ctx, baseRepo, headRepo, headGitRepo, baseRef, headRef, compareReq.DirectComparison(), fileOnly) if err != nil { - ctx.ServerError("GetCompareInfo", err) - return nil + return nil, err } + + // Treat as pull request if both references are branches + allowCreatePullRequest := baseRef.IsBranch() && headRef.IsBranch() && permBase.CanReadIssuesOrPulls(true) + allowCreatePullRequest = allowCreatePullRequest && compareInfo.MergeBase != "" + ctx.Data["PageIsComparePull"] = allowCreatePullRequest if compareReq.DirectComparison() { ctx.Data["BeforeCommitID"] = compareInfo.BaseCommitID } else { ctx.Data["BeforeCommitID"] = compareInfo.MergeBase } - return &compareInfo + return &compareInfo, nil } func prepareNewPullRequestTitleContent(ci *git_service.CompareInfo, commits []*git_model.SignCommitWithStatuses) (title, content string) { @@ -454,12 +379,11 @@ func prepareNewPullRequestTitleContent(ci *git_service.CompareInfo, commits []*g return title, content } -// PrepareCompareDiff renders compare diff page -func PrepareCompareDiff( - ctx *context.Context, - ci *git_service.CompareInfo, - whitespaceBehavior gitcmd.TrustedCmdArgs, -) (nothingToCompare bool) { +// prepareCompareDiff renders compare diff page. TODO: need to refactor it and other "compare diff" related functions together +func prepareCompareDiff(ctx *context.Context, ci *git_service.CompareInfo, whitespaceBehavior gitcmd.TrustedCmdArgs) (nothingToCompare bool) { + if ci.MergeBase == "" { + return true + } repo := ctx.Repo.Repository headCommitID := ci.HeadCommitID @@ -568,9 +492,6 @@ func PrepareCompareDiff( ctx.Data["CommitCount"] = len(commits) ctx.Data["title"], ctx.Data["content"] = prepareNewPullRequestTitleContent(ci, commits) - ctx.Data["Username"] = ci.HeadRepo.OwnerName - ctx.Data["Reponame"] = ci.HeadRepo.Name - setCompareContext(ctx, beforeCommit, headCommit, ci.HeadRepo.OwnerName, repo.Name) return false @@ -594,16 +515,24 @@ func getBranchesAndTagsForRepo(ctx gocontext.Context, repo *repo_model.Repositor // CompareDiff show different from one commit to another commit func CompareDiff(ctx *context.Context) { - ci := ParseCompareInfo(ctx) + ci, err := parseCompareInfo(ctx) if ctx.Written() { return } + if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) { + ctx.NotFound(nil) + return + } else if err != nil { + ctx.ServerError("ParseCompareInfo", err) + return + } ctx.Data["PageIsViewCode"] = true ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes ctx.Data["CompareInfo"] = ci - nothingToCompare := PrepareCompareDiff(ctx, ci, gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) + // TODO: need to refactor "prepare compare" related functions together + nothingToCompare := prepareCompareDiff(ctx, ci, gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string))) if ctx.Written() { return } @@ -621,16 +550,13 @@ func CompareDiff(ctx *context.Context) { return } - headBranches, err := git_model.FindBranchNames(ctx, git_model.FindBranchOptions{ - RepoID: ci.HeadRepo.ID, - ListOptions: db.ListOptionsAll, - IsDeletedBranch: optional.Some(false), - }) + headBranches, headTags, err := getBranchesAndTagsForRepo(ctx, ci.HeadRepo) if err != nil { - ctx.ServerError("GetBranches", err) + ctx.ServerError("GetBranchesAndTagsForRepo", err) return } ctx.Data["HeadBranches"] = headBranches + ctx.Data["HeadTags"] = headTags // For compare repo branches PrepareBranchList(ctx) @@ -638,13 +564,20 @@ func CompareDiff(ctx *context.Context) { return } - headTags, err := repo_model.GetTagNamesByRepoID(ctx, ci.HeadRepo.ID) - if err != nil { - ctx.ServerError("GetTagNamesByRepoID", err) - return + if ci.MergeBase != "" { + prepareCreatePullRequestPage(ctx, ci, nothingToCompare) + if ctx.Written() { + return + } + } else { + ctx.Flash.Error(ctx.Tr("repo.pulls.no_common_history"), true) + ctx.Data["PageIsComparePull"] = false + ctx.Data["CommitCount"] = 0 } - ctx.Data["HeadTags"] = headTags + ctx.HTML(http.StatusOK, tplCompare) +} +func prepareCreatePullRequestPage(ctx *context.Context, ci *git_service.CompareInfo, nothingToCompare bool) { if ctx.Data["PageIsComparePull"] == true { pr, err := issues_model.GetUnmergedPullRequest(ctx, ci.HeadRepo.ID, ctx.Repo.Repository.ID, ci.HeadRef.ShortName(), ci.BaseRef.ShortName(), issues_model.PullRequestFlowGithub) if err != nil { @@ -685,7 +618,7 @@ func CompareDiff(ctx *context.Context) { if content, ok := ctx.Data["content"].(string); ok && content != "" { // If a template content is set, prepend the "content". In this case that's only // applicable if you have one commit to compare and that commit has a message. - // In that case the commit message will be prepend to the template body. + // In that case the commit message will be prepended to the template body. if templateContent, ok := ctx.Data[pullRequestTemplateKey].(string); ok && templateContent != "" { // Re-use the same key as that's prioritized over the "content" key. // Add two new lines between the content to ensure there's always at least @@ -713,14 +646,8 @@ func CompareDiff(ctx *context.Context) { ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWrite(unit.TypePullRequests) - if unit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypePullRequests); err == nil { - config := unit.PullRequestsConfig() - ctx.Data["AllowMaintainerEdit"] = config.DefaultAllowMaintainerEdit - } else { - ctx.Data["AllowMaintainerEdit"] = false - } - - ctx.HTML(http.StatusOK, tplCompare) + prConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig() + ctx.Data["AllowMaintainerEdit"] = prConfig.DefaultAllowMaintainerEdit } // attachCommentsToLines attaches comments to their corresponding diff lines diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 7b31a26d6f1..805fa6f419b 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1281,24 +1281,26 @@ func PullsNewRedirect(ctx *context.Context) { // CompareAndPullRequestPost response for creating pull request func CompareAndPullRequestPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateIssueForm) - ctx.Data["Title"] = ctx.Tr("repo.pulls.compare_changes") - ctx.Data["PageIsComparePull"] = true - ctx.Data["IsDiffCompare"] = true - ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes - ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled - upload.AddUploadContext(ctx, "comment") - ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWrite(unit.TypePullRequests) + repo := ctx.Repo.Repository - var ( - repo = ctx.Repo.Repository - attachments []string - ) - - ci := ParseCompareInfo(ctx) + ci, err := parseCompareInfo(ctx) if ctx.Written() { return } - + if errors.Is(err, util.ErrNotExist) { + ctx.JSONErrorNotFound() + return + } else if errors.Is(err, util.ErrInvalidArgument) { + ctx.JSONError(err.Error()) + return + } else if err != nil { + ctx.ServerError("ParseCompareInfo", err) + return + } + if ci.MergeBase == "" { + ctx.JSONError(ctx.Tr("repo.pulls.no_common_history")) + return + } validateRet := ValidateRepoMetasForNewIssue(ctx, *form, true) if ctx.Written() { return @@ -1306,6 +1308,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { labelIDs, assigneeIDs, milestoneID, projectID := validateRet.LabelIDs, validateRet.AssigneeIDs, validateRet.MilestoneID, validateRet.ProjectID + var attachments []string if setting.Attachment.Enabled { attachments = form.Files } diff --git a/services/git/compare.go b/services/git/compare.go index a8c29801125..40a0eaac089 100644 --- a/services/git/compare.go +++ b/services/git/compare.go @@ -45,6 +45,7 @@ func (ci *CompareInfo) DirectComparison() bool { // GetCompareInfo generates and returns compare information between base and head branches of repositories. // It does its best to fill the fields as many as it can. +// MergeBase can be empty if the base and head are unrelated. func GetCompareInfo(ctx context.Context, baseRepo, headRepo *repo_model.Repository, headGitRepo *git.Repository, baseRef, headRef git.RefName, directComparison, fileOnly bool) (compareInfo CompareInfo, err error) { baseCommitID, err1 := gitrepo.GetFullCommitID(ctx, baseRepo, baseRef.String()) headCommitID, err2 := gitrepo.GetFullCommitID(ctx, headRepo, headRef.String()) @@ -75,13 +76,17 @@ func GetCompareInfo(ctx context.Context, baseRepo, headRepo *repo_model.Reposito if !directComparison { compareInfo.MergeBase, err = gitrepo.MergeBase(ctx, headRepo, compareInfo.BaseCommitID, compareInfo.HeadCommitID) - if err != nil { + if err != nil && !errors.Is(err, util.ErrNotExist) { return compareInfo, fmt.Errorf("MergeBase: %w", err) } } else { compareInfo.MergeBase = compareInfo.BaseCommitID } + if compareInfo.MergeBase == "" { + return compareInfo, nil + } + // We have a common base - therefore we know that ... should work if !fileOnly { // In git log/rev-list, the "..." syntax represents the symmetric difference between two references, @@ -92,12 +97,10 @@ func GetCompareInfo(ctx context.Context, baseRepo, headRepo *repo_model.Reposito if err != nil { return compareInfo, fmt.Errorf("ShowPrettyFormatLogToList: %w", err) } - } else { - compareInfo.Commits = []*git.Commit{} } // Count number of changed files. - // This probably should be removed as we need to use shortstat elsewhere + // TODO: This probably should be removed as we need to use shortstat elsewhere // Now there is git diff --shortstat but this appears to be slower than simply iterating with --nameonly compareInfo.NumFiles, err = headGitRepo.GetDiffNumChangedFiles(compareInfo.BaseCommitID, compareInfo.HeadCommitID, directComparison) return compareInfo, err diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index afd44f26a47..9ed4b73174d 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -13,6 +13,7 @@ {{ctx.Locale.Tr "action.compare_commits_general"}} {{end}} + {{template "base/alert" .}} {{$BaseCompareName := $.Repository.FullName -}} {{$HeadCompareName := $.HeadRepo.FullName -}} {{$OwnForkCompareName := "" -}} diff --git a/tests/integration/compare_test.go b/tests/integration/compare_test.go index 3f0034c86be..5e50fcf0431 100644 --- a/tests/integration/compare_test.go +++ b/tests/integration/compare_test.go @@ -10,13 +10,16 @@ import ( "strings" "testing" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/test" repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestCompareTag(t *testing.T) { @@ -124,6 +127,38 @@ func TestCompareBranches(t *testing.T) { inspectCompare(t, htmlDoc, diffCount, diffChanges) } +func TestCompareBranchesNoCommonMergeBase(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "repo1"}) + + repoPath := repo_model.RepoPath(user2.Name, repo1.Name) + _, _, runErr := gitcmd.NewCommand("fast-import").WithDir(repoPath).WithStdinBytes([]byte(strings.TrimSpace(` +commit refs/heads/unrelated-history +committer User 1714310400 +0000 +data 13 +Second commit +M 100644 inline file2.txt +data 12 +Hello from 2 +`))).RunStdString(t.Context()) + require.NoError(t, runErr) + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/user2/repo1/compare/master...unrelated-history") + resp := session.MakeRequest(t, req, http.StatusOK) + body := resp.Body.String() + htmlDoc := NewHTMLParser(t, resp.Body) + + selection := htmlDoc.doc.Find(".ui.dropdown.select-branch") + assert.Lenf(t, selection.Nodes, 2, "The template has changed") + assert.Contains(t, body, "These branches do not share a common merge base") + assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...unrelated-history"]`).Length()) + assert.Equal(t, 1, htmlDoc.doc.Find(`a.item[href="/user2/repo1/compare/master...master"]`).Length()) + assert.Equal(t, 0, htmlDoc.doc.Find(".pullrequest-form").Length()) +} + func TestCompareCodeExpand(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})