feat: add branch_count to repository API (#35351) (#36743)

Description
This PR adds a branch_count field to the repository API response.
Currently, clients have to fetch all branches via /branches just to
determine the total number of branches. This addition brings Gitea
closer to parity with GitLab's API and improves efficiency for UI/CLI
clients that need this metric.

Linked Issue
Fixes #35351

Changes
API Structs: Added BranchCount field to Repository struct in
modules/structs/repo.go.

Database Logic: Implemented CountBranches in models/git/branch.go using
XORM for efficient counting.

Service Layer: Updated the ToRepo conversion logic in
services/convert/repository.go to populate the new field during API
serialisation.

Tests: Added a new unit test TestCountBranches in
models/git/branch_test.go to verify counts (including handling of
deleted branches).

Screenshots
<img width="196" height="121" alt="Screenshot 2026-02-24 at 21 41 07"
src="https://github.com/user-attachments/assets/cd023e92-f338-448b-9e49-0a5d54cc96c2"
/>

Testing
Manually verified the output using curl against a local Gitea instance.

Verified that adding a branch increments the count and deleting a branch
(soft-delete) decrements it.

Ran backend linting: make lint-backend (Passed).

Ran specific unit test: go test -v -tags "sqlite sqlite_unlock_notify"
./models/git -run TestCountBranches (Passed).

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
James Robinson
2026-02-27 14:10:01 +00:00
committed by GitHub
parent 619db646f5
commit fde7f7db28
5 changed files with 44 additions and 0 deletions

View File

@@ -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))
}

View File

@@ -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)
}

View File

@@ -73,6 +73,7 @@ type Repository struct {
Stars int `json:"stars_count"`
Forks int `json:"forks_count"`
Watchers int `json:"watchers_count"`
BranchCount int `json:"branch_count"`
OpenIssues int `json:"open_issues_count"`
OpenPulls int `json:"open_pr_counter"`
Releases int `json:"release_counter"`

View File

@@ -8,6 +8,7 @@ import (
"time"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
@@ -144,6 +145,11 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
RepoID: repo.ID,
})
branchCount, err := git_model.CountBranches(ctx, repo.ID, false)
if err != nil {
log.Error("CountBranches [%d]: %v", repo.ID, err)
}
mirrorInterval := ""
var mirrorUpdated time.Time
if repo.IsMirror {
@@ -205,6 +211,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
Stars: repo.NumStars,
Forks: repo.NumForks,
Watchers: repo.NumWatches,
BranchCount: int(branchCount),
OpenIssues: repo.NumOpenIssues,
OpenPulls: repo.NumOpenPulls,
Releases: int(numReleases),

View File

@@ -28128,6 +28128,11 @@
"type": "string",
"x-go-name": "AvatarURL"
},
"branch_count": {
"type": "integer",
"format": "int64",
"x-go-name": "BranchCount"
},
"clone_url": {
"type": "string",
"x-go-name": "CloneURL"