Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global code search support #3664

Merged
merged 6 commits into from
Mar 16, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -1945,6 +1945,12 @@ func GetRepositoryByID(id int64) (*Repository, error) {
return getRepositoryByID(x, id)
}

// GetRepositoriesMapByIDs returns the repositories by given id slice.
func GetRepositoriesMapByIDs(ids []int64) (map[int64]*Repository, error) {
var repos = make(map[int64]*Repository, len(ids))
return repos, x.In("id", ids).Find(&repos)
}

// GetUserRepositories returns a list of repositories of given user.
func GetUserRepositories(userID int64, private bool, page, pageSize int, orderBy string) ([]*Repository, error) {
if len(orderBy) == 0 {
Expand Down
25 changes: 25 additions & 0 deletions models/repo_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,28 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, err

return repos, count, nil
}

// FindUserAccessibleRepoIDs find all accessible repositories' ID by user's id
func FindUserAccessibleRepoIDs(userID int64) ([]int64, error) {
var accessCond builder.Cond = builder.Eq{"is_private": false}

if userID > 0 {
accessCond = accessCond.Or(
builder.Eq{"owner_id": userID},
builder.And(
builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", userID),
builder.Neq{"owner_id": userID},
),
)
}

repoIDs := make([]int64, 0, 10)
if err := x.
Table("repository").
Cols("id").
Where(accessCond).
Find(&repoIDs); err != nil {
return nil, fmt.Errorf("FindUserAccesibleRepoIDs: %v", err)
}
return repoIDs, nil
}
27 changes: 21 additions & 6 deletions modules/indexer/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/blevesearch/bleve/analysis/token/lowercase"
"github.com/blevesearch/bleve/analysis/token/unique"
"github.com/blevesearch/bleve/analysis/tokenizer/unicode"
"github.com/blevesearch/bleve/search/query"
"github.com/ethantkoenig/rupture"
)

