Commit e27fa5bc authored by Sean McGivern's avatar Sean McGivern

Merge branch 'glm-shorthand-reference' into 'master'

GLM shorthand reference for projects from the same namespace

Closes #21679

See merge request !7255
parents dc5af2ec fe957b5e
...@@ -174,7 +174,7 @@ module GitlabMarkdownHelper ...@@ -174,7 +174,7 @@ module GitlabMarkdownHelper
# Returns a String # Returns a String
def cross_project_reference(project, entity) def cross_project_reference(project, entity)
if entity.respond_to?(:to_reference) if entity.respond_to?(:to_reference)
"#{project.to_reference}#{entity.to_reference}" entity.to_reference(project)
else else
'' ''
end end
......
...@@ -82,12 +82,6 @@ module LabelsHelper ...@@ -82,12 +82,6 @@ module LabelsHelper
span.html_safe span.html_safe
end end
def render_colored_cross_project_label(label, source_project = nil, tooltip: true)
label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace
label_suffix = " <i>in #{escape_once(label_suffix)}</i>"
render_colored_label(label, label_suffix, tooltip: tooltip)
end
def suggested_colors def suggested_colors
[ [
'#0033CC', '#0033CC',
...@@ -166,6 +160,5 @@ module LabelsHelper ...@@ -166,6 +160,5 @@ module LabelsHelper
end end
# Required for Banzai::Filter::LabelReferenceFilter # Required for Banzai::Filter::LabelReferenceFilter
module_function :render_colored_label, :render_colored_cross_project_label, module_function :render_colored_label, :text_color_for_bg, :escape_once
:text_color_for_bg, :escape_once
end end
...@@ -92,19 +92,11 @@ class Commit ...@@ -92,19 +92,11 @@ class Commit
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
if cross_project_reference?(from_project) commit_reference(from_project, id)
project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil)
if cross_project_reference?(from_project) commit_reference(from_project, short_id)
project.to_reference + self.class.reference_prefix + self.short_id
else
self.short_id
end
end end
def diff_line_count def diff_line_count
...@@ -329,6 +321,16 @@ class Commit ...@@ -329,6 +321,16 @@ class Commit
private private
def commit_reference(from_project, referable_commit_id)
reference = project.to_reference(from_project)
if reference.present?
"#{reference}#{self.class.reference_prefix}#{referable_commit_id}"
else
referable_commit_id
end
end
def find_author_by_any_email def find_author_by_any_email
User.find_by_any_email(author_email.downcase) User.find_by_any_email(author_email.downcase)
end end
......
...@@ -90,22 +90,25 @@ class CommitRange ...@@ -90,22 +90,25 @@ class CommitRange
alias_method :id, :to_s alias_method :id, :to_s
def to_reference(from_project = nil) def to_reference(from_project = nil)
if cross_project_reference?(from_project) project_reference = project.to_reference(from_project)
project.to_reference + self.class.reference_prefix + self.id
if project_reference.present?
project_reference + self.class.reference_prefix + self.id
else else
self.id self.id
end end
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil)
project_reference = project.to_reference(from_project)
reference = ref_from + notation + ref_to reference = ref_from + notation + ref_to
if cross_project_reference?(from_project) if project_reference.present?
reference = project.to_reference + self.class.reference_prefix + reference project_reference + self.class.reference_prefix + reference
end else
reference reference
end end
end
# Return a Hash of parameters for passing to a URL helper # Return a Hash of parameters for passing to a URL helper
# #
......
...@@ -72,17 +72,4 @@ module Referable ...@@ -72,17 +72,4 @@ module Referable
}x }x
end end
end end
private
# Check if a reference is being done cross-project
#
# from_project - Refering Project object
def cross_project_reference?(from_project)
if self.is_a?(Project)
self != from_project
else
from_project && self.project && self.project != from_project
end
end
end end
...@@ -153,11 +153,7 @@ class Issue < ActiveRecord::Base ...@@ -153,11 +153,7 @@ class Issue < ActiveRecord::Base
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project) "#{project.to_reference(from_project)}#{reference}"
reference = project.to_reference + reference
end
reference
end end
def referenced_merge_requests(current_user = nil) def referenced_merge_requests(current_user = nil)
......
...@@ -146,7 +146,8 @@ class Label < ActiveRecord::Base ...@@ -146,7 +146,8 @@ class Label < ActiveRecord::Base
# #
# Label.first.to_reference # => "~1" # Label.first.to_reference # => "~1"
# Label.first.to_reference(format: :name) # => "~\"bug\"" # Label.first.to_reference(format: :name) # => "~\"bug\""
# Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1" # Label.first.to_reference(project, same_namespace_project) # => "gitlab-ce~1"
# Label.first.to_reference(project, another_namespace_project) # => "gitlab-org/gitlab-ce~1"
# #
# Returns a String # Returns a String
# #
...@@ -154,8 +155,8 @@ class Label < ActiveRecord::Base ...@@ -154,8 +155,8 @@ class Label < ActiveRecord::Base
format_reference = label_format_reference(format) format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(source_project, target_project) if source_project
source_project.to_reference + reference "#{source_project.to_reference(target_project)}#{reference}"
else else
reference reference
end end
...@@ -169,10 +170,6 @@ class Label < ActiveRecord::Base ...@@ -169,10 +170,6 @@ class Label < ActiveRecord::Base
private private
def cross_project_reference?(source_project, target_project)
source_project && target_project && source_project != target_project
end
def issues_count(user, params = {}) def issues_count(user, params = {})
params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all') params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all')
IssuesFinder.new(user, params.with_indifferent_access).execute.count IssuesFinder.new(user, params.with_indifferent_access).execute.count
......
...@@ -176,11 +176,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -176,11 +176,7 @@ class MergeRequest < ActiveRecord::Base
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project) "#{project.to_reference(from_project)}#{reference}"
reference = project.to_reference + reference
end
reference
end end
def first_commit def first_commit
......
...@@ -115,17 +115,14 @@ class Milestone < ActiveRecord::Base ...@@ -115,17 +115,14 @@ class Milestone < ActiveRecord::Base
# #
# Milestone.first.to_reference # => "%1" # Milestone.first.to_reference # => "%1"
# Milestone.first.to_reference(format: :name) # => "%\"goal\"" # Milestone.first.to_reference(format: :name) # => "%\"goal\""
# Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1" # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1"
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
# #
def to_reference(from_project = nil, format: :iid) def to_reference(from_project = nil, format: :iid)
format_reference = milestone_format_reference(format) format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}" reference = "#{self.class.reference_prefix}#{format_reference}"
if cross_project_reference?(from_project) "#{project.to_reference(from_project)}#{reference}"
project.to_reference + reference
else
reference
end
end end
def reference_link_text(from_project = nil) def reference_link_text(from_project = nil)
......
...@@ -419,7 +419,11 @@ class Project < ActiveRecord::Base ...@@ -419,7 +419,11 @@ class Project < ActiveRecord::Base
def reference_pattern def reference_pattern
name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
%r{(?<project>#{name_pattern}/#{name_pattern})}
%r{
((?<namespace>#{name_pattern})\/)?
(?<project>#{name_pattern})
}x
end end
def trending def trending
...@@ -650,8 +654,20 @@ class Project < ActiveRecord::Base ...@@ -650,8 +654,20 @@ class Project < ActiveRecord::Base
end end
end end
def to_reference(_from_project = nil) def to_reference(from_project = nil)
if cross_namespace_reference?(from_project)
path_with_namespace path_with_namespace
elsif cross_project_reference?(from_project)
path
end
end
def to_human_reference(from_project = nil)
if cross_namespace_reference?(from_project)
name_with_namespace
elsif cross_project_reference?(from_project)
name
end
end end
def web_url def web_url
...@@ -1327,10 +1343,21 @@ class Project < ActiveRecord::Base ...@@ -1327,10 +1343,21 @@ class Project < ActiveRecord::Base
private private
# Check if a reference is being done cross-project
#
# from_project - Refering Project object
def cross_project_reference?(from_project)
from_project && self != from_project
end
def pushes_since_gc_redis_key def pushes_since_gc_redis_key
"projects/#{id}/pushes_since_gc" "projects/#{id}/pushes_since_gc"
end end
def cross_namespace_reference?(from_project)
from_project && namespace != from_project.namespace
end
def default_branch_protected? def default_branch_protected?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
......
...@@ -67,12 +67,12 @@ class Snippet < ActiveRecord::Base ...@@ -67,12 +67,12 @@ class Snippet < ActiveRecord::Base
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}" reference = "#{self.class.reference_prefix}#{id}"
if cross_project_reference?(from_project) if project.present?
reference = project.to_reference + reference "#{project.to_reference(from_project)}#{reference}"
end else
reference reference
end end
end
def self.content_types def self.content_types
[ [
......
---
title: Add shorthand support to gitlab markdown references
merge_request: 7255
author: Oswaldo Ferreira
...@@ -267,6 +267,18 @@ GFM also recognizes certain cross-project references: ...@@ -267,6 +267,18 @@ GFM also recognizes certain cross-project references:
| `namespace/project@9ba12248...b19a04f5` | commit range comparison | | `namespace/project@9ba12248...b19a04f5` | commit range comparison |
| `namespace/project~"Some label"` | issues with given label | | `namespace/project~"Some label"` | issues with given label |
It also has a shorthand version to reference other projects from the same namespace:
| input | references |
|:------------------------------|:------------------------|
| `project#123` | issue |
| `project!123` | merge request |
| `project%123` | milestone |
| `project$123` | snippet |
| `project@9ba12248` | specific commit |
| `project@9ba12248...b19a04f5` | commit range comparison |
| `project~"Some label"` | issues with given label |
### Task Lists ### Task Lists
> If this is not rendered correctly, see > If this is not rendered correctly, see
......
...@@ -33,7 +33,7 @@ module Banzai ...@@ -33,7 +33,7 @@ module Banzai
# Returns a String replaced with the return of the block. # Returns a String replaced with the return of the block.
def self.references_in(text, pattern = object_class.reference_pattern) def self.references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match| text.gsub(pattern) do |match|
yield match, $~[object_sym].to_i, $~[:project], $~ yield match, $~[object_sym].to_i, $~[:project], $~[:namespace], $~
end end
end end
...@@ -145,8 +145,9 @@ module Banzai ...@@ -145,8 +145,9 @@ module Banzai
# Returns a String with references replaced with links. All links # Returns a String with references replaced with links. All links
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text, pattern, link_content: nil) def object_link_filter(text, pattern, link_content: nil)
references_in(text, pattern) do |match, id, project_ref, matches| references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches|
project = project_from_ref_cached(project_ref) project_path = full_project_path(namespace_ref, project_ref)
project = project_from_ref_cached(project_path)
if project && object = find_object_cached(project, id) if project && object = find_object_cached(project, id)
title = object_link_title(object) title = object_link_title(object)
...@@ -217,10 +218,9 @@ module Banzai ...@@ -217,10 +218,9 @@ module Banzai
nodes.each do |node| nodes.each do |node|
node.to_html.scan(regex) do node.to_html.scan(regex) do
project = $~[:project] || current_project_path project_path = full_project_path($~[:namespace], $~[:project])
symbol = $~[object_sym] symbol = $~[object_sym]
refs[project_path] << symbol if object_class.reference_valid?(symbol)
refs[project] << symbol if object_class.reference_valid?(symbol)
end end
end end
...@@ -272,8 +272,19 @@ module Banzai ...@@ -272,8 +272,19 @@ module Banzai
@current_project_path ||= project.path_with_namespace @current_project_path ||= project.path_with_namespace
end end
def current_project_namespace_path
@current_project_namespace_path ||= project.namespace.path
end
private private
def full_project_path(namespace, project_ref)
return current_project_path unless project_ref
namespace_ref = namespace || current_project_namespace_path
"#{namespace_ref}/#{project_ref}"
end
def project_refs_cache def project_refs_cache
RequestStore[:banzai_project_refs] ||= {} RequestStore[:banzai_project_refs] ||= {}
end end
......
...@@ -12,7 +12,7 @@ module Banzai ...@@ -12,7 +12,7 @@ module Banzai
def self.references_in(text, pattern = CommitRange.reference_pattern) def self.references_in(text, pattern = CommitRange.reference_pattern)
text.gsub(pattern) do |match| text.gsub(pattern) do |match|
yield match, $~[:commit_range], $~[:project], $~ yield match, $~[:commit_range], $~[:project], $~[:namespace], $~
end end
end end
......
...@@ -12,7 +12,7 @@ module Banzai ...@@ -12,7 +12,7 @@ module Banzai
def self.references_in(text, pattern = Commit.reference_pattern) def self.references_in(text, pattern = Commit.reference_pattern)
text.gsub(pattern) do |match| text.gsub(pattern) do |match|
yield match, $~[:commit], $~[:project], $~ yield match, $~[:commit], $~[:project], $~[:namespace], $~
end end
end end
......
...@@ -14,16 +14,18 @@ module Banzai ...@@ -14,16 +14,18 @@ module Banzai
def self.references_in(text, pattern = Label.reference_pattern) def self.references_in(text, pattern = Label.reference_pattern)
unescape_html_entities(text).gsub(pattern) do |match| unescape_html_entities(text).gsub(pattern) do |match|
yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~ yield match, $~[:label_id].to_i, $~[:label_name], $~[:project], $~[:namespace], $~
end end
end end
def references_in(text, pattern = Label.reference_pattern) def references_in(text, pattern = Label.reference_pattern)
unescape_html_entities(text).gsub(pattern) do |match| unescape_html_entities(text).gsub(pattern) do |match|
label = find_label($~[:project], $~[:label_id], $~[:label_name]) namespace, project = $~[:namespace], $~[:project]
project_path = full_project_path(namespace, project)
label = find_label(project_path, $~[:label_id], $~[:label_name])
if label if label
yield match, label.id, $~[:project], $~ yield match, label.id, project, namespace, $~
else else
match match
end end
...@@ -64,48 +66,12 @@ module Banzai ...@@ -64,48 +66,12 @@ module Banzai
end end
def object_link_text(object, matches) def object_link_text(object, matches)
if same_group?(object) && namespace_match?(matches) project_path = full_project_path(matches[:namespace], matches[:project])
render_same_project_label(object) project_from_ref = project_from_ref_cached(project_path)
elsif same_project?(object) reference = project_from_ref.to_human_reference(project)
render_same_project_label(object) label_suffix = " <i>in #{reference}</i>" if reference.present?
else
render_cross_project_label(object, matches)
end
end
def same_group?(object)
object.is_a?(GroupLabel) && object.group == project.group
end
def namespace_match?(matches)
matches[:project].blank? || matches[:project] == project.path_with_namespace
end
def same_project?(object)
object.is_a?(ProjectLabel) && object.project == project
end
def user
context[:current_user] || context[:author]
end
def project
context[:project]
end
def render_same_project_label(object)
LabelsHelper.render_colored_label(object)
end
def render_cross_project_label(object, matches)
source_project =
if matches[:project]
Project.find_with_namespace(matches[:project])
else
object.project
end
LabelsHelper.render_colored_cross_project_label(object, source_project) LabelsHelper.render_colored_label(object, label_suffix)
end end
def unescape_html_entities(text) def unescape_html_entities(text)
......
...@@ -19,18 +19,20 @@ module Banzai ...@@ -19,18 +19,20 @@ module Banzai
return super(text, pattern) if pattern != Milestone.reference_pattern return super(text, pattern) if pattern != Milestone.reference_pattern
text.gsub(pattern) do |match| text.gsub(pattern) do |match|
milestone = find_milestone($~[:project], $~[:milestone_iid], $~[:milestone_name]) milestone = find_milestone($~[:project], $~[:namespace], $~[:milestone_iid], $~[:milestone_name])
if milestone if milestone
yield match, milestone.iid, $~[:project], $~ yield match, milestone.iid, $~[:project], $~[:namespace], $~
else else
match match
end end
end end
end end
def find_milestone(project_ref, milestone_id, milestone_name) def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
project = project_from_ref(project_ref) project_path = full_project_path(namespace_ref, project_ref)
project = project_from_ref(project_path)
return unless project return unless project
milestone_params = milestone_params(milestone_id, milestone_name) milestone_params = milestone_params(milestone_id, milestone_name)
...@@ -52,11 +54,13 @@ module Banzai ...@@ -52,11 +54,13 @@ module Banzai
end end
def object_link_text(object, matches) def object_link_text(object, matches)
if context[:project] == object.project milestone_link = escape_once(super)
super reference = object.project.to_reference(project)
if reference.present?
"#{milestone_link} <i>in #{reference}</i>".html_safe
else else
"#{escape_once(super)} <i>in #{escape_once(object.project.path_with_namespace)}</i>". milestone_link
html_safe
end end
end end
......
...@@ -28,7 +28,7 @@ feature 'issue move to another project' do ...@@ -28,7 +28,7 @@ feature '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 } let(:cross_reference) { old_project.to_reference(new_project) }
background do background do
old_project.team << [user, :reporter] old_project.team << [user, :reporter]
......
...@@ -40,7 +40,7 @@ feature 'Create New Merge Request', feature: true, js: true do ...@@ -40,7 +40,7 @@ feature 'Create New Merge Request', feature: true, js: true do
visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id }) visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
expect(page).not_to have_content private_project.to_reference expect(page).not_to have_content private_project.path_with_namespace
end end
end end
......
...@@ -7,7 +7,7 @@ describe LabelsHelper do ...@@ -7,7 +7,7 @@ describe LabelsHelper do
context 'without subject' do context 'without subject' do
it "uses the label's project" do it "uses the label's project" do
expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name%5B%5D=#{label.name}">.*</a>} expect(link_to_label(label)).to match %r{<a href="/#{label.project.path_with_namespace}/issues\?label_name%5B%5D=#{label.name}">.*</a>}
end end
end end
...@@ -32,7 +32,7 @@ describe LabelsHelper do ...@@ -32,7 +32,7 @@ describe LabelsHelper do
['issue', :issue, 'merge_request', :merge_request].each do |type| ['issue', :issue, 'merge_request', :merge_request].each do |type|
context "set to #{type}" do context "set to #{type}" do
it 'links to correct page' do it 'links to correct page' do
expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>} expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.path_with_namespace}/#{type.to_s.pluralize}\?label_name%5B%5D=#{label.name}">.*</a>}
end end
end end
end end
......
...@@ -5,7 +5,7 @@ describe Banzai::Filter::AbstractReferenceFilter do ...@@ -5,7 +5,7 @@ describe Banzai::Filter::AbstractReferenceFilter do
describe '#references_per_project' do describe '#references_per_project' do
it 'returns a Hash containing references grouped per project paths' do it 'returns a Hash containing references grouped per project paths' do
doc = Nokogiri::HTML.fragment("#1 #{project.to_reference}#2") doc = Nokogiri::HTML.fragment("#1 #{project.path_with_namespace}#2")
filter = described_class.new(doc, project: project) filter = described_class.new(doc, project: project)
expect(filter).to receive(:object_class).exactly(4).times.and_return(Issue) expect(filter).to receive(:object_class).exactly(4).times.and_return(Issue)
...@@ -14,7 +14,7 @@ describe Banzai::Filter::AbstractReferenceFilter do ...@@ -14,7 +14,7 @@ describe Banzai::Filter::AbstractReferenceFilter do
refs = filter.references_per_project refs = filter.references_per_project
expect(refs).to be_an_instance_of(Hash) expect(refs).to be_an_instance_of(Hash)
expect(refs[project.to_reference]).to eq(Set.new(%w[1 2])) expect(refs[project.path_with_namespace]).to eq(Set.new(%w[1 2]))
end end
end end
......
...@@ -59,9 +59,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do ...@@ -59,9 +59,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
it 'ignores invalid commit IDs' do it 'ignores invalid commit IDs' do
exp = act = "See #{commit1.id.reverse}...#{commit2.id}" exp = act = "See #{commit1.id.reverse}...#{commit2.id}"
expect(project).to receive(:valid_repo?).and_return(true) allow(project.repository).to receive(:commit).with(commit1.id.reverse)
expect(project.repository).to receive(:commit).with(commit1.id.reverse)
expect(project.repository).to receive(:commit).with(commit2.id)
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
...@@ -100,15 +98,45 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do ...@@ -100,15 +98,45 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
end end
end end
context 'cross-project reference' do context 'cross-project / cross-namespace complete reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, :public) }
let(:project2) { create(:project, :public, namespace: namespace) } let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" }
let(:reference) { range.to_reference(project) }
before do it 'links to a valid reference' do
range.project = project2 doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'link has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.css('a').first.text).
to eql("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}")
end
it 'has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.text).to eql("Fixed (#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}.)")
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.path_with_namespace}@#{commit1.id.reverse}...#{commit2.id}"
expect(reference_filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.path_with_namespace}@#{commit1.id}...#{commit2.id.reverse}"
expect(reference_filter(act).to_html).to eq exp
end
end end
context 'cross-project / same-namespace complete reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:project, :public, namespace: namespace) }
let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) }
let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" }
it 'links to a valid reference' do it 'links to a valid reference' do
doc = reference_filter("See #{reference}") doc = reference_filter("See #{reference}")
...@@ -116,24 +144,65 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do ...@@ -116,24 +144,65 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end end
it 'links with adjacent text' do it 'link has valid text' do
doc = reference_filter("Fixed (#{reference}.)") doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}") expect(doc.css('a').first.text).
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}")
end
it 'has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.text).to eql("Fixed (#{project2.path}@#{commit1.short_id}...#{commit2.short_id}.)")
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.path}@#{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.path}@#{commit1.id}...#{commit2.id.reverse}"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'cross-project shorthand reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:project, :public, namespace: namespace) }
let(:project2) { create(:project, :public, path: "same-namespace", namespace: namespace) }
let(:reference) { "#{project2.path}@#{commit1.id}...#{commit2.id}" }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'link has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.css('a').first.text).
to eql("#{project2.path}@#{commit1.short_id}...#{commit2.short_id}")
end
it 'has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.text).to eql("Fixed (#{project2.path}@#{commit1.short_id}...#{commit2.short_id}.)")
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.path}@#{commit1.id.reverse}...#{commit2.id}"
expect(reference_filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.path}@#{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
context 'cross-project URL reference' do context 'cross-project URL reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace) }
let(:project2) { create(:project, :public, namespace: namespace) } let(:project2) { create(:project, :public, namespace: namespace) }
let(:range) { CommitRange.new("#{commit1.id}...master", project) } let(:range) { CommitRange.new("#{commit1.id}...master", project) }
let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') } let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
......
...@@ -41,6 +41,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do ...@@ -41,6 +41,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
it 'links with adjacent text' do it 'links with adjacent text' do
doc = reference_filter("See (#{reference}.)") doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
end end
...@@ -48,8 +49,6 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do ...@@ -48,8 +49,6 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
invalid = invalidate_reference(reference) invalid = invalidate_reference(reference)
exp = act = "See #{invalid}" exp = act = "See #{invalid}"
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(invalid)
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
...@@ -95,34 +94,85 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do ...@@ -95,34 +94,85 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
end end
end end
context 'cross-project reference' do context 'cross-project / cross-namespace complete reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace) }
let(:project2) { create(:project, :public, namespace: namespace) } let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit } let(:commit) { project2.commit }
let(:reference) { commit.to_reference(project) } let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" }
it 'links to a valid reference' do it 'link has valid text' do
doc = reference_filter("See #{reference}") doc = reference_filter("See (#{reference}.)")
expect(doc.css('a').first.attr('href')). expect(doc.css('a').first.text).to eql("#{project2.path_with_namespace}@#{commit.short_id}")
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end end
it 'links with adjacent text' do it 'has valid text' do
doc = reference_filter("Fixed (#{reference}.)") doc = reference_filter("See (#{reference}.)")
expect(doc.text).to eql("See (#{project2.path_with_namespace}@#{commit.short_id}.)")
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Committed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'cross-project / same-namespace complete reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, namespace: namespace) }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit }
let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" }
it 'link has valid text' do
doc = reference_filter("See (#{reference}.)")
exp = Regexp.escape(project2.to_reference) expect(doc.css('a').first.text).to eql("#{project2.path}@#{commit.short_id}")
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/) end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text).to eql("See (#{project2.path}@#{commit.short_id}.)")
end end
it 'ignores invalid commit IDs on the referenced project' do it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Committed #{invalidate_reference(reference)}" exp = act = "Committed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'cross-project shorthand reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, namespace: namespace) }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit }
let(:reference) { "#{project2.path_with_namespace}@#{commit.short_id}" }
it 'link has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.css('a').first.text).to eql("#{project2.path}@#{commit.short_id}")
end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text).to eql("See (#{project2.path}@#{commit.short_id}.)")
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Committed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
context 'cross-project URL reference' do context 'cross-project URL reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace) }
let(:project2) { create(:project, :public, namespace: namespace) } let(:project2) { create(:project, :public, namespace: namespace) }
let(:commit) { project2.commit } let(:commit) { project2.commit }
let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) }
......
...@@ -24,7 +24,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -24,7 +24,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
context 'internal reference' do context 'internal reference' do
it_behaves_like 'a reference containing an element node' it_behaves_like 'a reference containing an element node'
let(:reference) { issue.to_reference } let(:reference) { "##{issue.iid}" }
it 'ignores valid references when using non-default tracker' do it 'ignores valid references when using non-default tracker' do
allow(project).to receive(:default_issues_tracker?).and_return(false) allow(project).to receive(:default_issues_tracker?).and_return(false)
...@@ -42,7 +42,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -42,7 +42,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
it 'links with adjacent text' do it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)") doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) expect(doc.text).to eql("Fixed (#{reference}.)")
end end
it 'ignores invalid issue IDs' do it 'ignores invalid issue IDs' do
...@@ -116,13 +116,56 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -116,13 +116,56 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
end end
end end
context 'cross-project reference' do context 'cross-project / cross-namespace complete reference' do
it_behaves_like 'a reference containing an element node' it_behaves_like 'a reference containing an element node'
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object).
with(project2, issue.iid).
and_return(nil)
exp = act = "Issue #{reference}"
expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'link has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.css('a').first.text).to eql("#{project2.path_with_namespace}##{issue.iid}")
end
it 'has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.text).to eq("Fixed (#{project2.path_with_namespace}##{issue.iid}.)")
end
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'cross-project / same-namespace complete reference' do
it_behaves_like 'a reference containing an element node'
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, :public, namespace: namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) } let(:issue) { create(:issue, project: project2) }
let(:reference) { issue.to_reference(project) } let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
it 'ignores valid references when cross-reference project uses external tracker' do it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object). expect_any_instance_of(described_class).to receive(:find_object).
...@@ -140,9 +183,16 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -140,9 +183,16 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
to eq helper.url_for_issue(issue.iid, project2) to eq helper.url_for_issue(issue.iid, project2)
end end
it 'links with adjacent text' do it 'link has valid text' do
doc = reference_filter("Fixed (#{reference}.)") doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
expect(doc.css('a').first.text).to eql("#{project2.path}##{issue.iid}")
end
it 'has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.text).to eq("Fixed (#{project2.path}##{issue.iid}.)")
end end
it 'ignores invalid issue IDs on the referenced project' do it 'ignores invalid issue IDs on the referenced project' do
...@@ -150,9 +200,47 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -150,9 +200,47 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end
context 'cross-project shorthand reference' do
it_behaves_like 'a reference containing an element node'
it 'ignores out-of-bounds issue IDs on the referenced project' do let(:namespace) { create(:namespace) }
exp = act = "Fixed ##{Gitlab::Database::MAX_INT_VALUE + 1}" let(:project) { create(:empty_project, :public, namespace: namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { "#{project2.path}##{issue.iid}" }
it 'ignores valid references when cross-reference project uses external tracker' do
expect_any_instance_of(described_class).to receive(:find_object).
with(project2, issue.iid).
and_return(nil)
exp = act = "Issue #{reference}"
expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
it 'link has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.css('a').first.text).to eql("#{project2.path}##{issue.iid}")
end
it 'has valid text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.text).to eq("Fixed (#{project2.path}##{issue.iid}.)")
end
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:project, :public) } let(:project) { create(:empty_project, :public) }
let(:merge) { create(:merge_request, source_project: project) } let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do it 'requires project context' do
...@@ -86,23 +86,97 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do ...@@ -86,23 +86,97 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
end end
end end
context 'cross-project reference' do context 'cross-project / cross-namespace complete reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public) }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) } let(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { merge.to_reference(project) } let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" }
it 'links to a valid reference' do it 'links to a valid reference' do
doc = reference_filter("See #{reference}") doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')). expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace, to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge) project2, merge)
end end
it 'links with adjacent text' do it 'link has valid text' do
doc = reference_filter("Merge (#{reference}.)") doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
expect(doc.css('a').first.text).to eq(reference)
end
it 'has valid text' do
doc = reference_filter("Merge (#{reference}.)")
expect(doc.text).to eq("Merge (#{reference}.)")
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'cross-project / same-namespace complete reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, :public, namespace: namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let!(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace,
project2, merge)
end
it 'link has valid text' do
doc = reference_filter("Merge (#{reference}.)")
expect(doc.css('a').first.text).to eq("#{project2.path}!#{merge.iid}")
end
it 'has valid text' do
doc = reference_filter("Merge (#{reference}.)")
expect(doc.text).to eq("Merge (#{project2.path}!#{merge.iid}.)")
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'cross-project shorthand reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, :public, namespace: namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let!(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { "#{project2.path}!#{merge.iid}" }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace,
project2, merge)
end
it 'link has valid text' do
doc = reference_filter("Merge (#{reference}.)")
expect(doc.css('a').first.text).to eq("#{project2.path}!#{merge.iid}")
end
it 'has valid text' do
doc = reference_filter("Merge (#{reference}.)")
expect(doc.text).to eq("Merge (#{project2.path}!#{merge.iid}.)")
end end
it 'ignores invalid merge IDs on the referenced project' do it 'ignores invalid merge IDs on the referenced project' do
......
...@@ -148,12 +148,50 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do ...@@ -148,12 +148,50 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
end end
end end
describe 'cross project milestone references' do describe 'cross-project / cross-namespace complete reference' do
let(:another_project) { create(:empty_project, :public) } let(:namespace) { create(:namespace) }
let(:project_path) { another_project.path_with_namespace } let(:another_project) { create(:empty_project, :public, namespace: namespace) }
let(:milestone) { create(:milestone, project: another_project) } let(:milestone) { create(:milestone, project: another_project) }
let(:reference) { milestone.to_reference(project) } let(:reference) { "#{another_project.path_with_namespace}%#{milestone.iid}" }
let!(:result) { reference_filter("See #{reference}") }
it 'points to referenced project milestone page' do
expect(result.css('a').first.attr('href')).to eq urls.
namespace_project_milestone_url(another_project.namespace,
another_project,
milestone)
end
it 'link has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.css('a').first.text).
to eq("#{milestone.name} in #{another_project.path_with_namespace}")
end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text).
to eq("See (#{milestone.name} in #{another_project.path_with_namespace}.)")
end
it 'escapes the name attribute' do
allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="})
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.text).
to eq "#{milestone.name} in #{another_project.path_with_namespace}"
end
end
describe 'cross-project / same-namespace complete reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, :public, namespace: namespace) }
let(:another_project) { create(:empty_project, :public, namespace: namespace) }
let(:milestone) { create(:milestone, project: another_project) }
let(:reference) { "#{another_project.path_with_namespace}%#{milestone.iid}" }
let!(:result) { reference_filter("See #{reference}") } let!(:result) { reference_filter("See #{reference}") }
it 'points to referenced project milestone page' do it 'points to referenced project milestone page' do
...@@ -163,14 +201,66 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do ...@@ -163,14 +201,66 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
milestone) milestone)
end end
it 'contains cross project content' do it 'link has valid text' do
expect(result.css('a').first.text).to eq "#{milestone.name} in #{project_path}" doc = reference_filter("See (#{reference}.)")
expect(doc.css('a').first.text).
to eq("#{milestone.name} in #{another_project.path}")
end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text).
to eq("See (#{milestone.name} in #{another_project.path}.)")
end end
it 'escapes the name attribute' do it 'escapes the name attribute' do
allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="}) allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="})
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.text).
to eq "#{milestone.name} in #{another_project.path}"
end
end
describe 'cross project shorthand reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, :public, namespace: namespace) }
let(:another_project) { create(:empty_project, :public, namespace: namespace) }
let(:milestone) { create(:milestone, project: another_project) }
let(:reference) { "#{another_project.path}%#{milestone.iid}" }
let!(:result) { reference_filter("See #{reference}") }
it 'points to referenced project milestone page' do
expect(result.css('a').first.attr('href')).to eq urls.
namespace_project_milestone_url(another_project.namespace,
another_project,
milestone)
end
it 'link has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.css('a').first.text).
to eq("#{milestone.name} in #{another_project.path}")
end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text).
to eq("See (#{milestone.name} in #{another_project.path}.)")
end
it 'escapes the name attribute' do
allow_any_instance_of(Milestone).to receive(:title).and_return(%{"></a>whatever<a title="})
doc = reference_filter("See #{reference}") doc = reference_filter("See #{reference}")
expect(doc.css('a').first.text).to eq "#{milestone.name} in #{project_path}"
expect(doc.css('a').first.text).
to eq "#{milestone.name} in #{another_project.path}"
end end
end end
end end
...@@ -79,11 +79,11 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do ...@@ -79,11 +79,11 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
end end
end end
context 'cross-project reference' do context 'cross-project / cross-namespace complete reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:project2) { create(:empty_project, :public, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) } let!(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { snippet.to_reference(project) } let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" }
it 'links to a valid reference' do it 'links to a valid reference' do
doc = reference_filter("See #{reference}") doc = reference_filter("See #{reference}")
...@@ -92,9 +92,82 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do ...@@ -92,9 +92,82 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end end
it 'links with adjacent text' do it 'link has valid text' do
doc = reference_filter("See (#{reference}.)") doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
expect(doc.css('a').first.text).to eql(reference)
end
it 'has valid text' do
doc = reference_filter("See (#{reference}.)")
expect(doc.text).to eql("See (#{reference}.)")
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'cross-project / same-namespace complete reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, :public, namespace: namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let!(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'link has valid text' do
doc = reference_filter("See (#{project2.path}$#{snippet.id}.)")
expect(doc.css('a').first.text).to eql("#{project2.path}$#{snippet.id}")
end
it 'has valid text' do
doc = reference_filter("See (#{project2.path}$#{snippet.id}.)")
expect(doc.text).to eql("See (#{project2.path}$#{snippet.id}.)")
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to eq exp
end
end
context 'cross-project shorthand reference' do
let(:namespace) { create(:namespace) }
let(:project) { create(:empty_project, :public, namespace: namespace) }
let(:project2) { create(:empty_project, :public, namespace: namespace) }
let!(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { "#{project2.path}$#{snippet.id}" }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'link has valid text' do
doc = reference_filter("See (#{project2.path}$#{snippet.id}.)")
expect(doc.css('a').first.text).to eql("#{project2.path}$#{snippet.id}")
end
it 'has valid text' do
doc = reference_filter("See (#{project2.path}$#{snippet.id}.)")
expect(doc.text).to eql("See (#{project2.path}$#{snippet.id}.)")
end end
it 'ignores invalid snippet IDs on the referenced project' do it 'ignores invalid snippet IDs on the referenced project' do
......
...@@ -64,7 +64,7 @@ describe Gitlab::Gfm::ReferenceRewriter do ...@@ -64,7 +64,7 @@ describe Gitlab::Gfm::ReferenceRewriter do
context 'description with project labels' do context 'description with project labels' do
let!(:label) { create(:label, id: 123, name: 'test', project: old_project) } let!(:label) { create(:label, id: 123, name: 'test', project: old_project) }
let(:project_ref) { old_project.to_reference } let(:project_ref) { old_project.to_reference(new_project) }
context 'label referenced by id' do context 'label referenced by id' do
let(:text) { '#1 and ~123' } let(:text) { '#1 and ~123' }
...@@ -80,7 +80,7 @@ describe Gitlab::Gfm::ReferenceRewriter do ...@@ -80,7 +80,7 @@ describe Gitlab::Gfm::ReferenceRewriter do
context 'description with group labels' do context 'description with group labels' do
let(:old_group) { create(:group) } let(:old_group) { create(:group) }
let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) } let!(:group_label) { create(:group_label, id: 321, name: 'group label', group: old_group) }
let(:project_ref) { old_project.to_reference } let(:project_ref) { old_project.to_reference(new_project) }
before do before do
old_project.update(namespace: old_group) old_project.update(namespace: old_group)
......
...@@ -45,7 +45,7 @@ describe CommitRange, models: true do ...@@ -45,7 +45,7 @@ describe CommitRange, models: true do
end end
describe '#to_reference' do describe '#to_reference' do
let(:cross) { create(:project) } let(:cross) { create(:empty_project, namespace: project.namespace) }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}" expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}"
...@@ -56,12 +56,12 @@ describe CommitRange, models: true do ...@@ -56,12 +56,12 @@ describe CommitRange, models: true do
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}" expect(range.to_reference(cross)).to eq "#{project.path}@#{full_sha_from}...#{full_sha_to}"
end end
end end
describe '#reference_link_text' do describe '#reference_link_text' do
let(:cross) { create(:project) } let(:cross) { create(:empty_project, namespace: project.namespace) }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}" expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}"
...@@ -72,7 +72,7 @@ describe CommitRange, models: true do ...@@ -72,7 +72,7 @@ describe CommitRange, models: true do
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}" expect(range.reference_link_text(cross)).to eq "#{project.path}@#{sha_from}...#{sha_to}"
end end
end end
......
...@@ -34,24 +34,30 @@ describe Commit, models: true do ...@@ -34,24 +34,30 @@ describe Commit, models: true do
end end
describe '#to_reference' do describe '#to_reference' do
let(:project) { create(:project, path: 'sample-project') }
let(:commit) { project.commit }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(commit.to_reference).to eq commit.id expect(commit.to_reference).to eq commit.id
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
cross = double('project') another_project = build(:project, name: 'another-project', namespace: project.namespace)
expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}" expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}"
end end
end end
describe '#reference_link_text' do describe '#reference_link_text' do
let(:project) { create(:project, path: 'sample-project') }
let(:commit) { project.commit }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(commit.reference_link_text).to eq commit.short_id expect(commit.reference_link_text).to eq commit.short_id
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
cross = double('project') another_project = build(:project, name: 'another-project', namespace: project.namespace)
expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}" expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}"
end end
end end
......
...@@ -37,6 +37,16 @@ describe GroupLabel, models: true do ...@@ -37,6 +37,16 @@ describe GroupLabel, models: true do
end end
end end
context 'cross-project' do
let(:namespace) { build_stubbed(:namespace) }
let(:source_project) { build_stubbed(:empty_project, name: 'project-1', namespace: namespace) }
let(:target_project) { build_stubbed(:empty_project, name: 'project-2', namespace: namespace) }
it 'returns a String reference to the object' do
expect(label.to_reference(source_project, target_project)).to eq %(project-1~#{label.id})
end
end
context 'using invalid format' do context 'using invalid format' do
it 'raises error' do it 'raises error' do
expect { label.to_reference(format: :invalid) } expect { label.to_reference(format: :invalid) }
......
...@@ -43,14 +43,16 @@ describe Issue, models: true do ...@@ -43,14 +43,16 @@ describe Issue, models: true do
end end
describe '#to_reference' do describe '#to_reference' do
let(:project) { build(:empty_project, name: 'sample-project') }
let(:issue) { build(:issue, iid: 1, project: project) }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "##{subject.iid}" expect(issue.to_reference).to eq "#1"
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
cross = double('project') another_project = build(:project, name: 'another-project', namespace: project.namespace)
expect(subject.to_reference(cross)). expect(issue.to_reference(another_project)).to eq "sample-project#1"
to eq "#{subject.project.to_reference}##{subject.iid}"
end end
end end
......
...@@ -142,13 +142,16 @@ describe MergeRequest, models: true do ...@@ -142,13 +142,16 @@ describe MergeRequest, models: true do
end end
describe '#to_reference' do describe '#to_reference' do
let(:project) { build(:empty_project, name: 'sample-project') }
let(:merge_request) { build(:merge_request, target_project: project, iid: 1) }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "!#{subject.iid}" expect(merge_request.to_reference).to eq "!1"
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
cross = double('project') another_project = build(:project, name: 'another-project', namespace: project.namespace)
expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}" expect(merge_request.to_reference(another_project)).to eq "sample-project!1"
end end
end end
......
...@@ -246,4 +246,18 @@ describe Milestone, models: true do ...@@ -246,4 +246,18 @@ describe Milestone, models: true do
end end
end end
end end
describe '#to_reference' do
let(:project) { build(:empty_project, name: 'sample-project') }
let(:milestone) { build(:milestone, iid: 1, project: project) }
it 'returns a String reference to the object' do
expect(milestone.to_reference).to eq "%1"
end
it 'supports a cross-project reference' do
another_project = build(:project, name: 'another-project', namespace: project.namespace)
expect(milestone.to_reference(another_project)).to eq "sample-project%1"
end
end
end end
...@@ -105,14 +105,14 @@ describe ProjectLabel, models: true do ...@@ -105,14 +105,14 @@ describe ProjectLabel, models: true do
context 'using name' do context 'using name' do
it 'returns cross reference with label name' do it 'returns cross reference with label name' do
expect(label.to_reference(project, format: :name)) expect(label.to_reference(project, format: :name))
.to eq %Q(#{label.project.to_reference}~"#{label.name}") .to eq %Q(#{label.project.path_with_namespace}~"#{label.name}")
end end
end end
context 'using id' do context 'using id' do
it 'returns cross reference with label id' do it 'returns cross reference with label id' do
expect(label.to_reference(project, format: :id)) expect(label.to_reference(project, format: :id))
.to eq %Q(#{label.project.to_reference}~#{label.id}) .to eq %Q(#{label.project.path_with_namespace}~#{label.id})
end end
end end
end end
......
...@@ -258,10 +258,70 @@ describe Project, models: true do ...@@ -258,10 +258,70 @@ describe Project, models: true do
end end
describe '#to_reference' do describe '#to_reference' do
let(:project) { create(:empty_project) } let(:owner) { create(:user, name: 'Gitlab') }
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
let(:project) { create(:empty_project, path: 'sample-project', namespace: namespace) }
context 'when nil argument' do
it 'returns nil' do
expect(project.to_reference).to be_nil
end
end
context 'when same project argument' do
it 'returns nil' do
expect(project.to_reference(project)).to be_nil
end
end
it 'returns a String reference to the object' do context 'when cross namespace project argument' do
expect(project.to_reference).to eq project.path_with_namespace let(:another_namespace_project) { create(:empty_project, name: 'another-project') }
it 'returns complete path to the project' do
expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project'
end
end
context 'when same namespace / cross-project argument' do
let(:another_project) { create(:empty_project, namespace: namespace) }
it 'returns complete path to the project' do
expect(project.to_reference(another_project)).to eq 'sample-project'
end
end
end
describe '#to_human_reference' do
let(:owner) { create(:user, name: 'Gitlab') }
let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) }
let(:project) { create(:empty_project, name: 'Sample project', namespace: namespace) }
context 'when nil argument' do
it 'returns nil' do
expect(project.to_human_reference).to be_nil
end
end
context 'when same project argument' do
it 'returns nil' do
expect(project.to_human_reference(project)).to be_nil
end
end
context 'when cross namespace project argument' do
let(:another_namespace_project) { create(:empty_project, name: 'another-project') }
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
context 'when same namespace / cross-project argument' do
let(:another_project) { create(:empty_project, namespace: namespace) }
it 'returns name of the project' do
expect(project.to_human_reference(another_project)).to eq 'Sample project'
end
end end
end end
......
...@@ -33,16 +33,31 @@ describe Snippet, models: true do ...@@ -33,16 +33,31 @@ describe Snippet, models: true do
end end
describe '#to_reference' do describe '#to_reference' do
let(:project) { create(:empty_project) } context 'when snippet belongs to a project' do
let(:snippet) { create(:snippet, project: project) } let(:project) { build(:empty_project, name: 'sample-project') }
let(:snippet) { build(:snippet, id: 1, project: project) }
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(snippet.to_reference).to eq "$#{snippet.id}" expect(snippet.to_reference).to eq "$1"
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
cross = double('project') another_project = build(:project, name: 'another-project', namespace: project.namespace)
expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}" expect(snippet.to_reference(another_project)).to eq "sample-project$1"
end
end
context 'when snippet does not belong to a project' do
let(:snippet) { build(:snippet, id: 1, project: nil) }
it 'returns a String reference to the object' do
expect(snippet.to_reference).to eq "$1"
end
it 'still returns shortest reference when project arg present' do
another_project = build(:project, name: 'another-project')
expect(snippet.to_reference(another_project)).to eq "$1"
end
end end
end end
......
...@@ -189,7 +189,7 @@ describe Issues::MoveService, services: true do ...@@ -189,7 +189,7 @@ describe Issues::MoveService, services: true do
it 'rewrites references using a cross reference to old project' do it 'rewrites references using a cross reference to old project' do
expect(new_note.note) expect(new_note.note)
.to eq "Note with reference to merge request #{old_project.to_reference}!1" .to eq "Note with reference to merge request #{old_project.to_reference(new_project)}!1"
end end
end end
...@@ -217,7 +217,7 @@ describe Issues::MoveService, services: true do ...@@ -217,7 +217,7 @@ describe Issues::MoveService, services: true do
it 'rewrites referenced issues creating cross project reference' do it 'rewrites referenced issues creating cross project reference' do
expect(new_issue.description) expect(new_issue.description)
.to eq "Some description #{old_project.to_reference}#{another_issue.to_reference}" .to eq "Some description #{another_issue.to_reference(new_project)}"
end end
end end
......
...@@ -530,7 +530,7 @@ describe SystemNoteService, services: true do ...@@ -530,7 +530,7 @@ describe SystemNoteService, services: true do
end end
it 'mentions referenced project' do it 'mentions referenced project' do
expect(subject.note).to include new_project.to_reference expect(subject.note).to include new_project.path_with_namespace
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