···19791979pulls.auto_merge_newly_scheduled_comment = `scheduled this pull request to auto merge when all checks succeed %[1]s`
19801980pulls.auto_merge_canceled_schedule_comment = `canceled auto merging this pull request when all checks succeed %[1]s`
1981198119821982+pulls.delete_after_merge.head_branch.is_default = The head branch you want to delete is the default branch and cannot be deleted.
19831983+pulls.delete_after_merge.head_branch.is_protected = The head branch you want to delete is a protected branch and cannot be deleted.
19841984+pulls.delete_after_merge.head_branch.insufficient_branch = You don't have permission to delete the head branch.
19851985+19821986pulls.delete.title = Delete this pull request?
19831987pulls.delete.text = Do you really want to delete this pull request? (This will permanently remove all content. Consider closing it instead, if you intend to keep it archived)
19841988
+1
release-notes/5718.md
···11+Because of a missing permission check, the branch used to propose a pull request to a repository can always be deleted by the user performing the merge. It was fixed so that such a deletion is only allowed if the user performing the merge has write permission to the repository from which the pull request was made.
+8-25
routers/api/v1/repo/pull.go
···2828 "code.gitea.io/gitea/modules/setting"
2929 api "code.gitea.io/gitea/modules/structs"
3030 "code.gitea.io/gitea/modules/timeutil"
3131+ "code.gitea.io/gitea/modules/util"
3132 "code.gitea.io/gitea/modules/web"
3233 "code.gitea.io/gitea/routers/api/v1/utils"
3334 asymkey_service "code.gitea.io/gitea/services/asymkey"
···10341035 log.Trace("Pull request merged: %d", pr.ID)
1035103610361037 if form.DeleteBranchAfterMerge {
10371037- // Don't cleanup when there are other PR's that use this branch as head branch.
10381038- exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
10391039- if err != nil {
10401040- ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
10411041- return
10421042- }
10431043- if exist {
10441044- ctx.Status(http.StatusOK)
10451045- return
10461046- }
10471047-10481038 var headRepo *git.Repository
10491039 if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil {
10501040 headRepo = ctx.Repo.GitRepo
···10561046 }
10571047 defer headRepo.Close()
10581048 }
10591059- if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
10601060- ctx.Error(http.StatusInternalServerError, "RetargetChildrenOnMerge", err)
10611061- return
10621062- }
10631063- if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil {
10491049+10501050+ if err := repo_service.DeleteBranchAfterMerge(ctx, ctx.Doer, pr, headRepo); err != nil {
10641051 switch {
10651065- case git.IsErrBranchNotExist(err):
10661066- ctx.NotFound(err)
10671052 case errors.Is(err, repo_service.ErrBranchIsDefault):
10681068- ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
10531053+ ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("the head branch is the default branch"))
10691054 case errors.Is(err, git_model.ErrBranchIsProtected):
10701070- ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
10551055+ ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("the head branch is protected"))
10561056+ case errors.Is(err, util.ErrPermissionDenied):
10571057+ ctx.Error(http.StatusForbidden, "HeadBranch", fmt.Errorf("insufficient permission to delete head branch"))
10711058 default:
10721072- ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
10591059+ ctx.Error(http.StatusInternalServerError, "DeleteBranchAfterMerge", err)
10731060 }
10741061 return
10751075- }
10761076- if err := issues_model.AddDeletePRBranchComment(ctx, ctx.Doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
10771077- // Do not fail here as branch has already been deleted
10781078- log.Error("DeleteBranch: %v", err)
10791062 }
10801063 }
10811064
+17-12
routers/web/repo/pull.go
···13891389 log.Trace("Pull request merged: %d", pr.ID)
1390139013911391 if form.DeleteBranchAfterMerge {
13921392- // Don't cleanup when other pr use this branch as head branch
13931393- exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
13941394- if err != nil {
13951395- ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
13961396- return
13971397- }
13981398- if exist {
13991399- ctx.JSONRedirect(issue.Link())
14001400- return
14011401- }
14021402-14031392 var headRepo *git.Repository
14041393 if ctx.Repo != nil && ctx.Repo.Repository != nil && pr.HeadRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
14051394 headRepo = ctx.Repo.GitRepo
14061395 } else {
13961396+ var err error
14071397 headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
14081398 if err != nil {
14091399 ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
···14111401 }
14121402 defer headRepo.Close()
14131403 }
14141414- deleteBranch(ctx, pr, headRepo)
14041404+14051405+ if err := repo_service.DeleteBranchAfterMerge(ctx, ctx.Doer, pr, headRepo); err != nil {
14061406+ switch {
14071407+ case errors.Is(err, repo_service.ErrBranchIsDefault):
14081408+ ctx.Flash.Error(ctx.Tr("repo.pulls.delete_after_merge.head_branch.is_default"))
14091409+ case errors.Is(err, git_model.ErrBranchIsProtected):
14101410+ ctx.Flash.Error(ctx.Tr("repo.pulls.delete_after_merge.head_branch.is_protected"))
14111411+ case errors.Is(err, util.ErrPermissionDenied):
14121412+ ctx.Flash.Error(ctx.Tr("repo.pulls.delete_after_merge.head_branch.insufficient_branch"))
14131413+ default:
14141414+ ctx.ServerError("DeleteBranchAfterMerge", err)
14151415+ }
14161416+14171417+ ctx.JSONRedirect(issue.Link())
14181418+ return
14191419+ }
14151420 }
1416142114171422 ctx.JSONRedirect(issue.Link())
+39
services/repository/branch.go
···1414 "code.gitea.io/gitea/models/db"
1515 git_model "code.gitea.io/gitea/models/git"
1616 issues_model "code.gitea.io/gitea/models/issues"
1717+ "code.gitea.io/gitea/models/perm/access"
1718 repo_model "code.gitea.io/gitea/models/repo"
1919+ "code.gitea.io/gitea/models/unit"
1820 user_model "code.gitea.io/gitea/models/user"
1921 "code.gitea.io/gitea/modules/git"
2022 "code.gitea.io/gitea/modules/gitrepo"
···2426 "code.gitea.io/gitea/modules/queue"
2527 repo_module "code.gitea.io/gitea/modules/repository"
2628 "code.gitea.io/gitea/modules/timeutil"
2929+ "code.gitea.io/gitea/modules/util"
2730 webhook_module "code.gitea.io/gitea/modules/webhook"
2831 notify_service "code.gitea.io/gitea/services/notify"
3232+ pull_service "code.gitea.io/gitea/services/pull"
2933 files_service "code.gitea.io/gitea/services/repository/files"
30343135 "xorm.io/builder"
···470474 RepoName: repo.Name,
471475 }); err != nil {
472476 log.Error("Update: %v", err)
477477+ }
478478+479479+ return nil
480480+}
481481+482482+// DeleteBranchAfterMerge deletes the head branch after a PR was merged assiociated with the head branch.
483483+func DeleteBranchAfterMerge(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest, headRepo *git.Repository) error {
484484+ // Don't cleanup when there are other PR's that use this branch as head branch.
485485+ exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
486486+ if err != nil {
487487+ return err
488488+ }
489489+ if exist {
490490+ return nil
491491+ }
492492+493493+ // Ensure the doer has write permissions to the head repository of the branch it wants to delete.
494494+ perm, err := access.GetUserRepoPermission(ctx, pr.HeadRepo, doer)
495495+ if err != nil {
496496+ return err
497497+ }
498498+ if !perm.CanWrite(unit.TypeCode) {
499499+ return util.NewPermissionDeniedErrorf("Must have write permission to the head repository")
500500+ }
501501+502502+ if err := pull_service.RetargetChildrenOnMerge(ctx, doer, pr); err != nil {
503503+ return err
504504+ }
505505+ if err := DeleteBranch(ctx, doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil {
506506+ return err
507507+ }
508508+509509+ if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
510510+ // Do not fail here as branch has already been deleted
511511+ log.Error("DeleteBranchAfterMerge: %v", err)
473512 }
474513475514 return nil