From 5582b855d2053e1a5482f0adcd75c37e7cb36ff8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 20 Feb 2026 22:24:36 -0800 Subject: [PATCH 1/7] Delete non-exist branch should return 404 --- services/repository/branch.go | 3 +++ services/repository/branch_test.go | 36 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 services/repository/branch_test.go diff --git a/services/repository/branch.go b/services/repository/branch.go index b3310b2e68c6e..48a5578aec625 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -596,6 +596,9 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R if err != nil && !errors.Is(err, util.ErrNotExist) { return err } + if notExist && branchCommit == nil { + return git.ErrBranchNotExist{Name: branchName} + } if err := db.WithTx(ctx, func(ctx context.Context) error { if !notExist { diff --git a/services/repository/branch_test.go b/services/repository/branch_test.go new file mode 100644 index 0000000000000..c4ed307c837ab --- /dev/null +++ b/services/repository/branch_test.go @@ -0,0 +1,36 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "testing" + + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDeleteBranchReturnsNotFoundWhenMissing(t *testing.T) { + unittest.PrepareTestEnv(t) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) + require.NoError(t, err) + defer gitRepo.Close() + + require.NoError(t, DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil)) + + err = DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil) + assert.True(t, git.IsErrBranchNotExist(err)) + + err = DeleteBranch(t.Context(), doer, repo, gitRepo, "branch-does-not-exist", nil) + assert.True(t, git.IsErrBranchNotExist(err)) +} From 245639455798a0a8973ec557c032f9a4a5f23af5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 22 Feb 2026 13:37:20 -0800 Subject: [PATCH 2/7] Use integration test because there is no push queue in unit tests --- services/repository/branch_test.go | 36 --------------------------- tests/integration/repo_branch_test.go | 24 ++++++++++++++++++ 2 files changed, 24 insertions(+), 36 deletions(-) delete mode 100644 services/repository/branch_test.go diff --git a/services/repository/branch_test.go b/services/repository/branch_test.go deleted file mode 100644 index c4ed307c837ab..0000000000000 --- a/services/repository/branch_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2025 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repository - -import ( - "testing" - - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDeleteBranchReturnsNotFoundWhenMissing(t *testing.T) { - unittest.PrepareTestEnv(t) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) - require.NoError(t, err) - defer gitRepo.Close() - - require.NoError(t, DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil)) - - err = DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil) - assert.True(t, git.IsErrBranchNotExist(err)) - - err = DeleteBranch(t.Context(), doer, repo, gitRepo, "branch-does-not-exist", nil) - assert.True(t, git.IsErrBranchNotExist(err)) -} diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index 6cf9990d2eaee..2502216815784 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -13,11 +13,16 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" + repo_service "code.gitea.io/gitea/services/repository" "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { @@ -35,6 +40,25 @@ func TestCreateBranch(t *testing.T) { onGiteaRun(t, testCreateBranches) } +func TestDeleteBranchReturnsNotFoundWhenMissing(t *testing.T) { + onGiteaRun(t, func(t *testing.T, _ *url.URL) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) + require.NoError(t, err) + defer gitRepo.Close() + + require.NoError(t, repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil)) + + err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil) + assert.True(t, git.IsErrBranchNotExist(err)) + + err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch-does-not-exist", nil) + assert.True(t, git.IsErrBranchNotExist(err)) + }) +} + func testCreateBranches(t *testing.T, giteaURL *url.URL) { tests := []struct { OldRefSubURL string From 2126c6d3e149ea929ba0f1e4fa133c06645897cc Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 24 Feb 2026 21:32:30 -0800 Subject: [PATCH 3/7] adjustment --- services/repository/branch.go | 10 ++++----- tests/integration/repo_branch_test.go | 31 +++++++++++++++------------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/services/repository/branch.go b/services/repository/branch.go index 48a5578aec625..29f372bfc2371 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -590,18 +590,18 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R } // database branch record not exist or it's a deleted branch - notExist := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted + notExistInDB := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted branchCommit, err := gitRepo.GetBranchCommit(branchName) if err != nil && !errors.Is(err, util.ErrNotExist) { return err } - if notExist && branchCommit == nil { + if notExistInDB && branchCommit == nil { // branch does not exist and data are synced between db and git data return git.ErrBranchNotExist{Name: branchName} } if err := db.WithTx(ctx, func(ctx context.Context) error { - if !notExist { + if !notExistInDB { if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { return err } @@ -612,7 +612,7 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return fmt.Errorf("DeleteBranch: %v", err) } } - if branchCommit == nil { + if branchCommit == nil { // branch does not exist in git data, we just mark it as deleted in database return nil } @@ -621,12 +621,12 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return err } + // if branch does not exist in git data, we don't need to send event if branchCommit == nil { return nil } // Don't return error below this - objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) if err := PushUpdate( &repo_module.PushUpdateOptions{ diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index 2502216815784..33952a4fbe681 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -37,26 +37,29 @@ func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubU } func TestCreateBranch(t *testing.T) { - onGiteaRun(t, testCreateBranches) + onGiteaRun(t, + func(t *testing.T, u *url.URL) { + testCreateBranches(t, u) + testDeleteBranchReturnsNotFoundWhenMissing(t, u) + }, + ) } -func TestDeleteBranchReturnsNotFoundWhenMissing(t *testing.T) { - onGiteaRun(t, func(t *testing.T, _ *url.URL) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) +func testDeleteBranchReturnsNotFoundWhenMissing(t *testing.T, giteaURL *url.URL) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) - require.NoError(t, err) - defer gitRepo.Close() + gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) + require.NoError(t, err) + defer gitRepo.Close() - require.NoError(t, repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil)) + require.NoError(t, repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil)) - err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil) - assert.True(t, git.IsErrBranchNotExist(err)) + err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil) + assert.True(t, git.IsErrBranchNotExist(err)) - err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch-does-not-exist", nil) - assert.True(t, git.IsErrBranchNotExist(err)) - }) + err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch-does-not-exist", nil) + assert.True(t, git.IsErrBranchNotExist(err)) } func testCreateBranches(t *testing.T, giteaURL *url.URL) { From 9dee849f7014e8f6dcb6d595e923a599d6d3b361 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Feb 2026 15:26:12 -0800 Subject: [PATCH 4/7] Fix bug --- services/repository/branch.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/services/repository/branch.go b/services/repository/branch.go index 29f372bfc2371..5a0891f6f47cd 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -590,28 +590,29 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R } // database branch record not exist or it's a deleted branch - notExistInDB := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted + existAndNonDeletedInDB := err == nil && !rawBranch.IsDeleted branchCommit, err := gitRepo.GetBranchCommit(branchName) if err != nil && !errors.Is(err, util.ErrNotExist) { return err } - if notExistInDB && branchCommit == nil { // branch does not exist and data are synced between db and git data + if !existAndNonDeletedInDB && branchCommit == nil { // branch not exist or deleted from database and data are synced between db and git data return git.ErrBranchNotExist{Name: branchName} } if err := db.WithTx(ctx, func(ctx context.Context) error { - if !notExistInDB { + if existAndNonDeletedInDB { if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { return err } - } - 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) + 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) + } } } + if branchCommit == nil { // branch does not exist in git data, we just mark it as deleted in database return nil } From f92b8e7db4bf1bbe25ac728724d68b16a3013eb5 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 3 Mar 2026 01:10:59 +0800 Subject: [PATCH 5/7] refactor --- models/git/branch.go | 15 +++- models/git/branch_test.go | 26 ++++++- routers/api/v1/repo/branch.go | 2 +- routers/private/hook_post_receive.go | 6 +- routers/web/repo/branch.go | 2 +- services/repository/branch.go | 95 +++++++++++++---------- tests/integration/actions_trigger_test.go | 24 +++++- 7 files changed, 120 insertions(+), 50 deletions(-) diff --git a/models/git/branch.go b/models/git/branch.go index bc6e2a174863c..698c43f147242 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -247,7 +247,7 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64 return err } for _, branch := range branches { - if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil { + if err := MarkBranchAsDeleted(ctx, repoID, branch.Name, doerID); err != nil { return err } } @@ -268,8 +268,8 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string }) } -// AddDeletedBranch adds a deleted branch to the database -func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { +// MarkBranchAsDeleted marks branch as deleted +func MarkBranchAsDeleted(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { branch, err := GetBranch(ctx, repoID, branchName) if err != nil { return err @@ -583,3 +583,12 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o return newBranches, nil } + +// CountBranches returns the number of branches in the repository +func CountBranches(ctx context.Context, repoID int64, includeDeleted bool) (int64, error) { + sess := db.GetEngine(ctx).Where("repo_id=?", repoID) + if !includeDeleted { + sess.And("is_deleted=?", false) + } + return sess.Count(new(Branch)) +} diff --git a/models/git/branch_test.go b/models/git/branch_test.go index 9e6148946d70f..3832df9350953 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -28,8 +28,8 @@ func TestAddDeletedBranch(t *testing.T) { firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) assert.True(t, firstBranch.IsDeleted) - assert.NoError(t, git_model.AddDeletedBranch(t.Context(), repo.ID, firstBranch.Name, firstBranch.DeletedByID)) - assert.NoError(t, git_model.AddDeletedBranch(t.Context(), repo.ID, "branch2", int64(1))) + assert.NoError(t, git_model.MarkBranchAsDeleted(t.Context(), repo.ID, firstBranch.Name, firstBranch.DeletedByID)) + assert.NoError(t, git_model.MarkBranchAsDeleted(t.Context(), repo.ID, "branch2", int64(1))) secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"}) assert.True(t, secondBranch.IsDeleted) @@ -263,3 +263,25 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, deletedBranch) } + +func TestCountBranches(t *testing.T) { + // 1. Setup - Exactly like TestAddDeletedBranch + assert.NoError(t, unittest.PrepareTestDatabase()) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + // 2. Execution - Using t.Context() to match the rest of the file + initialCount, err := git_model.CountBranches(t.Context(), repo.ID, false) + assert.NoError(t, err) + + // 3. Database Action - Using t.Context() + err = db.Insert(t.Context(), &git_model.Branch{ + RepoID: repo.ID, + Name: "test-branch-for-counting", + }) + assert.NoError(t, err) + + // 4. Verification + newCount, err := git_model.CountBranches(t.Context(), repo.ID, false) + assert.NoError(t, err) + assert.Equal(t, initialCount+1, newCount) +} diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 82fd68bdececd..eaa8afb1e133c 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -150,7 +150,7 @@ func DeleteBranch(ctx *context.APIContext) { } } - if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil { + if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): ctx.APIErrorNotFound(err) diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index e8bef7d6c14bb..b595a95b23f4c 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -106,10 +106,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { } if update.IsDelRef() { - if err := git_model.AddDeletedBranch(ctx, repo.ID, update.RefFullName.BranchName(), update.PusherID); err != nil { - log.Error("Failed to add deleted branch: %s/%s Error: %v", ownerName, repoName, err) + if err := git_model.MarkBranchAsDeleted(ctx, repo.ID, update.RefFullName.BranchName(), update.PusherID); err != nil { + log.Error("Failed to mark branch as deleted: %s/%s Error: %v", ownerName, repoName, err) ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to add deleted branch: %s/%s Error: %v", ownerName, repoName, err), + Err: fmt.Sprintf("Failed to mark branch as deleted: %s/%s Error: %v", ownerName, repoName, err), }) return } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index f5630356004f5..1d6961f6fc8f7 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -95,7 +95,7 @@ func DeleteBranchPost(ctx *context.Context) { defer jsonRedirectBranches(ctx) branchName := ctx.FormString("name") - if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil { + if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): log.Debug("DeleteBranch: Can't delete non existing branch '%s'", branchName) diff --git a/services/repository/branch.go b/services/repository/branch.go index 5a0891f6f47cd..87aa3bbc330bd 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -573,8 +573,38 @@ func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchNam return nil } +func deleteBranchInternal(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branchName string, branchCommit *git.Commit) (branchExisted bool, err error) { + activeInDB, err := git_model.IsBranchExist(ctx, repo.ID, branchName) + if err != nil { + return false, fmt.Errorf("IsBranchExist: %w", err) + } + + // process the branch in db + if activeInDB { + if err := git_model.MarkBranchAsDeleted(ctx, repo.ID, branchName, doer.ID); err != nil { + return false, err + } + } + + // process the branch in git + if branchCommit != nil { + err := gitrepo.DeleteBranch(ctx, repo, branchName, true) + if err != nil { + return false, fmt.Errorf("DeleteBranch: %w", err) + } + // since the branch existed in git, return branchExisted=true + branchExisted = true + } else { + // the branch didn't exist in git, return activeInDB to indicate whether the branch was active in DB, + // for consistency with that the user had seen on the web ui or in the branch list API response. + branchExisted = activeInDB + } + + return branchExisted, nil +} + // DeleteBranch delete branch -func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string, pr *issues_model.PullRequest) error { +func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error { err := repo.MustNotBeArchived() if err != nil { return err @@ -584,50 +614,31 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return err } - rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName) - if err != nil && !git_model.IsErrBranchNotExist(err) { - return fmt.Errorf("GetBranch: %vc", err) - } - - // database branch record not exist or it's a deleted branch - existAndNonDeletedInDB := err == nil && !rawBranch.IsDeleted - branchCommit, err := gitRepo.GetBranchCommit(branchName) + // branchCommit can be nil if the branch doesn't exist in git if err != nil && !errors.Is(err, util.ErrNotExist) { return err } - if !existAndNonDeletedInDB && branchCommit == nil { // branch not exist or deleted from database and data are synced between db and git data - return git.ErrBranchNotExist{Name: branchName} - } - if err := db.WithTx(ctx, func(ctx context.Context) error { - if existAndNonDeletedInDB { - if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { - return err - } - - 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) - } - } - } - - if branchCommit == nil { // branch does not exist in git data, we just mark it as deleted in database - return nil - } - - return gitrepo.DeleteBranch(ctx, repo, branchName, true) - }); err != nil { + branchExisted, err := db.WithTx2(ctx, func(ctx context.Context) (bool, error) { + return deleteBranchInternal(ctx, doer, repo, branchName, branchCommit) + }) + if err != nil { return err } - // if branch does not exist in git data, we don't need to send event - if branchCommit == nil { - return nil + if !branchExisted { + return git.ErrBranchNotExist{Name: branchName} + } + + // Don't return error below this since the deletion has succeeded + if branchCommit != nil { + deleteBranchSuccessPostProcess(doer, repo, branchName, branchCommit) } + return nil +} - // Don't return error below this +func deleteBranchSuccessPostProcess(doer *user_model.User, repo *repo_model.Repository, branchName string, branchCommit *git.Commit) { objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) if err := PushUpdate( &repo_module.PushUpdateOptions{ @@ -641,8 +652,6 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R }); err != nil { log.Error("PushUpdateOptions: %v", err) } - - return nil } type BranchSyncOptions struct { @@ -890,9 +899,17 @@ func DeleteBranchAfterMerge(ctx context.Context, doer *user_model.User, prID int return util.ErrorWrapTranslatable(util.ErrUnprocessableContent, "repo.branch.delete_branch_has_new_commits", fullBranchName) } - err = DeleteBranch(ctx, doer, pr.HeadRepo, gitHeadRepo, pr.HeadBranch, pr) + err = DeleteBranch(ctx, doer, pr.HeadRepo, gitHeadRepo, pr.HeadBranch) if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) { return errFailedToDelete(err) } - return err + if err != nil { + return err + } + + // intentionally ignore the following error, since the branch has already been deleted successfully + if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil { + log.Error("AddDeletePRBranchComment: %v", err) + } + return nil } diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 7fff796af62ae..e63e1b26a482c 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -38,6 +38,7 @@ import ( files_service "code.gitea.io/gitea/services/repository/files" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPullRequestTargetEvent(t *testing.T) { @@ -437,7 +438,7 @@ jobs: assert.NotNil(t, run) // delete the branch - err = repo_service.DeleteBranch(t.Context(), user2, repo, gitRepo, "test-create-branch", nil) + err = repo_service.DeleteBranch(t.Context(), user2, repo, gitRepo, "test-create-branch") assert.NoError(t, err) run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ Title: "add workflow", @@ -906,6 +907,27 @@ jobs: CommitSHA: branch.CommitID, }) assert.NotNil(t, run) + + // Now trigger with rundetails + values.Set("return_run_details", "true") + + req = NewRequestWithURLValues(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/workflows/dispatch.yml/dispatches", repo.FullName()), values). + AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + runDetails := &api.RunDetails{} + require.NoError(t, json.NewDecoder(resp.Body).Decode(runDetails)) + assert.NotEqual(t, 0, runDetails.WorkflowRunID) + + run = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ + ID: runDetails.WorkflowRunID, + Title: "add workflow", + RepoID: repo.ID, + Event: "workflow_dispatch", + Ref: "refs/heads/main", + WorkflowID: "dispatch.yml", + CommitSHA: branch.CommitID, + }) + assert.NotNil(t, run) }) } From b71e87b92731f1792dd730c093abef0f2f9ddd9f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 3 Mar 2026 01:18:50 +0800 Subject: [PATCH 6/7] fix merge --- tests/integration/repo_branch_test.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index 33952a4fbe681..8ef487e557f26 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -36,16 +36,14 @@ func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubU return test.RedirectURL(resp) } -func TestCreateBranch(t *testing.T) { - onGiteaRun(t, - func(t *testing.T, u *url.URL) { - testCreateBranches(t, u) - testDeleteBranchReturnsNotFoundWhenMissing(t, u) - }, - ) +func TestCreateDeleteBranch(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + testCreateBranches(t, u) + testDeleteBranch(t, u) + }) } -func testDeleteBranchReturnsNotFoundWhenMissing(t *testing.T, giteaURL *url.URL) { +func testDeleteBranch(t *testing.T, giteaURL *url.URL) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -53,12 +51,12 @@ func testDeleteBranchReturnsNotFoundWhenMissing(t *testing.T, giteaURL *url.URL) require.NoError(t, err) defer gitRepo.Close() - require.NoError(t, repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil)) + require.NoError(t, repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2")) - err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2", nil) + err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2") assert.True(t, git.IsErrBranchNotExist(err)) - err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch-does-not-exist", nil) + err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch-does-not-exist") assert.True(t, git.IsErrBranchNotExist(err)) } From ca3da142f6c43a3f39199bff7ee0933199672a88 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 3 Mar 2026 01:31:39 +0800 Subject: [PATCH 7/7] use api tests --- tests/integration/api_branch_test.go | 2 ++ tests/integration/repo_branch_test.go | 29 ++------------------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 1163c1e8c9c09..f5f33dacf092f 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -402,6 +402,8 @@ func TestAPIBranchProtection(t *testing.T) { // Test branch deletion testAPIDeleteBranch(t, "master", http.StatusForbidden) testAPIDeleteBranch(t, "branch2", http.StatusNoContent) + testAPIDeleteBranch(t, "branch2", http.StatusNotFound) // deleted branch, there is a record in DB with IsDelete=true + testAPIDeleteBranch(t, "no-such-branch", http.StatusNotFound) // non-existing branch, not exist in git or DB } func TestAPICreateBranchWithSyncBranches(t *testing.T) { diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index 8ef487e557f26..6cf9990d2eaee 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -13,16 +13,11 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" - repo_service "code.gitea.io/gitea/services/repository" "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { @@ -36,28 +31,8 @@ func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubU return test.RedirectURL(resp) } -func TestCreateDeleteBranch(t *testing.T) { - onGiteaRun(t, func(t *testing.T, u *url.URL) { - testCreateBranches(t, u) - testDeleteBranch(t, u) - }) -} - -func testDeleteBranch(t *testing.T, giteaURL *url.URL) { - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - - gitRepo, err := gitrepo.OpenRepository(t.Context(), repo) - require.NoError(t, err) - defer gitRepo.Close() - - require.NoError(t, repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2")) - - err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch2") - assert.True(t, git.IsErrBranchNotExist(err)) - - err = repo_service.DeleteBranch(t.Context(), doer, repo, gitRepo, "branch-does-not-exist") - assert.True(t, git.IsErrBranchNotExist(err)) +func TestCreateBranch(t *testing.T) { + onGiteaRun(t, testCreateBranches) } func testCreateBranches(t *testing.T, giteaURL *url.URL) {