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

Feature: Find files in repo #15028

Merged
merged 36 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d83aecd
Create finding files page ui in repo page
rogerluo410 Mar 16, 2021
efe69d4
Modify router
rogerluo410 Mar 16, 2021
c4e3195
Get tree entries for find repo files.
rogerluo410 Mar 17, 2021
ea4110f
Conplete find files in repo.
rogerluo410 Mar 18, 2021
2d6a36b
Make fmt
rogerluo410 Mar 18, 2021
bbf1b6d
Merge branch 'master' into dev-find-file
rogerluo410 Mar 18, 2021
5ce4a6d
Update comment.
rogerluo410 Mar 18, 2021
5239d40
Move find files JS to individual file.
rogerluo410 Mar 19, 2021
09fe0f4
Fix go lint
rogerluo410 Mar 19, 2021
c7f622d
Update find repo files.
rogerluo410 Mar 23, 2021
6878f15
Merge branch 'master' into dev-find-file
rogerluo410 Mar 23, 2021
7d32375
gen swagger.
rogerluo410 Mar 23, 2021
ba6b069
gen swagger
rogerluo410 Mar 23, 2021
224c5d9
Add comment to redo ci
rogerluo410 Mar 23, 2021
4b90809
Add enry.IsVendor to exclude entries
rogerluo410 Mar 24, 2021
6661746
Merge branch 'master' into dev-find-file
rogerluo410 Nov 25, 2021
4e52afb
Make lint
rogerluo410 Nov 25, 2021
58802bd
Fix histroy conflicts
rogerluo410 Nov 25, 2021
1c5866d
Missing file
rogerluo410 Nov 25, 2021
a338737
Change HTML id naming style
rogerluo410 Nov 25, 2021
48dbb92
Merge branch 'master' into dev-find-file
rogerluo410 May 31, 2022
c34cd2f
Fix jquery $ variable not found issue
rogerluo410 May 31, 2022
22e79f5
Fix break too early when filtering.
rogerluo410 May 31, 2022
a1188ba
Fix id naming
rogerluo410 May 31, 2022
28951f9
revert package-lock.json
wxiaoguang Jun 7, 2022
12a9b8a
Merge branch 'main' into dev-find-file
wxiaoguang Jun 7, 2022
110afce
refactor
wxiaoguang Jun 7, 2022
02e17eb
fmt
wxiaoguang Jun 7, 2022
77ddc4d
fine tune HTML layout
wxiaoguang Jun 7, 2022
eaef506
Merge branch 'main' into dev-find-file
wxiaoguang Jun 7, 2022
c3b4e22
fine tune
wxiaoguang Jun 7, 2022
86116fe
Merge branch 'main' into dev-find-file
lunny Jun 8, 2022
7ac1f66
Apply suggestions from code review
wxiaoguang Jun 9, 2022
20b7325
add some comments
wxiaoguang Jun 9, 2022
65b69f2
use hidden instead of style, optimize strSubMatch for remaining chars
wxiaoguang Jun 9, 2022
5758ba3
Merge branch 'main' into dev-find-file
wxiaoguang Jun 9, 2022
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
1 change: 1 addition & 0 deletions models/repo/fork.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"

"xorm.io/builder"
)

Expand Down
4 changes: 2 additions & 2 deletions models/repo/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
"path/filepath"
"testing"

"code.gitea.io/gitea/models/unittest"

_ "code.gitea.io/gitea/models" // register table model
_ "code.gitea.io/gitea/models/perm/access" // register table model
_ "code.gitea.io/gitea/models/repo" // register table model
_ "code.gitea.io/gitea/models/user" // register table model

"code.gitea.io/gitea/models/unittest"
)

func TestMain(m *testing.M) {
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 @@ -2284,6 +2284,9 @@ topic.done = Done
topic.count_prompt = You can not select more than 25 topics
topic.format_prompt = Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

find_file.go_to_file = Go to file
find_file.no_matching = No matching file found

error.csv.too_large = Can't render this file because it is too large.
error.csv.unexpected = Can't render this file because it contains an unexpected character in line %d and column %d.
error.csv.invalid_field_count = Can't render this file because it has a wrong number of fields in line %d.
Expand Down
24 changes: 24 additions & 0 deletions routers/web/repo/find.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package repo

import (
"net/http"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
)

const (
tplFindFiles base.TplName = "repo/find/files"
)

// FindFiles render the page to find repository files
func FindFiles(ctx *context.Context) {
path := ctx.Params("*")
ctx.Data["TreeLink"] = ctx.Repo.RepoLink + "/src/" + path
ctx.Data["DataLink"] = ctx.Repo.RepoLink + "/tree-list/" + path
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
ctx.HTML(http.StatusOK, tplFindFiles)
}
55 changes: 55 additions & 0 deletions routers/web/repo/treelist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package repo

import (
"net/http"

"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"

"github.com/go-enry/go-enry/v2"
)

// TreeList get all files' entries of a repository
func TreeList(ctx *context.Context) {
tree, err := ctx.Repo.Commit.SubTree("/")
if err != nil {
ctx.ServerError("Repo.Commit.SubTree", err)
return
}

entries, err := tree.ListEntriesRecursive()
lunny marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
ctx.ServerError("Repo.Commit.SubTree", err)
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
return
}
entries.CustomSort(base.NaturalSortLess)

var files []string
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
for _, entry := range entries {
if !isExcludedEntry(entry) {
files = append(files, entry.Name())
}
}
ctx.JSON(http.StatusOK, files)
}