Expand Down Expand Up @@ -158,6 +159,7 @@ func DeleteRepoFromIndexer(repoID int64) error {

// RepoSearchResult result of performing a search in a repo
type RepoSearchResult struct {
RepoID int64
StartIndex int
EndIndex int
Filename string
Expand All @@ -166,17 +168,29 @@ type RepoSearchResult struct {

// SearchRepoByKeyword searches for files in the specified repo.
// Returns the matching file-paths
func SearchRepoByKeyword(repoID int64, keyword string, page, pageSize int) (int64, []*RepoSearchResult, error) {
func SearchRepoByKeyword(repoIDs []int64, keyword string, page, pageSize int) (int64, []*RepoSearchResult, error) {
phraseQuery := bleve.NewMatchPhraseQuery(keyword)
phraseQuery.FieldVal = "Content"
phraseQuery.Analyzer = repoIndexerAnalyzer
indexerQuery := bleve.NewConjunctionQuery(
numericEqualityQuery(repoID, "RepoID"),
phraseQuery,
)

var indexerQuery query.Query
if len(repoIDs) > 0 {
var repoQueries = make([]query.Query, 0, len(repoIDs))
for _, repoID := range repoIDs {
repoQueries = append(repoQueries, numericEqualityQuery(repoID, "RepoID"))
}

indexerQuery = bleve.NewConjunctionQuery(
bleve.NewDisjunctionQuery(repoQueries...),
phraseQuery,
)
} else {
indexerQuery = phraseQuery
}

from := (page - 1) * pageSize
searchRequest := bleve.NewSearchRequestOptions(indexerQuery, pageSize, from, false)
searchRequest.Fields = []string{"Content"}
searchRequest.Fields = []string{"Content", "RepoID"}
searchRequest.IncludeLocations = true

result, err := repoIndexer.Search(searchRequest)
Expand All @@ -199,6 +213,7 @@ func SearchRepoByKeyword(repoID int64, keyword string, page, pageSize int) (int6
}
}
searchResults[i] = &RepoSearchResult{
RepoID: int64(hit.Fields["RepoID"].(float64)),
StartIndex: startIndex,
EndIndex: endIndex,
Filename: filenameOfIndexerID(hit.ID),
Expand Down
6 changes: 4 additions & 2 deletions modules/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

// Result a search result to display
type Result struct {
RepoID int64
Filename string
HighlightClass string
LineNumbers []int
Expand Down Expand Up @@ -98,6 +99,7 @@ func searchResult(result *indexer.RepoSearchResult, startIndex, endIndex int) (*
index += len(line)
}
return &Result{
RepoID: result.RepoID,
Filename: result.Filename,
HighlightClass: highlight.FileNameToHighlightClass(result.Filename),
LineNumbers: lineNumbers,
Expand All @@ -106,12 +108,12 @@ func searchResult(result *indexer.RepoSearchResult, startIndex, endIndex int) (*
}

// PerformSearch perform a search on a repository
func PerformSearch(repoID int64, keyword string, page, pageSize int) (int, []*Result, error) {
func PerformSearch(repoIDs []int64, keyword string, page, pageSize int) (int, []*Result, error) {
if len(keyword) == 0 {
return 0, nil, nil
}

total, results, err := indexer.SearchRepoByKeyword(repoID, keyword, page, pageSize)
total, results, err := indexer.SearchRepoByKeyword(repoIDs, keyword, page, pageSize)
if err != nil {
return 0, nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,12 @@ repos = Repositories
users = Users
organizations = Organizations
search = Search
code = Code
repo_no_results = No matching repositories have been found.
user_no_results = No matching users have been found.
org_no_results = No matching organizations have been found.
code_no_results = No matching codes have been found.
code_search_results = Search results for "%s"

[auth]
create_new_account = Create Account
Expand Down
116 changes: 116 additions & 0 deletions routers/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/search"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/user"
Expand All @@ -27,6 +28,8 @@ const (
tplExploreUsers base.TplName = "explore/users"
// tplExploreOrganizations explore organizations page template
tplExploreOrganizations base.TplName = "explore/organizations"
// tplExploreCode explore code page template
tplExploreCode base.TplName = "explore/code"
)

// Home render home page
Expand All @@ -49,6 +52,7 @@ func Home(ctx *context.Context) {
}

ctx.Data["PageIsHome"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.HTML(200, tplHome)
}

Expand Down Expand Up @@ -124,6 +128,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
ctx.Data["Total"] = count
ctx.Data["Page"] = paginater.New(int(count), opts.PageSize, page, 5)
ctx.Data["Repos"] = repos
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

ctx.HTML(200, opts.TplName)
}
Expand All @@ -133,6 +138,7 @@ func ExploreRepos(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreRepositories"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

var ownerID int64
if ctx.User != nil && !ctx.User.IsAdmin {
Expand Down Expand Up @@ -194,6 +200,7 @@ func RenderUserSearch(ctx *context.Context, opts *models.SearchUserOptions, tplN
ctx.Data["Page"] = paginater.New(int(count), opts.PageSize, opts.Page, 5)
ctx.Data["Users"] = users
ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

ctx.HTML(200, tplName)
}
Expand All @@ -203,6 +210,7 @@ func ExploreUsers(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreUsers"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeIndividual,
Expand All @@ -216,13 +224,121 @@ func ExploreOrganizations(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreOrganizations"] = true
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled

RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeOrganization,
PageSize: setting.UI.ExplorePagingNum,
}, tplExploreOrganizations)
}

// ExploreCode render explore code page
func ExploreCode(ctx *context.Context) {
if !setting.Indexer.RepoIndexerEnabled {
ctx.Redirect("/explore", 302)
return
}

ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true
ctx.Data["PageIsExploreCode"] = true

keyword := strings.TrimSpace(ctx.Query("q"))
page := ctx.QueryInt("page")
if page <= 0 {
page = 1
}

var (
repoIDs []int64
err error
isAdmin bool
userID int64
)
if ctx.User != nil {
userID = ctx.User.ID
isAdmin = ctx.User.IsAdmin
}

// guest user or non-admin user
if ctx.User == nil || !isAdmin {
repoIDs, err = models.FindUserAccessibleRepoIDs(userID)
if err != nil {
ctx.ServerError("SearchResults", err)
return
}
}

var (
total int
searchResults []*search.Result
)

// if non-admin login user, we need check UnitTypeCode at first
if ctx.User != nil && len(repoIDs) > 0 {
repoMaps, err := models.GetRepositoriesMapByIDs(repoIDs)
if err != nil {
ctx.ServerError("SearchResults", err)
return
}

var rightRepoMap = make(map[int64]*models.Repository, len(repoMaps))
repoIDs = make([]int64, 0, len(repoMaps))
for id, repo := range repoMaps {
if repo.CheckUnitUser(userID, isAdmin, models.UnitTypeCode) {
rightRepoMap[id] = repo
repoIDs = append(repoIDs, id)
}
}

ctx.Data["RepoMaps"] = rightRepoMap

total, searchResults, err = search.PerformSearch(repoIDs, keyword, page, setting.UI.RepoSearchPagingNum)
if err != nil {
ctx.ServerError("SearchResults", err)
return
}
// if non-login user or isAdmin, no need to check UnitTypeCode
} else if (ctx.User == nil && len(repoIDs) > 0) || isAdmin {
total, searchResults, err = search.PerformSearch(repoIDs, keyword, page, setting.UI.RepoSearchPagingNum)
if err != nil {
ctx.ServerError("SearchResults", err)
return
}

var loadRepoIDs = make([]int64, 0, len(searchResults))
for _, result := range searchResults {
var find bool
for _, id := range loadRepoIDs {
if id == result.RepoID {
find = true
break
}
}
if !find {
loadRepoIDs = append(loadRepoIDs, result.RepoID)
}
}

repoMaps, err := models.GetRepositoriesMapByIDs(loadRepoIDs)
if err != nil {
ctx.ServerError("SearchResults", err)
return
}

ctx.Data["RepoMaps"] = repoMaps
}

ctx.Data["Keyword"] = keyword
pager := paginater.New(total, setting.UI.RepoSearchPagingNum, page, 5)
ctx.Data["Page"] = pager
ctx.Data["SearchResults"] = searchResults
ctx.Data["RequireHighlightJS"] = true
ctx.Data["PageIsViewCode"] = true
ctx.HTML(200, tplExploreCode)
}

// NotFound render 404 page
func NotFound(ctx *context.Context) {
ctx.Data["Title"] = "Page Not Found"
Expand Down
3 changes: 2 additions & 1 deletion routers/repo/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ func Search(ctx *context.Context) {
if page <= 0 {
page = 1
}
total, searchResults, err := search.PerformSearch(ctx.Repo.Repository.ID, keyword, page, setting.UI.RepoSearchPagingNum)
total, searchResults, err := search.PerformSearch([]int64{ctx.Repo.Repository.ID},
keyword, page, setting.UI.RepoSearchPagingNum)
if err != nil {
ctx.ServerError("SearchResults", err)
return
Expand Down
1 change: 1 addition & 0 deletions routers/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/repos", routers.ExploreRepos)
m.Get("/users", routers.ExploreUsers)
m.Get("/organizations", routers.ExploreOrganizations)
m.Get("/code", routers.ExploreCode)
}, ignSignIn)
m.Combo("/install", routers.InstallInit).Get(routers.Install).
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
Expand Down
55 changes: 55 additions & 0 deletions templates/explore/code.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{{template "base/head" .}}
<div class="explore users">
{{template "explore/navbar" .}}
<div class="ui container">
<form class="ui form" style="max-width: 100%">
<div class="ui fluid action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus>
<input type="hidden" name="tab" value="{{$.TabName}}">
<button class="ui blue button">{{.i18n.Tr "explore.search"}}</button>
</div>
</form>
<div class="ui divider"></div>

<div class="ui user list">
{{if .SearchResults}}
<h3>
{{.i18n.Tr "explore.code_search_results" (.Keyword|Escape) | Str2html }}
</h3>
<div class="repository search">
{{range $result := .SearchResults}}
{{$repo := (index $.RepoMaps .RepoID)}}
<div class="diff-file-box diff-box file-content non-diff-file-content repo-search-result">
<h4 class="ui top attached normal header">
<span class="file"><a rel="nofollow" href="{{EscapePound $repo.HTMLURL}}">{{$repo.FullName}}</a> - {{.Filename}}</span>
<a class="ui basic grey tiny button" rel="nofollow" href="{{EscapePound $repo.HTMLURL}}/src/branch/{{$repo.DefaultBranch}}/{{EscapePound .Filename}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
</h4>
<div class="ui attached table segment">
<div class="file-body file-code code-view">
<table>
<tbody>
<tr>
<td class="lines-num">
{{range .LineNumbers}}
<a href="{{EscapePound $repo.HTMLURL}}/src/branch/{{$repo.DefaultBranch}}/{{EscapePound $result.Filename}}#L{{.}}"><span>{{.}}</span></a>
{{end}}
</td>
<td class="lines-code"><pre><code class="{{.HighlightClass}}"><ol class="linenums">{{.FormattedLines}}</ol></code></pre></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{{end}}
</div>
{{else}}
<div>{{$.i18n.Tr "explore.code_no_results"}}</div>
{{end}}
</div>

{{template "base/paginate" .}}
</div>
</div>
{{template "base/footer" .}}

Loading