Skip to content

Commit 8bdc0ac

Browse files
authored
Fix pull request update showing too many commits with multiple branches (#22856)
When the base repository contains multiple branches with the same commits as the base branch, pull requests can show a long list of commits already in the base branch as having been added. What this is supposed to do is exclude commits already in the base branch. But the mechansim to do so assumed a commit only exists in a single branch. Now use `git rev-list A B --not branchName` instead of filtering commits afterwards. The logic to detect if there was a force push also was wrong for multiple branches. If the old commit existed in any branch in the base repository it would assume there was no force push. Instead check if the old commit is an ancestor of the new commit.
1 parent 689770c commit 8bdc0ac

File tree

5 files changed

+49
-99
lines changed

5 files changed

+49
-99
lines changed

modules/git/commit.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,19 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) {
218218
return false, err
219219
}
220220

221+
// IsForcePush returns true if a push from oldCommitHash to this is a force push
222+
func (c *Commit) IsForcePush(oldCommitID string) (bool, error) {
223+
if oldCommitID == EmptySHA {
224+
return false, nil
225+
}
226+
oldCommit, err := c.repo.GetCommit(oldCommitID)
227+
if err != nil {
228+
return false, err
229+
}
230+
hasPreviousCommit, err := c.HasPreviousCommit(oldCommit.ID)
231+
return !hasPreviousCommit, err
232+
}
233+
221234
// CommitsBeforeLimit returns num commits before current revision
222235
func (c *Commit) CommitsBeforeLimit(num int) ([]*Commit, error) {
223236
return c.repo.getCommitsBeforeLimit(c.ID, num)

modules/git/repo_commit.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,27 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in
323323
return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
324324
}
325325

