Commit dfb441f9 authored by Valery Sizov's avatar Valery Sizov

ES: Global code search

parent 84ff13a8
...@@ -108,7 +108,7 @@ gem 'seed-fu', '~> 2.3.5' ...@@ -108,7 +108,7 @@ gem 'seed-fu', '~> 2.3.5'
# Search # Search
gem 'elasticsearch-model' gem 'elasticsearch-model'
gem 'elasticsearch-rails' gem 'elasticsearch-rails'
gem 'gitlab-elasticsearch-git', '~> 0.0.17', require: "elasticsearch/git" gem 'gitlab-elasticsearch-git', '~> 1.0.0', require: "elasticsearch/git"
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
......
...@@ -283,7 +283,7 @@ GEM ...@@ -283,7 +283,7 @@ GEM
mime-types (>= 1.19) mime-types (>= 1.19)
rugged (>= 0.23.0b) rugged (>= 0.23.0b)
github-markup (1.4.0) github-markup (1.4.0)
gitlab-elasticsearch-git (0.0.17) gitlab-elasticsearch-git (1.0.0)
activemodel (~> 4.2) activemodel (~> 4.2)
activesupport (~> 4.2) activesupport (~> 4.2)
charlock_holmes (~> 0.7) charlock_holmes (~> 0.7)
...@@ -886,7 +886,7 @@ DEPENDENCIES ...@@ -886,7 +886,7 @@ DEPENDENCIES
gemojione (~> 3.0) gemojione (~> 3.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
github-markup (~> 1.4) github-markup (~> 1.4)
gitlab-elasticsearch-git (~> 0.0.17) gitlab-elasticsearch-git (~> 1.0.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab_git (~> 10.6.3) gitlab_git (~> 10.6.3)
......
...@@ -38,9 +38,10 @@ class SearchController < ApplicationController ...@@ -38,9 +38,10 @@ class SearchController < ApplicationController
Search::SnippetService.new(current_user, params).execute Search::SnippetService.new(current_user, params).execute
else else
unless %w(projects issues merge_requests milestones).include?(@scope) unless %w(projects issues merge_requests milestones blobs commits).include?(@scope)
@scope = 'projects' @scope = 'projects'
end end
Search::GlobalService.new(current_user, params).execute Search::GlobalService.new(current_user, params).execute
end end
......
...@@ -30,6 +30,92 @@ module SearchHelper ...@@ -30,6 +30,92 @@ module SearchHelper
"Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\"" "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
end end
def parse_search_result(result)
if result.is_a?(String)
parse_search_result_from_grep(result)
else
parse_search_result_from_elastic(result)
end
end
def parse_search_result_from_elastic(result)
ref = result["_source"]["blob"]["commit_sha"]
filename = result["_source"]["blob"]["path"]
extname = File.extname(filename)
basename = filename.sub(/#{extname}$/, '')
content = result["_source"]["blob"]["content"]
total_lines = content.lines.size
highlighted_content = result["highlight"]["blob.content"]
term = highlighted_content && highlighted_content[0].match(/gitlabelasticsearch→(.*?)←gitlabelasticsearch/)[1]
found_line_number = 0
content.each_line.each_with_index do |line, index|
if term && line.include?(term)
found_line_number = index
break
end
end
from = if found_line_number >= 2
found_line_number - 2
else
found_line_number
end
to = if (total_lines - found_line_number) > 3
found_line_number + 2
else
found_line_number
end
data = content.lines[from..to]
OpenStruct.new(
filename: filename,
basename: basename,
ref: ref,
startline: from + 1,
data: data.join
)
end
def parse_search_result_from_grep(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
def find_project_for_blob(blob)
Project.find(blob['_parent'])
end
private private
# Autocomplete results for various settings pages # Autocomplete results for various settings pages
......
...@@ -11,8 +11,8 @@ module Elastic ...@@ -11,8 +11,8 @@ module Elastic
project.id project.id
end end
def self.repositories_count def project_id
Project.cached_count project.id
end end
def client_for_indexing def client_for_indexing
...@@ -27,6 +27,40 @@ module Elastic ...@@ -27,6 +27,40 @@ module Elastic
end end
end end
end end
def find_commits_by_message_with_elastic(query, page: 1, per_page: 20)
response = project.repository.search(query, type: :commit, page: page, per: per_page)[:commits][:results]
commits = response.map do |result|
commit result["_source"]["commit"]["sha"]
end
# Before "map" we had a paginated array so we need to recover it
offset = per_page * ((page || 1) - 1)
Kaminari.paginate_array(commits, total_count: response.total_count, limit: per_page, offset: offset)
end
end
class_methods do
def find_commits_by_message_with_elastic(query, page: 1, per_page: 20, options: {})
response = Repository.search(
query,
type: :commit,
page: page,
per: per_page,
options: options
)[:commits][:results]
commits = response.map do |result|
sha = result["_source"]["commit"]["sha"]
project = Project.find(result["_source"]["commit"]["rid"])
project.commit(sha)
end
# Before "map" we had a paginated array so we need to recover it
offset = per_page * ((page || 1) - 1)
Kaminari.paginate_array(commits, total_count: response.total_count, limit: per_page, offset: offset)
end
end end
end end
end end
...@@ -11,8 +11,8 @@ module Elastic ...@@ -11,8 +11,8 @@ module Elastic
"wiki_#{project.id}" "wiki_#{project.id}"
end end
def self.repositories_count def project_id
Project.with_wiki_enabled.count project.id
end end
def client_for_indexing def client_for_indexing
......
...@@ -131,12 +131,6 @@ class Repository ...@@ -131,12 +131,6 @@ class Repository
commits commits
end end
def find_commits_by_message_with_elastic(query)
project.repository.search(query, type: :commit)[:commits][:results].map do |result|
commit result["_source"]["commit"]["sha"]
end
end
def find_branch(name, fresh_repo: true) def find_branch(name, fresh_repo: true)
# Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may # Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may
# cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate # cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate
...@@ -1146,88 +1140,6 @@ class Repository ...@@ -1146,88 +1140,6 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end end
def parse_search_result(result)
if result.is_a?(String)
parse_search_result_from_grep(result)
else
parse_search_result_from_elastic(result)
end
end
def parse_search_result_from_elastic(result)
ref = result["_source"]["blob"]["commit_sha"]
filename = result["_source"]["blob"]["path"]
extname = File.extname(filename)
basename = filename.sub(/#{extname}$/, '')
content = result["_source"]["blob"]["content"]
total_lines = content.lines.size
highlighted_content = result["highlight"]["blob.content"]
term = highlighted_content && highlighted_content[0].match(/gitlabelasticsearch→(.*?)←gitlabelasticsearch/)[1]
found_line_number = 0
content.each_line.each_with_index do |line, index|
if term && line.include?(term)
found_line_number = index
break
end
end
from = if found_line_number >= 2
found_line_number - 2
else
found_line_number
end
to = if (total_lines - found_line_number) > 3
found_line_number + 2
else
found_line_number
end
data = content.lines[from..to]
OpenStruct.new(
filename: filename,
basename: basename,
ref: ref,
startline: from + 1,
data: data.join
)
end
def parse_search_result_from_grep(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
def fetch_ref(source_path, source_ref, target_ref) 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}) args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo) Gitlab::Popen.popen(args, path_to_repo)
...@@ -1255,7 +1167,7 @@ class Repository ...@@ -1255,7 +1167,7 @@ class Repository
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
update_ref!(ref, newrev, oldrev) update_ref!(ref, newrev, oldrev)
if was_empty || !target_branch if was_empty || !target_branch
# If repo was empty expire cache # If repo was empty expire cache
after_create if was_empty after_create if was_empty
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
.commit-info-block .commit-info-block
.commit-row-title .commit-row-title
%span.item-title %span.item-title
- unless @project
#{project.name_with_namespace}:
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
%span.commit-row-message.visible-xs-inline %span.commit-row-message.visible-xs-inline
&middot; &middot;
......
...@@ -69,3 +69,14 @@ ...@@ -69,3 +69,14 @@
Milestones Milestones
%span.badge %span.badge
= @search_results.milestones_count = @search_results.milestones_count
- if current_application_settings.elasticsearch_search?
%li{class: ("active" if @scope == 'blobs')}
= link_to search_filter_path(scope: 'blobs') do
Code
%span.badge
= @search_results.blobs_count
%li{class: ("active" if @scope == 'commits')}
= link_to search_filter_path(scope: 'commits') do
Commits
%span.badge
= @search_results.commits_count
- blob = @project.repository.parse_search_result(blob) - parsed_blob = parse_search_result(blob)
- project = @project || find_project_for_blob(blob)
- blob_link = namespace_project_blob_path(project.namespace, project, tree_join(parsed_blob.ref, parsed_blob.filename))
.blob-result .blob-result
.file-holder .file-holder
.file-title .file-title
- blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename))
= link_to blob_link do = link_to blob_link do
%i.fa.fa-file = icon('fa-file')
%strong %strong
= blob.filename - if @project
= parsed_blob.filename
- else
#{project.name_with_namespace}:
%i= parsed_blob.filename
.file-content.code.term .file-content.code.term
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link = render 'shared/file_highlight', blob: parsed_blob, first_line_number: parsed_blob.startline, blob_link: blob_link
.search-result-row .search-result-row
= render 'projects/commits/commit', project: @project, commit: commit = render 'projects/commits/commit', project: commit.project, commit: commit
- wiki_blob = @project.repository.parse_search_result(wiki_blob) - wiki_blob = parse_search_result(wiki_blob)
.blob-result .blob-result
.file-holder .file-holder
.file-title .file-title
......
...@@ -41,6 +41,10 @@ class Repository ...@@ -41,6 +41,10 @@ class Repository
PROJECT_ID PROJECT_ID
end end
def project_id
PROJECT_ID
end
def path_to_repo def path_to_repo
REPO_PATH REPO_PATH
end end
......
...@@ -31,7 +31,7 @@ is installed or on a separate server. ...@@ -31,7 +31,7 @@ is installed or on a separate server.
These are the minimum requirements needed for Elasticsearch to work: These are the minimum requirements needed for Elasticsearch to work:
- GitLab 8.4+ - GitLab 8.4+
- Elasticsearch 2.0+ (with [Delete By Query Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.0/plugins-delete-by-query.html) installed) - Elasticsearch 2.4+ (with [Delete By Query Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/plugins-delete-by-query.html) installed)
## Install Elasticsearch ## Install Elasticsearch
......
...@@ -182,6 +182,35 @@ To make sure you didn't miss anything run a more thorough check: ...@@ -182,6 +182,35 @@ To make sure you didn't miss anything run a more thorough check:
If all items are green, then congratulations, the upgrade is complete! If all items are green, then congratulations, the upgrade is complete!
### 11. Elasticsearch index update (if you currently use Elasticsearch)
In 8.12 release we changed the index mapping and this is why the whole index should be removed and built from scratch. Also the Elasticsearch 2.3.* contains a bug that causes to fail all queries that use highlight feature and Parent Child relationship at once, so we recommend to use the version 2.4 and newer. After updating your Elasticsearch server, please re-create your index by using one of two ways listed below:
1. Re-create the index. The following command is acceptable for not very big GitLab instances (storage size no more than few gigabytes).
```
# Omnibus installations
sudo gitlab-rake gitlab:elastic:index
# Installations from source
bundle exec rake gitlab:elastic:index
```
1. For very big GitLab instances you have to remove index first. Note: Consider disabling ES search feature (**Admin > Settings**) before removing the index, in order to allow your users to use regular search while you recreate the Elasticsearch index.
```
# Omnibus installations
sudo gitlab-rake gitlab:elastic:delete_indexes
sudo gitlab-rake gitlab:elastic:clear_index_status
# Installations from source
bundle exec rake gitlab:elastic:delete_indexes
bundle exec rake gitlab:elastic:clear_index_status
```
Then we recommend to follow [Add GitLab's data to the Elasticsearch index](../integration/elasticsearch.md#add-gitlabs-data-to-the-elasticsearch-index).
## Things went south? Revert to previous version (8.11) ## Things went south? Revert to previous version (8.11)
### 1. Revert the code to the previous version ### 1. Revert the code to the previous version
......
...@@ -28,17 +28,12 @@ module Gitlab ...@@ -28,17 +28,12 @@ module Gitlab
when 'wiki_blobs' when 'wiki_blobs'
wiki_blobs.page(page).per(per_page) wiki_blobs.page(page).per(per_page)
when 'commits' when 'commits'
Kaminari.paginate_array(commits).page(page).per(per_page) commits(page: page, per_page: per_page)
else else
super super
end end
end end
def total_count
@total_count ||= issues_count + merge_requests_count + blobs_count +
notes_count + wiki_blobs_count + commits_count
end
def blobs_count def blobs_count
@blobs_count ||= blobs.total_count @blobs_count ||= blobs.total_count
end end
...@@ -52,7 +47,7 @@ module Gitlab ...@@ -52,7 +47,7 @@ module Gitlab
end end
def commits_count def commits_count
@commits_count ||= commits.count @commits_count ||= commits.total_count
end end
private private
...@@ -98,17 +93,19 @@ module Gitlab ...@@ -98,17 +93,19 @@ module Gitlab
Note.elastic_search(query, options: opt) Note.elastic_search(query, options: opt)
end end
def commits def commits(page: 1, per_page: 20)
if project.empty_repo? || query.blank? if project.empty_repo? || query.blank?
Kaminari.paginate_array([]) Kaminari.paginate_array([])
else else
# We use elastic for default branch only # We use elastic for default branch only
if root_ref? if root_ref?
project.repository.find_commits_by_message_with_elastic(query) project.repository.find_commits_by_message_with_elastic(
else query,
Kaminari.paginate_array( page: (page || 1).to_i,
project.repository.find_commits_by_message(query).compact per_page: per_page
) )
else
project.repository.find_commits_by_message(query).compact
end end
end end
end end
......
...@@ -24,19 +24,27 @@ module Gitlab ...@@ -24,19 +24,27 @@ module Gitlab
merge_requests.page(page).per(per_page).records merge_requests.page(page).per(per_page).records
when 'milestones' when 'milestones'
milestones.page(page).per(per_page).records milestones.page(page).per(per_page).records
when 'blobs'
blobs.page(page).per(per_page)
when 'commits'
commits(page: page, per_page: per_page)
else else
Kaminari.paginate_array([]) Kaminari.paginate_array([])
end end
end end
def total_count
@total_count ||= projects_count + issues_count + merge_requests_count + milestones_count
end
def projects_count def projects_count
@projects_count ||= projects.total_count @projects_count ||= projects.total_count
end end
def blobs_count
@blobs_count ||= blobs.total_count
end
def commits_count
@commits_count ||= commits.total_count
end
def issues_count def issues_count
@issues_count ||= issues.total_count @issues_count ||= issues.total_count
end end
...@@ -49,10 +57,6 @@ module Gitlab ...@@ -49,10 +57,6 @@ module Gitlab
@milestones_count ||= milestones.total_count @milestones_count ||= milestones.total_count
end end
def empty?
total_count.zero?
end
private private
def projects def projects
...@@ -92,6 +96,64 @@ module Gitlab ...@@ -92,6 +96,64 @@ module Gitlab
MergeRequest.elastic_search(query, options: opt) MergeRequest.elastic_search(query, options: opt)
end end
def blobs
if query.blank?
Kaminari.paginate_array([])
else
opt = {
additional_filter: build_filter_by_project(limit_project_ids, @public_and_internal_projects)
}
Repository.search(
query,
type: :blob,
options: opt.merge({ highlight: true })
)[:blobs][:results].response
end
end
def commits(page: 1, per_page: 20)
if query.blank?
Kaminari.paginate_array([])
else
options = {
additional_filter: build_filter_by_project(limit_project_ids, @public_and_internal_projects)
}
Repository.find_commits_by_message_with_elastic(
query,
page: (page || 1).to_i,
per_page: per_page,
options: options
)
end
end
def build_filter_by_project(project_ids, public_and_internal_projects)
conditions = [{ terms: { id: project_ids } }]
if public_and_internal_projects
conditions << {
term: { visibility_level: Project::PUBLIC }
}
conditions << {
term: { visibility_level: Project::INTERNAL }
}
end
{
has_parent: {
parent_type: 'project',
query: {
bool: {
should: conditions
}
}
}
}
end
def default_scope def default_scope
'projects' 'projects'
end end
......
...@@ -69,6 +69,7 @@ module Gitlab ...@@ -69,6 +69,7 @@ module Gitlab
def merge_requests def merge_requests
merge_requests = MergeRequest.in_projects(project_ids_relation) merge_requests = MergeRequest.in_projects(project_ids_relation)
if query =~ /[#!](\d+)\z/ if query =~ /[#!](\d+)\z/
merge_requests = merge_requests.where(iid: $1) merge_requests = merge_requests.where(iid: $1)
else else
......
...@@ -34,4 +34,41 @@ feature 'Global elastic search', feature: true do ...@@ -34,4 +34,41 @@ feature 'Global elastic search', feature: true do
expect(page).to have_selector('.gl-pagination .page', count: 2) expect(page).to have_selector('.gl-pagination .page', count: 2)
end end
end end
describe 'I search through the blobs' do
before do
project.repository.index_blobs
Gitlab::Elastic::Helper.refresh_index
end
it "finds files" do
visit dashboard_projects_path
fill_in "search", with: "def"
click_button "Go"
select_filter("Code")
expect(page).to have_selector('.file-content .code')
end
end
describe 'I search through the commits' do
before do
project.repository.index_commits
Gitlab::Elastic::Helper.refresh_index
end
it "finds commits" do
visit dashboard_projects_path
fill_in "search", with: "add"
click_button "Go"
select_filter("Commits")
expect(page).to have_selector('.commit-row-description')
end
end
end end
...@@ -6,6 +6,71 @@ describe SearchHelper do ...@@ -6,6 +6,71 @@ describe SearchHelper do
str str
end 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(186)
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 '#parse_search_result_from_elastic' do
before do
stub_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
Gitlab::Elastic::Helper.create_empty_index
end
after do
Gitlab::Elastic::Helper.delete_index
stub_application_setting(elasticsearch_search: false, elasticsearch_indexing: false)
end
it "returns parsed result" do
project = create :project
project.repository.index_blobs
Gitlab::Elastic::Helper.refresh_index
result = project.repository.search(
'def popen',
type: :blob,
options: { highlight: true }
)[:blobs][:results][0]
parsed_result = helper.parse_search_result_from_elastic(result)
expect(parsed_result.ref). to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
expect(parsed_result.filename).to eq('files/ruby/popen.rb')
expect(parsed_result.startline).to eq(2)
expect(parsed_result.data).to include("Popen")
end
end
describe 'search_autocomplete_source' do describe 'search_autocomplete_source' do
context "with no current user" do context "with no current user" do
before do before do
......
...@@ -41,7 +41,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -41,7 +41,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
end end
it 'lists issues that title or description contain the query' do it 'lists found issues' do
results = described_class.new(user, 'hello world', limit_project_ids) results = described_class.new(user, 'hello world', limit_project_ids)
issues = results.objects('issues') issues = results.objects('issues')
...@@ -51,7 +51,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -51,7 +51,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
expect(results.issues_count).to eq 2 expect(results.issues_count).to eq 2
end end
it 'returns empty list when issues title or description does not contain the query' do it 'returns empty list when issues are not found' do
results = described_class.new(user, 'security', limit_project_ids) results = described_class.new(user, 'security', limit_project_ids)
expect(results.objects('issues')).to be_empty expect(results.objects('issues')).to be_empty
...@@ -297,7 +297,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -297,7 +297,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
end end
it 'lists merge requests that title or description contain the query' do it 'lists found merge requests' do
results = described_class.new(user, 'hello world', limit_project_ids) results = described_class.new(user, 'hello world', limit_project_ids)
merge_requests = results.objects('merge_requests') merge_requests = results.objects('merge_requests')
...@@ -307,7 +307,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -307,7 +307,7 @@ describe Gitlab::Elastic::SearchResults, lib: true do
expect(results.merge_requests_count).to eq 2 expect(results.merge_requests_count).to eq 2
end end
it 'returns empty list when merge requests title or description does not contain the query' do it 'returns empty list when merge requests are not found' do
results = described_class.new(user, 'security', limit_project_ids) results = described_class.new(user, 'security', limit_project_ids)
expect(results.objects('merge_requests')).to be_empty expect(results.objects('merge_requests')).to be_empty
...@@ -366,4 +366,72 @@ describe Gitlab::Elastic::SearchResults, lib: true do ...@@ -366,4 +366,72 @@ describe Gitlab::Elastic::SearchResults, lib: true do
expect(result.projects_count).to eq(1) expect(result.projects_count).to eq(1)
end end
end end
describe 'Blobs' do
before do
project_1.repository.index_blobs
Gitlab::Elastic::Helper.refresh_index
end
it 'founds blobs' do
results = described_class.new(user, 'def', limit_project_ids)
blobs = results.objects('blobs')
expect(blobs.first["_source"]["blob"]["content"]).to include("def")
expect(results.blobs_count).to eq 4
end
it 'founds blobs from public projects only' do
project_2 = create :project, :private
project_2.repository.index_blobs
Gitlab::Elastic::Helper.refresh_index
results = described_class.new(user, 'def', [project_1.id])
expect(results.blobs_count).to eq 4
results = described_class.new(user, 'def', [project_1.id, project_2.id])
expect(results.blobs_count).to eq 8
end
it 'returns zero when blobs are not found' do
results = described_class.new(user, 'asdfg', limit_project_ids)
expect(results.blobs_count).to eq 0
end
end
describe 'Commits' do
before do
project_1.repository.index_commits
Gitlab::Elastic::Helper.refresh_index
end
it 'founds commits' do
results = described_class.new(user, 'add', limit_project_ids)
commits = results.objects('commits')
expect(commits.first.message).to include("add")
expect(results.commits_count).to eq 5
end
it 'founds commits from public projects only' do
project_2 = create :project, :private
project_2.repository.index_commits
Gitlab::Elastic::Helper.refresh_index
results = described_class.new(user, 'add', [project_1.id])
expect(results.commits_count).to eq 5
results = described_class.new(user, 'add', [project_1.id, project_2.id])
expect(results.commits_count).to eq 10
end
it 'returns zero when commits are not found' do
results = described_class.new(user, 'asdfg', limit_project_ids)
expect(results.commits_count).to eq 0
end
end
end end
...@@ -24,4 +24,34 @@ describe Repository, elastic: true do ...@@ -24,4 +24,34 @@ describe Repository, elastic: true do
expect(project.repository.search('def popen')[:blobs][:total_count]).to eq(1) expect(project.repository.search('def popen')[:blobs][:total_count]).to eq(1)
expect(project.repository.search('initial')[:commits][:total_count]).to eq(1) expect(project.repository.search('initial')[:commits][:total_count]).to eq(1)
end end
describe "class method find_commits_by_message_with_elastic" do
it "returns commits" do
project = create :project
project1 = create :project
project.repository.index_commits
project1.repository.index_commits
Gitlab::Elastic::Helper.refresh_index
expect(Repository.find_commits_by_message_with_elastic('initial').first).to be_a(Commit)
expect(Repository.find_commits_by_message_with_elastic('initial').count).to eq(2)
expect(Repository.find_commits_by_message_with_elastic('initial').total_count).to eq(2)
end
end
describe "find_commits_by_message_with_elastic" do
it "returns commits" do
project = create :project
project.repository.index_commits
Gitlab::Elastic::Helper.refresh_index
expect(project.repository.find_commits_by_message_with_elastic('initial').first).to be_a(Commit)
expect(project.repository.find_commits_by_message_with_elastic('initial').count).to eq(1)
expect(project.repository.find_commits_by_message_with_elastic('initial').total_count).to eq(1)
end
end
end end
...@@ -132,7 +132,7 @@ describe Repository, models: true do ...@@ -132,7 +132,7 @@ describe Repository, models: true do
end end
end end
describe :commit_file do describe 'commit_file' do
it 'commits change to a file successfully' do it 'commits change to a file successfully' do
expect do expect do
repository.commit_file(user, 'CHANGELOG', 'Changelog!', repository.commit_file(user, 'CHANGELOG', 'Changelog!',
...@@ -146,7 +146,7 @@ describe Repository, models: true do ...@@ -146,7 +146,7 @@ describe Repository, models: true do
end end
end end
describe :update_file do describe 'update_file' do
it 'updates filename successfully' do it 'updates filename successfully' do
expect do expect do
repository.update_file(user, 'NEWLICENSE', 'Copyright!', repository.update_file(user, 'NEWLICENSE', 'Copyright!',
...@@ -186,32 +186,6 @@ describe Repository, models: true do ...@@ -186,32 +186,6 @@ describe Repository, models: true do
it { is_expected.to be_an String } it { is_expected.to be_an String }
it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") } it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") }
end end
describe 'parsing result' do
subject { repository.parse_search_result(search_result) }
let(:search_result) { results.first }
it { is_expected.to be_an OpenStruct }
it { expect(subject.filename).to eq('CHANGELOG') }
it { expect(subject.basename).to eq('CHANGELOG') }
it { expect(subject.ref).to eq('master') }
it { expect(subject.startline).to eq(186) }
it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
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 end
describe "#changelog" do describe "#changelog" do
...@@ -761,7 +735,7 @@ describe Repository, models: true do ...@@ -761,7 +735,7 @@ describe Repository, models: true do
end end
end end
describe :skip_merged_commit do describe 'skip_merged_commit' do
subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } } subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') } it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
...@@ -1068,39 +1042,33 @@ describe Repository, models: true do ...@@ -1068,39 +1042,33 @@ describe Repository, models: true do
stub_application_setting(elasticsearch_search: false, elasticsearch_indexing: false) stub_application_setting(elasticsearch_search: false, elasticsearch_indexing: false)
end end
describe :find_commits_by_message_with_elastic do describe "class method find_commits_by_message_with_elastic" do
it "returns commits" do it "returns commits" do
project = create :project project = create :project
project1 = create :project
project.repository.index_commits project.repository.index_commits
project1.repository.index_commits
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
expect(project.repository.find_commits_by_message_with_elastic('initial').first).to be_a(Commit) expect(described_class.find_commits_by_message_with_elastic('initial').first).to be_a(Commit)
expect(project.repository.find_commits_by_message_with_elastic('initial').count).to eq(1) expect(described_class.find_commits_by_message_with_elastic('initial').count).to eq(2)
expect(described_class.find_commits_by_message_with_elastic('initial').total_count).to eq(2)
end end
end end
describe :parse_search_result_from_elastic do describe "find_commits_by_message_with_elastic" do
it "returns parsed result" do it "returns commits" do
project = create :project project = create :project
project.repository.index_blobs project.repository.index_commits
Gitlab::Elastic::Helper.refresh_index Gitlab::Elastic::Helper.refresh_index
result = project.repository.search( expect(project.repository.find_commits_by_message_with_elastic('initial').first).to be_a(Commit)
'def popen', expect(project.repository.find_commits_by_message_with_elastic('initial').count).to eq(1)
type: :blob, expect(project.repository.find_commits_by_message_with_elastic('initial').total_count).to eq(1)
options: { highlight: true }
)[:blobs][:results][0]
parsed_result = project.repository.parse_search_result_from_elastic(result)
expect(parsed_result.ref). to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
expect(parsed_result.filename).to eq('files/ruby/popen.rb')
expect(parsed_result.startline).to eq(2)
expect(parsed_result.data).to include("Popen")
end end
end end
end end
......
require 'webmock' require 'webmock'
require 'webmock/rspec' require 'webmock/rspec'
WebMock.disable_net_connect!(allow_localhost: true, allow: 'elasticsearch') WebMock.disable_net_connect!(
WebMock.disable_net_connect!(allow_localhost: true, allow: 'registry.gitlab.com-gitlab-org-test-elastic-image') allow_localhost: true,
allow: ['elasticsearch', 'registry.gitlab.com-gitlab-org-test-elastic-image']
)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment