mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-11 19:03:10 +09:00
Fix the wrong push commits in the pull request when force push (#36914)
Fix #36905 The changes focus on force-push PR timeline handling and commit range calculation: - Reworked pull-request push comment creation to use a new `gitrepo.GetCommitIDsBetweenReverse` helper, with special handling for force pushes (merge-base based range, tolerate missing/invalid old commits, and keep force-push timeline entries). - Added `Comment.GetPushActionContent` to parse push comment payloads and used it to delete only non-force-push push comments during force pushes. - Removed the old `Repository.CommitsBetweenNotBase` helper from `modules/git/repo_commit.go` in favor of the new commit ID range helper. - Added tests for `GetCommitIDsBetweenReverse` (normal range, `notRef` filtering, fallback branch usage) and expanded pull comment tests to cover force-push edge cases. <img width="989" height="563" alt="image" src="https://github.com/user-attachments/assets/a01e1bc2-fa8a-4028-8a35-d484e601ff3b" /> --------- Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com> Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -5,117 +5,166 @@ package pull
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
// getCommitIDsFromRepo get commit IDs from repo in between oldCommitID and newCommitID
|
||||
// Commit on baseBranch will skip
|
||||
func getCommitIDsFromRepo(ctx context.Context, repo *repo_model.Repository, oldCommitID, newCommitID, baseBranch string) (commitIDs []string, err error) {
|
||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
||||
const maxPushCommitsInCommentCount = 1000
|
||||
|
||||
func preparePushPullCommentPushActionContent(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string, isForcePush bool) (data issues_model.PushActionContent, shouldCreate bool, err error) {
|
||||
if isForcePush {
|
||||
// if it's a force push, we need to get the whole pull request commits
|
||||
// the force-push timeline comment should always be created, so all errors are ignored and logged only.
|
||||
mergeBase, err := gitrepo.MergeBase(ctx, pr.BaseRepo, pr.BaseBranch, newCommitID)
|
||||
if err != nil {
|
||||
log.Debug("MergeBase %q..%q failed: %v", pr.BaseBranch, newCommitID, err)
|
||||
} else {
|
||||
data.CommitIDs, err = gitrepo.GetCommitIDsBetweenReverse(ctx, pr.BaseRepo, mergeBase, newCommitID, "", maxPushCommitsInCommentCount)
|
||||
if err != nil {
|
||||
log.Debug("GetCommitIDsBetweenReverse %q..%q failed: %v", mergeBase, newCommitID, err)
|
||||
}
|
||||
}
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
// for a normal push, it maybe an empty pull request, only non-empty pull request need to create push comment
|
||||
data.CommitIDs, err = gitrepo.GetCommitIDsBetweenReverse(ctx, pr.BaseRepo, oldCommitID, newCommitID, pr.BaseBranch, maxPushCommitsInCommentCount)
|
||||
return data, len(data.CommitIDs) > 0, err
|
||||
}
|
||||
|
||||
func reconcileOldCommitCommentsForForcePush(ctx context.Context, oldCommitComments []*issues_model.Comment, newData *issues_model.PushActionContent) (needDeleteCommentIDs []int64) {
|
||||
newPushCommitIDMaps := container.SetOf(newData.CommitIDs...)
|
||||
for _, oldCommitComment := range oldCommitComments {
|
||||
oldData, err := oldCommitComment.GetPushActionContent()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if oldData.IsForcePush {
|
||||
// old comment is for force push, it should be kept
|
||||
continue
|
||||
}
|
||||
|
||||
// remove the old comment's commit IDs which are not in the new "force" push
|
||||
oldData.CommitIDs = slices.DeleteFunc(oldData.CommitIDs, func(oldCommitID string) bool { return !newPushCommitIDMaps.Contains(oldCommitID) })
|
||||
// if old comment doesn't contain any commit ID after the force push, then it can be deleted
|
||||
if len(oldData.CommitIDs) == 0 {
|
||||
needDeleteCommentIDs = append(needDeleteCommentIDs, oldCommitComment.ID)
|
||||
continue
|
||||
}
|
||||
// remove new comment's commit IDs which are already in old comment
|
||||
for _, oldCommitID := range oldData.CommitIDs {
|
||||
newData.CommitIDs = slices.DeleteFunc(newData.CommitIDs, func(newCommitID string) bool { return newCommitID == oldCommitID })
|
||||
}
|
||||
|
||||
// update the old comment's content (the commit IDs have been changed)
|
||||
updatedOldContent, _ := json.Marshal(oldData)
|
||||
_, err = db.GetEngine(ctx).ID(oldCommitComment.ID).Cols("content").NoAutoTime().Update(&issues_model.Comment{Content: string(updatedOldContent)})
|
||||
if err != nil {
|
||||
log.Error("Update Comment content failed: %v", err)
|
||||
}
|
||||
}
|
||||
return needDeleteCommentIDs
|
||||
}
|
||||
|
||||
func cleanUpOldCommitCommentsForNewForcePush(ctx context.Context, pr *issues_model.PullRequest, data *issues_model.PushActionContent) error {
|
||||
// All old non-force-push commit comments will be deleted if they are not in the new commit list.
|
||||
var oldCommitComments []*issues_model.Comment
|
||||
err := db.GetEngine(ctx).Table("comment").
|
||||
Where("issue_id = ?", pr.IssueID).And("type = ?", issues_model.CommentTypePullRequestPush).
|
||||
Find(&oldCommitComments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
oldCommit, err := gitRepo.GetCommit(oldCommitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
newCommit, err := gitRepo.GetCommit(newCommitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
needDeleteCommentIDs := reconcileOldCommitCommentsForForcePush(ctx, oldCommitComments, data)
|
||||
if len(needDeleteCommentIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find commits between new and old commit excluding base branch commits
|
||||
commits, err := gitRepo.CommitsBetweenNotBase(newCommit, oldCommit, baseBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commitIDs = make([]string, 0, len(commits))
|
||||
for i := len(commits) - 1; i >= 0; i-- {
|
||||
commitIDs = append(commitIDs, commits[i].ID.String())
|
||||
}
|
||||
|
||||
return commitIDs, err
|
||||
_, err = db.GetEngine(ctx).In("id", needDeleteCommentIDs).Delete(&issues_model.Comment{})
|
||||
return err
|
||||
}
|
||||
|
||||
// CreatePushPullComment create push code to pull base comment
|
||||
func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *issues_model.PullRequest, oldCommitID, newCommitID string, isForcePush bool) (comment *issues_model.Comment, err error) {
|
||||
if pr.HasMerged || oldCommitID == "" || newCommitID == "" {
|
||||
return nil, nil //nolint:nilnil // return nil because no comment needs to be created
|
||||
func CreatePushPullComment(ctx context.Context, pusher *user_model.User, pr *issues_model.PullRequest, oldRef, newRef string, isForcePush bool) (comment *issues_model.Comment, created bool, err error) {
|
||||
if pr.HasMerged || oldRef == "" || newRef == "" {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
opts := &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
Doer: pusher,
|
||||
Repo: pr.BaseRepo,
|
||||
Issue: pr.Issue,
|
||||
}
|
||||
|
||||
var data issues_model.PushActionContent
|
||||
data.CommitIDs, err = getCommitIDsFromRepo(ctx, pr.BaseRepo, oldCommitID, newCommitID, pr.BaseBranch)
|
||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
|
||||
if err != nil {
|
||||
// For force-push events, a missing/unreachable old commit should not prevent
|
||||
// deleting stale push comments or creating the force-push timeline entry.
|
||||
if !isForcePush {
|
||||
return nil, err
|
||||
}
|
||||
log.Error("getCommitIDsFromRepo: %v", err)
|
||||
return nil, false, err
|
||||
}
|
||||
// It maybe an empty pull request. Only non-empty pull request need to create push comment
|
||||
// for force push, we always need to delete the old push comment so don't return here.
|
||||
if len(data.CommitIDs) == 0 && !isForcePush {
|
||||
return nil, nil //nolint:nilnil // return nil because no comment needs to be created
|
||||
defer closer.Close()
|
||||
|
||||
oldCommitID := oldRef
|
||||
if !git.IsEmptyCommitID(oldRef) {
|
||||
oldCommitID, err = gitRepo.GetRefCommitID(oldRef)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
newCommitID, err := gitRepo.GetRefCommitID(newRef)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return db.WithTx2(ctx, func(ctx context.Context) (*issues_model.Comment, error) {
|
||||
data, shouldCreate, err := preparePushPullCommentPushActionContent(ctx, pr, oldCommitID, newCommitID, isForcePush)
|
||||
if !shouldCreate {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
comment, err = db.WithTx2(ctx, func(ctx context.Context) (comment *issues_model.Comment, err error) {
|
||||
if isForcePush {
|
||||
// Push commits comment should not have history, cross references, reactions and other
|
||||
// plain comment related records, so that we just need to delete the comment itself.
|
||||
if _, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
|
||||
And("type = ?", issues_model.CommentTypePullRequestPush).
|
||||
NoAutoCondition().
|
||||
Delete(new(issues_model.Comment)); err != nil {
|
||||
return nil, err
|
||||
err := cleanUpOldCommitCommentsForNewForcePush(ctx, pr, &data)
|
||||
if err != nil {
|
||||
log.Error("CleanUpOldCommitComments failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(data.CommitIDs) > 0 {
|
||||
dataJSON, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// if the push has commit IDs, add a "normal push" commit comment
|
||||
dataJSON, _ := json.Marshal(data)
|
||||
opts := &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
Doer: pusher,
|
||||
Repo: pr.BaseRepo,
|
||||
Issue: pr.Issue,
|
||||
Content: string(dataJSON),
|
||||
}
|
||||
opts.Content = string(dataJSON)
|
||||
comment, err = issues_model.CreateComment(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if isForcePush { // if it's a force push, we need to add a force push comment
|
||||
data.CommitIDs = []string{oldCommitID, newCommitID}
|
||||
data.IsForcePush = true
|
||||
dataJSON, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if isForcePush {
|
||||
// if it's a force push, we need to add a force push comment
|
||||
forcePushDataJSON, _ := json.Marshal(&issues_model.PushActionContent{IsForcePush: true, CommitIDs: []string{oldCommitID, newCommitID}})
|
||||
opts := &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
Doer: pusher,
|
||||
Repo: pr.BaseRepo,
|
||||
Issue: pr.Issue,
|
||||
Content: string(forcePushDataJSON),
|
||||
|
||||
// It seems the field is unnecessary anymore because PushActionContent includes IsForcePush field.
|
||||
// However, it can't be simply removed.
|
||||
IsForcePush: true, // See the comment of "Comment.IsForcePush"
|
||||
}
|
||||
opts.Content = string(dataJSON)
|
||||
opts.IsForcePush = true // FIXME: it seems the field is unnecessary any more because PushActionContent includes IsForcePush field
|
||||
comment, err = issues_model.CreateComment(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return comment, err
|
||||
return comment, nil
|
||||
})
|
||||
return comment, comment != nil, err
|
||||
}
|
||||
|
||||
@@ -6,168 +6,118 @@ package pull
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreatePushPullCommentForcePushDeletesOldComments(t *testing.T) {
|
||||
t.Run("base-branch-only", func(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
pusher := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
|
||||
require.NoError(t, pr.LoadIssue(t.Context()))
|
||||
require.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
|
||||
assert.NoError(t, pr.LoadIssue(t.Context()))
|
||||
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
|
||||
pusher := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), pr.BaseRepo)
|
||||
require.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
insertCommitComment := func(t *testing.T, content issues_model.PushActionContent) {
|
||||
contentJSON, _ := json.Marshal(content)
|
||||
_, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
Doer: pusher,
|
||||
Repo: pr.BaseRepo,
|
||||
Issue: pr.Issue,
|
||||
Content: "{}",
|
||||
Content: string(contentJSON),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
_, err = issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
Doer: pusher,
|
||||
Repo: pr.BaseRepo,
|
||||
Issue: pr.Issue,
|
||||
Content: "{}",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
assertCommitCommentCount := func(t *testing.T, expectedTotalCount, expectedForcePushCount int) {
|
||||
comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
|
||||
IssueID: pr.IssueID,
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, comments, 2)
|
||||
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), pr.BaseRepo)
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
headCommit, err := gitRepo.GetBranchCommit(pr.BaseBranch)
|
||||
assert.NoError(t, err)
|
||||
oldCommit := headCommit
|
||||
if headCommit.ParentCount() > 0 {
|
||||
parentCommit, err := headCommit.Parent(0)
|
||||
assert.NoError(t, err)
|
||||
oldCommit = parentCommit
|
||||
}
|
||||
|
||||
comment, err := CreatePushPullComment(t.Context(), pusher, pr, oldCommit.ID.String(), headCommit.ID.String(), true)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, comment)
|
||||
var createdData issues_model.PushActionContent
|
||||
assert.NoError(t, json.Unmarshal([]byte(comment.Content), &createdData))
|
||||
assert.True(t, createdData.IsForcePush)
|
||||
|
||||
// When both commits are on the base branch, CommitsBetweenNotBase should
|
||||
// typically return no commits, so only the force-push comment is expected.
|
||||
commits, err := gitRepo.CommitsBetweenNotBase(headCommit, oldCommit, pr.BaseBranch)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, commits)
|
||||
|
||||
comments, err = issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
|
||||
IssueID: pr.IssueID,
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, comments, 1)
|
||||
|
||||
forcePushCount := 0
|
||||
require.NoError(t, err)
|
||||
totalCount, forcePushCount := len(comments), 0
|
||||
for _, comment := range comments {
|
||||
var pushData issues_model.PushActionContent
|
||||
assert.NoError(t, json.Unmarshal([]byte(comment.Content), &pushData))
|
||||
pushData, err := comment.GetPushActionContent()
|
||||
require.NoError(t, err)
|
||||
if pushData.IsForcePush {
|
||||
forcePushCount++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 1, forcePushCount)
|
||||
})
|
||||
assert.Equal(t, expectedTotalCount, totalCount, "total comment count should match")
|
||||
assert.Equal(t, expectedForcePushCount, forcePushCount, "force push comment count should match")
|
||||
}
|
||||
|
||||
t.Run("head-vs-base-branch", func(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
|
||||
assert.NoError(t, pr.LoadIssue(t.Context()))
|
||||
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
||||
|
||||
pusher := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
_, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
Doer: pusher,
|
||||
Repo: pr.BaseRepo,
|
||||
Issue: pr.Issue,
|
||||
Content: "{}",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
_, err = issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
Doer: pusher,
|
||||
Repo: pr.BaseRepo,
|
||||
Issue: pr.Issue,
|
||||
Content: "{}",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
|
||||
IssueID: pr.IssueID,
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, comments, 2)
|
||||
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), pr.BaseRepo)
|
||||
assert.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
// In this subtest, use the head branch for the new commit and the base branch
|
||||
// for the old commit so that CommitsBetweenNotBase returns non-empty results.
|
||||
headCommit, err := gitRepo.GetBranchCommit(pr.HeadBranch)
|
||||
assert.NoError(t, err)
|
||||
t.Run("base-branch-only", func(t *testing.T) {
|
||||
require.NoError(t, db.TruncateBeans(t.Context(), &issues_model.Comment{}))
|
||||
insertCommitComment(t, issues_model.PushActionContent{})
|
||||
insertCommitComment(t, issues_model.PushActionContent{})
|
||||
assertCommitCommentCount(t, 2, 0)
|
||||
|
||||
baseCommit, err := gitRepo.GetBranchCommit(pr.BaseBranch)
|
||||
assert.NoError(t, err)
|
||||
oldCommit := baseCommit
|
||||
|
||||
comment, err := CreatePushPullComment(t.Context(), pusher, pr, oldCommit.ID.String(), headCommit.ID.String(), true)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, comment)
|
||||
var createdData issues_model.PushActionContent
|
||||
assert.NoError(t, json.Unmarshal([]byte(comment.Content), &createdData))
|
||||
// force push, the old push comments should be deleted, and one new force-push comment should be created.
|
||||
// the pushed branch is the same as base branch, so no commit between old and new commit, no regular push comment
|
||||
comment, _, err := CreatePushPullComment(t.Context(), pusher, pr, baseCommit.ID.String(), baseCommit.ID.String(), true)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, comment)
|
||||
assertCommitCommentCount(t, 1, 1)
|
||||
|
||||
createdData, err := comment.GetPushActionContent()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, createdData.IsForcePush)
|
||||
assert.Equal(t, []string{baseCommit.ID.String(), baseCommit.ID.String()}, createdData.CommitIDs)
|
||||
})
|
||||
|
||||
commits, err := gitRepo.CommitsBetweenNotBase(headCommit, oldCommit, pr.BaseBranch)
|
||||
assert.NoError(t, err)
|
||||
// For this scenario we expect at least one commit between head and base
|
||||
// that is not on the base branch, so data.CommitIDs should be non-empty.
|
||||
assert.NotEmpty(t, commits)
|
||||
t.Run("force-push-ignores-missing-old-commit", func(t *testing.T) {
|
||||
require.NoError(t, db.TruncateBeans(t.Context(), &issues_model.Comment{}))
|
||||
headCommit, err := gitRepo.GetBranchCommit(pr.HeadBranch)
|
||||
require.NoError(t, err)
|
||||
|
||||
comments, err = issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
|
||||
IssueID: pr.IssueID,
|
||||
Type: issues_model.CommentTypePullRequestPush,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
// Two comments should exist now: one regular push comment and one force-push comment.
|
||||
assert.Len(t, comments, 2)
|
||||
commitIDZero := git.Sha1ObjectFormat.EmptyObjectID().String()
|
||||
comment, _, err := CreatePushPullComment(t.Context(), pusher, pr, commitIDZero, headCommit.ID.String(), true)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, comment)
|
||||
createdData, err := comment.GetPushActionContent()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, createdData.IsForcePush)
|
||||
assert.Equal(t, []string{commitIDZero, headCommit.ID.String()}, createdData.CommitIDs)
|
||||
assertCommitCommentCount(t, 2, 1)
|
||||
|
||||
forcePushCount := 0
|
||||
for _, comment := range comments {
|
||||
var pushData issues_model.PushActionContent
|
||||
assert.NoError(t, json.Unmarshal([]byte(comment.Content), &pushData))
|
||||
if pushData.IsForcePush {
|
||||
forcePushCount++
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 1, forcePushCount)
|
||||
// force push again, the old force push comment should not be deleted, new we have 2 force push comments.
|
||||
_, _, err = CreatePushPullComment(t.Context(), pusher, pr, commitIDZero, headCommit.ID.String(), true)
|
||||
require.NoError(t, err)
|
||||
assertCommitCommentCount(t, 3, 2)
|
||||
})
|
||||
|
||||
t.Run("head-vs-base-branch", func(t *testing.T) {
|
||||
require.NoError(t, db.TruncateBeans(t.Context(), &issues_model.Comment{}))
|
||||
insertCommitComment(t, issues_model.PushActionContent{})
|
||||
insertCommitComment(t, issues_model.PushActionContent{})
|
||||
insertCommitComment(t, issues_model.PushActionContent{})
|
||||
insertCommitComment(t, issues_model.PushActionContent{})
|
||||
assertCommitCommentCount(t, 4, 0)
|
||||
|
||||
baseCommit, err := gitRepo.GetBranchCommit(pr.BaseBranch)
|
||||
require.NoError(t, err)
|
||||
headCommit, err := gitRepo.GetBranchCommit(pr.HeadBranch)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = CreatePushPullComment(t.Context(), pusher, pr, baseCommit.ID.String(), headCommit.ID.String(), true)
|
||||
require.NoError(t, err)
|
||||
// 2 comments should exist now: one regular push comment and one force-push comment.
|
||||
assertCommitCommentCount(t, 2, 1)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
|
||||
}
|
||||
|
||||
// add first push codes comment
|
||||
if _, err := CreatePushPullComment(ctx, issue.Poster, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName(), false); err != nil {
|
||||
if _, _, err := CreatePushPullComment(ctx, issue.Poster, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName(), false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = CreatePushPullComment(ctx, doer, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName(), false)
|
||||
_, _, err = CreatePushPullComment(ctx, doer, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitHeadRefName(), false)
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -411,8 +411,10 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
||||
|
||||
// create push comment before check pull request status,
|
||||
// then when the status is mergeable, the comment is already in database, to make testing easy and stable
|
||||
comment, err := CreatePushPullComment(ctx, opts.Doer, pr, opts.OldCommitID, opts.NewCommitID, opts.IsForcePush)
|
||||
if err == nil && comment != nil {
|
||||
comment, commentCreated, err := CreatePushPullComment(ctx, opts.Doer, pr, opts.OldCommitID, opts.NewCommitID, opts.IsForcePush)
|
||||
if err != nil {
|
||||
log.Error("CreatePushPullComment: %v", err)
|
||||
} else if commentCreated {
|
||||
notify_service.PullRequestPushCommits(ctx, opts.Doer, pr, comment)
|
||||
}
|
||||
// The caller can be in a goroutine or a "push queue", "conflict check" can be time-consuming,
|
||||
|
||||
Reference in New Issue
Block a user