326+
// CommitsBetweenNotBase returns a list that contains commits between [before, last), excluding commits in baseBranch.
327+
// If before is detached (removed by reset + push) it is not included.
328+
func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch string) ([]*Commit, error) {
329+
var stdout []byte
330+
var err error
331+
if before == nil {
332+
stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path})
333+
} else {
334+
stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path})
335+
if err != nil && strings.Contains(err.Error(), "no merge base") {
336+
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
337+
// previously it would return the results of git rev-list before last so let's try that...
338+
stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path})
339+
}
340+
}
341+
if err != nil {
342+
return nil, err
343+
}
344+
return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
345+
}
346+
326347
// CommitsBetweenIDs return commits between twoe commits
327348
func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error) {
328349
lastCommit, err := repo.GetCommit(last)

modules/repository/push.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
package repository
55

66
import (
7-
"context"
87
"strings"
98

10-
repo_model "code.gitea.io/gitea/models/repo"
119
"code.gitea.io/gitea/modules/git"
1210
)
1311

@@ -96,19 +94,3 @@ func (opts *PushUpdateOptions) RefName() string {
9694
func (opts *PushUpdateOptions) RepoFullName() string {
9795
return opts.RepoUserName + "/" + opts.RepoName
9896
}
99-
100-
// IsForcePush detect if a push is a force push
101-
func IsForcePush(ctx context.Context, opts *PushUpdateOptions) (bool, error) {
102-
if !opts.IsUpdateBranch() {
103-
return false, nil
104-
}
105-
106-
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(opts.OldCommitID, "^"+opts.NewCommitID).
107-
RunStdString(&git.RunOpts{Dir: repo_model.RepoPath(opts.RepoUserName, opts.RepoName)})
108-
if err != nil {
109-
return false, err
110-
} else if len(output) > 0 {
111-
return true, nil
112-
}
113-
return false, nil
114-
}

services/pull/comment.go

Lines changed: 12 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,58 +14,6 @@ import (
1414
issue_service "code.gitea.io/gitea/services/issue"
1515
)
1616

17-
type commitBranchCheckItem struct {
18-
Commit *git.Commit
19-
Checked bool
20-
}
21-
22-
func commitBranchCheck(gitRepo *git.Repository, startCommit *git.Commit, endCommitID, baseBranch string, commitList map[string]*commitBranchCheckItem) error {
23-
if startCommit.ID.String() == endCommitID {
24-
return nil
25-
}
26-
27-
checkStack := make([]string, 0, 10)
28-
checkStack = append(checkStack, startCommit.ID.String())
29-
30-
for len(checkStack) > 0 {
31-
commitID := checkStack[0]
32-
checkStack = checkStack[1:]
33-
34-
item, ok := commitList[commitID]
35-
if !ok {
36-
continue
37-
}
38-
39-
if item.Commit.ID.String() == endCommitID {
40-
continue
41-
}
42-
43-
if err := item.Commit.LoadBranchName(); err != nil {
44-
return err
45-
}
46-
47-
if item.Commit.Branch == baseBranch {
48-
continue
49-
}
50-
51-
if item.Checked {
52-
continue
53-
}
54-
55-
item.Checked = true
56-
57-
parentNum := item.Commit.ParentCount()
58-
for i := 0; i < parentNum; i++ {
59-
parentCommit, err := item.Commit.Parent(i)
60-
if err != nil {
61-
return err
62-
}
63-
checkStack = append(checkStack, parentCommit.ID.String())
64-
}
65-
}
66-
return nil
67-
}
68-
6917
// getCommitIDsFromRepo get commit IDs from repo in between oldCommitID and newCommitID
7018
// isForcePush will be true if oldCommit isn't on the branch
7119
// Commit on baseBranch will skip
@@ -82,47 +30,33 @@ func getCommitIDsFromRepo(ctx context.Context, repo *repo_model.Repository, oldC
8230
return nil, false, err
8331
}
8432

85-
if err = oldCommit.LoadBranchName(); err != nil {
86-
return nil, false, err
87-
}
88-
89-
if len(oldCommit.Branch) == 0 {
90-
commitIDs = make([]string, 2)
91-
commitIDs[0] = oldCommitID
92-
commitIDs[1] = newCommitID
93-
94-
return commitIDs, true, err
95-
}
96-
9733
newCommit, err := gitRepo.GetCommit(newCommitID)
9834
if err != nil {
9935
return nil, false, err
10036
}
10137

102-
commits, err := newCommit.CommitsBeforeUntil(oldCommitID)
38+
isForcePush, err = newCommit.IsForcePush(oldCommitID)
10339
if err != nil {
10440
return nil, false, err
10541
}
10642

107-
commitIDs = make([]string, 0, len(commits))
108-
commitChecks := make(map[string]*commitBranchCheckItem)
43+
if isForcePush {
44+
commitIDs = make([]string, 2)
45+
commitIDs[0] = oldCommitID
46+
commitIDs[1] = newCommitID
10947

110-
for _, commit := range commits {
111-
commitChecks[commit.ID.String()] = &commitBranchCheckItem{
112-
Commit: commit,
113-
Checked: false,
114-
}
48+
return commitIDs, isForcePush, err
11549
}
11650

117-
if err = commitBranchCheck(gitRepo, newCommit, oldCommitID, baseBranch, commitChecks); err != nil {
118-
return
51+
// Find commits between new and old commit exclusing base branch commits
52+
commits, err := gitRepo.CommitsBetweenNotBase(newCommit, oldCommit, baseBranch)
53+
if err != nil {
54+
return nil, false, err
11955
}
12056

57+
commitIDs = make([]string, 0, len(commits))
12158
for i := len(commits) - 1; i >= 0; i-- {
122-
commitID := commits[i].ID.String()
123-
if item, ok := commitChecks[commitID]; ok && item.Checked {
124-
commitIDs = append(commitIDs, commitID)
125-
}
59+
commitIDs = append(commitIDs, commits[i].ID.String())
12660
}
12761

12862
return commitIDs, isForcePush, err

services/repository/push.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,12 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
206206
return fmt.Errorf("newCommit.CommitsBeforeUntil: %w", err)
207207
}
208208

209-
isForce, err := repo_module.IsForcePush(ctx, opts)
209+
isForcePush, err := newCommit.IsForcePush(opts.OldCommitID)
210210
if err != nil {
211-
log.Error("isForcePush %s:%s failed: %v", repo.FullName(), branch, err)
211+
log.Error("IsForcePush %s:%s failed: %v", repo.FullName(), branch, err)
212212
}
213213

214-
if isForce {
214+
if isForcePush {
215215
log.Trace("Push %s is a force push", opts.NewCommitID)
216216

217217
cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))

0 commit comments

Comments
 (0)