aboutsummaryrefslogtreecommitdiffstats
path: root/services/pull
diff options
context:
space:
mode:
Diffstat (limited to 'services/pull')
-rw-r--r--services/pull/check.go5
-rw-r--r--services/pull/merge.go119
-rw-r--r--services/pull/merge_prepare.go45
-rw-r--r--services/pull/patch.go28
-rw-r--r--services/pull/pull.go27
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)
}