aboutsummaryrefslogtreecommitdiffstats
path: root/services/pull
diff options
context:
space:
mode:
Diffstat (limited to 'services/pull')
-rw-r--r--services/pull/check.go7
-rw-r--r--services/pull/merge.go83
-rw-r--r--services/pull/merge_squash.go11
-rw-r--r--services/pull/patch.go18
-rw-r--r--services/pull/pull.go111
5 files changed, 156 insertions, 74 deletions
diff --git a/services/pull/check.go b/services/pull/check.go
index bffca394a8..e1adc3ca3b 100644
--- a/services/pull/check.go
+++ b/services/pull/check.go
@@ -282,9 +282,6 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
return false
}
- pr.MergedCommitID = commit.ID.String()
- pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix())
- pr.Status = issues_model.PullRequestStatusManuallyMerged
merger, _ := user_model.GetUserByEmail(ctx, commit.Author.Email)
// When the commit author is unknown set the BaseRepo owner as merger
@@ -297,10 +294,8 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
}
merger = pr.BaseRepo.Owner
}
- pr.Merger = merger
- pr.MergerID = merger.ID
- if merged, err := pr.SetMerged(ctx); err != nil {
+ if merged, err := SetMerged(ctx, pr, commit.ID.String(), timeutil.TimeStamp(commit.Author.When.Unix()), merger, issues_model.PullRequestStatusManuallyMerged); err != nil {
log.Error("%-v setMerged : %v", pr, err)
return false
} else if !merged {
diff --git a/services/pull/merge.go b/services/pull/merge.go
index fba85f1e51..9c909ef795 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -17,6 +17,7 @@ import (
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
+ pull_model "code.gitea.io/gitea/models/pull"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -263,14 +264,17 @@ func handleCloseCrossReferences(ctx context.Context, pr *issues_model.PullReques
if err = ref.Issue.LoadRepo(ctx); err != nil {
return err
}
- isClosed := ref.RefAction == references.XRefActionCloses
- if isClosed != ref.Issue.IsClosed {
- if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, isClosed); err != nil {
+ if ref.RefAction == references.XRefActionCloses && !ref.Issue.IsClosed {
+ if err = issue_service.CloseIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
// Allow ErrDependenciesLeft
if !issues_model.IsErrDependenciesLeft(err) {
return err
}
}
+ } else if ref.RefAction == references.XRefActionReopens && ref.Issue.IsClosed {
+ if err = issue_service.ReopenIssue(ctx, ref.Issue, doer, pr.MergedCommitID); err != nil {
+ return err
+ }
}
}
return nil
@@ -629,14 +633,8 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use
return fmt.Errorf("Wrong commit ID")
}
- pr.MergedCommitID = commitID
- pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix())
- pr.Status = issues_model.PullRequestStatusManuallyMerged
- pr.Merger = doer
- pr.MergerID = doer.ID
-
var merged bool
- if merged, err = pr.SetMerged(ctx); err != nil {
+ if merged, err = SetMerged(ctx, pr, commitID, timeutil.TimeStamp(commit.Author.When.Unix()), doer, issues_model.PullRequestStatusManuallyMerged); err != nil {
return err
} else if !merged {
return fmt.Errorf("SetMerged failed")
@@ -653,3 +651,68 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use
return handleCloseCrossReferences(ctx, pr, doer)
}
+
+// SetMerged sets a pull request to merged and closes the corresponding issue
+func SetMerged(ctx context.Context, pr *issues_model.PullRequest, mergedCommitID string, mergedTimeStamp timeutil.TimeStamp, merger *user_model.User, mergeStatus issues_model.PullRequestStatus) (bool, error) {
+ if pr.HasMerged {
+ return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index)
+ }
+
+ pr.HasMerged = true
+ pr.MergedCommitID = mergedCommitID
+ pr.MergedUnix = mergedTimeStamp
+ pr.Merger = merger
+ pr.MergerID = merger.ID
+ pr.Status = mergeStatus
+ // reset the conflicted files as there cannot be any if we're merged
+ pr.ConflictedFiles = []string{}
+
+ if pr.MergedCommitID == "" || pr.MergedUnix == 0 || pr.Merger == nil {
+ return false, fmt.Errorf("unable to merge PullRequest[%d], some required fields are empty", pr.Index)
+ }
+
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return false, err
+ }
+ defer committer.Close()
+
+ pr.Issue = nil
+ if err := pr.LoadIssue(ctx); err != nil {
+ return false, err
+ }
+
+ if err := pr.Issue.LoadRepo(ctx); err != nil {
+ return false, err
+ }
+
+ if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
+ return false, err
+ }
+
+ // Removing an auto merge pull and ignore if not exist
+ if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) {
+ return false, fmt.Errorf("DeleteScheduledAutoMerge[%d]: %v", pr.ID, err)
+ }
+
+ // Set issue as closed
+ if _, err := issues_model.SetIssueAsClosed(ctx, pr.Issue, pr.Merger, true); err != nil {
+ return false, fmt.Errorf("ChangeIssueStatus: %w", err)
+ }
+
+ // We need to save all of the data used to compute this merge as it may have already been changed by TestPatch. FIXME: need to set some state to prevent TestPatch from running whilst we are merging.
+ if cnt, err := db.GetEngine(ctx).Where("id = ?", pr.ID).
+ And("has_merged = ?", false).
+ Cols("has_merged, status, merge_base, merged_commit_id, merger_id, merged_unix, conflicted_files").
+ Update(pr); err != nil {
+ return false, fmt.Errorf("failed to update pr[%d]: %w", pr.ID, err)
+ } else if cnt != 1 {
+ return false, issues_model.ErrIssueAlreadyChanged
+ }
+
+ if err := committer.Commit(); err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go
index 197d8102dd..7258671888 100644
--- a/services/pull/merge_squash.go
+++ b/services/pull/merge_squash.go
@@ -5,12 +5,12 @@ package pull
import (
"fmt"
+ "strings"
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/log"
"code.gitea.io/gitea/modules/setting"
)
@@ -25,12 +25,12 @@ func getAuthorSignatureSquash(ctx *mergeContext) (*git.Signature, error) {
// Try to get an signature from the same user in one of the commits, as the
// poster email might be private or commits might have a different signature
// than the primary email address of the poster.
- gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpenPath(ctx, ctx.tmpBasePath)
+ gitRepo, err := git.OpenRepository(ctx, ctx.tmpBasePath)
if err != nil {
log.Error("%-v Unable to open base repository: %v", ctx.pr, err)
return nil, err
}
- defer closer.Close()
+ defer gitRepo.Close()
commits, err := gitRepo.CommitsBetweenIDs(trackingBranch, "HEAD")
if err != nil {
@@ -66,7 +66,10 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error {
if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() {
// add trailer
- message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String())
+ if !strings.Contains(message, fmt.Sprintf("Co-authored-by: %s", sig.String())) {
+ message += fmt.Sprintf("\nCo-authored-by: %s", sig.String())
+ }
+ message += fmt.Sprintf("\nCo-committed-by: %s\n", sig.String())
}
cmdCommit := git.NewCommand(ctx, "commit").
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email).
diff --git a/services/pull/patch.go b/services/pull/patch.go
index 36ca9dbdb6..13623d73c6 100644
--- a/services/pull/patch.go
+++ b/services/pull/patch.go
@@ -41,9 +41,19 @@ func DownloadDiffOrPatch(ctx context.Context, pr *issues_model.PullRequest, w io
}
defer closer.Close()
- if err := gitRepo.GetDiffOrPatch(pr.MergeBase, pr.GetGitRefName(), w, patch, binary); err != nil {
- log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
- return fmt.Errorf("Unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
+ compareArg := pr.MergeBase + "..." + pr.GetGitRefName()
+ switch {
+ case patch:
+ err = gitRepo.GetPatch(compareArg, w)
+ case binary:
+ err = gitRepo.GetDiffBinary(compareArg, w)
+ default:
+ err = gitRepo.GetDiff(compareArg, w)
+ }
+
+ if err != nil {
+ log.Error("unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
+ return fmt.Errorf("unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
}
return nil
}
@@ -354,7 +364,7 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo *
_ = util.Remove(tmpPatchFile.Name())
}()
- if err := gitRepo.GetDiffBinary(pr.MergeBase, "tracking", tmpPatchFile); err != nil {
+ if err := gitRepo.GetDiffBinary(pr.MergeBase+"...tracking", tmpPatchFile); err != nil {
tmpPatchFile.Close()
log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
return false, fmt.Errorf("unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err)
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 0256c2c3f6..5d3758eca6 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -6,6 +6,7 @@ package pull
import (
"bytes"
"context"
+ "errors"
"fmt"
"io"
"os"
@@ -64,7 +65,8 @@ func NewPullRequest(ctx context.Context, opts *NewPullRequestOptions) error {
}
// user should be a collaborator or a member of the organization for base repo
- if !issue.Poster.IsAdmin {
+ canCreate := issue.Poster.IsAdmin || pr.Flow == issues_model.PullRequestFlowAGit
+ if !canCreate {
canCreate, err := repo_model.IsOwnerMemberCollaborator(ctx, repo, issue.Poster.ID)
if err != nil {
return err
@@ -264,6 +266,7 @@ func ChangeTargetBranch(ctx context.Context, pr *issues_model.PullRequest, doer
ID: pr.Issue.ID,
RepoID: pr.Issue.RepoID,
Index: pr.Issue.Index,
+ IsPull: true,
}
}
@@ -634,33 +637,9 @@ func UpdateRef(ctx context.Context, pr *issues_model.PullRequest) (err error) {
return err
}
-type errlist []error
-
-func (errs errlist) Error() string {
- if len(errs) > 0 {
- var buf strings.Builder
- for i, err := range errs {
- if i > 0 {
- buf.WriteString(", ")
- }
- buf.WriteString(err.Error())
- }
- return buf.String()
- }
- return ""
-}
-
-// RetargetChildrenOnMerge retarget children pull requests on merge if possible
-func RetargetChildrenOnMerge(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) error {
- if setting.Repository.PullRequest.RetargetChildrenOnMerge && pr.BaseRepoID == pr.HeadRepoID {
- return RetargetBranchPulls(ctx, doer, pr.HeadRepoID, pr.HeadBranch, pr.BaseBranch)
- }
- return nil
-}
-
-// RetargetBranchPulls change target branch for all pull requests whose base branch is the branch
+// retargetBranchPulls change target branch for all pull requests whose base branch is the branch
// Both branch and targetBranch must be in the same repo (for security reasons)
-func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch, targetBranch string) error {
+func retargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch, targetBranch string) error {
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
if err != nil {
return err
@@ -670,7 +649,7 @@ func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6
return err
}
- var errs errlist
+ var errs []error
for _, pr := range prs {
if err = pr.Issue.LoadRepo(ctx); err != nil {
errs = append(errs, err)
@@ -680,40 +659,75 @@ func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6
errs = append(errs, err)
}
}
-
- if len(errs) > 0 {
- return errs
- }
- return nil
+ return errors.Join(errs...)
}
-// CloseBranchPulls close all the pull requests who's head branch is the branch
-func CloseBranchPulls(ctx context.Context, doer *user_model.User, repoID int64, branch string) error {
- prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repoID, branch)
+// AdjustPullsCausedByBranchDeleted close all the pull requests who's head branch is the branch
+// Or Close all the plls who's base branch is the branch if setting.Repository.PullRequest.RetargetChildrenOnMerge is false.
+// If it's true, Retarget all these pulls to the default branch.
+func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branch string) error {
+ // branch as head branch
+ prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch)
if err != nil {
return err
}
- prs2, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
+ if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
+ return err
+ }
+ issues_model.PullRequestList(prs).SetHeadRepo(repo)
+ if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil {
+ return err
+ }
+
+ var errs []error
+ for _, pr := range prs {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrIssueIsClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
+ errs = append(errs, err)
+ }
+ if err == nil {
+ if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
+ log.Error("AddDeletePRBranchComment: %v", err)
+ errs = append(errs, err)
+ }
+ }
+ }
+
+ if setting.Repository.PullRequest.RetargetChildrenOnMerge {
+ if err := retargetBranchPulls(ctx, doer, repo.ID, branch, repo.DefaultBranch); err != nil {
+ log.Error("retargetBranchPulls failed: %v", err)
+ errs = append(errs, err)
+ }
+ return errors.Join(errs...)
+ }
+
+ // branch as base branch
+ prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repo.ID, branch)
if err != nil {
return err
}
- prs = append(prs, prs2...)
if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
return err
}
+ issues_model.PullRequestList(prs).SetBaseRepo(repo)
+ if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil {
+ return err
+ }
- var errs errlist
+ errs = nil
for _, pr := range prs {
- if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
+ if err = issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.BaseBranch); err != nil {
+ log.Error("AddDeletePRBranchComment: %v", err)
errs = append(errs, err)
}
+ if err == nil {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrIssueIsClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
+ errs = append(errs, err)
+ }
+ }
}
- if len(errs) > 0 {
- return errs
- }
- return nil
+ return errors.Join(errs...)
}
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository
@@ -723,7 +737,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
return err
}
- var errs errlist
+ var errs []error
for _, branch := range branches {
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch.Name)
if err != nil {
@@ -740,16 +754,13 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
if pr.BaseRepoID == repo.ID {
continue
}
- if err = issue_service.ChangeStatus(ctx, pr.Issue, doer, "", true); err != nil && !issues_model.IsErrPullWasClosed(err) {
+ if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrIssueIsClosed(err) {
errs = append(errs, err)
}
}
}
- if len(errs) > 0 {
- return errs
- }
- return nil
+ return errors.Join(errs...)
}
var commitMessageTrailersPattern = regexp.MustCompile(`(?:^|\n\n)(?:[\w-]+[ \t]*:[^\n]+\n*(?:[ \t]+[^\n]+\n*)*)+$`)