Honor delete branch on merge repo setting when using merge API (#35488)

Fix #35463.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Kemal Zebari
2025-10-21 22:06:56 -07:00
committed by GitHub
parent 5f0697243c
commit a9f2ea720b
20 changed files with 334 additions and 264 deletions

View File

@@ -129,7 +129,7 @@ func TestAPIRepoIssueConfigPaths(t *testing.T) {
configData, err := yaml.Marshal(configMap)
assert.NoError(t, err)
_, err = createFileInBranch(owner, repo, fullPath, repo.DefaultBranch, string(configData))
_, err = createFile(owner, repo, fullPath, string(configData))
assert.NoError(t, err)
issueConfig := getIssueConfig(t, owner.Name, repo.Name)

View File

@@ -17,19 +17,23 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/gitdiff"
issue_service "code.gitea.io/gitea/services/issue"
pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAPIViewPulls(t *testing.T) {
@@ -186,6 +190,76 @@ func TestAPIMergePullWIP(t *testing.T) {
MakeRequest(t, req, http.StatusMethodNotAllowed)
}
func TestAPIMergePull(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
apiCtx := NewAPITestContext(t, repo.OwnerName, repo.Name, auth_model.AccessTokenScopeWriteRepository)
checkBranchExists := func(t *testing.T, branchName string, status int) {
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", owner.Name, repo.Name, branchName)).AddTokenAuth(apiCtx.Token)
MakeRequest(t, req, status)
}
createTestBranchPR := func(t *testing.T, branchName string) *api.PullRequest {
testCreateFileInBranch(t, owner, repo, createFileInBranchOptions{NewBranch: branchName}, map[string]string{"a-new-file-" + branchName + ".txt": "dummy content"})
prDTO, err := doAPICreatePullRequest(apiCtx, repo.OwnerName, repo.Name, repo.DefaultBranch, branchName)(t)
require.NoError(t, err)
return &prDTO
}
performMerge := func(t *testing.T, prIndex int64, params map[string]any, optExpectedStatus ...int) {
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner.Name, repo.Name, prIndex), params).AddTokenAuth(apiCtx.Token)
expectedStatus := util.OptionalArg(optExpectedStatus, http.StatusOK)
MakeRequest(t, req, expectedStatus)
}
t.Run("Normal", func(t *testing.T) {
newBranch := "test-pull-1"
prDTO := createTestBranchPR(t, newBranch)
performMerge(t, prDTO.Index, map[string]any{"do": "merge"})
checkBranchExists(t, newBranch, http.StatusOK)
// try to merge again, make sure we cannot perform a merge on the same PR
performMerge(t, prDTO.Index, map[string]any{"do": "merge"}, http.StatusMethodNotAllowed)
})
t.Run("DeleteBranchAfterMergePassedByFormField", func(t *testing.T) {
newBranch := "test-pull-2"
prDTO := createTestBranchPR(t, newBranch)
performMerge(t, prDTO.Index, map[string]any{"do": "merge", "delete_branch_after_merge": true})
checkBranchExists(t, newBranch, http.StatusNotFound)
})
updateRepoUnitDefaultDeleteBranchAfterMerge := func(t *testing.T, repo *repo_model.Repository, value bool) {
prUnit, err := repo.GetUnit(t.Context(), unit_model.TypePullRequests)
require.NoError(t, err)
prUnit.PullRequestsConfig().DefaultDeleteBranchAfterMerge = value
require.NoError(t, repo_service.UpdateRepositoryUnits(t.Context(), repo, []repo_model.RepoUnit{{
RepoID: repo.ID,
Type: unit_model.TypePullRequests,
Config: prUnit.PullRequestsConfig(),
}}, nil))
}
t.Run("DeleteBranchAfterMergePassedByRepoSettings", func(t *testing.T) {
newBranch := "test-pull-3"
prDTO := createTestBranchPR(t, newBranch)
updateRepoUnitDefaultDeleteBranchAfterMerge(t, repo, true)
performMerge(t, prDTO.Index, map[string]any{"do": "merge"})
checkBranchExists(t, newBranch, http.StatusNotFound)
})
t.Run("DeleteBranchAfterMergeFormFieldIsSetButNotRepoSettings", func(t *testing.T) {
newBranch := "test-pull-4"
prDTO := createTestBranchPR(t, newBranch)
updateRepoUnitDefaultDeleteBranchAfterMerge(t, repo, false)
performMerge(t, prDTO.Index, map[string]any{"do": "merge", "delete_branch_after_merge": true})
checkBranchExists(t, newBranch, http.StatusNotFound)
})
})
}
func TestAPICreatePullSuccess(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10})

