feat(api): add q parameter to list branches API for server-side filtering (#37982)

The GET /repos/{owner}/{repo}/branches endpoint currently has no way to
filter branches by name server-side, forcing API consumers to paginate
through all branches and filter client-side.

The UI already supports branch search (added in
[#27055](https://github.com/go-gitea/gitea/pull/27055)). The underlying
DB layer has a Keyword field on FindBranchOptions in
models/git/branch_list.go that does a LIKE %keyword% SQL filter, it just
wasn't wired up to the API handler.

This PR exposes a ?q= query parameter on the endpoint that maps to
FindBranchOptions.Keyword.

Example:

```GET /repos/owner/repo/branches?q=feature ```
Closes #37981

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Harsh Mahajan
2026-06-04 04:51:48 +05:30
committed by GitHub
parent b2748d7654
commit 792fa5eeba
5 changed files with 41 additions and 0 deletions

View File

@@ -128,3 +128,20 @@ func TestAPIRepoBranchesMirror(t *testing.T) {
assert.NoError(t, err)
assert.JSONEq(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}", string(bs))
}
func TestAPIRepoBranchesSearch(t *testing.T) {
defer tests.PrepareTestEnv(t)()
token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteRepository)
// "test" matches "test_branch" but not "master"
resp := MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=test").AddTokenAuth(token), http.StatusOK)
branches := DecodeJSON(t, resp, []api.Branch{})
assert.Len(t, branches, 1)
assert.Equal(t, "test_branch", branches[0].Name)
// no match returns empty list
resp = MakeRequest(t, NewRequestf(t, "GET", "/api/v1/repos/org3/repo3/branches?q=doesnotexist").AddTokenAuth(token), http.StatusOK)
branches = DecodeJSON(t, resp, []api.Branch{})
assert.Empty(t, branches)
}

View File

@@ -323,6 +323,10 @@ func logUnexpectedResponse(t testing.TB, recorder *httptest.ResponseRecorder) {
}
}
// DecodeJSON decodes then response as JSON into typed variable and return it
// HINT: don't use it on existing variable (reuse existing variable):
// if the existing var already contains some values but the new input doesn't, then it leads to wrong test result in edge cases.
// For slice decoding, use: v := DecodeJSON(t, resp, []T{})
func DecodeJSON[T any](t testing.TB, resp *httptest.ResponseRecorder, v T) (ret T) {
t.Helper()