fix(pull): reject pull requests without common history (#39872)

Pull requests between branches without a common history show no commits
and can never be merged. The web form already rejects them, but the API
and crafted `..` compare POSTs did not.

The API compared with `..` since
https://github.com/go-gitea/gitea/pull/36105, it now uses `...` again
and returns 422 when there is no merge base.
This commit is contained in:
silverwind authored and GitHub committed 2026-11-01 07:53:22 +00:00
1 parent 91a49fadc1
commit 2a5c34c379
3 files changed
+13 -9

No files matched your search

+5 -1
View File
@@ -404,7 +404,7 @@ func CreatePullRequest(ctx *context.APIContext) {
)
// Get repo/branch information
compareResult, closer := parseCompareInfo(ctx, form.Base+".."+form.Head)
compareResult, closer := parseCompareInfo(ctx, form.Base+"..."+form.Head)
if ctx.Written() {
return
}
@@ -414,6 +414,10 @@ func CreatePullRequest(ctx *context.APIContext) {
ctx.APIError(http.StatusUnprocessableEntity, "Invalid PullRequest: base and head must be branches")
return
}
if compareResult.CompareBase == "" {
ctx.APIError(http.StatusUnprocessableEntity, "Invalid PullRequest: base and head have no common history")
return
}
// Check if another PR exists with the same targets
existingPr, err := issues_model.GetUnmergedPullRequest(ctx, compareResult.HeadRepo.ID, ctx.Repo.Repository.ID,
+1 -1
View File
@@ -1362,7 +1362,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}
ci := comparePageInfo.compareInfo
if ci.CompareBase == "" {
if ci.CompareBase == "" || ci.DirectComparison() {
ctx.JSONError(ctx.Tr("repo.pulls.no_common_history"))
return
}
+7 -7
View File
@@ -451,13 +451,7 @@ func TestCantMergeUnrelated(t *testing.T) {
assert.NoError(t, err)
commitSha := strings.TrimSpace(stdout)
_, _, err = gitcmd.NewCommand("branch", "unrelated").
AddDynamicArguments(commitSha).
WithRepo(repo1).
RunStdString(t.Context())
assert.NoError(t, err)
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n")
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "unrelated", "README.md", "Hello, World (Edited Once)\n")
// Use API to create a conflicting pr
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
@@ -468,6 +462,12 @@ func TestCantMergeUnrelated(t *testing.T) {
}).AddTokenAuth(token)
session.MakeRequest(t, req, http.StatusCreated)
assert.NoError(t, git.UpdateRef(t.Context(), repo1, git.BranchPrefix+"unrelated", commitSha))
session.MakeRequest(t, NewRequestWithJSON(t, http.MethodPost, "/api/v1/repos/user1/repo1/pulls", &api.CreatePullRequestOption{
Head: "unrelated", Base: "base", Title: "create an unrelated pr",
}).AddTokenAuth(token), http.StatusUnprocessableEntity)
session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user1/repo1/compare/master..unrelated", map[string]string{"title": "create an unrelated pr"}), http.StatusBadRequest)
// Now this PR could be marked conflict - or at least a race may occur - so drop down to pure code at this point...
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
HeadRepoID: repo1.ID,