View File

@@ -133,7 +133,7 @@ func BenchmarkAPICreateFileSmall(b *testing.B) {
b.ResetTimer()
for n := 0; b.Loop(); n++ {
treePath := fmt.Sprintf("update/file%d.txt", n)
_, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
_, _ = createFile(user2, repo1, treePath)
}
})
}
@@ -149,7 +149,7 @@ func BenchmarkAPICreateFileMedium(b *testing.B) {
for n := 0; b.Loop(); n++ {
treePath := fmt.Sprintf("update/file%d.txt", n)
copy(data, treePath)
_, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
_, _ = createFile(user2, repo1, treePath)
}
})
}

View File

@@ -6,26 +6,36 @@ package integration
import (
"context"
"strings"
"testing"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/require"
)
func createFileInBranch(user *user_model.User, repo *repo_model.Repository, treePath, branchName, content string) (*api.FilesResponse, error) {
type createFileInBranchOptions struct {
OldBranch, NewBranch string
}
func testCreateFileInBranch(t *testing.T, user *user_model.User, repo *repo_model.Repository, createOpts createFileInBranchOptions, files map[string]string) *api.FilesResponse {
resp, err := createFileInBranch(user, repo, createOpts, files)
require.NoError(t, err)
return resp
}
func createFileInBranch(user *user_model.User, repo *repo_model.Repository, createOpts createFileInBranchOptions, files map[string]string) (*api.FilesResponse, error) {
ctx := context.TODO()
opts := &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{
{
Operation: "create",
TreePath: treePath,
ContentReader: strings.NewReader(content),
},
},
OldBranch: branchName,
Author: nil,
Committer: nil,
opts := &files_service.ChangeRepoFilesOptions{OldBranch: createOpts.OldBranch, NewBranch: createOpts.NewBranch}
for path, content := range files {
opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
Operation: "create",
TreePath: path,
ContentReader: strings.NewReader(content),
})
}
return files_service.ChangeRepoFiles(ctx, repo, user, opts)
}
@@ -53,10 +63,12 @@ func createOrReplaceFileInBranch(user *user_model.User, repo *repo_model.Reposit
return err
}
_, err = createFileInBranch(user, repo, treePath, branchName, content)
_, err = createFileInBranch(user, repo, createFileInBranchOptions{OldBranch: branchName}, map[string]string{treePath: content})
return err
}
func createFile(user *user_model.User, repo *repo_model.Repository, treePath string) (*api.FilesResponse, error) {
return createFileInBranch(user, repo, treePath, repo.DefaultBranch, "This is a NEW file")
// TODO: replace all usages of this function with testCreateFileInBranch or testCreateFile
func createFile(user *user_model.User, repo *repo_model.Repository, treePath string, optContent ...string) (*api.FilesResponse, error) {
content := util.OptionalArg(optContent, "This is a NEW file") // some tests need this default content because its SHA is hardcoded
return createFileInBranch(user, repo, createFileInBranchOptions{}, map[string]string{treePath: content})
}

View File

@@ -93,7 +93,7 @@ func testPullCleanUp(t *testing.T, session *TestSession, user, repo, pullnum str
// Click the little button to create a pull
htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find(".timeline-item .delete-button").Attr("data-url")
link, exists := htmlDoc.doc.Find(".timeline-item .delete-branch-after-merge").Attr("data-url")
assert.True(t, exists, "The template has changed, can not find delete button url")
req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(),