mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-12 22:03:30 +02: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:
parent
3c17daf615
commit
f59d1d3cef
@ -7,6 +7,7 @@ package issues
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"slices"
|
"slices"
|
||||||
@ -21,6 +22,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/htmlutil"
|
"code.gitea.io/gitea/modules/htmlutil"
|
||||||
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
@ -326,21 +328,34 @@ type Comment struct {
|
|||||||
RefIssue *Issue `xorm:"-"`
|
RefIssue *Issue `xorm:"-"`
|
||||||
RefComment *Comment `xorm:"-"`
|
RefComment *Comment `xorm:"-"`
|
||||||
|
|
||||||
Commits []*git_model.SignCommitWithStatuses `xorm:"-"`
|
Commits []*git_model.SignCommitWithStatuses `xorm:"-"`
|
||||||
OldCommit string `xorm:"-"`
|
OldCommit string `xorm:"-"`
|
||||||
NewCommit string `xorm:"-"`
|
NewCommit string `xorm:"-"`
|
||||||
CommitsNum int64 `xorm:"-"`
|
CommitsNum int64 `xorm:"-"`
|
||||||
IsForcePush bool `xorm:"-"`
|
|
||||||
|
// Templates still use it. It is not persisted in database, it is only set when creating or loading
|
||||||
|
IsForcePush bool `xorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
db.RegisterModel(new(Comment))
|
db.RegisterModel(new(Comment))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushActionContent is content of push pull comment
|
// PushActionContent is content of pull request's push comment
|
||||||
type PushActionContent struct {
|
type PushActionContent struct {
|
||||||
IsForcePush bool `json:"is_force_push"`
|
IsForcePush bool `json:"is_force_push"`
|
||||||
CommitIDs []string `json:"commit_ids"`
|
// if IsForcePush=true, CommitIDs contains the commit pair [old head, new head]
|
||||||
|
// if IsForcePush=false, CommitIDs contains the new commits newly pushed to the head branch
|
||||||
|
CommitIDs []string `json:"commit_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Comment) GetPushActionContent() (*PushActionContent, error) {
|
||||||
|
if c.Type != CommentTypePullRequestPush {
|
||||||
|
return nil, errors.New("not a pull request push comment")
|
||||||
|
}
|
||||||
|
var data PushActionContent
|
||||||
|
_ = json.Unmarshal(util.UnsafeStringToBytes(c.Content), &data)
|
||||||
|
return &data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadIssue loads the issue reference for the comment
|
// LoadIssue loads the issue reference for the comment
|
||||||
|
|||||||
@ -22,4 +22,6 @@ func TestIsValidSHAPattern(t *testing.T) {
|
|||||||
assert.Equal(t, "2e65efe2a145dda7ee51d1741299f848e5bf752e", ComputeBlobHash(Sha1ObjectFormat, []byte("a")).String())
|
assert.Equal(t, "2e65efe2a145dda7ee51d1741299f848e5bf752e", ComputeBlobHash(Sha1ObjectFormat, []byte("a")).String())
|
||||||
assert.Equal(t, "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813", ComputeBlobHash(Sha256ObjectFormat, nil).String())
|
assert.Equal(t, "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813", ComputeBlobHash(Sha256ObjectFormat, nil).String())
|
||||||
assert.Equal(t, "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7", ComputeBlobHash(Sha256ObjectFormat, []byte("a")).String())
|
assert.Equal(t, "eb337bcee2061c5313c9a1392116b6c76039e9e30d71467ae359b36277e17dc7", ComputeBlobHash(Sha256ObjectFormat, []byte("a")).String())
|
||||||
|
assert.True(t, IsEmptyCommitID(""))
|
||||||
|
assert.True(t, IsEmptyCommitID("0000000000000000000000000000000000000000"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -362,39 +362,6 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in
|
|||||||
return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
|
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 = gitcmd.NewCommand("rev-list").
|
|
||||||
AddDynamicArguments(last.ID.String()).
|
|
||||||
AddOptionValues("--not", baseBranch).
|
|
||||||
WithDir(repo.Path).
|
|
||||||
RunStdBytes(repo.Ctx)
|
|
||||||
} else {
|
|
||||||
stdout, _, err = gitcmd.NewCommand("rev-list").
|
|
||||||
AddDynamicArguments(before.ID.String()+".."+last.ID.String()).
|
|
||||||
AddOptionValues("--not", baseBranch).
|
|
||||||
WithDir(repo.Path).
|
|
||||||
RunStdBytes(repo.Ctx)
|
|
||||||
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 = gitcmd.NewCommand("rev-list").
|
|
||||||
AddDynamicArguments(before.ID.String(), last.ID.String()).
|
|
||||||
AddOptionValues("--not", baseBranch).
|
|
||||||
WithDir(repo.Path).
|
|
||||||
RunStdBytes(repo.Ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommitsBetweenIDs return commits between twoe commits
|
// CommitsBetweenIDs return commits between twoe commits
|
||||||
func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error) {
|
func (repo *Repository) CommitsBetweenIDs(last, before string) ([]*Commit, error) {
|
||||||
lastCommit, err := repo.GetCommit(last)
|
lastCommit, err := repo.GetCommit(last)
|
||||||
|
|||||||
@ -42,3 +42,30 @@ func GetDivergingCommits(ctx context.Context, repo Repository, baseBranch, targe
|
|||||||
}
|
}
|
||||||
return &DivergeObject{Ahead: ahead, Behind: behind}, nil
|
return &DivergeObject{Ahead: ahead, Behind: behind}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommitIDsBetweenReverse returns the last commit IDs between two commits in reverse order (from old to new) with limit.
|
||||||
|
// If the result exceeds the limit, the old commits IDs will be ignored
|
||||||
|
func GetCommitIDsBetweenReverse(ctx context.Context, repo Repository, startRef, endRef, notRef string, limit int) ([]string, error) {
|
||||||
|
genCmd := func(reversions ...string) *gitcmd.Command {
|
||||||
|
cmd := gitcmd.NewCommand("rev-list", "--reverse").
|
||||||
|
AddArguments("-n").AddDynamicArguments(strconv.Itoa(limit)).
|
||||||
|
AddDynamicArguments(reversions...)
|
||||||
|
if notRef != "" { // --not should be kept as the last parameter of git command, otherwise the result will be wrong
|
||||||
|
cmd.AddOptionValues("--not", notRef)
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
stdout, _, err := RunCmdString(ctx, repo, genCmd(startRef+".."+endRef))
|
||||||
|
// example git error message: fatal: origin/main..HEAD: no merge base
|
||||||
|
if err != nil && strings.Contains(err.Stderr(), "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 = RunCmdString(ctx, repo, genCmd(startRef, endRef))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
commitIDs := strings.Fields(strings.TrimSpace(stdout))
|
||||||
|
return commitIDs, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -40,3 +40,75 @@ func TestRepoGetDivergingCommits(t *testing.T) {
|
|||||||
Behind: 2,
|
Behind: 2,
|
||||||
}, do)
|
}, do)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetCommitIDsBetweenReverse(t *testing.T) {
|
||||||
|
repo := &mockRepository{path: "repo1_bare"}
|
||||||
|
|
||||||
|
// tests raw commit IDs
|
||||||
|
commitIDs, err := GetCommitIDsBetweenReverse(t.Context(), repo,
|
||||||
|
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||||
|
"ce064814f4a0d337b333e646ece456cd39fab612",
|
||||||
|
"",
|
||||||
|
100,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"8006ff9adbf0cb94da7dad9e537e53817f9fa5c0",
|
||||||
|
"6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1",
|
||||||
|
"37991dec2c8e592043f47155ce4808d4580f9123",
|
||||||
|
"feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
|
||||||
|
"ce064814f4a0d337b333e646ece456cd39fab612",
|
||||||
|
}, commitIDs)
|
||||||
|
|
||||||
|
commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo,
|
||||||
|
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||||
|
"ce064814f4a0d337b333e646ece456cd39fab612",
|
||||||
|
"6fbd69e9823458e6c4a2fc5c0f6bc022b2f2acd1",
|
||||||
|
100,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"37991dec2c8e592043f47155ce4808d4580f9123",
|
||||||
|
"feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
|
||||||
|
"ce064814f4a0d337b333e646ece456cd39fab612",
|
||||||
|
}, commitIDs)
|
||||||
|
|
||||||
|
commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo,
|
||||||
|
"8d92fc957a4d7cfd98bc375f0b7bb189a0d6c9f2",
|
||||||
|
"ce064814f4a0d337b333e646ece456cd39fab612",
|
||||||
|
"",
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"37991dec2c8e592043f47155ce4808d4580f9123",
|
||||||
|
"feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
|
||||||
|
"ce064814f4a0d337b333e646ece456cd39fab612",
|
||||||
|
}, commitIDs)
|
||||||
|
|
||||||
|
// test branch names instead of raw commit IDs.
|
||||||
|
commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo,
|
||||||
|
"test",
|
||||||
|
"master",
|
||||||
|
"",
|
||||||
|
100,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
|
||||||
|
"ce064814f4a0d337b333e646ece456cd39fab612",
|
||||||
|
}, commitIDs)
|
||||||
|
|
||||||
|
// add notref to exclude test
|
||||||
|
commitIDs, err = GetCommitIDsBetweenReverse(t.Context(), repo,
|
||||||
|
"test",
|
||||||
|
"master",
|
||||||
|
"test",
|
||||||
|
100,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"feaf4ba6bc635fec442f46ddd4512416ec43c2c2",
|
||||||
|
"ce064814f4a0d337b333e646ece456cd39fab612",
|
||||||
|
}, commitIDs)
|
||||||
|
}
|
||||||
|
|||||||
@ -282,12 +282,15 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load pull issue. Error: %w", err)
|
return nil, fmt.Errorf("failed to load pull issue. Error: %w", err)
|
||||||
}
|
}
|
||||||
comment, err := pull_service.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i], forcePush.Value())
|
|
||||||
if err == nil && comment != nil {
|
isForcePush := forcePush.Value()
|
||||||
|
comment, commentCreated, err := pull_service.CreatePushPullComment(ctx, pusher, pr, oldCommitID, opts.NewCommitIDs[i], isForcePush)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("CreatePushPullComment: %v", err)
|
||||||
|
} else if commentCreated {
|
||||||
notify_service.PullRequestPushCommits(ctx, pusher, pr, comment)
|
notify_service.PullRequestPushCommits(ctx, pusher, pr, comment)
|
||||||
}
|
}
|
||||||
notify_service.PullRequestSynchronized(ctx, pusher, pr)
|
notify_service.PullRequestSynchronized(ctx, pusher, pr)
|
||||||
isForcePush := comment != nil && comment.IsForcePush
|
|
||||||
|
|
||||||
results = append(results, private.HookProcReceiveRefResult{
|
results = append(results, private.HookProcReceiveRefResult{
|
||||||
OldOID: oldCommitID,
|
OldOID: oldCommitID,
|
||||||
|
|||||||
@ -5,117 +5,166 @@ package pull
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getCommitIDsFromRepo get commit IDs from repo in between oldCommitID and newCommitID
|
const maxPushCommitsInCommentCount = 1000
|
||||||
// Commit on baseBranch will skip
|
|
||||||
func getCommitIDsFromRepo(ctx context.Context, repo *repo_model.Repository, oldCommitID, newCommitID, baseBranch string) (commitIDs []string, err error) {
|
func preparePushPullCommentPushActionContent(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string, isForcePush bool) (data issues_model.PushActionContent, shouldCreate bool, err error) {
|
||||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
|
||||||
defer closer.Close()
|
|
||||||
|
|
||||||
oldCommit, err := gitRepo.GetCommit(oldCommitID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newCommit, err := gitRepo.GetCommit(newCommitID)
|
needDeleteCommentIDs := reconcileOldCommitCommentsForForcePush(ctx, oldCommitComments, data)
|
||||||
if err != nil {
|
if len(needDeleteCommentIDs) == 0 {
|
||||||
return nil, err
|
return nil
|
||||||
}
|
}
|
||||||
|
_, err = db.GetEngine(ctx).In("id", needDeleteCommentIDs).Delete(&issues_model.Comment{})
|
||||||
// Find commits between new and old commit excluding base branch commits
|
return err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePushPullComment create push code to pull base comment
|
// 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) {
|
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 || oldCommitID == "" || newCommitID == "" {
|
if pr.HasMerged || oldRef == "" || newRef == "" {
|
||||||
return nil, nil //nolint:nilnil // return nil because no comment needs to be created
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &issues_model.CreateCommentOptions{
|
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// For force-push events, a missing/unreachable old commit should not prevent
|
return nil, false, err
|
||||||
// deleting stale push comments or creating the force-push timeline entry.
|
|
||||||
if !isForcePush {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Error("getCommitIDsFromRepo: %v", err)
|
|
||||||
}
|
}
|
||||||
// It maybe an empty pull request. Only non-empty pull request need to create push comment
|
defer closer.Close()
|
||||||
// for force push, we always need to delete the old push comment so don't return here.
|
|
||||||
if len(data.CommitIDs) == 0 && !isForcePush {
|
oldCommitID := oldRef
|
||||||
return nil, nil //nolint:nilnil // return nil because no comment needs to be created
|
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 {
|
if isForcePush {
|
||||||
// Push commits comment should not have history, cross references, reactions and other
|
err := cleanUpOldCommitCommentsForNewForcePush(ctx, pr, &data)
|
||||||
// plain comment related records, so that we just need to delete the comment itself.
|
if err != nil {
|
||||||
if _, err := db.GetEngine(ctx).Where("issue_id = ?", pr.IssueID).
|
log.Error("CleanUpOldCommitComments failed: %v", err)
|
||||||
And("type = ?", issues_model.CommentTypePullRequestPush).
|
|
||||||
NoAutoCondition().
|
|
||||||
Delete(new(issues_model.Comment)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.CommitIDs) > 0 {
|
if len(data.CommitIDs) > 0 {
|
||||||
dataJSON, err := json.Marshal(data)
|
// if the push has commit IDs, add a "normal push" commit comment
|
||||||
if err != nil {
|
dataJSON, _ := json.Marshal(data)
|
||||||
return nil, err
|
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)
|
comment, err = issues_model.CreateComment(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isForcePush { // if it's a force push, we need to add a force push comment
|
if isForcePush {
|
||||||
data.CommitIDs = []string{oldCommitID, newCommitID}
|
// if it's a force push, we need to add a force push comment
|
||||||
data.IsForcePush = true
|
forcePushDataJSON, _ := json.Marshal(&issues_model.PushActionContent{IsForcePush: true, CommitIDs: []string{oldCommitID, newCommitID}})
|
||||||
dataJSON, err := json.Marshal(data)
|
opts := &issues_model.CreateCommentOptions{
|
||||||
if err != nil {
|
Type: issues_model.CommentTypePullRequestPush,
|
||||||
return nil, err
|
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)
|
comment, err = issues_model.CreateComment(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return comment, nil
|
||||||
return comment, err
|
|
||||||
})
|
})
|
||||||
|
return comment, comment != nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,168 +6,118 @@ package pull
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreatePushPullCommentForcePushDeletesOldComments(t *testing.T) {
|
func TestCreatePushPullCommentForcePushDeletesOldComments(t *testing.T) {
|
||||||
t.Run("base-branch-only", func(t *testing.T) {
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
assert.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})
|
gitRepo, err := gitrepo.OpenRepository(t.Context(), pr.BaseRepo)
|
||||||
assert.NoError(t, pr.LoadIssue(t.Context()))
|
require.NoError(t, err)
|
||||||
assert.NoError(t, pr.LoadBaseRepo(t.Context()))
|
defer gitRepo.Close()
|
||||||
|
|
||||||
pusher := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
|
||||||
|
|
||||||
|
insertCommitComment := func(t *testing.T, content issues_model.PushActionContent) {
|
||||||
|
contentJSON, _ := json.Marshal(content)
|
||||||
_, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{
|
_, err := issues_model.CreateComment(t.Context(), &issues_model.CreateCommentOptions{
|
||||||
Type: issues_model.CommentTypePullRequestPush,
|
Type: issues_model.CommentTypePullRequestPush,
|
||||||
Doer: pusher,
|
Doer: pusher,
|
||||||
Repo: pr.BaseRepo,
|
Repo: pr.BaseRepo,
|
||||||
Issue: pr.Issue,
|
Issue: pr.Issue,
|
||||||
Content: "{}",
|
Content: string(contentJSON),
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
require.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)
|
|
||||||
|
|
||||||
|
assertCommitCommentCount := func(t *testing.T, expectedTotalCount, expectedForcePushCount int) {
|
||||||
comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
|
comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
|
||||||
IssueID: pr.IssueID,
|
IssueID: pr.IssueID,
|
||||||
Type: issues_model.CommentTypePullRequestPush,
|
Type: issues_model.CommentTypePullRequestPush,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Len(t, comments, 2)
|
totalCount, forcePushCount := len(comments), 0
|
||||||
|
|
||||||
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
|
|
||||||
for _, comment := range comments {
|
for _, comment := range comments {
|
||||||
var pushData issues_model.PushActionContent
|
pushData, err := comment.GetPushActionContent()
|
||||||
assert.NoError(t, json.Unmarshal([]byte(comment.Content), &pushData))
|
require.NoError(t, err)
|
||||||
if pushData.IsForcePush {
|
if pushData.IsForcePush {
|
||||||
forcePushCount++
|
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) {
|
t.Run("base-branch-only", func(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, db.TruncateBeans(t.Context(), &issues_model.Comment{}))
|
||||||
|
insertCommitComment(t, issues_model.PushActionContent{})
|
||||||
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
|
insertCommitComment(t, issues_model.PushActionContent{})
|
||||||
assert.NoError(t, pr.LoadIssue(t.Context()))
|
assertCommitCommentCount(t, 2, 0)
|
||||||
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)
|
|
||||||
|
|
||||||
baseCommit, err := gitRepo.GetBranchCommit(pr.BaseBranch)
|
baseCommit, err := gitRepo.GetBranchCommit(pr.BaseBranch)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
oldCommit := baseCommit
|
|
||||||
|
|
||||||
comment, err := CreatePushPullComment(t.Context(), pusher, pr, oldCommit.ID.String(), headCommit.ID.String(), true)
|
// force push, the old push comments should be deleted, and one new force-push comment should be created.
|
||||||
assert.NoError(t, err)
|
// the pushed branch is the same as base branch, so no commit between old and new commit, no regular push comment
|
||||||
assert.NotNil(t, comment)
|
comment, _, err := CreatePushPullComment(t.Context(), pusher, pr, baseCommit.ID.String(), baseCommit.ID.String(), true)
|
||||||
var createdData issues_model.PushActionContent
|
require.NoError(t, err)
|
||||||
assert.NoError(t, json.Unmarshal([]byte(comment.Content), &createdData))
|
require.NotNil(t, comment)
|
||||||
|
assertCommitCommentCount(t, 1, 1)
|
||||||
|
|
||||||
|
createdData, err := comment.GetPushActionContent()
|
||||||
|
require.NoError(t, err)
|
||||||
assert.True(t, createdData.IsForcePush)
|
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)
|
t.Run("force-push-ignores-missing-old-commit", func(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
require.NoError(t, db.TruncateBeans(t.Context(), &issues_model.Comment{}))
|
||||||
// For this scenario we expect at least one commit between head and base
|
headCommit, err := gitRepo.GetBranchCommit(pr.HeadBranch)
|
||||||
// that is not on the base branch, so data.CommitIDs should be non-empty.
|
require.NoError(t, err)
|
||||||
assert.NotEmpty(t, commits)
|
|
||||||
|
|
||||||
comments, err = issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
|
commitIDZero := git.Sha1ObjectFormat.EmptyObjectID().String()
|
||||||
IssueID: pr.IssueID,
|
comment, _, err := CreatePushPullComment(t.Context(), pusher, pr, commitIDZero, headCommit.ID.String(), true)
|
||||||
Type: issues_model.CommentTypePullRequestPush,
|
require.NoError(t, err)
|
||||||
})
|
require.NotNil(t, comment)
|
||||||
assert.NoError(t, err)
|
createdData, err := comment.GetPushActionContent()
|
||||||
// Two comments should exist now: one regular push comment and one force-push comment.
|
require.NoError(t, err)
|
||||||
assert.Len(t, comments, 2)
|
assert.True(t, createdData.IsForcePush)
|
||||||
|
assert.Equal(t, []string{commitIDZero, headCommit.ID.String()}, createdData.CommitIDs)
|
||||||
|
assertCommitCommentCount(t, 2, 1)
|
||||||
|
|
||||||
forcePushCount := 0
|
// force push again, the old force push comment should not be deleted, new we have 2 force push comments.
|
||||||
for _, comment := range comments {
|
_, _, err = CreatePushPullComment(t.Context(), pusher, pr, commitIDZero, headCommit.ID.String(), true)
|
||||||
var pushData issues_model.PushActionContent
|
require.NoError(t, err)
|
||||||
assert.NoError(t, json.Unmarshal([]byte(comment.Content), &pushData))
|
assertCommitCommentCount(t, 3, 2)
|
||||||
if pushData.IsForcePush {
|
})
|
||||||
forcePushCount++
|
|
||||||
}
|
t.Run("head-vs-base-branch", func(t *testing.T) {
|
||||||
}
|
require.NoError(t, db.TruncateBeans(t.Context(), &issues_model.Comment{}))
|
||||||
assert.Equal(t, 1, forcePushCount)
|
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
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +343,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
|
|||||||
return err
|
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
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -411,8 +411,10 @@ func AddTestPullRequestTask(opts TestPullRequestOptions) {
|
|||||||
|
|
||||||
// create push comment before check pull request status,
|
// 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
|
// 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)
|
comment, commentCreated, err := CreatePushPullComment(ctx, opts.Doer, pr, opts.OldCommitID, opts.NewCommitID, opts.IsForcePush)
|
||||||
if err == nil && comment != nil {
|
if err != nil {
|
||||||
|
log.Error("CreatePushPullComment: %v", err)
|
||||||
|
} else if commentCreated {
|
||||||
notify_service.PullRequestPushCommits(ctx, opts.Doer, pr, comment)
|
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,
|
// The caller can be in a goroutine or a "push queue", "conflict check" can be time-consuming,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user