mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-20 11:43:57 +08:00
Automerge supports deleting branch automatically after merging (#32343)
Resolve #32341 ~Depends on #27151~ - [x] It will display a checkbox of deleting the head branch on the pull request view page when starting an auto-merge task. - [x] Add permission check before deleting the branch - [x] Add delete branch comment for those closing pull requests because of head branch or base branch was deleted. - [x] Merge `RetargetChildrenOnMerge` and `AddDeletePRBranchComment` into `service.DeleteBranch`.
This commit is contained in:
parent
2298ff2152
commit
39d51e7c82
@ -166,6 +166,23 @@ func (prs PullRequestList) getRepositoryIDs() []int64 {
|
|||||||
return repoIDs.Values()
|
return repoIDs.Values()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (prs PullRequestList) SetBaseRepo(baseRepo *repo_model.Repository) {
|
||||||
|
for _, pr := range prs {
|
||||||
|
if pr.BaseRepo == nil {
|
||||||
|
pr.BaseRepo = baseRepo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prs PullRequestList) SetHeadRepo(headRepo *repo_model.Repository) {
|
||||||
|
for _, pr := range prs {
|
||||||
|
if pr.HeadRepo == nil {
|
||||||
|
pr.HeadRepo = headRepo
|
||||||
|
pr.isHeadRepoLoaded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (prs PullRequestList) LoadRepositories(ctx context.Context) error {
|
func (prs PullRequestList) LoadRepositories(ctx context.Context) error {
|
||||||
repoIDs := prs.getRepositoryIDs()
|
repoIDs := prs.getRepositoryIDs()
|
||||||
reposMap := make(map[int64]*repo_model.Repository, len(repoIDs))
|
reposMap := make(map[int64]*repo_model.Repository, len(repoIDs))
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/migrations/v1_21"
|
"code.gitea.io/gitea/models/migrations/v1_21"
|
||||||
"code.gitea.io/gitea/models/migrations/v1_22"
|
"code.gitea.io/gitea/models/migrations/v1_22"
|
||||||
"code.gitea.io/gitea/models/migrations/v1_23"
|
"code.gitea.io/gitea/models/migrations/v1_23"
|
||||||
|
"code.gitea.io/gitea/models/migrations/v1_24"
|
||||||
"code.gitea.io/gitea/models/migrations/v1_6"
|
"code.gitea.io/gitea/models/migrations/v1_6"
|
||||||
"code.gitea.io/gitea/models/migrations/v1_7"
|
"code.gitea.io/gitea/models/migrations/v1_7"
|
||||||
"code.gitea.io/gitea/models/migrations/v1_8"
|
"code.gitea.io/gitea/models/migrations/v1_8"
|
||||||
@ -369,6 +370,9 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
|
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
|
||||||
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
|
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
|
||||||
newMigration(311, "Add TimeEstimate to Issue table", v1_23.AddTimeEstimateColumnToIssueTable),
|
newMigration(311, "Add TimeEstimate to Issue table", v1_23.AddTimeEstimateColumnToIssueTable),
|
||||||
|
|
||||||
|
// Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312)
|
||||||
|
newMigration(312, "Add DeleteBranchAfterMerge to AutoMerge", v1_24.AddDeleteBranchAfterMergeForAutoMerge),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
21
models/migrations/v1_24/v312.go
Normal file
21
models/migrations/v1_24/v312.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_24 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pullAutoMerge struct {
|
||||||
|
DeleteBranchAfterMerge bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName return database table name for xorm
|
||||||
|
func (pullAutoMerge) TableName() string {
|
||||||
|
return "pull_auto_merge"
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDeleteBranchAfterMergeForAutoMerge(x *xorm.Engine) error {
|
||||||
|
return x.Sync(new(pullAutoMerge))
|
||||||
|
}
|
@ -15,13 +15,14 @@ import (
|
|||||||
|
|
||||||
// AutoMerge represents a pull request scheduled for merging when checks succeed
|
// AutoMerge represents a pull request scheduled for merging when checks succeed
|
||||||
type AutoMerge struct {
|
type AutoMerge struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
PullID int64 `xorm:"UNIQUE"`
|
PullID int64 `xorm:"UNIQUE"`
|
||||||
DoerID int64 `xorm:"INDEX NOT NULL"`
|
DoerID int64 `xorm:"INDEX NOT NULL"`
|
||||||
Doer *user_model.User `xorm:"-"`
|
Doer *user_model.User `xorm:"-"`
|
||||||
MergeStyle repo_model.MergeStyle `xorm:"varchar(30)"`
|
MergeStyle repo_model.MergeStyle `xorm:"varchar(30)"`
|
||||||
Message string `xorm:"LONGTEXT"`
|
Message string `xorm:"LONGTEXT"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
DeleteBranchAfterMerge bool
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName return database table name for xorm
|
// TableName return database table name for xorm
|
||||||
@ -49,7 +50,7 @@ func IsErrAlreadyScheduledToAutoMerge(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ScheduleAutoMerge schedules a pull request to be merged when all checks succeed
|
// ScheduleAutoMerge schedules a pull request to be merged when all checks succeed
|
||||||
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, style repo_model.MergeStyle, message string) error {
|
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, style repo_model.MergeStyle, message string, deleteBranchAfterMerge bool) error {
|
||||||
// Check if we already have a merge scheduled for that pull request
|
// Check if we already have a merge scheduled for that pull request
|
||||||
if exists, _, err := GetScheduledMergeByPullID(ctx, pullID); err != nil {
|
if exists, _, err := GetScheduledMergeByPullID(ctx, pullID); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -58,10 +59,11 @@ func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err := db.GetEngine(ctx).Insert(&AutoMerge{
|
_, err := db.GetEngine(ctx).Insert(&AutoMerge{
|
||||||
DoerID: doer.ID,
|
DoerID: doer.ID,
|
||||||
PullID: pullID,
|
PullID: pullID,
|
||||||
MergeStyle: style,
|
MergeStyle: style,
|
||||||
Message: message,
|
Message: message,
|
||||||
|
DeleteBranchAfterMerge: deleteBranchAfterMerge,
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ func DeleteBranch(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
|
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case git.IsErrBranchNotExist(err):
|
case git.IsErrBranchNotExist(err):
|
||||||
ctx.NotFound(err)
|
ctx.NotFound(err)
|
||||||
|
@ -971,7 +971,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if form.MergeWhenChecksSucceed {
|
if form.MergeWhenChecksSucceed {
|
||||||
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message)
|
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, form.DeleteBranchAfterMerge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if pull_model.IsErrAlreadyScheduledToAutoMerge(err) {
|
if pull_model.IsErrAlreadyScheduledToAutoMerge(err) {
|
||||||
ctx.Error(http.StatusConflict, "ScheduleAutoMerge", err)
|
ctx.Error(http.StatusConflict, "ScheduleAutoMerge", err)
|
||||||
@ -1043,11 +1043,8 @@ func MergePullRequest(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
defer headRepo.Close()
|
defer headRepo.Close()
|
||||||
}
|
}
|
||||||
if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "RetargetChildrenOnMerge", err)
|
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch, pr); err != nil {
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil {
|
|
||||||
switch {
|
switch {
|
||||||
case git.IsErrBranchNotExist(err):
|
case git.IsErrBranchNotExist(err):
|
||||||
ctx.NotFound(err)
|
ctx.NotFound(err)
|
||||||
@ -1060,10 +1057,6 @@ func MergePullRequest(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := issues_model.AddDeletePRBranchComment(ctx, ctx.Doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
|
|
||||||
// Do not fail here as branch has already been deleted
|
|
||||||
log.Error("DeleteBranch: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func TestHandlePullRequestMerging(t *testing.T) {
|
|||||||
|
|
||||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
err = pull_model.ScheduleAutoMerge(db.DefaultContext, user1, pr.ID, repo_model.MergeStyleSquash, "squash merge a pr")
|
err = pull_model.ScheduleAutoMerge(db.DefaultContext, user1, pr.ID, repo_model.MergeStyleSquash, "squash merge a pr", false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
autoMerge := unittest.AssertExistsAndLoadBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
autoMerge := unittest.AssertExistsAndLoadBean(t, &pull_model.AutoMerge{PullID: pr.ID})
|
||||||
|
@ -97,7 +97,7 @@ func DeleteBranchPost(ctx *context.Context) {
|
|||||||
defer redirect(ctx)
|
defer redirect(ctx)
|
||||||
branchName := ctx.FormString("name")
|
branchName := ctx.FormString("name")
|
||||||
|
|
||||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
|
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case git.IsErrBranchNotExist(err):
|
case git.IsErrBranchNotExist(err):
|
||||||
log.Debug("DeleteBranch: Can't delete non existing branch '%s'", branchName)
|
log.Debug("DeleteBranch: Can't delete non existing branch '%s'", branchName)
|
||||||
|
@ -1097,7 +1097,7 @@ func MergePullRequest(ctx *context.Context) {
|
|||||||
// delete all scheduled auto merges
|
// delete all scheduled auto merges
|
||||||
_ = pull_model.DeleteScheduledAutoMerge(ctx, pr.ID)
|
_ = pull_model.DeleteScheduledAutoMerge(ctx, pr.ID)
|
||||||
// schedule auto merge
|
// schedule auto merge
|
||||||
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message)
|
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, form.DeleteBranchAfterMerge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ScheduleAutoMerge", err)
|
ctx.ServerError("ScheduleAutoMerge", err)
|
||||||
return
|
return
|
||||||
@ -1504,12 +1504,7 @@ func CleanUpPullRequest(ctx *context.Context) {
|
|||||||
func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) {
|
func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) {
|
||||||
fullBranchName := pr.HeadRepo.FullName() + ":" + pr.HeadBranch
|
fullBranchName := pr.HeadRepo.FullName() + ":" + pr.HeadBranch
|
||||||
|
|
||||||
if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
|
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch, pr); err != nil {
|
||||||
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil {
|
|
||||||
switch {
|
switch {
|
||||||
case git.IsErrBranchNotExist(err):
|
case git.IsErrBranchNotExist(err):
|
||||||
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName))
|
||||||
@ -1524,11 +1519,6 @@ func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *g
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := issues_model.AddDeletePRBranchComment(ctx, ctx.Doer, pr.BaseRepo, pr.IssueID, pr.HeadBranch); err != nil {
|
|
||||||
// Do not fail here as branch has already been deleted
|
|
||||||
log.Error("DeleteBranch: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName))
|
ctx.Flash.Success(ctx.Tr("repo.branch.deletion_success", fullBranchName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/queue"
|
"code.gitea.io/gitea/modules/queue"
|
||||||
notify_service "code.gitea.io/gitea/services/notify"
|
notify_service "code.gitea.io/gitea/services/notify"
|
||||||
pull_service "code.gitea.io/gitea/services/pull"
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
// prAutoMergeQueue represents a queue to handle update pull request tests
|
// prAutoMergeQueue represents a queue to handle update pull request tests
|
||||||
@ -63,9 +64,9 @@ func addToQueue(pr *issues_model.PullRequest, sha string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ScheduleAutoMerge if schedule is false and no error, pull can be merged directly
|
// ScheduleAutoMerge if schedule is false and no error, pull can be merged directly
|
||||||
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string) (scheduled bool, err error) {
|
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pull *issues_model.PullRequest, style repo_model.MergeStyle, message string, deleteBranchAfterMerge bool) (scheduled bool, err error) {
|
||||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message); err != nil {
|
if err := pull_model.ScheduleAutoMerge(ctx, doer, pull.ID, style, message, deleteBranchAfterMerge); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
scheduled = true
|
scheduled = true
|
||||||
@ -303,4 +304,10 @@ func handlePullRequestAutoMerge(pullID int64, sha string) {
|
|||||||
// on the pull request page. But this should not be finished in a bug fix PR which will be backport to release branch.
|
// on the pull request page. But this should not be finished in a bug fix PR which will be backport to release branch.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr.Flow == issues_model.PullRequestFlowGithub && scheduledPRM.DeleteBranchAfterMerge {
|
||||||
|
if err := repo_service.DeleteBranch(ctx, doer, pr.HeadRepo, headGitRepo, pr.HeadBranch, pr); err != nil {
|
||||||
|
log.Error("DeletePullRequestHeadBranch: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package pull
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@ -636,33 +637,9 @@ func UpdateRef(ctx context.Context, pr *issues_model.PullRequest) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type errlist []error
|
// retargetBranchPulls change target branch for all pull requests whose base branch is the branch
|
||||||
|
|
||||||
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
|
|
||||||
// Both branch and targetBranch must be in the same repo (for security reasons)
|
// 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)
|
prs, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -672,7 +649,7 @@ func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs errlist
|
var errs []error
|
||||||
for _, pr := range prs {
|
for _, pr := range prs {
|
||||||
if err = pr.Issue.LoadRepo(ctx); err != nil {
|
if err = pr.Issue.LoadRepo(ctx); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
@ -682,40 +659,75 @@ func RetargetBranchPulls(ctx context.Context, doer *user_model.User, repoID int6
|
|||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return errors.Join(errs...)
|
||||||
if len(errs) > 0 {
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseBranchPulls close all the pull requests who's head branch is the branch
|
// AdjustPullsCausedByBranchDeleted 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 {
|
// Or Close all the plls who's base branch is the branch if setting.Repository.PullRequest.RetargetChildrenOnMerge is false.
|
||||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repoID, branch)
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
prs2, err := issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repoID, branch)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prs = append(prs, prs2...)
|
|
||||||
if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
|
if err := issues_model.PullRequestList(prs).LoadAttributes(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
issues_model.PullRequestList(prs).SetHeadRepo(repo)
|
||||||
|
if err := issues_model.PullRequestList(prs).LoadRepositories(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var errs errlist
|
var errs []error
|
||||||
for _, pr := range prs {
|
for _, pr := range prs {
|
||||||
if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrIssueIsClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
|
if err = issue_service.CloseIssue(ctx, pr.Issue, doer, ""); err != nil && !issues_model.IsErrIssueIsClosed(err) && !issues_model.IsErrDependenciesLeft(err) {
|
||||||
errs = append(errs, 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 len(errs) > 0 {
|
|
||||||
return errs
|
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...)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// branch as base branch
|
||||||
|
prs, err = issues_model.GetUnmergedPullRequestsByBaseInfo(ctx, repo.ID, branch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
errs = nil
|
||||||
|
for _, pr := range prs {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
// CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository
|
||||||
@ -725,7 +737,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var errs errlist
|
var errs []error
|
||||||
for _, branch := range branches {
|
for _, branch := range branches {
|
||||||
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch.Name)
|
prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -748,10 +760,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 {
|
return errors.Join(errs...)
|
||||||
return errs
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var commitMessageTrailersPattern = regexp.MustCompile(`(?:^|\n\n)(?:[\w-]+[ \t]*:[^\n]+\n*(?:[ \t]+[^\n]+\n*)*)+$`)
|
var commitMessageTrailersPattern = regexp.MustCompile(`(?:^|\n\n)(?:[\w-]+[ \t]*:[^\n]+\n*(?:[ \t]+[^\n]+\n*)*)+$`)
|
||||||
|
@ -489,7 +489,7 @@ func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchNam
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteBranch delete branch
|
// DeleteBranch delete branch
|
||||||
func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error {
|
func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string, pr *issues_model.PullRequest) error {
|
||||||
err := repo.MustNotBeArchived()
|
err := repo.MustNotBeArchived()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -519,6 +519,12 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr != nil {
|
||||||
|
if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
|
||||||
|
return fmt.Errorf("DeleteBranch: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
return gitRepo.DeleteBranch(branchName, git.DeleteBranchOptions{
|
||||||
Force: true,
|
Force: true,
|
||||||
})
|
})
|
||||||
|
@ -275,7 +275,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notify_service.DeleteRef(ctx, pusher, repo, opts.RefFullName)
|
notify_service.DeleteRef(ctx, pusher, repo, opts.RefFullName)
|
||||||
if err = pull_service.CloseBranchPulls(ctx, pusher, repo.ID, branch); err != nil {
|
|
||||||
|
if err := pull_service.AdjustPullsCausedByBranchDeleted(ctx, pusher, repo, branch); err != nil {
|
||||||
// close all related pulls
|
// close all related pulls
|
||||||
log.Error("close related pull request failed: %v", err)
|
log.Error("close related pull request failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -405,7 +405,7 @@ func TestCreateDeleteRefEvent(t *testing.T) {
|
|||||||
assert.NotNil(t, run)
|
assert.NotNil(t, run)
|
||||||
|
|
||||||
// delete the branch
|
// delete the branch
|
||||||
err = repo_service.DeleteBranch(db.DefaultContext, user2, repo, gitRepo, "test-create-branch")
|
err = repo_service.DeleteBranch(db.DefaultContext, user2, repo, gitRepo, "test-create-branch", nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{
|
||||||
Title: "add workflow",
|
Title: "add workflow",
|
||||||
|
@ -585,6 +585,8 @@ func TestPullDontRetargetChildOnWrongRepo(t *testing.T) {
|
|||||||
elemChildPR := strings.Split(test.RedirectURL(respChildPR), "/")
|
elemChildPR := strings.Split(test.RedirectURL(respChildPR), "/")
|
||||||
assert.EqualValues(t, "pulls", elemChildPR[3])
|
assert.EqualValues(t, "pulls", elemChildPR[3])
|
||||||
|
|
||||||
|
defer test.MockVariableValue(&setting.Repository.PullRequest.RetargetChildrenOnMerge, false)()
|
||||||
|
|
||||||
testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true)
|
testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true)
|
||||||
|
|
||||||
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
|
||||||
@ -736,12 +738,12 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) {
|
|||||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|
||||||
// first time insert automerge record, return true
|
// first time insert automerge record, return true
|
||||||
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, scheduled)
|
assert.True(t, scheduled)
|
||||||
|
|
||||||
// second time insert automerge record, return false because it does exist
|
// second time insert automerge record, return false because it does exist
|
||||||
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.False(t, scheduled)
|
assert.False(t, scheduled)
|
||||||
|
|
||||||
@ -820,12 +822,12 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApproval(t *testing.T) {
|
|||||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
|
||||||
// first time insert automerge record, return true
|
// first time insert automerge record, return true
|
||||||
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, scheduled)
|
assert.True(t, scheduled)
|
||||||
|
|
||||||
// second time insert automerge record, return false because it does exist
|
// second time insert automerge record, return false because it does exist
|
||||||
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.False(t, scheduled)
|
assert.False(t, scheduled)
|
||||||
|
|
||||||
@ -949,12 +951,12 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
|
|||||||
|
|
||||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
// first time insert automerge record, return true
|
// first time insert automerge record, return true
|
||||||
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
scheduled, err := automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, scheduled)
|
assert.True(t, scheduled)
|
||||||
|
|
||||||
// second time insert automerge record, return false because it does exist
|
// second time insert automerge record, return false because it does exist
|
||||||
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test")
|
scheduled, err = automerge.ScheduleAutoMerge(db.DefaultContext, user1, pr, repo_model.MergeStyleMerge, "auto merge test", false)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.False(t, scheduled)
|
assert.False(t, scheduled)
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ function clearMergeMessage() {
|
|||||||
{{ mergeForm.textCancel }}
|
{{ mergeForm.textCancel }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable && !autoMergeWhenSucceed">
|
<div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable">
|
||||||
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
|
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
|
||||||
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
|
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user