summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValery Sizov <valery@gitlab.com>2016-11-10 19:27:09 +0200
committerValery Sizov <valery@gitlab.com>2016-11-16 14:25:54 +0200
commita5632e802b72db01c0fb0b8bec77c0fc28b41427 (patch)
tree78a4d3d6fb2ac537e9bc38d0e912275170d9fd3a
parent37abb20cec2a9836a7d1d70a7f7535af6646df1e (diff)
downloadgitlab-ce-23117-search-for-a-filename-in-a-project.tar.gz
Search for a filename in a project23117-search-for-a-filename-in-a-project
-rw-r--r--app/assets/stylesheets/pages/search.scss4
-rw-r--r--app/helpers/search_helper.rb29
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb17
-rw-r--r--app/views/search/results/_blob.html.haml12
-rw-r--r--changelogs/unreleased/23117-search-for-a-filename-in-a-project.yml4
-rw-r--r--lib/gitlab/project_search_results.rb51
-rw-r--r--spec/helpers/search_helper_spec.rb32
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb51
-rw-r--r--spec/models/repository_spec.rb34
11 files changed, 155 insertions, 83 deletions
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 1a116f6a919..49f65fe4901 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -8,6 +8,10 @@
border-bottom: none;
}
}
+
+ .blob-result {
+ margin: 5px 0;
+ }
}
.search {
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index aba3a3f9c5d..cdb9663877c 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -31,34 +31,7 @@ module SearchHelper
end
def parse_search_result(result)
- ref = nil
- filename = nil
- basename = nil
- startline = 0
-
- result.each_line.each_with_index do |line, index|
- if line =~ /^.*:.*:\d+:/
- ref, filename, startline = line.split(':')
- startline = startline.to_i - index
- extname = Regexp.escape(File.extname(filename))
- basename = filename.sub(/#{extname}$/, '')
- break
- end
- end
-
- data = ""
-
- result.each_line do |line|
- data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
- end
-
- OpenStruct.new(
- filename: filename,
- basename: basename,
- ref: ref,
- startline: startline,
- data: data
- )
+ Gitlab::ProjectSearchResults.parse_search_result(result)
end
private
diff --git a/app/models/project.rb b/app/models/project.rb
index bab2f0c53ca..327f81412f8 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -879,7 +879,7 @@ class Project < ActiveRecord::Base
end
def empty_repo?
- !repository.exists? || !repository.has_visible_content?
+ repository.empty_repo?
end
def repo
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 46f70da2452..9db96347322 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -127,7 +127,7 @@ class ProjectWiki
end
def search_files(query)
- repository.search_files(query, default_branch)
+ repository.search_files_by_content(query, default_branch)
end
def repository
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 4282197faa5..a035768a6c1 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1063,16 +1063,25 @@ class Repository
merge_base(ancestor_id, descendant_id) == ancestor_id
end
- def search_files(query, ref)
- unless exists? && has_visible_content? && query.present?
- return []
- end
+ def empty_repo?
+ !exists? || !has_visible_content?
+ end
+
+ def search_files_by_content(query, ref)
+ return [] if empty_repo? || query.blank?
offset = 2
args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end
+ def search_files_by_name(query, ref)
+ return [] if empty_repo? || query.blank?
+
+ args = %W(#{Gitlab.config.git.bin_path} ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
+ Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:strip)
+ end
+
def fetch_ref(source_path, source_ref, target_ref)
args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 6f0a0ea36ec..9e8adc82583 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,11 +1,13 @@
-- blob = parse_search_result(blob)
+- file_name, blob = blob
.blob-result
.file-holder
.file-title
- - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename))
+ - ref = @search_results.repository_ref
+ - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(ref, file_name))
= link_to blob_link do
%i.fa.fa-file
%strong
- = blob.filename
- .file-content.code.term
- = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link
+ = file_name
+ - if blob
+ .file-content.code.term
+ = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link
diff --git a/changelogs/unreleased/23117-search-for-a-filename-in-a-project.yml b/changelogs/unreleased/23117-search-for-a-filename-in-a-project.yml
new file mode 100644
index 00000000000..156f8d779ca
--- /dev/null
+++ b/changelogs/unreleased/23117-search-for-a-filename-in-a-project.yml
@@ -0,0 +1,4 @@
+---
+title: Search for a filename in a project
+merge_request:
+author:
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index b8326a64b22..66e6b29e798 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -5,7 +5,7 @@ module Gitlab
def initialize(current_user, project, query, repository_ref = nil)
@current_user = current_user
@project = project
- @repository_ref = repository_ref.presence
+ @repository_ref = repository_ref.presence || project.default_branch
@query = query
end
@@ -40,10 +40,57 @@ module Gitlab
@commits_count ||= commits.count
end
+ def self.parse_search_result(result)
+ ref = nil
+ filename = nil
+ basename = nil
+ startline = 0
+
+ result.each_line.each_with_index do |line, index|
+ if line =~ /^.*:.*:\d+:/
+ ref, filename, startline = line.split(':')
+ startline = startline.to_i - index
+ extname = Regexp.escape(File.extname(filename))
+ basename = filename.sub(/#{extname}$/, '')
+ break
+ end
+ end
+
+ data = ""
+
+ result.each_line do |line|
+ data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
+ end
+
+ OpenStruct.new(
+ filename: filename,
+ basename: basename,
+ ref: ref,
+ startline: startline,
+ data: data
+ )
+ end
+
private
def blobs
- @blobs ||= project.repository.search_files(query, repository_ref)
+ @blobs ||= begin
+ blobs = project.repository.search_files_by_content(query, repository_ref).first(100)
+ found_file_names = Set.new
+
+ results = blobs.map do |blob|
+ blob = self.class.parse_search_result(blob)
+ found_file_names << blob.filename
+
+ [blob.filename, blob]
+ end
+
+ project.repository.search_files_by_name(query, repository_ref).first(100).each do |filename|
+ results << [filename, nil] unless found_file_names.include?(filename)
+ end
+
+ results.sort_by(&:first)
+ end
end
def wiki_blobs
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 64aa41020c9..4b2ca3514f8 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -6,38 +6,6 @@ describe SearchHelper do
str
end
- describe 'parsing result' do
- let(:project) { create(:project) }
- let(:repository) { project.repository }
- let(:results) { repository.search_files('feature', 'master') }
- let(:search_result) { results.first }
-
- subject { helper.parse_search_result(search_result) }
-
- it "returns a valid OpenStruct object" do
- is_expected.to be_an OpenStruct
- expect(subject.filename).to eq('CHANGELOG')
- expect(subject.basename).to eq('CHANGELOG')
- expect(subject.ref).to eq('master')
- expect(subject.startline).to eq(188)
- expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
- end
-
- context "when filename has extension" do
- let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
-
- it { expect(subject.filename).to eq('CONTRIBUTE.md') }
- it { expect(subject.basename).to eq('CONTRIBUTE') }
- end
-
- context "when file under directory" do
- let(:search_result) { "master:a/b/c.md:5:a b c\n" }
-
- it { expect(subject.filename).to eq('a/b/c.md') }
- it { expect(subject.basename).to eq('a/b/c') }
- end
- end
-
describe 'search_autocomplete_source' do
context "with no current user" do
before do
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 29abb4d4d07..a0fdad87eee 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -6,22 +6,65 @@ describe Gitlab::ProjectSearchResults, lib: true do
let(:query) { 'hello world' }
describe 'initialize with empty ref' do
- let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, '') }
+ let(:results) { described_class.new(user, project, query, '') }
it { expect(results.project).to eq(project) }
- it { expect(results.repository_ref).to be_nil }
it { expect(results.query).to eq('hello world') }
end
describe 'initialize with ref' do
let(:ref) { 'refs/heads/test' }
- let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, ref) }
+ let(:results) { described_class.new(user, project, query, ref) }
it { expect(results.project).to eq(project) }
it { expect(results.repository_ref).to eq(ref) }
it { expect(results.query).to eq('hello world') }
end
+ describe 'blob search' do
+ let(:results) { described_class.new(user, project, 'files').objects('blobs') }
+
+ it 'finds by name' do
+ expect(results).to include(["files/images/wm.svg", nil])
+ end
+
+ it 'finds by content' do
+ blob = results.select { |result| result.first == "CHANGELOG" }.flatten.last
+
+ expect(blob.filename).to eq("CHANGELOG")
+ end
+
+ describe 'parsing results' do
+ let(:results) { project.repository.search_files_by_content('feature', 'master') }
+ let(:search_result) { results.first }
+
+ subject { described_class.parse_search_result(search_result) }
+
+ it "returns a valid OpenStruct object" do
+ is_expected.to be_an OpenStruct
+ expect(subject.filename).to eq('CHANGELOG')
+ expect(subject.basename).to eq('CHANGELOG')
+ expect(subject.ref).to eq('master')
+ expect(subject.startline).to eq(188)
+ expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
+ end
+
+ context "when filename has extension" do
+ let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
+
+ it { expect(subject.filename).to eq('CONTRIBUTE.md') }
+ it { expect(subject.basename).to eq('CONTRIBUTE') }
+ end
+
+ context "when file under directory" do
+ let(:search_result) { "master:a/b/c.md:5:a b c\n" }
+
+ it { expect(subject.filename).to eq('a/b/c.md') }
+ it { expect(subject.basename).to eq('a/b/c') }
+ end
+ end
+ end
+
describe 'confidential issues' do
let(:query) { 'issue' }
let(:author) { create(:user) }
@@ -66,7 +109,7 @@ describe Gitlab::ProjectSearchResults, lib: true do
end
it 'lists project confidential issues for assignee' do
- results = described_class.new(assignee, project.id, query)
+ results = described_class.new(assignee, project, query)
issues = results.objects('issues')
expect(issues).to include issue
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index fe26b4ac18c..3bd5741f2b7 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -393,33 +393,33 @@ describe Repository, models: true do
end
end
- describe "search_files" do
- let(:results) { repository.search_files('feature', 'master') }
+ describe "search_files_by_content" do
+ let(:results) { repository.search_files_by_content('feature', 'master') }
subject { results }
it { is_expected.to be_an Array }
it 'regex-escapes the query string' do
- results = repository.search_files("test\\", 'master')
+ results = repository.search_files_by_content("test\\", 'master')
expect(results.first).not_to start_with('fatal:')
end
it 'properly handles an unmatched parenthesis' do
- results = repository.search_files("test(", 'master')
+ results = repository.search_files_by_content("test(", 'master')
expect(results.first).not_to start_with('fatal:')
end
it 'properly handles when query is not present' do
- results = repository.search_files('', 'master')
+ results = repository.search_files_by_content('', 'master')
expect(results).to match_array([])
end
it 'properly handles query when repo is empty' do
repository = create(:empty_project).repository
- results = repository.search_files('test', 'master')
+ results = repository.search_files_by_content('test', 'master')
expect(results).to match_array([])
end
@@ -432,6 +432,28 @@ describe Repository, models: true do
end
end
+ describe "search_files_by_name" do
+ let(:results) { repository.search_files_by_name('files', 'master') }
+
+ it 'returns result' do
+ expect(results.first).to eq('files/html/500.html')
+ end
+
+ it 'properly handles when query is not present' do
+ results = repository.search_files_by_name('', 'master')
+
+ expect(results).to match_array([])
+ end
+
+ it 'properly handles query when repo is empty' do
+ repository = create(:empty_project).repository
+
+ results = repository.search_files_by_name('test', 'master')
+
+ expect(results).to match_array([])
+ end
+ end
+
describe '#create_ref' do
it 'redirects the call to fetch_ref' do
ref, ref_path = '1', '2'