diff options
Diffstat (limited to 'services/pull')
-rw-r--r-- | services/pull/check.go | 5 | ||||
-rw-r--r-- | services/pull/merge.go | 119 | ||||
-rw-r--r-- | services/pull/merge_prepare.go | 45 | ||||
-rw-r--r-- | services/pull/patch.go | 28 | ||||
-rw-r--r-- | services/pull/pull.go | 27 |
5 files changed, 197 insertions, 27 deletions
diff --git a/services/pull/check.go b/services/pull/check.go index 736be4611b..bffca394a8 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -11,7 +11,6 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -36,7 +35,7 @@ var prPatchCheckerQueue *queue.WorkerPoolQueue[string] var ( ErrIsClosed = errors.New("pull is closed") - ErrUserNotAllowedToMerge = models.ErrDisallowedToMerge{} + ErrUserNotAllowedToMerge = ErrDisallowedToMerge{} ErrHasMerged = errors.New("has already been merged") ErrIsWorkInProgress = errors.New("work in progress PRs cannot be merged") ErrIsChecking = errors.New("cannot merge while conflict checking is in progress") @@ -106,7 +105,7 @@ func CheckPullMergeable(stdCtx context.Context, doer *user_model.User, perm *acc } if err := CheckPullBranchProtections(ctx, pr, false); err != nil { - if !models.IsErrDisallowedToMerge(err) { + if !IsErrDisallowedToMerge(err) { log.Error("Error whilst checking pull branch protection for %-v: %v", pr, err) return err } diff --git a/services/pull/merge.go b/services/pull/merge.go index a3fbe4f627..fba85f1e51 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -13,7 +13,6 @@ import ( "strconv" "strings" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -30,6 +29,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" issue_service "code.gitea.io/gitea/services/issue" notify_service "code.gitea.io/gitea/services/notify" ) @@ -159,6 +159,27 @@ func GetDefaultMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr return getMergeMessage(ctx, baseGitRepo, pr, mergeStyle, nil) } +// ErrInvalidMergeStyle represents an error if merging with disabled merge strategy +type ErrInvalidMergeStyle struct { + ID int64 + Style repo_model.MergeStyle +} + +// IsErrInvalidMergeStyle checks if an error is a ErrInvalidMergeStyle. +func IsErrInvalidMergeStyle(err error) bool { + _, ok := err.(ErrInvalidMergeStyle) + return ok +} + +func (err ErrInvalidMergeStyle) Error() string { + return fmt.Sprintf("merge strategy is not allowed or is invalid [repo_id: %d, strategy: %s]", + err.ID, err.Style) +} + +func (err ErrInvalidMergeStyle) Unwrap() error { + return util.ErrInvalidArgument +} + // Merge merges pull request to base repository. // Caller should check PR is ready to be merged (review and status checks) func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, baseGitRepo *git.Repository, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, wasAutoMerged bool) error { @@ -179,7 +200,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U // Check if merge style is correct and allowed if !prConfig.IsMergeStyleAllowed(mergeStyle) { - return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} + return ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} } releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID)) @@ -283,7 +304,7 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use return "", err } default: - return "", models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} + return "", ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: mergeStyle} } // OK we should cache our current head and origin/headbranch @@ -374,13 +395,66 @@ func commitAndSignNoAuthor(ctx *mergeContext, message string) error { return nil } +// ErrMergeConflicts represents an error if merging fails with a conflict +type ErrMergeConflicts struct { + Style repo_model.MergeStyle + StdOut string + StdErr string + Err error +} + +// IsErrMergeConflicts checks if an error is a ErrMergeConflicts. +func IsErrMergeConflicts(err error) bool { + _, ok := err.(ErrMergeConflicts) + return ok +} + +func (err ErrMergeConflicts) Error() string { + return fmt.Sprintf("Merge Conflict Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) +} + +// ErrMergeUnrelatedHistories represents an error if merging fails due to unrelated histories +type ErrMergeUnrelatedHistories struct { + Style repo_model.MergeStyle + StdOut string + StdErr string + Err error +} + +// IsErrMergeUnrelatedHistories checks if an error is a ErrMergeUnrelatedHistories. +func IsErrMergeUnrelatedHistories(err error) bool { + _, ok := err.(ErrMergeUnrelatedHistories) + return ok +} + +func (err ErrMergeUnrelatedHistories) Error() string { + return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) +} + +// ErrMergeDivergingFastForwardOnly represents an error if a fast-forward-only merge fails because the branches diverge +type ErrMergeDivergingFastForwardOnly struct { + StdOut string + StdErr string + Err error +} + +// IsErrMergeDivergingFastForwardOnly checks if an error is a ErrMergeDivergingFastForwardOnly. +func IsErrMergeDivergingFastForwardOnly(err error) bool { + _, ok := err.(ErrMergeDivergingFastForwardOnly) + return ok +} + +func (err ErrMergeDivergingFastForwardOnly) Error() string { + return fmt.Sprintf("Merge DivergingFastForwardOnly Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut) +} + func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *git.Command) error { if err := cmd.Run(ctx.RunOpts()); err != nil { // Merge will leave a MERGE_HEAD file in the .git folder if there is a conflict if _, statErr := os.Stat(filepath.Join(ctx.tmpBasePath, ".git", "MERGE_HEAD")); statErr == nil { // We have a merge conflict error log.Debug("MergeConflict %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) - return models.ErrMergeConflicts{ + return ErrMergeConflicts{ Style: mergeStyle, StdOut: ctx.outbuf.String(), StdErr: ctx.errbuf.String(), @@ -388,7 +462,7 @@ func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *g } } else if strings.Contains(ctx.errbuf.String(), "refusing to merge unrelated histories") { log.Debug("MergeUnrelatedHistories %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) - return models.ErrMergeUnrelatedHistories{ + return ErrMergeUnrelatedHistories{ Style: mergeStyle, StdOut: ctx.outbuf.String(), StdErr: ctx.errbuf.String(), @@ -396,7 +470,7 @@ func runMergeCommand(ctx *mergeContext, mergeStyle repo_model.MergeStyle, cmd *g } } else if mergeStyle == repo_model.MergeStyleFastForwardOnly && strings.Contains(ctx.errbuf.String(), "Not possible to fast-forward, aborting") { log.Debug("MergeDivergingFastForwardOnly %-v: %v\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) - return models.ErrMergeDivergingFastForwardOnly{ + return ErrMergeDivergingFastForwardOnly{ StdOut: ctx.outbuf.String(), StdErr: ctx.errbuf.String(), Err: err, @@ -431,6 +505,25 @@ func IsUserAllowedToMerge(ctx context.Context, pr *issues_model.PullRequest, p a return false, nil } +// ErrDisallowedToMerge represents an error that a branch is protected and the current user is not allowed to modify it. +type ErrDisallowedToMerge struct { + Reason string +} + +// IsErrDisallowedToMerge checks if an error is an ErrDisallowedToMerge. +func IsErrDisallowedToMerge(err error) bool { + _, ok := err.(ErrDisallowedToMerge) + return ok +} + +func (err ErrDisallowedToMerge) Error() string { + return fmt.Sprintf("not allowed to merge [reason: %s]", err.Reason) +} + +func (err ErrDisallowedToMerge) Unwrap() error { + return util.ErrPermissionDenied +} + // CheckPullBranchProtections checks whether the PR is ready to be merged (reviews and status checks) func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullRequest, skipProtectedFilesCheck bool) (err error) { if err = pr.LoadBaseRepo(ctx); err != nil { @@ -450,29 +543,29 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques return err } if !isPass { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "Not all required status checks successful", } } if !issues_model.HasEnoughApprovals(ctx, pb, pr) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "Does not have enough approvals", } } if issues_model.MergeBlockedByRejectedReview(ctx, pb, pr) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "There are requested changes", } } if issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pr) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "There are official review requests", } } if issues_model.MergeBlockedByOutdatedBranch(pb, pr) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "The head branch is behind the base branch", } } @@ -482,7 +575,7 @@ func CheckPullBranchProtections(ctx context.Context, pr *issues_model.PullReques } if pb.MergeBlockedByProtectedFiles(pr.ChangedProtectedFiles) { - return models.ErrDisallowedToMerge{ + return ErrDisallowedToMerge{ Reason: "Changed protected files", } } @@ -511,7 +604,7 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use // Check if merge style is correct and allowed if !prConfig.IsMergeStyleAllowed(repo_model.MergeStyleManuallyMerged) { - return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged} + return ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged} } objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index 88f6c037eb..2e1cc8cf85 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -14,7 +14,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -43,6 +42,23 @@ func (ctx *mergeContext) RunOpts() *git.RunOpts { } } +// ErrSHADoesNotMatch represents a "SHADoesNotMatch" kind of error. +type ErrSHADoesNotMatch struct { + Path string + GivenSHA string + CurrentSHA string +} + +// IsErrSHADoesNotMatch checks if an error is a ErrSHADoesNotMatch. +func IsErrSHADoesNotMatch(err error) bool { + _, ok := err.(ErrSHADoesNotMatch) + return ok +} + +func (err ErrSHADoesNotMatch) Error() string { + return fmt.Sprintf("sha does not match [given: %s, expected: %s]", err.GivenSHA, err.CurrentSHA) +} + func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, expectedHeadCommitID string) (mergeCtx *mergeContext, cancel context.CancelFunc, err error) { // Clone base repo. prCtx, cancel, err := createTemporaryRepoForPR(ctx, pr) @@ -65,7 +81,7 @@ func createTemporaryRepoForMerge(ctx context.Context, pr *issues_model.PullReque } if strings.TrimSpace(trackingCommitID) != expectedHeadCommitID { defer cancel() - return nil, nil, models.ErrSHADoesNotMatch{ + return nil, nil, ErrSHADoesNotMatch{ GivenSHA: expectedHeadCommitID, CurrentSHA: trackingCommitID, } @@ -233,8 +249,27 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string, o return err } +// ErrRebaseConflicts represents an error if rebase fails with a conflict +type ErrRebaseConflicts struct { + Style repo_model.MergeStyle + CommitSHA string + StdOut string + StdErr string + Err error +} + +// IsErrRebaseConflicts checks if an error is a ErrRebaseConflicts. +func IsErrRebaseConflicts(err error) bool { + _, ok := err.(ErrRebaseConflicts) + return ok +} + +func (err ErrRebaseConflicts) Error() string { + return fmt.Sprintf("Rebase Error: %v: Whilst Rebasing: %s\n%s\n%s", err.Err, err.CommitSHA, err.StdErr, err.StdOut) +} + // rebaseTrackingOnToBase checks out the tracking branch as staging and rebases it on to the base branch -// if there is a conflict it will return a models.ErrRebaseConflicts +// if there is a conflict it will return an ErrRebaseConflicts func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) error { // Checkout head branch if err := git.NewCommand(ctx, "checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch). @@ -268,11 +303,11 @@ func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) } } if !ok { - log.Error("Unable to determine failing commit sha for failing rebase in temp repo for %-v. Cannot cast as models.ErrRebaseConflicts.", ctx.pr) + log.Error("Unable to determine failing commit sha for failing rebase in temp repo for %-v. Cannot cast as ErrRebaseConflicts.", ctx.pr) return fmt.Errorf("unable to git rebase staging on to base in temp repo for %v: %w\n%s\n%s", ctx.pr, err, ctx.outbuf.String(), ctx.errbuf.String()) } log.Debug("Conflict when rebasing staging on to base in %-v at %s: %v\n%s\n%s", ctx.pr, commitSha, err, ctx.outbuf.String(), ctx.errbuf.String()) - return models.ErrRebaseConflicts{ + return ErrRebaseConflicts{ CommitSHA: commitSha, Style: mergeStyle, StdOut: ctx.outbuf.String(), diff --git a/services/pull/patch.go b/services/pull/patch.go index 0934a86c89..36ca9dbdb6 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -13,7 +13,6 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/models" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/unit" @@ -502,6 +501,29 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * return false, nil } +// ErrFilePathProtected represents a "FilePathProtected" kind of error. +type ErrFilePathProtected struct { + Message string + Path string +} + +// IsErrFilePathProtected checks if an error is an ErrFilePathProtected. +func IsErrFilePathProtected(err error) bool { + _, ok := err.(ErrFilePathProtected) + return ok +} + +func (err ErrFilePathProtected) Error() string { + if err.Message != "" { + return err.Message + } + return fmt.Sprintf("path is protected and can not be changed [path: %s]", err.Path) +} + +func (err ErrFilePathProtected) Unwrap() error { + return util.ErrPermissionDenied +} + // CheckFileProtection check file Protection func CheckFileProtection(repo *git.Repository, branchName, oldCommitID, newCommitID string, patterns []glob.Glob, limit int, env []string) ([]string, error) { if len(patterns) == 0 { @@ -525,7 +547,7 @@ func CheckFileProtection(repo *git.Repository, branchName, oldCommitID, newCommi } } if len(changedProtectedFiles) > 0 { - err = models.ErrFilePathProtected{ + err = ErrFilePathProtected{ Path: changedProtectedFiles[0], } } @@ -575,7 +597,7 @@ func checkPullFilesProtection(ctx context.Context, pr *issues_model.PullRequest, } pr.ChangedProtectedFiles, err = CheckFileProtection(gitRepo, pr.HeadBranch, pr.MergeBase, "tracking", pb.GetProtectedFilePatterns(), 10, os.Environ()) - if err != nil && !models.IsErrFilePathProtected(err) { + if err != nil && !IsErrFilePathProtected(err) { return err } return nil diff --git a/services/pull/pull.go b/services/pull/pull.go index 3362cb97ff..0256c2c3f6 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -13,7 +13,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -224,6 +223,28 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error { return nil } +// ErrPullRequestHasMerged represents a "PullRequestHasMerged"-error +type ErrPullRequestHasMerged struct { + ID int64 + IssueID int64 + HeadRepoID int64 + BaseRepoID int64 + HeadBranch string + BaseBranch string +} + +// IsErrPullRequestHasMerged checks if an error is a ErrPullRequestHasMerged. +func IsErrPullRequestHasMerged(err error) bool { + _, ok := err.(ErrPullRequestHasMerged) + return ok +} + +// Error does pretty-printing :D +func (err ErrPullRequestHasMerged) Error() string { + return fmt.Sprintf("pull request has merged [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]", + err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBranch, err.BaseBranch) +} + // ChangeTargetBranch changes the target branch of this pull request, as the given user. func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, targetBranch string) (err error) { releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID)) @@ -247,7 +268,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer } if pr.HasMerged { - return models.ErrPullRequestHasMerged{ + return ErrPullRequestHasMerged{ ID: pr.ID, IssueID: pr.Index, HeadRepoID: pr.HeadRepoID, @@ -654,7 +675,7 @@ func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6 if err = pr.Issue.LoadRepo(ctx); err != nil { errs = append(errs, err) } else if err = ChangeTargetBranch(ctx, pr, doer, targetBranch); err != nil && - !issues_model.IsErrIssueIsClosed(err) && !models.IsErrPullRequestHasMerged(err) && + !issues_model.IsErrIssueIsClosed(err) && !IsErrPullRequestHasMerged(err) && !issues_model.IsErrPullRequestAlreadyExists(err) { errs = append(errs, err) } |