func isExcludedEntry(entry *git.TreeEntry) bool {
if entry.IsDir() {
return true
}

if entry.IsSubModule() {
return true
}

if enry.IsVendor(entry.Name()) {
return true
}

return false
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 6 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,12 @@ func RegisterRoutes(m *web.Route) {
m.Group("/milestone", func() {
m.Get("/{id}", repo.MilestoneIssuesAndPulls)
}, reqRepoIssuesOrPullsReader, context.RepoRef())
m.Get("/find/*", repo.FindFiles)
m.Group("/tree-list", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.TreeList)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.TreeList)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.TreeList)
})
m.Get("/compare", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists, ignSignIn, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff)
m.Combo("/compare/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.SetEditorconfigIfExists).
Get(ignSignIn, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff).
Expand Down
21 changes: 21 additions & 0 deletions templates/repo/find/files.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{template "base/head" .}}
<div class="page-content repository">
{{template "repo/header" .}}
<div class="ui container">
<div class="df ac">
<a href="{{$.RepoLink}}">{{.RepoName}}</a>
<span class="mx-3">/</span>
<div class="ui input f1">
<input id="repo-file-find-input" type="text" autofocus data-url-data-link="{{.DataLink}}" data-url-tree-link="{{.TreeLink}}">
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
<table id="repo-find-file-table" class="ui single line table">
<tbody>
</tbody>
</table>
<div id="repo-find-file-no-result" class="ui row center mt-5" style="display:none;">
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
<h3>{{.i18n.Tr "repo.find_file.no_matching"}}</h3>
</div>
</div>
</div>
{{template "base/footer" .}}
5 changes: 5 additions & 0 deletions templates/repo/home.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@
</a>
</div>
{{end}}
<div class="fitted item mx-0">
<a href="{{.BaseRepo.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">
{{.i18n.Tr "repo.find_file.go_to_file"}}
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
</a>
</div>
{{else}}
<div class="fitted item"><span class="ui breadcrumb repo-path"><a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span class="divider">/</span>{{if eq $i $l}}<span class="active section" title="{{$v}}">{{EllipsisString $v 30}}</span>{{else}}{{ $p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{EllipsisString $v 30}}</a></span>{{end}}{{end}}</span></div>
{{end}}
Expand Down
65 changes: 65 additions & 0 deletions web_src/js/features/repo-findfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import $ from 'jquery';

import {svg} from '../svg.js';
import {strSubMatch} from '../utils.js';
const {csrf} = window.config;

const threshold = 50;
let files = [];
let $repoFindFileInput, $repoFindFileTableBody, $repoFindFileNoResult;

function filterRepoFiles(filter) {
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
const treeLink = $repoFindFileInput.attr('data-url-tree-link');
$repoFindFileTableBody.empty();

const fileRes = [];
if (filter) {
for (let i = 0; i < files.length && fileRes.length < threshold; i++) {
const subMatch = strSubMatch(files[i], filter);
if (subMatch.length > 1) {
fileRes.push(subMatch);
}
}
} else {
for (let i = 0; i < files.length && i < threshold; i++) {
fileRes.push([files[i]]);
}
}

const tmplRow = `<tr><td><a></a></td></tr>`;

$repoFindFileNoResult.toggle(fileRes.length === 0);
for (const matchRes of fileRes) {
const $row = $(tmplRow);
const $a = $row.find('a');
$a.attr('href', `${treeLink}/${matchRes.join('')}`);
const $octiconFile = $(svg('octicon-file')).addClass('mr-3');
$a.append($octiconFile);
for (let j = 0; j < matchRes.length; j++) {
if (!matchRes[j]) continue;
const $span = $('<span>').text(matchRes[j]);
if (j % 2 === 1) $span.addClass('ui text red');
$a.append($span);
}
lafriks marked this conversation as resolved.
Show resolved Hide resolved
$repoFindFileTableBody.append($row);
}
}

async function loadRepoFiles() {
files = await $.ajax({
url: $repoFindFileInput.attr('data-url-data-link'),
headers: {'X-Csrf-Token': csrf}
});
filterRepoFiles($repoFindFileInput.val());
}

export function initFindFileInRepo() {
$repoFindFileInput = $('#repo-file-find-input');
if (!$repoFindFileInput.length) return;

$repoFindFileTableBody = $('#repo-find-file-table tbody');
$repoFindFileNoResult = $('#repo-find-file-no-result');
$repoFindFileInput.on('input', () => filterRepoFiles($repoFindFileInput.val()));

loadRepoFiles();
}
2 changes: 2 additions & 0 deletions web_src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {initMarkupAnchors} from './markup/anchors.js';
import {initNotificationCount, initNotificationsTable} from './features/notification.js';
import {initRepoIssueContentHistory} from './features/repo-issue-content.js';
import {initStopwatch} from './features/stopwatch.js';
import {initFindFileInRepo} from './features/repo-findfile.js';
import {initCommentContent, initMarkupContent} from './markup/content.js';

import {initUserAuthLinkAccountView, initUserAuthOauth2} from './features/user-auth.js';
Expand Down Expand Up @@ -124,6 +125,7 @@ $(document).ready(() => {
initSshKeyFormParser();
initStopwatch();
initTableSort();
initFindFileInRepo();

initAdminCommon();
initAdminEmails();
Expand Down
2 changes: 2 additions & 0 deletions web_src/js/svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import octiconRepo from '../../public/img/svg/octicon-repo.svg';
import octiconRepoForked from '../../public/img/svg/octicon-repo-forked.svg';
import octiconRepoTemplate from '../../public/img/svg/octicon-repo-template.svg';
import octiconTriangleDown from '../../public/img/svg/octicon-triangle-down.svg';
import octiconFile from '../../public/img/svg/octicon-file.svg';

import Vue from 'vue';

Expand All @@ -36,6 +37,7 @@ export const svgs = {
'octicon-repo-forked': octiconRepoForked,
'octicon-repo-template': octiconRepoTemplate,
'octicon-triangle-down': octiconTriangleDown,
'octicon-file': octiconFile,
};

const parser = new DOMParser();
Expand Down
32 changes: 32 additions & 0 deletions web_src/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,35 @@ export function parseIssueHref(href) {
const [_, owner, repo, type, index] = /([^/]+)\/([^/]+)\/(issues|pulls)\/([0-9]+)/.exec(path) || [];
return {owner, repo, type, index};
}

// return the sub-match result as an array: [unmatched, matched, unmatched, matched, ...]
// see unit tests for examples
export function strSubMatch(full, sub) {
const res = [''];
let i = 0, j = 0;
for (; i < sub.length && j < full.length;) {
while (j < full.length) {
if (sub[i] === full[j]) {
if (res.length % 2 !== 0) res.push('');
res[res.length - 1] += full[j];
j++;
i++;
} else {
if (res.length % 2 === 0) res.push('');
res[res.length - 1] += full[j];
j++;
break;
}
}
}
if (i !== sub.length) {
return [full];
}
if (j < full.length) {
if (res.length % 2 === 0) res.push('');
for (; j < full.length; j++) {
res[res.length - 1] += full[j];
}
}
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved
return res;
}
15 changes: 14 additions & 1 deletion web_src/js/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
basename, extname, isObject, uniq, stripTags, joinPaths, parseIssueHref,
basename, extname, isObject, uniq, stripTags, joinPaths, parseIssueHref, strSubMatch,
} from './utils.js';

test('basename', () => {
Expand Down Expand Up @@ -84,3 +84,16 @@ test('parseIssueHref', () => {
expect(parseIssueHref('https://example.com/sub/sub2/owner/repo/issues/1#hash')).toEqual({owner: 'owner', repo: 'repo', type: 'issues', index: '1'});
expect(parseIssueHref('')).toEqual({owner: undefined, repo: undefined, type: undefined, index: undefined});
});


test('strSubMatch', () => {
expect(strSubMatch('abc', '')).toEqual(['abc']);
expect(strSubMatch('abc', 'a')).toEqual(['', 'a', 'bc']);
expect(strSubMatch('abc', 'b')).toEqual(['a', 'b', 'c']);
expect(strSubMatch('abc', 'c')).toEqual(['ab', 'c']);
expect(strSubMatch('abc', 'ac')).toEqual(['', 'a', 'b', 'c']);
expect(strSubMatch('abc', 'z')).toEqual(['abc']);
expect(strSubMatch('abc', 'az')).toEqual(['abc']);

expect(strSubMatch('aabbcc', 'abc')).toEqual(['', 'a', 'a', 'b', 'b', 'c', 'c']);
});