Commit e0997f1b authored by Sean McGivern's avatar Sean McGivern

Record whether or not a repository contains ambiguous refs

A branch can't be a prefix (including a slash) of another branch. For
instance, you can't have a branch `a` and a branch `a/b`. Same with
tags. However, you can have a branch `a` and a tag `a/b`, which is
ambiguous.

In ExtractsRef, we do some work to handle these cases. That can involve
a fairly slow Redis SMEMBERS call. If a repository has no ambiguous refs
of this form (which should be the majority), we can perform some
optimisations.

For the purposes of this method, we only consider refs ambiguous before
the first slash. If we have a branch `a/b/c` and a tag `a/b`, this
method won't detect that.
parent a0942103
......@@ -43,7 +43,7 @@ class Repository
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? root_ref merged_branch_names
has_visible_content? issue_template_names merge_request_template_names
user_defined_metrics_dashboard_paths xcode_project?).freeze
user_defined_metrics_dashboard_paths xcode_project? has_ambiguous_refs?).freeze
# Methods that use cache_method but only memoize the value
MEMOIZED_CACHED_METHODS = %i(license).freeze
......@@ -196,6 +196,31 @@ class Repository
tag_exists?(ref) && branch_exists?(ref)
end
# It's possible for a tag name to be a prefix (including slash) of a branch
# name, or vice versa. For instance, a tag named `foo` means we can't create a
# tag `foo/bar`, but we _can_ create a branch `foo/bar`.
#
# If we know a repository has no refs of this type (which is the common case)
# then separating refs from paths - as in ExtractsRef - can be faster.
#
# This method only checks one level deep, so only prefixes that contain no
# slashes are considered. If a repository has a tag `foo/bar` and a branch
# `foo/bar/baz`, it will return false.
def has_ambiguous_refs?
return false unless branch_names.present? && tag_names.present?
with_slash, no_slash = (branch_names + tag_names).partition { |ref| ref.include?('/') }
return false if with_slash.empty?
prefixes = no_slash.map { |ref| Regexp.escape(ref) }.join('|')
prefix_regex = %r{^#{prefixes}/}
with_slash.any? do |ref|
prefix_regex.match?(ref)
end
end
def expand_ref(ref)
if tag_exists?(ref)
Gitlab::Git::TAG_REF_PREFIX + ref
......
......@@ -1245,6 +1245,32 @@ RSpec.describe Repository do
end
end
describe '#has_ambiguous_refs?' do
using RSpec::Parameterized::TableSyntax
where(:branch_names, :tag_names, :result) do
nil | nil | false
%w() | %w() | false
%w(a b) | %w() | false
%w() | %w(c d) | false
%w(a b) | %w(c d) | false
%w(a/b) | %w(c/d) | false
%w(a b) | %w(c d a/z) | true
%w(a b c/z) | %w(c d) | true
%w(a/b/z) | %w(a/b) | false # we only consider refs ambiguous before the first slash
%w(a/b/z) | %w(a/b a) | true
end
with_them do
it do
allow(repository).to receive(:branch_names).and_return(branch_names)
allow(repository).to receive(:tag_names).and_return(tag_names)
expect(repository.has_ambiguous_refs?).to eq(result)
end
end
end
describe '#expand_ref' do
let(:ref) { 'ref' }
......
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