diff --git a/modules/git/commit.go b/modules/git/commit.go index 4a55645d30..6e8fcb3e08 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -218,6 +218,19 @@ func (c *Commit) HasPreviousCommit(commitHash SHA1) (bool, error) { return false, err } +// IsForcePush returns true if a push from oldCommitHash to this is a force push +func (c *Commit) IsForcePush(oldCommitID string) (bool, error) { + if oldCommitID == EmptySHA { + return false, nil + } + oldCommit, err := c.repo.GetCommit(oldCommitID) + if err != nil { + return false, err + } + hasPreviousCommit, err := c.HasPreviousCommit(oldCommit.ID) + return !hasPreviousCommit, err +} + // CommitsBeforeLimit returns num commits before current revision func (c *Commit) CommitsBeforeLimit(num int) ([]*Commit, error) { return c.repo.getCommitsBeforeLimit(c.ID, num) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index a62e0670fe..0e1b00ce08 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -323,6 +323,27 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout)) } +// CommitsBetweenNotBase returns a list that contains commits between [before, last), excluding commits in baseBranch. +// If before is detached (removed by reset + push) it is not included. +func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch string) ([]*Commit, error) { + var stdout []byte + var err error + if before == nil { + stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + } else { + stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + if err != nil && strings.Contains(err.Error(), "no merge base") { + // future versions of git >= 2.28 are likely to return an error if before and last have become unrelated. + // previously it would return the results of git rev-list before last so let's try that... + stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path}) + } + } + if err != nil { + return nil, err + } + return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout)) +} + // CommitsBetweenIDs return commits between twoe commits func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error) { lastCommit, err := repo.GetCommit(last) diff --git a/modules/repository/push.go b/modules/repository/push.go index 1fa711b359..aa1552351d 100644 --- a/modules/repository/push.go +++ b/modules/repository/push.go @@ -4,10 +4,8 @@ package repository import ( - "context" "strings" - repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" ) @@ -96,19 +94,3 @@ func (opts *PushUpdateOptions) RefName() string { func (opts *PushUpdateOptions) RepoFullName() string { return opts.RepoUserName + "/" + opts.RepoName } - -// IsForcePush detect if a push is a force push -func IsForcePush(ctx context.Context, opts *PushUpdateOptions) (bool, error) { - if !opts.IsUpdateBranch() { - return false, nil - } - - output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(opts.OldCommitID, "^"+opts.NewCommitID). - RunStdString(&git.RunOpts{Dir: repo_model.RepoPath(opts.RepoUserName, opts.RepoName)}) - if err != nil { - return false, err - } else if len(output) > 0 { - return true, nil - } - return false, nil -} diff --git a/services/pull/comment.go b/services/pull/comment.go index 068aca6cd1..933ad09a85 100644 --- a/services/pull/comment.go +++ b/services/pull/comment.go @@ -14,58 +14,6 @@ import ( issue_service "code.gitea.io/gitea/services/issue" ) -type commitBranchCheckItem struct { - Commit *git.Commit - Checked bool -} - -func commitBranchCheck(gitRepo *git.Repository, startCommit *git.Commit, endCommitID, baseBranch string, commitList map[string]*commitBranchCheckItem) error { - if startCommit.ID.String() == endCommitID { - return nil - } - - checkStack := make([]string, 0, 10) - checkStack = append(checkStack, startCommit.ID.String()) - - for len(checkStack) > 0 { - commitID := checkStack[0] - checkStack = checkStack[1:] - - item, ok := commitList[commitID] - if !ok { - continue - } - - if item.Commit.ID.String() == endCommitID { - continue - } - - if err := item.Commit.LoadBranchName(); err != nil { - return err - } - - if item.Commit.Branch == baseBranch { - continue - } - - if item.Checked { - continue - } - - item.Checked = true - - parentNum := item.Commit.ParentCount() - for i := 0; i < parentNum; i++ { - parentCommit, err := item.Commit.Parent(i) - if err != nil { - return err - } - checkStack = append(checkStack, parentCommit.ID.String()) - } - } - return nil -} - // getCommitIDsFromRepo get commit IDs from repo in between oldCommitID and newCommitID // isForcePush will be true if oldCommit isn't on the branch // Commit on baseBranch will skip @@ -82,47 +30,33 @@ func getCommitIDsFromRepo(ctx context.Context, repo *repo_model.Repository, oldC return nil, false, err } - if err = oldCommit.LoadBranchName(); err != nil { - return nil, false, err - } - - if len(oldCommit.Branch) == 0 { - commitIDs = make([]string, 2) - commitIDs[0] = oldCommitID - commitIDs[1] = newCommitID - - return commitIDs, true, err - } - newCommit, err := gitRepo.GetCommit(newCommitID) if err != nil { return nil, false, err } - commits, err := newCommit.CommitsBeforeUntil(oldCommitID) + isForcePush, err = newCommit.IsForcePush(oldCommitID) + if err != nil { + return nil, false, err + } + + if isForcePush { + commitIDs = make([]string, 2) + commitIDs[0] = oldCommitID + commitIDs[1] = newCommitID + + return commitIDs, isForcePush, err + } + + // Find commits between new and old commit exclusing base branch commits + commits, err := gitRepo.CommitsBetweenNotBase(newCommit, oldCommit, baseBranch) if err != nil { return nil, false, err } commitIDs = make([]string, 0, len(commits)) - commitChecks := make(map[string]*commitBranchCheckItem) - - for _, commit := range commits { - commitChecks[commit.ID.String()] = &commitBranchCheckItem{ - Commit: commit, - Checked: false, - } - } - - if err = commitBranchCheck(gitRepo, newCommit, oldCommitID, baseBranch, commitChecks); err != nil { - return - } - for i := len(commits) - 1; i >= 0; i-- { - commitID := commits[i].ID.String() - if item, ok := commitChecks[commitID]; ok && item.Checked { - commitIDs = append(commitIDs, commitID) - } + commitIDs = append(commitIDs, commits[i].ID.String()) } return commitIDs, isForcePush, err diff --git a/services/repository/push.go b/services/repository/push.go index cdf030396f..4b574e3440 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -206,12 +206,12 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { return fmt.Errorf("newCommit.CommitsBeforeUntil: %w", err) } - isForce, err := repo_module.IsForcePush(ctx, opts) + isForcePush, err := newCommit.IsForcePush(opts.OldCommitID) if err != nil { - log.Error("isForcePush %s:%s failed: %v", repo.FullName(), branch, err) + log.Error("IsForcePush %s:%s failed: %v", repo.FullName(), branch, err) } - if isForce { + if isForcePush { log.Trace("Push %s is a force push", opts.NewCommitID) cache.Remove(repo.GetCommitsCountCacheKey(opts.RefName(), true))