Commit bc6dda96 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '198116-project-to_reference-does-not-produce-a-valid-reference' into 'master'

Resolve "Project#to_reference does not produce a valid reference"

Closes #198116

See merge request gitlab-org/gitlab!23452
parents cef5b4f1 56f43f29
...@@ -484,10 +484,10 @@ class Commit ...@@ -484,10 +484,10 @@ class Commit
end end
def commit_reference(from, referable_commit_id, full: false) def commit_reference(from, referable_commit_id, full: false)
reference = project.to_reference(from, full: full) base = project.to_reference_base(from, full: full)
if reference.present? if base.present?
"#{reference}#{self.class.reference_prefix}#{referable_commit_id}" "#{base}#{self.class.reference_prefix}#{referable_commit_id}"
else else
referable_commit_id referable_commit_id
end end
......
...@@ -92,7 +92,7 @@ class CommitRange ...@@ -92,7 +92,7 @@ class CommitRange
alias_method :id, :to_s alias_method :id, :to_s
def to_reference(from = nil, full: false) def to_reference(from = nil, full: false)
project_reference = project.to_reference(from, full: full) project_reference = project.to_reference_base(from, full: full)
if project_reference.present? if project_reference.present?
project_reference + self.class.reference_prefix + self.id project_reference + self.class.reference_prefix + self.id
...@@ -102,7 +102,7 @@ class CommitRange ...@@ -102,7 +102,7 @@ class CommitRange
end end
def reference_link_text(from = nil) def reference_link_text(from = nil)
project_reference = project.to_reference(from) project_reference = project.to_reference_base(from)
reference = ref_from + notation + ref_to reference = ref_from + notation + ref_to
if project_reference.present? if project_reference.present?
......
...@@ -23,6 +23,14 @@ module Referable ...@@ -23,6 +23,14 @@ module Referable
'' ''
end end
# If this referable object can serve as the base for the
# reference of child objects (e.g. projects are the base of
# issues), but it is formatted differently, then you may wish
# to override this method.
def to_reference_base(from = nil, full:)
to_reference(from, full: full)
end
def reference_link_text(from = nil) def reference_link_text(from = nil)
to_reference(from) to_reference(from)
end end
......
...@@ -173,7 +173,7 @@ class Issue < ApplicationRecord ...@@ -173,7 +173,7 @@ class Issue < ApplicationRecord
def to_reference(from = nil, full: false) def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
"#{project.to_reference(from, full: full)}#{reference}" "#{project.to_reference_base(from, full: full)}#{reference}"
end end
def suggested_branch_name def suggested_branch_name
......
...@@ -225,7 +225,7 @@ class Label < ApplicationRecord ...@@ -225,7 +225,7 @@ class Label < ApplicationRecord
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
if from if from
"#{from.to_reference(target_project, full: full)}#{reference}" "#{from.to_reference_base(target_project, full: full)}#{reference}"
else else
reference reference
end end
......
...@@ -396,7 +396,7 @@ class MergeRequest < ApplicationRecord ...@@ -396,7 +396,7 @@ class MergeRequest < ApplicationRecord
def to_reference(from = nil, full: false) def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
"#{project.to_reference(from, full: full)}#{reference}" "#{project.to_reference_base(from, full: full)}#{reference}"
end end
def commits(limit: nil) def commits(limit: nil)
......
...@@ -228,7 +228,7 @@ class Milestone < ApplicationRecord ...@@ -228,7 +228,7 @@ class Milestone < ApplicationRecord
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
if project if project
"#{project.to_reference(from, full: full)}#{reference}" "#{project.to_reference_base(from, full: full)}#{reference}"
else else
reference reference
end end
......
...@@ -1068,12 +1068,19 @@ class Project < ApplicationRecord ...@@ -1068,12 +1068,19 @@ class Project < ApplicationRecord
end end
end end
def to_reference_with_postfix # Produce a valid reference (see Referable#to_reference)
"#{to_reference(full: true)}#{self.class.reference_postfix}" #
# NB: For projects, all references are 'full' - i.e. they all include the
# full_path, rather than just the project name. For this reason, we ignore
# the value of `full:` passed to this method, which is part of the Referable
# interface.
def to_reference(from = nil, full: false)
base = to_reference_base(from, full: true)
"#{base}#{self.class.reference_postfix}"
end end
# `from` argument can be a Namespace or Project. # `from` argument can be a Namespace or Project.
def to_reference(from = nil, full: false) def to_reference_base(from = nil, full: false)
if full || cross_namespace_reference?(from) if full || cross_namespace_reference?(from)
full_path full_path
elsif cross_project_reference?(from) elsif cross_project_reference?(from)
......
...@@ -180,7 +180,7 @@ class Snippet < ApplicationRecord ...@@ -180,7 +180,7 @@ class Snippet < ApplicationRecord
reference = "#{self.class.reference_prefix}#{id}" reference = "#{self.class.reference_prefix}#{id}"
if project.present? if project.present?
"#{project.to_reference(from, full: full)}#{reference}" "#{project.to_reference_base(from, full: full)}#{reference}"
else else
reference reference
end end
......
...@@ -141,7 +141,7 @@ describe API::IssueLinks do ...@@ -141,7 +141,7 @@ describe API::IssueLinks do
it 'returns 201 when sending full path of target project' do it 'returns 201 when sending full path of target project' do
post api("/projects/#{project.id}/issues/#{issue.iid}/links", user), post api("/projects/#{project.id}/issues/#{issue.iid}/links", user),
params: { target_project_id: project.to_reference(full: true), target_issue_iid: target_issue.iid } params: { target_project_id: project.full_path, target_issue_iid: target_issue.iid }
expect_link_response expect_link_response
end end
......
...@@ -121,7 +121,7 @@ module Banzai ...@@ -121,7 +121,7 @@ module Banzai
def object_link_text(object, matches) def object_link_text(object, matches)
milestone_link = escape_once(super) milestone_link = escape_once(super)
reference = object.project&.to_reference(project) reference = object.project&.to_reference_base(project)
if reference.present? if reference.present?
"#{milestone_link} <i>in #{reference}</i>".html_safe "#{milestone_link} <i>in #{reference}</i>".html_safe
......
...@@ -104,7 +104,7 @@ module Banzai ...@@ -104,7 +104,7 @@ module Banzai
def link_to_project(project, link_content: nil) def link_to_project(project, link_content: nil)
url = urls.project_url(project, only_path: context[:only_path]) url = urls.project_url(project, only_path: context[:only_path])
data = data_attribute(project: project.id) data = data_attribute(project: project.id)
content = link_content || project.to_reference_with_postfix content = link_content || project.to_reference
link_tag(url, data, content, project.name) link_tag(url, data, content, project.name)
end end
......
...@@ -32,7 +32,7 @@ describe 'issue move to another project' do ...@@ -32,7 +32,7 @@ describe 'issue move to another project' do
let(:new_project) { create(:project) } let(:new_project) { create(:project) }
let(:new_project_search) { create(:project) } let(:new_project_search) { create(:project) }
let(:text) { "Text with #{mr.to_reference}" } let(:text) { "Text with #{mr.to_reference}" }
let(:cross_reference) { old_project.to_reference(new_project) } let(:cross_reference) { old_project.to_reference_base(new_project) }
before do before do
old_project.add_reporter(user) old_project.add_reporter(user)
......
...@@ -229,10 +229,10 @@ describe Banzai::Filter::CommitRangeReferenceFilter do ...@@ -229,10 +229,10 @@ describe Banzai::Filter::CommitRangeReferenceFilter do
end end
it 'ignores invalid commit IDs on the referenced project' do it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" exp = act = "Fixed #{project2.to_reference_base}@#{commit1.id.reverse}...#{commit2.id}"
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" exp = act = "Fixed #{project2.to_reference_base}@#{commit1.id}...#{commit2.id.reverse}"
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
......
...@@ -369,7 +369,7 @@ describe Banzai::Filter::LabelReferenceFilter do ...@@ -369,7 +369,7 @@ describe Banzai::Filter::LabelReferenceFilter do
end end
context 'with project reference' do context 'with project reference' do
let(:reference) { "#{project.to_reference}#{group_label.to_reference(format: :name)}" } let(:reference) { "#{project.to_reference_base}#{group_label.to_reference(format: :name)}" }
it 'links to a valid reference' do it 'links to a valid reference' do
doc = reference_filter("See #{reference}", project: project) doc = reference_filter("See #{reference}", project: project)
...@@ -385,7 +385,7 @@ describe Banzai::Filter::LabelReferenceFilter do ...@@ -385,7 +385,7 @@ describe Banzai::Filter::LabelReferenceFilter do
end end
it 'ignores invalid label names' do it 'ignores invalid label names' do
exp = act = %(Label #{project.to_reference}#{Label.reference_prefix}"#{group_label.name.reverse}") exp = act = %(Label #{project.to_reference_base}#{Label.reference_prefix}"#{group_label.name.reverse}")
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
......
...@@ -367,15 +367,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do ...@@ -367,15 +367,17 @@ describe Banzai::Filter::MilestoneReferenceFilter do
expect(doc.css('a').first.text).to eq(urls.milestone_url(milestone)) expect(doc.css('a').first.text).to eq(urls.milestone_url(milestone))
end end
it 'does not support cross-project references' do it 'does not support cross-project references', :aggregate_failures do
another_group = create(:group) another_group = create(:group)
another_project = create(:project, :public, group: group) another_project = create(:project, :public, group: group)
project_reference = another_project.to_reference(project) project_reference = another_project.to_reference_base(project)
input_text = "See #{project_reference}#{reference}"
milestone.update!(group: another_group) milestone.update!(group: another_group)
doc = reference_filter("See #{project_reference}#{reference}") doc = reference_filter(input_text)
expect(input_text).to match(Milestone.reference_pattern)
expect(doc.css('a')).to be_empty expect(doc.css('a')).to be_empty
end end
......
...@@ -10,7 +10,7 @@ describe Banzai::Filter::ProjectReferenceFilter do ...@@ -10,7 +10,7 @@ describe Banzai::Filter::ProjectReferenceFilter do
end end
def get_reference(project) def get_reference(project)
project.to_reference_with_postfix project.to_reference
end end
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
......
...@@ -8,7 +8,7 @@ describe Gitlab::Gfm::ReferenceRewriter do ...@@ -8,7 +8,7 @@ describe Gitlab::Gfm::ReferenceRewriter do
let(:new_project) { create(:project, name: 'new-project', group: group) } let(:new_project) { create(:project, name: 'new-project', group: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:old_project_ref) { old_project.to_reference(new_project) } let(:old_project_ref) { old_project.to_reference_base(new_project) }
let(:text) { 'some text' } let(:text) { 'some text' }
before do before do
......
...@@ -131,23 +131,19 @@ describe Project do ...@@ -131,23 +131,19 @@ describe Project do
end end
context 'when creating a new project' do context 'when creating a new project' do
it 'automatically creates a CI/CD settings row' do let_it_be(:project) { create(:project) }
project = create(:project)
it 'automatically creates a CI/CD settings row' do
expect(project.ci_cd_settings).to be_an_instance_of(ProjectCiCdSetting) expect(project.ci_cd_settings).to be_an_instance_of(ProjectCiCdSetting)
expect(project.ci_cd_settings).to be_persisted expect(project.ci_cd_settings).to be_persisted
end end
it 'automatically creates a container expiration policy row' do it 'automatically creates a container expiration policy row' do
project = create(:project)
expect(project.container_expiration_policy).to be_an_instance_of(ContainerExpirationPolicy) expect(project.container_expiration_policy).to be_an_instance_of(ContainerExpirationPolicy)
expect(project.container_expiration_policy).to be_persisted expect(project.container_expiration_policy).to be_persisted
end end
it 'automatically creates a Pages metadata row' do it 'automatically creates a Pages metadata row' do
project = create(:project)
expect(project.pages_metadatum).to be_an_instance_of(ProjectPagesMetadatum) expect(project.pages_metadatum).to be_an_instance_of(ProjectPagesMetadatum)
expect(project.pages_metadatum).to be_persisted expect(project.pages_metadatum).to be_persisted
end end
...@@ -532,111 +528,114 @@ describe Project do ...@@ -532,111 +528,114 @@ describe Project do
it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) } it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) }
end end
describe '#to_reference_with_postfix' do describe 'reference methods' do
it 'returns the full path with reference_postfix' do let_it_be(:owner) { create(:user, name: 'Gitlab') }
namespace = create(:namespace, path: 'sample-namespace') let_it_be(:namespace) { create(:namespace, name: 'Sample namespace', path: 'sample-namespace', owner: owner) }
project = create(:project, path: 'sample-project', namespace: namespace) let_it_be(:project) { create(:project, name: 'Sample project', path: 'sample-project', namespace: namespace) }
let_it_be(:group) { create(:group, name: 'Group', path: 'sample-group') }
expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>' let_it_be(:another_project) { create(:project, namespace: namespace) }
end let_it_be(:another_namespace_project) { create(:project, name: 'another-project') }
end
describe '#to_reference' do describe '#to_reference' do
let(:owner) { create(:user, name: 'Gitlab') } it 'returns the path with reference_postfix' do
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) } expect(project.to_reference).to eq("#{project.full_path}>")
let(:project) { create(:project, path: 'sample-project', namespace: namespace) } end
let(:group) { create(:group, name: 'Group', path: 'sample-group') }
context 'when nil argument' do it 'returns the path with reference_postfix when arg is self' do
it 'returns nil' do expect(project.to_reference(project)).to eq("#{project.full_path}>")
expect(project.to_reference).to be_nil
end end
end
context 'when full is true' do it 'returns the full_path with reference_postfix when full' do
it 'returns complete path to the project' do expect(project.to_reference(full: true)).to eq("#{project.full_path}>")
expect(project.to_reference(full: true)).to eq 'sample-namespace/sample-project'
expect(project.to_reference(project, full: true)).to eq 'sample-namespace/sample-project'
expect(project.to_reference(group, full: true)).to eq 'sample-namespace/sample-project'
end end
end
context 'when same project argument' do it 'returns the full_path with reference_postfix when cross-project' do
it 'returns nil' do expect(project.to_reference(build_stubbed(:project))).to eq("#{project.full_path}>")
expect(project.to_reference(project)).to be_nil
end end
end end
context 'when cross namespace project argument' do describe '#to_reference_base' do
let(:another_namespace_project) { create(:project, name: 'another-project') } context 'when nil argument' do
it 'returns nil' do
it 'returns complete path to the project' do expect(project.to_reference_base).to be_nil
expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project' end
end end
end
context 'when same namespace / cross-project argument' do context 'when full is true' do
let(:another_project) { create(:project, namespace: namespace) } it 'returns complete path to the project', :aggregate_failures do
be_full_path = eq('sample-namespace/sample-project')
it 'returns path to the project' do expect(project.to_reference_base(full: true)).to be_full_path
expect(project.to_reference(another_project)).to eq 'sample-project' expect(project.to_reference_base(project, full: true)).to be_full_path
expect(project.to_reference_base(group, full: true)).to be_full_path
end
end end
end
context 'when different namespace / cross-project argument' do context 'when same project argument' do
let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) } it 'returns nil' do
let(:another_project) { create(:project, path: 'another-project', namespace: another_namespace) } expect(project.to_reference_base(project)).to be_nil
end
end
it 'returns full path to the project' do context 'when cross namespace project argument' do
expect(project.to_reference(another_project)).to eq 'sample-namespace/sample-project' it 'returns complete path to the project' do
expect(project.to_reference_base(another_namespace_project)).to eq 'sample-namespace/sample-project'
end
end end
end
context 'when argument is a namespace' do context 'when same namespace / cross-project argument' do
context 'with same project path' do
it 'returns path to the project' do it 'returns path to the project' do
expect(project.to_reference(namespace)).to eq 'sample-project' expect(project.to_reference_base(another_project)).to eq 'sample-project'
end end
end end
context 'with different project path' do context 'when different namespace / cross-project argument with same owner' do
let(:another_namespace_same_owner) { create(:namespace, path: 'another-namespace', owner: owner) }
let(:another_project_same_owner) { create(:project, path: 'another-project', namespace: another_namespace_same_owner) }
it 'returns full path to the project' do it 'returns full path to the project' do
expect(project.to_reference(group)).to eq 'sample-namespace/sample-project' expect(project.to_reference_base(another_project_same_owner)).to eq 'sample-namespace/sample-project'
end end
end end
end
end
describe '#to_human_reference' do context 'when argument is a namespace' do
let(:owner) { create(:user, name: 'Gitlab') } context 'with same project path' do
let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) } it 'returns path to the project' do
let(:project) { create(:project, name: 'Sample project', namespace: namespace) } expect(project.to_reference_base(namespace)).to eq 'sample-project'
end
end
context 'when nil argument' do context 'with different project path' do
it 'returns nil' do it 'returns full path to the project' do
expect(project.to_human_reference).to be_nil expect(project.to_reference_base(group)).to eq 'sample-namespace/sample-project'
end
end
end end
end end
context 'when same project argument' do describe '#to_human_reference' do
it 'returns nil' do context 'when nil argument' do
expect(project.to_human_reference(project)).to be_nil it 'returns nil' do
expect(project.to_human_reference).to be_nil
end
end end
end
context 'when cross namespace project argument' do
let(:another_namespace_project) { create(:project, name: 'another-project') }
it 'returns complete name with namespace of the project' do context 'when same project argument' do
expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project' it 'returns nil' do
expect(project.to_human_reference(project)).to be_nil
end
end end
end
context 'when same namespace / cross-project argument' do context 'when cross namespace project argument' do
let(:another_project) { create(:project, namespace: namespace) } it 'returns complete name with namespace of the project' do
expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project'
end
end
it 'returns name of the project' do context 'when same namespace / cross-project argument' do
expect(project.to_human_reference(another_project)).to eq 'Sample project' it 'returns name of the project' do
expect(project.to_human_reference(another_project)).to eq 'Sample project'
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