Commit 8cc25e55 authored by Kerri Miller's avatar Kerri Miller

Add #sectional_entry_for_path for path

Additionally extracts tests into shared_examples and standardizes the
response as an array, rather than a bare entry or nil.
parent 69b4dba6
...@@ -19,13 +19,30 @@ module Gitlab ...@@ -19,13 +19,30 @@ module Gitlab
end end
def entry_for_path(path) def entry_for_path(path)
return sectional_entry_for_path(path) if sectional_codeowners?
path = "/#{path}" unless path.start_with?('/') path = "/#{path}" unless path.start_with?('/')
matching_pattern = parsed_data.keys.reverse.detect do |pattern| matching_pattern = parsed_data.keys.reverse.detect do |pattern|
path_matches?(pattern, path) path_matches?(pattern, path)
end end
parsed_data[matching_pattern].dup if matching_pattern matching_pattern ? [parsed_data[matching_pattern].dup] : []
end
def sectional_entry_for_path(path)
path = "/#{path}" unless path.start_with?('/')
matches = []
parsed_data.each do |_, section_entries|
matching_pattern = section_entries.keys.reverse.detect do |pattern|
path_matches?(pattern, path)
end
matches << section_entries[matching_pattern].dup if matching_pattern
end
matches
end end
def path def path
......
...@@ -143,138 +143,157 @@ describe Gitlab::CodeOwners::File do ...@@ -143,138 +143,157 @@ describe Gitlab::CodeOwners::File do
end end
describe '#entry_for_path' do describe '#entry_for_path' do
context 'for a path without matches' do shared_examples_for "returns expected matches" do
let(:file_content) do context 'for a path without matches' do
<<~CONTENT let(:file_content) do
# Simulating a CODOWNERS without entries <<~CONTENT
CONTENT # Simulating a CODOWNERS without entries
CONTENT
end
it 'returns an empty array for an unmatched path' do
entry = file.entry_for_path('no_matches')
expect(entry).to be_a Array
expect(entry).to be_empty
end
end end
it 'returns an nil for an unmatched path' do it 'matches random files to a pattern' do
entry = file.entry_for_path('no_matches') entry = file.entry_for_path('app/assets/something.vue').first
expect(entry).to be_nil expect(entry.pattern).to eq('*')
expect(entry.owner_line).to include('default-codeowner')
end end
end
it 'matches random files to a pattern' do it 'uses the last pattern if multiple patterns match' do
entry = file.entry_for_path('app/assets/something.vue') entry = file.entry_for_path('hello.rb').first
expect(entry.pattern).to eq('*') expect(entry.pattern).to eq('*.rb')
expect(entry.owner_line).to include('default-codeowner') expect(entry.owner_line).to eq('@ruby-owner')
end end
it 'uses the last pattern if multiple patterns match' do it 'returns the usernames for a file matching a pattern with a glob' do
entry = file.entry_for_path('hello.rb') entry = file.entry_for_path('app/models/repository.rb').first
expect(entry.pattern).to eq('*.rb') expect(entry.owner_line).to eq('@ruby-owner')
expect(entry.owner_line).to eq('@ruby-owner') end
end
it 'returns the usernames for a file matching a pattern with a glob' do it 'allows specifying multiple users' do
entry = file.entry_for_path('app/models/repository.rb') entry = file.entry_for_path('CODEOWNERS').first
expect(entry.owner_line).to eq('@ruby-owner') expect(entry.owner_line).to include('multiple', 'owners', 'tab-separated')
end end
it 'allows specifying multiple users' do it 'returns emails and usernames for a matched pattern' do
entry = file.entry_for_path('CODEOWNERS') entry = file.entry_for_path('LICENSE').first
expect(entry.owner_line).to include('multiple', 'owners', 'tab-separated') expect(entry.owner_line).to include('legal', 'janedoe@gitlab.com')
end end
it 'returns emails and usernames for a matched pattern' do it 'allows escaping the pound sign used for comments' do
entry = file.entry_for_path('LICENSE') entry = file.entry_for_path('examples/#file_with_pound.rb').first
expect(entry.owner_line).to include('legal', 'janedoe@gitlab.com') expect(entry.owner_line).to include('owner-file-with-pound')
end end
it 'allows escaping the pound sign used for comments' do it 'returns the usernames for a file nested in a directory' do
entry = file.entry_for_path('examples/#file_with_pound.rb') entry = file.entry_for_path('docs/projects/index.md').first
expect(entry.owner_line).to include('owner-file-with-pound') expect(entry.owner_line).to include('all-docs')
end end
it 'returns the usernames for a file nested in a directory' do it 'returns the usernames for a pattern matched with a glob in a folder' do
entry = file.entry_for_path('docs/projects/index.md') entry = file.entry_for_path('docs/index.md').first
expect(entry.owner_line).to include('all-docs') expect(entry.owner_line).to include('root-docs')
end end
it 'returns the usernames for a pattern matched with a glob in a folder' do it 'allows matching files nested anywhere in the repository', :aggregate_failures do
entry = file.entry_for_path('docs/index.md') lib_entry = file.entry_for_path('lib/gitlab/git/repository.rb').first
other_lib_entry = file.entry_for_path('ee/lib/gitlab/git/repository.rb').first
expect(entry.owner_line).to include('root-docs') expect(lib_entry.owner_line).to include('lib-owner')
end expect(other_lib_entry.owner_line).to include('lib-owner')
end
it 'allows matching files nested anywhere in the repository', :aggregate_failures do it 'allows allows limiting the matching files to the root of the repository', :aggregate_failures do
lib_entry = file.entry_for_path('lib/gitlab/git/repository.rb') config_entry = file.entry_for_path('config/database.yml').first
other_lib_entry = file.entry_for_path('ee/lib/gitlab/git/repository.rb') other_config_entry = file.entry_for_path('other/config/database.yml').first
expect(lib_entry.owner_line).to include('lib-owner') expect(config_entry.owner_line).to include('config-owner')
expect(other_lib_entry.owner_line).to include('lib-owner') expect(other_config_entry.owner_line).to eq('@default-codeowner')
end end
it 'allows allows limiting the matching files to the root of the repository', :aggregate_failures do it 'correctly matches paths with spaces' do
config_entry = file.entry_for_path('config/database.yml') entry = file.entry_for_path('path with spaces/README.md').first
other_config_entry = file.entry_for_path('other/config/database.yml')
expect(config_entry.owner_line).to include('config-owner') expect(entry.owner_line).to eq('@space-owner')
expect(other_config_entry.owner_line).to eq('@default-codeowner') end
end
it 'correctly matches paths with spaces' do context 'paths with whitespaces and username lookalikes' do
entry = file.entry_for_path('path with spaces/README.md') let(:file_content) do
'a/weird\ path\ with/\ @username\ /\ and-email@lookalikes.com\ / @user-1 email@gitlab.org @user-2'
end
expect(entry.owner_line).to eq('@space-owner') it 'parses correctly' do
end entry = file.entry_for_path('a/weird path with/ @username / and-email@lookalikes.com /test.rb').first
context 'paths with whitespaces and username lookalikes' do expect(entry.owner_line).to include('user-1', 'user-2', 'email@gitlab.org')
let(:file_content) do expect(entry.owner_line).not_to include('username', 'and-email@lookalikes.com')
'a/weird\ path\ with/\ @username\ /\ and-email@lookalikes.com\ / @user-1 email@gitlab.org @user-2' end
end end
it 'parses correctly' do context 'a glob on the root directory' do
entry = file.entry_for_path('a/weird path with/ @username / and-email@lookalikes.com /test.rb') let(:file_content) do
'/* @user-1 @user-2'
end
expect(entry.owner_line).to include('user-1', 'user-2', 'email@gitlab.org') it 'matches files in the root directory' do
expect(entry.owner_line).not_to include('username', 'and-email@lookalikes.com') entry = file.entry_for_path('README.md').first
end
end
context 'a glob on the root directory' do expect(entry.owner_line).to include('user-1', 'user-2')
let(:file_content) do end
'/* @user-1 @user-2'
end
it 'matches files in the root directory' do it 'does not match nested files' do
entry = file.entry_for_path('README.md') entry = file.entry_for_path('nested/path/README.md').first
expect(entry.owner_line).to include('user-1', 'user-2') expect(entry).to be_nil
end end
it 'does not match nested files' do context 'partial matches' do
entry = file.entry_for_path('nested/path/README.md') let(:file_content) do
'foo/* @user-1 @user-2'
end
it 'does not match a file in a folder that looks the same' do
entry = file.entry_for_path('fufoo/bar').first
expect(entry).to be_nil
end
expect(entry).to be_nil it 'matches the file in any folder' do
expect(file.entry_for_path('baz/foo/bar').first.owner_line).to include('user-1', 'user-2')
expect(file.entry_for_path('/foo/bar').first.owner_line).to include('user-1', 'user-2')
end
end
end end
end end
context 'partial matches' do context "when feature flag `:sectional_codeowners` is enabled" do
let(:file_content) do before do
'foo/* @user-1 @user-2' stub_feature_flags(sectional_codeowners: true)
end end
it 'does not match a file in a folder that looks the same' do it_behaves_like "returns expected matches"
entry = file.entry_for_path('fufoo/bar') end
expect(entry).to be_nil context "when feature flag `:sectional_codeowners` is disabled" do
before do
stub_feature_flags(sectional_codeowners: false)
end end
it 'matches the file in any folder' do it_behaves_like "returns expected matches"
expect(file.entry_for_path('baz/foo/bar').owner_line).to include('user-1', 'user-2')
expect(file.entry_for_path('/foo/bar').owner_line).to include('user-1', 'user-2')
end
end end
end end
end end
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