Skip to content

Commit 34e38ab

Browse files
kemzebwxiaoguang
authored andcommitted
Honor delete branch on merge repo setting when using merge API (go-gitea#35488)
Fix go-gitea#35463. --------- Co-authored-by: wxiaoguang <[email protected]>
1 parent c84d17b commit 34e38ab

File tree

20 files changed

+333
-263
lines changed

20 files changed

+333
-263
lines changed

models/git/protected_branch.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package git
55

66
import (
77
"context"
8-
"errors"
98
"fmt"
109
"slices"
1110
"strings"
@@ -25,7 +24,7 @@ import (
2524
"xorm.io/builder"
2625
)
2726

28-
var ErrBranchIsProtected = errors.New("branch is protected")
27+
var ErrBranchIsProtected = util.ErrorWrap(util.ErrPermissionDenied, "branch is protected")
2928

3029
// ProtectedBranch struct
3130
type ProtectedBranch struct {

modules/util/error.go

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package util
66
import (
77
"errors"
88
"fmt"
9+
"html/template"
910
)
1011

1112
// Common Errors forming the base of our error system
@@ -40,22 +41,6 @@ func (w errorWrapper) Unwrap() error {
4041
return w.Err
4142
}
4243

43-
type LocaleWrapper struct {
44-
err error
45-
TrKey string
46-
TrArgs []any
47-
}
48-
49-
// Error returns the message
50-
func (w LocaleWrapper) Error() string {
51-
return w.err.Error()
52-
}
53-
54-
// Unwrap returns the underlying error
55-
func (w LocaleWrapper) Unwrap() error {
56-
return w.err
57-
}
58-
5944
// ErrorWrap returns an error that formats as the given text but unwraps as the provided error
6045
func ErrorWrap(unwrap error, message string, args ...any) error {
6146
if len(args) == 0 {
@@ -84,15 +69,39 @@ func NewNotExistErrorf(message string, args ...any) error {
8469
return ErrorWrap(ErrNotExist, message, args...)
8570
}
8671

87-
// ErrorWrapLocale wraps an err with a translation key and arguments
88-
func ErrorWrapLocale(err error, trKey string, trArgs ...any) error {
89-
return LocaleWrapper{err: err, TrKey: trKey, TrArgs: trArgs}
72+
// ErrorTranslatable wraps an error with translation information
73+
type ErrorTranslatable interface {
74+
error
75+
Unwrap() error
76+
Translate(ErrorLocaleTranslator) template.HTML
77+
}
78+
79+
type errorTranslatableWrapper struct {
80+
err error
81+
trKey string
82+
trArgs []any
83+
}
84+
85+
type ErrorLocaleTranslator interface {
86+
Tr(key string, args ...any) template.HTML
87+
}
88+
89+
func (w *errorTranslatableWrapper) Error() string { return w.err.Error() }
90+
91+
func (w *errorTranslatableWrapper) Unwrap() error { return w.err }
92+
93+
func (w *errorTranslatableWrapper) Translate(t ErrorLocaleTranslator) template.HTML {
94+
return t.Tr(w.trKey, w.trArgs...)
95+
}
96+
97+
func ErrorWrapTranslatable(err error, trKey string, trArgs ...any) ErrorTranslatable {
98+
return &errorTranslatableWrapper{err: err, trKey: trKey, trArgs: trArgs}
9099
}
91100

92-
func ErrorAsLocale(err error) *LocaleWrapper {
93-
var e LocaleWrapper
101+
func ErrorAsTranslatable(err error) ErrorTranslatable {
102+
var e *errorTranslatableWrapper
94103
if errors.As(err, &e) {
95-
return &e
104+
return e
96105
}
97106
return nil
98107
}

modules/util/error_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package util
5+
6+
import (
7+
"io"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestErrorTranslatable(t *testing.T) {
14+
var err error
15+
16+
err = ErrorWrapTranslatable(io.EOF, "key", 1)
17+
assert.ErrorIs(t, err, io.EOF)
18+
assert.Equal(t, "EOF", err.Error())
19+
assert.Equal(t, "key", err.(*errorTranslatableWrapper).trKey)
20+
assert.Equal(t, []any{1}, err.(*errorTranslatableWrapper).trArgs)
21+
22+
err = ErrorWrap(err, "new msg %d", 100)
23+
assert.ErrorIs(t, err, io.EOF)
24+
assert.Equal(t, "new msg 100", err.Error())
25+
26+
errTr := ErrorAsTranslatable(err)
27+
assert.Equal(t, "EOF", errTr.Error())
28+
assert.Equal(t, "key", errTr.(*errorTranslatableWrapper).trKey)
29+
}

routers/api/v1/repo/pull.go

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"time"
1414

1515
activities_model "code.gitea.io/gitea/models/activities"
16-
git_model "code.gitea.io/gitea/models/git"
1716
issues_model "code.gitea.io/gitea/models/issues"
1817
access_model "code.gitea.io/gitea/models/perm/access"
1918
pull_model "code.gitea.io/gitea/models/pull"
@@ -938,7 +937,7 @@ func MergePullRequest(ctx *context.APIContext) {
938937
} else if errors.Is(err, pull_service.ErrNoPermissionToMerge) {
939938
ctx.APIError(http.StatusMethodNotAllowed, "User not allowed to merge PR")
940939
} else if errors.Is(err, pull_service.ErrHasMerged) {
941-
ctx.APIError(http.StatusMethodNotAllowed, "")
940+
ctx.APIError(http.StatusMethodNotAllowed, "The PR is already merged")
942941
} else if errors.Is(err, pull_service.ErrIsWorkInProgress) {
943942
ctx.APIError(http.StatusMethodNotAllowed, "Work in progress PRs cannot be merged")
944943
} else if errors.Is(err, pull_service.ErrNotMergeableState) {
@@ -989,8 +988,14 @@ func MergePullRequest(ctx *context.APIContext) {
989988
message += "\n\n" + form.MergeMessageField
990989
}
991990

991+
deleteBranchAfterMerge, err := pull_service.ShouldDeleteBranchAfterMerge(ctx, form.DeleteBranchAfterMerge, ctx.Repo.Repository, pr)
992+
if err != nil {
993+
ctx.APIErrorInternal(err)
994+
return
995+
}
996+
992997
if form.MergeWhenChecksSucceed {
993-
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, form.DeleteBranchAfterMerge)
998+
scheduled, err := automerge.ScheduleAutoMerge(ctx, ctx.Doer, pr, repo_model.MergeStyle(form.Do), message, deleteBranchAfterMerge)
994999
if err != nil {
9951000
if pull_model.IsErrAlreadyScheduledToAutoMerge(err) {
9961001
ctx.APIError(http.StatusConflict, err)
@@ -1035,47 +1040,10 @@ func MergePullRequest(ctx *context.APIContext) {
10351040
}
10361041
log.Trace("Pull request merged: %d", pr.ID)
10371042

1038-
// for agit flow, we should not delete the agit reference after merge
1039-
if form.DeleteBranchAfterMerge && pr.Flow == issues_model.PullRequestFlowGithub {
1040-
// check permission even it has been checked in repo_service.DeleteBranch so that we don't need to
1041-
// do RetargetChildrenOnMerge
1042-
if err := repo_service.CanDeleteBranch(ctx, pr.HeadRepo, pr.HeadBranch, ctx.Doer); err == nil {
1043-
// Don't cleanup when there are other PR's that use this branch as head branch.
1044-
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
1045-
if err != nil {
1046-
ctx.APIErrorInternal(err)
1047-
return
1048-
}
1049-
if exist {
1050-
ctx.Status(http.StatusOK)
1051-
return
1052-
}
1053-
1054-
var headRepo *git.Repository
1055-
if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil {
1056-
headRepo = ctx.Repo.GitRepo
1057-
} else {
1058-
headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
1059-
if err != nil {
1060-
ctx.APIErrorInternal(err)
1061-
return
1062-
}
1063-
defer headRepo.Close()
1064-
}
1065-
1066-
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch, pr); err != nil {
1067-
switch {
1068-
case git.IsErrBranchNotExist(err):
1069-
ctx.APIErrorNotFound(err)
1070-
case errors.Is(err, repo_service.ErrBranchIsDefault):
1071-
ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch"))
1072-
case errors.Is(err, git_model.ErrBranchIsProtected):
1073-
ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
1074-
default:
1075-
ctx.APIErrorInternal(err)
1076-
}
1077-
return
1078-
}
1043+
if deleteBranchAfterMerge {
1044+
if err = repo_service.DeleteBranchAfterMerge(ctx, ctx.Doer, pr.ID, nil); err != nil {
1045+
// no way to tell users that what error happens, and the PR has been merged, so ignore the error
1046+
log.Debug("DeleteBranchAfterMerge: pr %d, err: %v", pr.ID, err)
10791047
}
10801048
}
10811049

routers/web/repo/actions/view.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,8 +850,8 @@ func Run(ctx *context_module.Context) {
850850
return nil
851851
})
852852
if err != nil {
853-
if errLocale := util.ErrorAsLocale(err); errLocale != nil {
854-
ctx.Flash.Error(ctx.Tr(errLocale.TrKey, errLocale.TrArgs...))
853+
if errTr := util.ErrorAsTranslatable(err); errTr != nil {
854+
ctx.Flash.Error(errTr.Translate(ctx.Locale))
855855
ctx.Redirect(redirectURL)
856856
} else {
857857
ctx.ServerError("DispatchActionWorkflow", err)

routers/web/repo/editor_apply_patch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func NewDiffPatchPost(ctx *context.Context) {
4141
Committer: parsed.GitCommitter,
4242
})
4343
if err != nil {
44-
err = util.ErrorWrapLocale(err, "repo.editor.fail_to_apply_patch")
44+
err = util.ErrorWrapTranslatable(err, "repo.editor.fail_to_apply_patch")
4545
}
4646
if err != nil {
4747
editorHandleFileOperationError(ctx, parsed.NewBranchName, err)

routers/web/repo/editor_cherry_pick.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func CherryPickPost(ctx *context.Context) {
7474
opts.Content = buf.String()
7575
_, err = files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, opts)
7676
if err != nil {
77-
err = util.ErrorWrapLocale(err, "repo.editor.fail_to_apply_patch")
77+
err = util.ErrorWrapTranslatable(err, "repo.editor.fail_to_apply_patch")
7878
}
7979
}
8080
if err != nil {

routers/web/repo/editor_error.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ func editorHandleFileOperationErrorRender(ctx *context_service.Context, message,
3838
}
3939

4040
func editorHandleFileOperationError(ctx *context_service.Context, targetBranchName string, err error) {
41-
if errAs := util.ErrorAsLocale(err); errAs != nil {
42-
ctx.JSONError(ctx.Tr(errAs.TrKey, errAs.TrArgs...))
41+
if errAs := util.ErrorAsTranslatable(err); errAs != nil {
42+
ctx.JSONError(errAs.Translate(ctx.Locale))
4343
} else if errAs, ok := errorAs[git.ErrNotExist](err); ok {
4444
ctx.JSONError(ctx.Tr("repo.editor.file_modifying_no_longer_exists", errAs.RelPath))
4545
} else if errAs, ok := errorAs[git_model.ErrLFSFileLocked](err); ok {

0 commit comments

Comments
 (0)