fix(pull): recover PRs whose recorded merge commit never reached the base repo (#39831) (#39840)

Backport #39831 by @lunny

When pushing the merge commit to the base repo fails (e.g.
non-fast-forward), the merge intent recorded by
https://github.com/go-gitea/gitea/pull/39442 is left behind.
`merged_commit_id` then points to a commit that does not exist in the
base repo, and `git branch --contains` fails with `no such commit`, so
the PR can never be checked or merged again.

- Clear the recorded merge intent if the push fails and the commit is
not on the base branch
- `IsCommitInBranch` now returns false for a missing commit, so
already-stuck PRs recover on the next check

Fixes: https://github.com/go-gitea/gitea/issues/39824
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Giteabot
2026-10-18 03:24:11 -07:00
committed by GitHub
co-authored by Lunny Xiao silverwind
parent cfcbdf41e2
commit 6bc0e8cd57
4 changed files with 77 additions and 2 deletions
+20 -2
View File
@@ -415,8 +415,26 @@ func (repo *Repository) GetCommitsFromIDs(ctx context.Context, commitIDs []strin
return commits
}
// IsCommitInBranch check if the commit is on the branch
// IsCommitInRepo checks if the commit exists in the repository.
func IsCommitInRepo(ctx context.Context, repo gitrepo.RepositoryFacade, commitID string) (bool, error) {
// "--quiet" makes it exit with 1 and no output if the commit doesn't exist
_, _, err := gitcmd.NewCommand("rev-parse", "--verify", "--quiet").
AddDynamicArguments(commitID + "^{commit}").
WithRepo(repo).
RunStdString(ctx)
if gitcmd.IsErrorExitCode(err, 1) {
return false, nil
}
return err == nil, err
}
// IsCommitInBranch checks if the commit is on the branch.
// It returns false (no error) if the commit doesn't exist or isn't on the branch.
func IsCommitInBranch(ctx context.Context, repo gitrepo.RepositoryFacade, commitID, branch string) (r bool, err error) {
if exist, err := IsCommitInRepo(ctx, repo, commitID); err != nil || !exist {
return false, err
}
stdout, _, err := gitcmd.NewCommand("branch", "--contains").
AddDynamicArguments(commitID, branch).
WithRepo(repo).
@@ -424,7 +442,7 @@ func IsCommitInBranch(ctx context.Context, repo gitrepo.RepositoryFacade, commit
if err != nil {
return false, err
}
return len(stdout) > 0, err
return len(stdout) > 0, nil
}
// GetCommitBranchStart returns the commit where the branch diverged
+18
View File
@@ -70,6 +70,20 @@ func TestGetCommitWithBadCommitID(t *testing.T) {
assert.True(t, IsErrNotExist(err))
}
func TestIsCommitInRepo(t *testing.T) {
bareRepo1, err := OpenRepositoryLocal(t.Context(), filepath.Join(testReposDir, "repo1_bare"))
assert.NoError(t, err)
defer bareRepo1.Close()
exist, err := IsCommitInRepo(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3")
assert.NoError(t, err)
assert.True(t, exist)
exist, err = IsCommitInRepo(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
assert.NoError(t, err)
assert.False(t, exist)
}
func TestIsCommitInBranch(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
bareRepo1, err := OpenRepositoryLocal(t.Context(), bareRepo1Path)
@@ -83,6 +97,10 @@ func TestIsCommitInBranch(t *testing.T) {
result, err = IsCommitInBranch(t.Context(), bareRepo1, "2839944139e0de9737a044f78b0e4b40d989a9e3", "branch2")
assert.NoError(t, err)
assert.False(t, result)
result, err = IsCommitInBranch(t.Context(), bareRepo1, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "branch1")
assert.NoError(t, err)
assert.False(t, result)
}
func TestRepository_CommitsBetween(t *testing.T) {