mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-03 02:21:17 +02:00
Merge branch 'main' into lunny/issue_dev
This commit is contained in:
commit
a84034a838
829 files changed
+14037
-11156
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -301,7 +300,7 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool {
|
||||
pr.Merger = merger
|
||||
pr.MergerID = merger.ID
|
||||
|
||||
if merged, err := pr.SetMerged(ctx); err != nil {
|
||||
if merged, err := SetMerged(ctx, pr); err != nil {
|
||||
log.Error("%-v setMerged : %v", pr, err)
|
||||
return false
|
||||
} else if !merged {
|
||||
|
||||
+172
-17
@@ -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))
|
||||
@@ -242,14 +263,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
|
||||
@@ -283,7 +307,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 +398,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 +465,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 +473,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 +508,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 +546,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 +578,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 +607,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)
|
||||
@@ -543,7 +639,7 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use
|
||||
pr.MergerID = doer.ID
|
||||
|
||||
var merged bool
|
||||
if merged, err = pr.SetMerged(ctx); err != nil {
|
||||
if merged, err = SetMerged(ctx, pr); err != nil {
|
||||
return err
|
||||
} else if !merged {
|
||||
return fmt.Errorf("SetMerged failed")
|
||||
@@ -560,3 +656,62 @@ 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) (bool, error) {
|
||||
if pr.HasMerged {
|
||||
return false, fmt.Errorf("PullRequest[%d] already merged", pr.Index)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
pr.HasMerged = true
|
||||
sess := db.GetEngine(ctx)
|
||||
|
||||
if _, err := sess.Exec("UPDATE `issue` SET `repo_id` = `repo_id` WHERE `id` = ?", pr.IssueID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if _, err := sess.Exec("UPDATE `pull_request` SET `issue_id` = `issue_id` WHERE `id` = ?", pr.ID); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
pr.Issue = nil
|
||||
if err := pr.LoadIssue(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if tmpPr, err := issues_model.GetPullRequestByID(ctx, pr.ID); err != nil {
|
||||
return false, err
|
||||
} else if tmpPr.HasMerged {
|
||||
if pr.Issue.IsClosed {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("PullRequest[%d] already merged but it's associated issue [%d] is not closed", pr.Index, pr.IssueID)
|
||||
} else if pr.Issue.IsClosed {
|
||||
return false, fmt.Errorf("PullRequest[%d] already closed", pr.Index)
|
||||
}
|
||||
|
||||
if err := pr.Issue.LoadRepo(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := pr.Issue.Repo.LoadOwner(ctx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if _, err := issues_model.ChangeIssueStatus(ctx, pr.Issue, pr.Merger, true, true); err != nil {
|
||||
return false, fmt.Errorf("ChangeIssueStatus: %w", err)
|
||||
}
|
||||
|
||||
// reset the conflicted files as there cannot be any if we're merged
|
||||
pr.ConflictedFiles = []string{}
|
||||
|
||||
// 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 _, err := sess.Where("id = ?", pr.ID).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)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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).
|
||||
|
||||
+39
-7
@@ -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"
|
||||
@@ -42,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
|
||||
}
|
||||
@@ -355,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)
|
||||
@@ -502,6 +511,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 +557,7 @@ func CheckFileProtection(repo *git.Repository, branchName, oldCommitID, newCommi
|
||||
}
|
||||
}
|
||||
if len(changedProtectedFiles) > 0 {
|
||||
err = models.ErrFilePathProtected{
|
||||
err = ErrFilePathProtected{
|
||||
Path: changedProtectedFiles[0],
|
||||
}
|
||||
}
|
||||
@@ -575,7 +607,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
|
||||
|
||||
+28
-6
@@ -14,7 +14,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"
|
||||
@@ -66,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
|
||||
@@ -242,6 +242,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))
|
||||
@@ -265,7 +287,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,
|
||||
@@ -672,7 +694,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)
|
||||
}
|
||||
@@ -703,7 +725,7 @@ func CloseBranchPulls(ctx context.Context, doer *user_model.User, repoID int64,
|
||||
|
||||
var errs errlist
|
||||
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 = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrPullWasClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
@@ -737,7 +759,7 @@ 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.IsErrPullWasClosed(err) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestRepoGetReviewers(t *testing.T) {
|
||||
// should not include doer and remove the poster
|
||||
reviewers, err = pull_service.GetReviewers(ctx, repo1, 11, 2)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, reviewers, 0)
|
||||
assert.Empty(t, reviewers)
|
||||
|
||||
// should not include PR poster, if PR poster would be otherwise eligible
|
||||
reviewers, err = pull_service.GetReviewers(ctx, repo1, 11, 4)
|
||||
@@ -43,7 +43,7 @@ func TestRepoGetReviewers(t *testing.T) {
|
||||
reviewers, err = pull_service.GetReviewers(ctx, repo2, 2, 4)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, reviewers, 1)
|
||||
assert.EqualValues(t, reviewers[0].ID, 2)
|
||||
assert.EqualValues(t, 2, reviewers[0].ID)
|
||||
|
||||
// test private org repo
|
||||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||
|
||||
Reference in new issue
Block a user