mirror of
https://github.com/go-gitea/gitea.git
synced 2026-06-04 09:44:56 +02:00
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:
parent
b2748d7654
commit
792fa5eeba
@ -306,6 +306,10 @@ func ListBranches(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: q
|
||||
// in: query
|
||||
// description: branch name substring to filter by
|
||||
// type: string
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/BranchList"
|
||||
@ -314,6 +318,7 @@ func ListBranches(ctx *context.APIContext) {
|
||||
var apiBranches []*api.Branch
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
keyword := ctx.FormString("q")
|
||||
|
||||
if !ctx.Repo.Repository.IsEmpty {
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
@ -325,6 +330,7 @@ func ListBranches(ctx *context.APIContext) {
|
||||
ListOptions: listOptions,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
IsDeletedBranch: optional.Some(false),
|
||||
Keyword: keyword,
|
||||
}
|
||||
var err error
|
||||
totalNumOfBranches, err = db.Count[git_model.Branch](ctx, branchOpts)
|
||||
|
||||
6
templates/swagger/v1_json.tmpl
generated
6
templates/swagger/v1_json.tmpl
generated
@ -7158,6 +7158,12 @@
|
||||
"description": "page size of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "branch name substring to filter by",
|
||||
"name": "q",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
8
templates/swagger/v1_openapi3_json.tmpl
generated
8
templates/swagger/v1_openapi3_json.tmpl
generated
@ -18224,6 +18224,14 @@
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "branch name substring to filter by",
|
||||
"in": "query",
|
||||
"name": "q",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user