From 7eebf0b5ed577a9d29069182792a0ac87b01ffd7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 18 Aug 2025 19:34:56 -0700 Subject: [PATCH 1/6] Support code search via API --- models/repo/repo_list.go | 26 +++ modules/indexer/code/internal/indexer.go | 2 + modules/indexer/code/search.go | 23 ++- modules/structs/repo_search.go | 34 ++++ routers/api/v1/api.go | 5 + routers/api/v1/repo/code/search.go | 194 ++++++++++++++++++++++ routers/api/v1/swagger/search.go | 15 ++ templates/swagger/v1_json.tmpl | 147 ++++++++++++++++ tests/integration/api_search_code_test.go | 46 +++++ 9 files changed, 488 insertions(+), 4 deletions(-) create mode 100644 modules/structs/repo_search.go create mode 100644 routers/api/v1/repo/code/search.go create mode 100644 routers/api/v1/swagger/search.go create mode 100644 tests/integration/api_search_code_test.go diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index f2cdd2f284673..66c310cfadc3a 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -771,3 +771,29 @@ func GetUserRepositories(ctx context.Context, opts SearchRepoOptions) (Repositor repos := make(RepositoryList, 0, opts.PageSize) return repos, count, db.SetSessionPagination(sess, &opts).Find(&repos) } + +// GetRepositoriesIDsByFullNames returns repository IDs by their full names. +func GetRepositoriesIDsByFullNames(ctx context.Context, fullRepoNames []string) ([]int64, error) { + if len(fullRepoNames) == 0 { + return nil, nil + } + + var cond builder.Cond = builder.NewCond() + for _, name := range fullRepoNames { + ownerName, repoName, ok := strings.Cut(name, "/") + if !ok { + continue + } + cond = cond.Or(builder.Eq{"name": repoName, "owner_name": ownerName}) + } + + repoIDs := make([]int64, 0, len(fullRepoNames)) + if err := db.GetEngine(ctx). + Where(cond). + Cols("id"). + Table("repository"). + Find(&repoIDs); err != nil { + return nil, fmt.Errorf("Find: %w", err) + } + return repoIDs, nil +} diff --git a/modules/indexer/code/internal/indexer.go b/modules/indexer/code/internal/indexer.go index d58b028124af8..38fd25f57ca50 100644 --- a/modules/indexer/code/internal/indexer.go +++ b/modules/indexer/code/internal/indexer.go @@ -29,6 +29,8 @@ type SearchOptions struct { SearchMode indexer.SearchModeType + NoHighlight bool // If true, return raw content, else highlight the search results + db.Paginator } diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go index a7a5d7d2e37c9..21cbe8904eaf4 100644 --- a/modules/indexer/code/search.go +++ b/modules/indexer/code/search.go @@ -26,7 +26,9 @@ type Result struct { } type ResultLine struct { - Num int + Num int + RawContent string // Raw content of the line + // FormattedContent is the HTML formatted content of the line, it will only be set if Hightlight is true FormattedContent template.HTML } @@ -86,7 +88,7 @@ func HighlightSearchResultCode(filename, language string, lineNums []int, code s return lines } -func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Result, error) { +func searchResult(result *internal.SearchResult, startIndex, endIndex int, noHighlight bool) (*Result, error) { startLineNum := 1 + strings.Count(result.Content[:startIndex], "\n") var formattedLinesBuffer bytes.Buffer @@ -117,6 +119,19 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res index += len(line) } + var lines []*ResultLine + if noHighlight { + lines = make([]*ResultLine, len(lineNums)) + for i, lineNum := range lineNums { + lines[i] = &ResultLine{ + Num: lineNum, + RawContent: contentLines[i], + } + } + } else { + lines = HighlightSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String()) + } + return &Result{ RepoID: result.RepoID, Filename: result.Filename, @@ -124,7 +139,7 @@ func searchResult(result *internal.SearchResult, startIndex, endIndex int) (*Res UpdatedUnix: result.UpdatedUnix, Language: result.Language, Color: result.Color, - Lines: HighlightSearchResultCode(result.Filename, result.Language, lineNums, formattedLinesBuffer.String()), + Lines: lines, }, nil } @@ -143,7 +158,7 @@ func PerformSearch(ctx context.Context, opts *SearchOptions) (int, []*Result, [] for i, result := range results { startIndex, endIndex := indices(result.Content, result.StartIndex, result.EndIndex) - displayResults[i], err = searchResult(result, startIndex, endIndex) + displayResults[i], err = searchResult(result, startIndex, endIndex, opts.NoHighlight) if err != nil { return 0, nil, nil, err } diff --git a/modules/structs/repo_search.go b/modules/structs/repo_search.go new file mode 100644 index 0000000000000..e6be3bfd396d9 --- /dev/null +++ b/modules/structs/repo_search.go @@ -0,0 +1,34 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +// CodeSearchResultLanguage result of top languages count in search results +type CodeSearchResultLanguage struct { + Language string + Color string + Count int +} + +type CodeSearchResultLine struct { + LineNumber int `json:"line_number"` + RawContent string `json:"raw_content"` +} + +type CodeSearchResult struct { + Name string `json:"name"` + Path string `json:"path"` + Language string `json:"language"` + Color string + Lines []CodeSearchResultLine + Sha string `json:"sha"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Repository *Repository `json:"repository"` +} + +type CodeSearchResults struct { + TotalCount int64 `json:"total_count"` + Items []CodeSearchResult `json:"items"` + Languages []CodeSearchResultLanguage `json:"languages,omitempty"` +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index f412e8a06caca..027f23385c371 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -90,6 +90,7 @@ import ( "code.gitea.io/gitea/routers/api/v1/org" "code.gitea.io/gitea/routers/api/v1/packages" "code.gitea.io/gitea/routers/api/v1/repo" + "code.gitea.io/gitea/routers/api/v1/repo/code" "code.gitea.io/gitea/routers/api/v1/settings" "code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/common" @@ -1768,6 +1769,10 @@ func Routes() *web.Router { m.Group("/topics", func() { m.Get("/search", repo.TopicSearch) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) + + m.Group("/search", func() { + m.Get("/code", code.GlobalSearch) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) }, sudo()) return m diff --git a/routers/api/v1/repo/code/search.go b/routers/api/v1/repo/code/search.go new file mode 100644 index 0000000000000..4dbefb9fd7ddd --- /dev/null +++ b/routers/api/v1/repo/code/search.go @@ -0,0 +1,194 @@ +package code + +import ( + "fmt" + "net/http" + "net/url" + "path" + "slices" + + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/indexer" + "code.gitea.io/gitea/modules/indexer/code" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/convert" +) + +// GlobalSearch search codes in all accessible repositories with the given keyword. +func GlobalSearch(ctx *context.APIContext) { + // swagger:operation GET /search/code search GlobalSearch + // --- + // summary: Search for repositories + // produces: + // - application/json + // parameters: + // - name: q + // in: query + // description: keyword + // type: string + // - name: repo + // in: query + // description: multiple repository names to search in + // type: string + // collectionFormat: multi + // - name: mode + // in: query + // description: include search of keyword within repository description + // type: string + // enum: [exact, words, fuzzy, regexp] + // - name: language + // in: query + // description: filter by programming language + // type: integer + // format: int64 + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/CodeSearchResults" + // "422": + // "$ref": "#/responses/validationError" + + if !setting.Indexer.RepoIndexerEnabled { + ctx.APIError(http.StatusBadRequest, "Repository indexing is disabled") + return + } + + q := ctx.FormTrim("q") + if q == "" { + ctx.APIError(http.StatusUnprocessableEntity, "Query cannot be empty") + return + } + + var ( + accessibleRepoIDs []int64 + err error + isAdmin bool + ) + if ctx.Doer != nil { + isAdmin = ctx.Doer.IsAdmin + } + + // guest user or non-admin user + if ctx.Doer == nil || !isAdmin { + accessibleRepoIDs, err = repo_model.FindUserCodeAccessibleRepoIDs(ctx, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + } + + repoNames := ctx.FormStrings("repo") + searchRepoIDs := make([]int64, 0, len(repoNames)) + if len(repoNames) > 0 { + var err error + searchRepoIDs, err = repo_model.GetRepositoriesIDsByFullNames(ctx, repoNames) + if err != nil { + ctx.APIErrorInternal(err) + return + } + } + if len(searchRepoIDs) > 0 { + for i := 0; i < len(searchRepoIDs); i++ { + if !slices.Contains(accessibleRepoIDs, searchRepoIDs[i]) { + searchRepoIDs = append(searchRepoIDs[:i], searchRepoIDs[i+1:]...) + i-- + } + } + } + if len(searchRepoIDs) > 0 { + accessibleRepoIDs = searchRepoIDs + } + + searchMode := indexer.SearchModeType(ctx.FormString("mode")) + listOpts := utils.GetListOptions(ctx) + + total, results, languages, err := code.PerformSearch(ctx, &code.SearchOptions{ + Keyword: q, + RepoIDs: accessibleRepoIDs, + Language: ctx.FormString("language"), + SearchMode: searchMode, + Paginator: &listOpts, + NoHighlight: true, // Default to no highlighting for performance, we don't need to highlight in the API search results + }) + if err != nil { + ctx.APIErrorInternal(err) + return + } + + ctx.SetTotalCountHeader(int64(total)) + searchResults := structs.CodeSearchResults{ + TotalCount: int64(total), + } + + for _, lang := range languages { + searchResults.Languages = append(searchResults.Languages, structs.CodeSearchResultLanguage{ + Language: lang.Language, + Color: lang.Color, + Count: lang.Count, + }) + } + + repoIDs := make(container.Set[int64], len(results)) + for _, result := range results { + repoIDs.Add(result.RepoID) + } + + repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs.Values()) + if err != nil { + ctx.APIErrorInternal(err) + return + } + + permissions := make(map[int64]access_model.Permission) + for _, repo := range repos { + permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + permissions[repo.ID] = permission + } + + for _, result := range results { + repo, ok := repos[result.RepoID] + if !ok { + log.Error("Repository with ID %d not found for search result: %v", result.RepoID, result) + continue + } + + apiURL := fmt.Sprintf("%s/contents/%s?ref=%s", repo.APIURL(), util.PathEscapeSegments(result.Filename), url.PathEscape(result.CommitID)) + htmlURL := fmt.Sprintf("%s/blob/%s/%s", repo.HTMLURL(), url.PathEscape(result.CommitID), util.PathEscapeSegments(result.Filename)) + ret := structs.CodeSearchResult{ + Name: path.Base(result.Filename), + Path: result.Filename, + Sha: result.CommitID, + URL: apiURL, + HTMLURL: htmlURL, + Language: result.Language, + Repository: convert.ToRepo(ctx, repo, permissions[repo.ID]), + } + for _, line := range result.Lines { + ret.Lines = append(ret.Lines, structs.CodeSearchResultLine{ + LineNumber: line.Num, + RawContent: line.RawContent, + }) + } + searchResults.Items = append(searchResults.Items, ret) + } + + ctx.JSON(200, searchResults) +} diff --git a/routers/api/v1/swagger/search.go b/routers/api/v1/swagger/search.go new file mode 100644 index 0000000000000..c69a356b3f9de --- /dev/null +++ b/routers/api/v1/swagger/search.go @@ -0,0 +1,15 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package swagger + +import ( + api "code.gitea.io/gitea/modules/structs" +) + +// CodeSearchResults +// swagger:response CodeSearchResults +type swaggerResponseCodeSearchResults struct { + // in:body + Body api.CodeSearchResults `json:"body"` +} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 749d86901de93..866a414fdadb8 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -17532,6 +17532,72 @@ } } }, + "/search/code": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "search" + ], + "summary": "Search for repositories", + "operationId": "GlobalSearch", + "parameters": [ + { + "type": "string", + "description": "keyword", + "name": "q", + "in": "query" + }, + { + "type": "string", + "collectionFormat": "multi", + "description": "multiple repository names to search in", + "name": "repo", + "in": "query" + }, + { + "enum": [ + "exact", + "words", + "fuzzy", + "regexp" + ], + "type": "string", + "description": "include search of keyword within repository description", + "name": "mode", + "in": "query" + }, + { + "type": "integer", + "format": "int64", + "description": "filter by programming language", + "name": "language", + "in": "query" + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/CodeSearchResults" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, "/settings/api": { "get": { "produces": [ @@ -22016,6 +22082,81 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "CodeSearchResult": { + "type": "object", + "properties": { + "git_url": { + "type": "string", + "x-go-name": "GitURL" + }, + "html_url": { + "type": "string", + "x-go-name": "HTMLURL" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "path": { + "type": "string", + "x-go-name": "Path" + }, + "repository": { + "$ref": "#/definitions/Repository" + }, + "sha": { + "type": "string", + "x-go-name": "Sha" + }, + "url": { + "type": "string", + "x-go-name": "URL" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "CodeSearchResultLanguage": { + "description": "CodeSearchResultLanguage result of top languages count in search results", + "type": "object", + "properties": { + "Color": { + "type": "string" + }, + "Count": { + "type": "integer", + "format": "int64" + }, + "Language": { + "type": "string" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "CodeSearchResults": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/CodeSearchResult" + }, + "x-go-name": "Items" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/CodeSearchResultLanguage" + }, + "x-go-name": "Languages" + }, + "total_count": { + "type": "integer", + "format": "int64", + "x-go-name": "TotalCount" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "CombinedStatus": { "description": "CombinedStatus holds the combined state of several statuses for a single commit", "type": "object", @@ -28745,6 +28886,12 @@ } } }, + "CodeSearchResults": { + "description": "CodeSearchResults", + "schema": { + "$ref": "#/definitions/CodeSearchResults" + } + }, "CombinedStatus": { "description": "CombinedStatus", "schema": { diff --git a/tests/integration/api_search_code_test.go b/tests/integration/api_search_code_test.go new file mode 100644 index 0000000000000..2488a599761c7 --- /dev/null +++ b/tests/integration/api_search_code_test.go @@ -0,0 +1,46 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPISearchCodeNotLogin(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // test with no keyword + req := NewRequest(t, "GET", "/api/v1/search/code") + resp := MakeRequest(t, req, http.StatusUnprocessableEntity) + + req = NewRequest(t, "GET", "/api/v1/search/code?q=Description") + resp = MakeRequest(t, req, http.StatusOK) + + var apiCodeSearchResults api.CodeSearchResults + DecodeJSON(t, resp, &apiCodeSearchResults) + assert.Equal(t, int64(4), apiCodeSearchResults.TotalCount) + assert.Len(t, apiCodeSearchResults.Items, 4) + assert.Equal(t, "README.md", apiCodeSearchResults.Items[0].Name) + assert.Equal(t, "README.md", apiCodeSearchResults.Items[0].Path) + assert.Equal(t, "Markdown", apiCodeSearchResults.Items[0].Language) + assert.Len(t, apiCodeSearchResults.Items[0].Lines, 2) + assert.Equal(t, "\n", apiCodeSearchResults.Items[0].Lines[0].RawContent) + assert.Equal(t, "Description for repo1", apiCodeSearchResults.Items[0].Lines[1].RawContent) + + assert.Equal(t, setting.AppURL+"api/v1/repos/user2/git_hooks_test/contents/README.md?ref=65f1bf27bc3bf70f64657658635e66094edbcb4d", apiCodeSearchResults.Items[0].URL) + assert.Equal(t, setting.AppURL+"user2/git_hooks_test/blob/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md", apiCodeSearchResults.Items[0].HTMLURL) + + assert.Equal(t, int64(37), apiCodeSearchResults.Items[0].Repository.ID) + + assert.Len(t, apiCodeSearchResults.Languages, 1) + assert.Equal(t, "Markdown", apiCodeSearchResults.Languages[0].Language) + assert.Equal(t, 4, apiCodeSearchResults.Languages[0].Count) +} From 0baf8c5f320a36fb27e512ecb92ec4d6e61e2760 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 19 Aug 2025 11:47:02 -0700 Subject: [PATCH 2/6] Fix lint --- models/repo/repo_list.go | 2 +- routers/api/v1/repo/code/search.go | 3 +++ tests/integration/api_search_code_test.go | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index 66c310cfadc3a..8270cf48680f7 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -778,7 +778,7 @@ func GetRepositoriesIDsByFullNames(ctx context.Context, fullRepoNames []string) return nil, nil } - var cond builder.Cond = builder.NewCond() + cond := builder.NewCond() for _, name := range fullRepoNames { ownerName, repoName, ok := strings.Cut(name, "/") if !ok { diff --git a/routers/api/v1/repo/code/search.go b/routers/api/v1/repo/code/search.go index 4dbefb9fd7ddd..b1953d8b35776 100644 --- a/routers/api/v1/repo/code/search.go +++ b/routers/api/v1/repo/code/search.go @@ -1,3 +1,6 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + package code import ( diff --git a/tests/integration/api_search_code_test.go b/tests/integration/api_search_code_test.go index 2488a599761c7..3c092d0494925 100644 --- a/tests/integration/api_search_code_test.go +++ b/tests/integration/api_search_code_test.go @@ -19,10 +19,10 @@ func TestAPISearchCodeNotLogin(t *testing.T) { // test with no keyword req := NewRequest(t, "GET", "/api/v1/search/code") - resp := MakeRequest(t, req, http.StatusUnprocessableEntity) + MakeRequest(t, req, http.StatusUnprocessableEntity) req = NewRequest(t, "GET", "/api/v1/search/code?q=Description") - resp = MakeRequest(t, req, http.StatusOK) + resp := MakeRequest(t, req, http.StatusOK) var apiCodeSearchResults api.CodeSearchResults DecodeJSON(t, resp, &apiCodeSearchResults) From 2bf94b72f859389cbde957ca5932cb40dc238839 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 19 Aug 2025 11:58:57 -0700 Subject: [PATCH 3/6] fix swagger documentations --- templates/swagger/v1_json.tmpl | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 866a414fdadb8..1f4e1d80d999d 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -22085,14 +22085,23 @@ "CodeSearchResult": { "type": "object", "properties": { - "git_url": { - "type": "string", - "x-go-name": "GitURL" + "Color": { + "type": "string" + }, + "Lines": { + "type": "array", + "items": { + "$ref": "#/definitions/CodeSearchResultLine" + } }, "html_url": { "type": "string", "x-go-name": "HTMLURL" }, + "language": { + "type": "string", + "x-go-name": "Language" + }, "name": { "type": "string", "x-go-name": "Name" @@ -22132,6 +22141,21 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "CodeSearchResultLine": { + "type": "object", + "properties": { + "line_number": { + "type": "integer", + "format": "int64", + "x-go-name": "LineNumber" + }, + "raw_content": { + "type": "string", + "x-go-name": "RawContent" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "CodeSearchResults": { "type": "object", "properties": { From 9386db1b46a54e087dda18b47ef489eb397826a5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 19 Aug 2025 20:45:49 -0700 Subject: [PATCH 4/6] Fix test --- tests/integration/api_search_code_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/api_search_code_test.go b/tests/integration/api_search_code_test.go index 3c092d0494925..81269376967c2 100644 --- a/tests/integration/api_search_code_test.go +++ b/tests/integration/api_search_code_test.go @@ -26,8 +26,8 @@ func TestAPISearchCodeNotLogin(t *testing.T) { var apiCodeSearchResults api.CodeSearchResults DecodeJSON(t, resp, &apiCodeSearchResults) - assert.Equal(t, int64(4), apiCodeSearchResults.TotalCount) - assert.Len(t, apiCodeSearchResults.Items, 4) + assert.Equal(t, int64(1), apiCodeSearchResults.TotalCount) + assert.Len(t, apiCodeSearchResults.Items, 1) assert.Equal(t, "README.md", apiCodeSearchResults.Items[0].Name) assert.Equal(t, "README.md", apiCodeSearchResults.Items[0].Path) assert.Equal(t, "Markdown", apiCodeSearchResults.Items[0].Language) @@ -35,12 +35,12 @@ func TestAPISearchCodeNotLogin(t *testing.T) { assert.Equal(t, "\n", apiCodeSearchResults.Items[0].Lines[0].RawContent) assert.Equal(t, "Description for repo1", apiCodeSearchResults.Items[0].Lines[1].RawContent) - assert.Equal(t, setting.AppURL+"api/v1/repos/user2/git_hooks_test/contents/README.md?ref=65f1bf27bc3bf70f64657658635e66094edbcb4d", apiCodeSearchResults.Items[0].URL) - assert.Equal(t, setting.AppURL+"user2/git_hooks_test/blob/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md", apiCodeSearchResults.Items[0].HTMLURL) + assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/contents/README.md?ref=65f1bf27bc3bf70f64657658635e66094edbcb4d", apiCodeSearchResults.Items[0].URL) + assert.Equal(t, setting.AppURL+"user2/repo1/blob/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md", apiCodeSearchResults.Items[0].HTMLURL) - assert.Equal(t, int64(37), apiCodeSearchResults.Items[0].Repository.ID) + assert.Equal(t, int64(1), apiCodeSearchResults.Items[0].Repository.ID) assert.Len(t, apiCodeSearchResults.Languages, 1) assert.Equal(t, "Markdown", apiCodeSearchResults.Languages[0].Language) - assert.Equal(t, 4, apiCodeSearchResults.Languages[0].Count) + assert.Equal(t, 1, apiCodeSearchResults.Languages[0].Count) } From 08b45b7a1f5fe34e77231b5230600eb7f6cc6968 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 20 Aug 2025 10:13:58 -0700 Subject: [PATCH 5/6] Fix test --- tests/integration/api_search_code_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/api_search_code_test.go b/tests/integration/api_search_code_test.go index 81269376967c2..90b7cbb15614b 100644 --- a/tests/integration/api_search_code_test.go +++ b/tests/integration/api_search_code_test.go @@ -35,8 +35,8 @@ func TestAPISearchCodeNotLogin(t *testing.T) { assert.Equal(t, "\n", apiCodeSearchResults.Items[0].Lines[0].RawContent) assert.Equal(t, "Description for repo1", apiCodeSearchResults.Items[0].Lines[1].RawContent) - assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/contents/README.md?ref=65f1bf27bc3bf70f64657658635e66094edbcb4d", apiCodeSearchResults.Items[0].URL) - assert.Equal(t, setting.AppURL+"user2/repo1/blob/65f1bf27bc3bf70f64657658635e66094edbcb4d/README.md", apiCodeSearchResults.Items[0].HTMLURL) + assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/contents/README.md?ref=26b303da6e256eb9f27b23d27c1c7fd22b6770db", apiCodeSearchResults.Items[0].URL) + assert.Equal(t, setting.AppURL+"user2/repo1/blob/26b303da6e256eb9f27b23d27c1c7fd22b6770db/README.md", apiCodeSearchResults.Items[0].HTMLURL) assert.Equal(t, int64(1), apiCodeSearchResults.Items[0].Repository.ID) From 1f3f69142720490db7bb00d9288f8348d17a9f79 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 20 Aug 2025 21:25:32 -0700 Subject: [PATCH 6/6] Fix test --- tests/integration/api_search_code_test.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/integration/api_search_code_test.go b/tests/integration/api_search_code_test.go index 90b7cbb15614b..589036264b9b3 100644 --- a/tests/integration/api_search_code_test.go +++ b/tests/integration/api_search_code_test.go @@ -7,6 +7,9 @@ import ( "net/http" "testing" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/tests" @@ -35,8 +38,16 @@ func TestAPISearchCodeNotLogin(t *testing.T) { assert.Equal(t, "\n", apiCodeSearchResults.Items[0].Lines[0].RawContent) assert.Equal(t, "Description for repo1", apiCodeSearchResults.Items[0].Lines[1].RawContent) - assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/contents/README.md?ref=26b303da6e256eb9f27b23d27c1c7fd22b6770db", apiCodeSearchResults.Items[0].URL) - assert.Equal(t, setting.AppURL+"user2/repo1/blob/26b303da6e256eb9f27b23d27c1c7fd22b6770db/README.md", apiCodeSearchResults.Items[0].HTMLURL) + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + gitRepo1, err := gitrepo.OpenRepository(t.Context(), repo1) + assert.NoError(t, err) + defer gitRepo1.Close() + + commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) + assert.NoError(t, err) + + assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/contents/README.md?ref="+commitID, apiCodeSearchResults.Items[0].URL) + assert.Equal(t, setting.AppURL+"user2/repo1/blob/"+commitID+"/README.md", apiCodeSearchResults.Items[0].HTMLURL) assert.Equal(t, int64(1), apiCodeSearchResults.Items[0].Repository.ID)