diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2023-08-11 13:27:23 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-11 05:27:23 +0000 |
commit | 368e9e0f1bf1094aad881336c698f540c047d9ac (patch) | |
tree | b65c37ad8c941cbf1ae70d57af81e49c5d557653 /services | |
parent | d6cf261be860e8022ccbad5585fc3508e840d409 (diff) | |
download | gitea-368e9e0f1bf1094aad881336c698f540c047d9ac.tar.gz gitea-368e9e0f1bf1094aad881336c698f540c047d9ac.zip |
Add transaction when creating pull request created dirty data (#26259) (#26437)
Backport #26259
This PR will introduce a transaction on creating pull request so that if
some step failed, it will rollback totally. And there will be no dirty
pull request exist.
Co-authored-by: Giteabot <teabot@gitea.io>
Diffstat (limited to 'services')
-rw-r--r-- | services/issue/assignee.go | 14 | ||||
-rw-r--r-- | services/issue/issue.go | 26 | ||||
-rw-r--r-- | services/pull/patch.go | 9 | ||||
-rw-r--r-- | services/pull/pull.go | 131 |
4 files changed, 107 insertions, 73 deletions
diff --git a/services/issue/assignee.go b/services/issue/assignee.go index 943a761e28..2ff0a97fa3 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -33,7 +33,7 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe if !found { // This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here - if _, _, err := ToggleAssignee(ctx, issue, doer, assignee.ID); err != nil { + if _, _, err := ToggleAssigneeWithNotify(ctx, issue, doer, assignee.ID); err != nil { return err } } @@ -42,8 +42,8 @@ func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doe return nil } -// ToggleAssignee changes a user between assigned and not assigned for this issue, and make issue comment for it. -func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) { +// ToggleAssigneeWithNoNotify changes a user between assigned and not assigned for this issue, and make issue comment for it. +func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) { removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID) if err != nil { return @@ -63,9 +63,9 @@ func ToggleAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_m // ReviewRequest add or remove a review request from a user for this PR, and make comment for it. func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) { if isAdd { - comment, err = issues_model.AddReviewRequest(issue, reviewer, doer) + comment, err = issues_model.AddReviewRequest(ctx, issue, reviewer, doer) } else { - comment, err = issues_model.RemoveReviewRequest(issue, reviewer, doer) + comment, err = issues_model.RemoveReviewRequest(ctx, issue, reviewer, doer) } if err != nil { @@ -230,9 +230,9 @@ func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, // TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it. func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) { if isAdd { - comment, err = issues_model.AddTeamReviewRequest(issue, reviewer, doer) + comment, err = issues_model.AddTeamReviewRequest(ctx, issue, reviewer, doer) } else { - comment, err = issues_model.RemoveTeamReviewRequest(issue, reviewer, doer) + comment, err = issues_model.RemoveTeamReviewRequest(ctx, issue, reviewer, doer) } if err != nil { diff --git a/services/issue/issue.go b/services/issue/issue.go index 03d34ba12d..596b27c588 100644 --- a/services/issue/issue.go +++ b/services/issue/issue.go @@ -27,7 +27,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *issues_mo } for _, assigneeID := range assigneeIDs { - if err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID); err != nil { + if _, err := AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, true); err != nil { return err } } @@ -122,7 +122,7 @@ func UpdateAssignees(ctx context.Context, issue *issues_model.Issue, oneAssignee // has access to the repo. for _, assignee := range allNewAssignees { // Extra method to prevent double adding (which would result in removing) - err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID) + _, err = AddAssigneeIfNotAssigned(ctx, issue, doer, assignee.ID, true) if err != nil { return err } @@ -167,36 +167,36 @@ func DeleteIssue(ctx context.Context, doer *user_model.User, gitRepo *git.Reposi // AddAssigneeIfNotAssigned adds an assignee only if he isn't already assigned to the issue. // Also checks for access of assigned user -func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (err error) { +func AddAssigneeIfNotAssigned(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64, notify bool) (comment *issues_model.Comment, err error) { assignee, err := user_model.GetUserByID(ctx, assigneeID) if err != nil { - return err + return nil, err } // Check if the user is already assigned isAssigned, err := issues_model.IsUserAssignedToIssue(ctx, issue, assignee) if err != nil { - return err + return nil, err } if isAssigned { // nothing to to - return nil + return nil, nil } valid, err := access_model.CanBeAssigned(ctx, assignee, issue.Repo, issue.IsPull) if err != nil { - return err + return nil, err } if !valid { - return repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name} + return nil, repo_model.ErrUserDoesNotHaveAccessToRepo{UserID: assigneeID, RepoName: issue.Repo.Name} } - _, _, err = ToggleAssignee(ctx, issue, doer, assigneeID) - if err != nil { - return err + if notify { + _, comment, err = ToggleAssigneeWithNotify(ctx, issue, doer, assigneeID) + return comment, err } - - return nil + _, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID) + return comment, err } // GetRefEndNamesAndURLs retrieves the ref end names (e.g. refs/heads/branch-name -> branch-name) diff --git a/services/pull/patch.go b/services/pull/patch.go index 9277355720..f8388892e5 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -62,14 +62,19 @@ func TestPatch(pr *issues_model.PullRequest) error { ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("TestPatch: %s", pr)) defer finished() - // Clone base repo. prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) if err != nil { - log.Error("createTemporaryRepoForPR %-v: %v", pr, err) + if !models.IsErrBranchDoesNotExist(err) { + log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err) + } return err } defer cancel() + return testPatch(ctx, prCtx, pr) +} + +func testPatch(ctx context.Context, prCtx *prContext, pr *issues_model.PullRequest) error { gitRepo, err := git.OpenRepository(ctx, prCtx.tmpBasePath) if err != nil { return fmt.Errorf("OpenRepository: %w", err) diff --git a/services/pull/pull.go b/services/pull/pull.go index 8880bfc6ea..b46446a1fc 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -23,7 +23,6 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" - "code.gitea.io/gitea/modules/process" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/sync" @@ -35,73 +34,70 @@ import ( var pullWorkingPool = sync.NewExclusivePool() // NewPullRequest creates new pull request with labels for repository. -func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error { - if err := TestPatch(pr); err != nil { - return err - } - - divergence, err := GetDiverging(ctx, pr) +func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *issues_model.Issue, labelIDs []int64, uuids []string, pr *issues_model.PullRequest, assigneeIDs []int64) error { + prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) if err != nil { - return err - } - pr.CommitsAhead = divergence.Ahead - pr.CommitsBehind = divergence.Behind - - if err := issues_model.NewPullRequest(ctx, repo, pull, labelIDs, uuids, pr); err != nil { - return err - } - - for _, assigneeID := range assigneeIDs { - if err := issue_service.AddAssigneeIfNotAssigned(ctx, pull, pull.Poster, assigneeID); err != nil { - return err + if !models.IsErrBranchDoesNotExist(err) { + log.Error("CreateTemporaryRepoForPR %-v: %v", pr, err) } + return err } + defer cancel() - pr.Issue = pull - pull.PullRequest = pr - - // Now - even if the request context has been cancelled as the PR has been created - // in the db and there is no way to cancel that transaction we have to proceed - therefore - // create new context and work from there - prCtx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("NewPullRequest: %s:%d", repo.FullName(), pr.Index)) - defer finished() - - if pr.Flow == issues_model.PullRequestFlowGithub { - err = PushToBaseRepo(prCtx, pr) - } else { - err = UpdateRef(prCtx, pr) - } - if err != nil { + if err := testPatch(ctx, prCtx, pr); err != nil { return err } - mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, pull, pull.Poster, pull.Content) + divergence, err := git.GetDivergingCommits(ctx, prCtx.tmpBasePath, baseBranch, trackingBranch) if err != nil { return err } + pr.CommitsAhead = divergence.Ahead + pr.CommitsBehind = divergence.Behind - notification.NotifyNewPullRequest(prCtx, pr, mentions) - if len(pull.Labels) > 0 { - notification.NotifyIssueChangeLabels(prCtx, pull.Poster, pull, pull.Labels, nil) - } - if pull.Milestone != nil { - notification.NotifyIssueChangeMilestone(prCtx, pull.Poster, pull, 0) - } + assigneeCommentMap := make(map[int64]*issues_model.Comment) // add first push codes comment - baseGitRepo, err := git.OpenRepository(prCtx, pr.BaseRepo.RepoPath()) + baseGitRepo, err := git.OpenRepository(ctx, pr.BaseRepo.RepoPath()) if err != nil { return err } defer baseGitRepo.Close() - compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), - git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false) - if err != nil { - return err - } + if err := db.WithTx(ctx, func(ctx context.Context) error { + if err := issues_model.NewPullRequest(ctx, repo, issue, labelIDs, uuids, pr); err != nil { + return err + } + + for _, assigneeID := range assigneeIDs { + comment, err := issue_service.AddAssigneeIfNotAssigned(ctx, issue, issue.Poster, assigneeID, false) + if err != nil { + return err + } + assigneeCommentMap[assigneeID] = comment + } + + pr.Issue = issue + issue.PullRequest = pr + + if pr.Flow == issues_model.PullRequestFlowGithub { + err = PushToBaseRepo(ctx, pr) + } else { + err = UpdateRef(ctx, pr) + } + if err != nil { + return err + } + + compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), + git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName(), false, false) + if err != nil { + return err + } + if len(compareInfo.Commits) == 0 { + return nil + } - if len(compareInfo.Commits) > 0 { data := issues_model.PushActionContent{IsForcePush: false} data.CommitIDs = make([]string, 0, len(compareInfo.Commits)) for i := len(compareInfo.Commits) - 1; i >= 0; i-- { @@ -115,14 +111,47 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, pull *issu ops := &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypePullRequestPush, - Doer: pull.Poster, + Doer: issue.Poster, Repo: repo, Issue: pr.Issue, IsForcePush: false, Content: string(dataJSON), } - _, _ = issue_service.CreateComment(ctx, ops) + if _, err = issues_model.CreateComment(ctx, ops); err != nil { + return err + } + + return nil + }); err != nil { + // cleanup: this will only remove the reference, the real commit will be clean up when next GC + if err1 := baseGitRepo.RemoveReference(pr.GetGitRefName()); err1 != nil { + log.Error("RemoveReference: %v", err1) + } + return err + } + baseGitRepo.Close() // close immediately to avoid notifications will open the repository again + + mentions, err := issues_model.FindAndUpdateIssueMentions(ctx, issue, issue.Poster, issue.Content) + if err != nil { + return err + } + + notification.NotifyNewPullRequest(ctx, pr, mentions) + if len(issue.Labels) > 0 { + notification.NotifyIssueChangeLabels(ctx, issue.Poster, issue, issue.Labels, nil) + } + if issue.Milestone != nil { + notification.NotifyIssueChangeMilestone(ctx, issue.Poster, issue, 0) + } + if len(assigneeIDs) > 0 { + for _, assigneeID := range assigneeIDs { + assignee, err := user_model.GetUserByID(ctx, assigneeID) + if err != nil { + return ErrDependenciesLeft + } + notification.NotifyIssueChangeAssignee(ctx, issue.Poster, issue, assignee, false, assigneeCommentMap[assigneeID]) + } } return nil |