Read about our upcoming Code of Conduct on this issue

Commit f272ee6e authored by Oswaldo Ferreira's avatar Oswaldo Ferreira
Browse files

Add shorthand support to gitlab markdown references

parent 7706b77848d1
...@@ -174,7 +174,7 @@ def truncate_if_block(node, truncated) ...@@ -174,7 +174,7 @@ def truncate_if_block(node, truncated)
# 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 @@ def render_colored_label(label, label_suffix = '', tooltip: true) ...@@ -82,12 +82,6 @@ def render_colored_label(label, label_suffix = '', tooltip: true)
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 @@ def label_deletion_confirm_text(label) ...@@ -166,6 +160,5 @@ def label_deletion_confirm_text(label)
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 @@ def self.link_reference_pattern ...@@ -92,19 +92,11 @@ def self.link_reference_pattern
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 @@ def diffs(diff_options = nil) ...@@ -329,6 +321,16 @@ def diffs(diff_options = nil)
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 @@ def to_s ...@@ -90,22 +90,25 @@ def to_s
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 @@ def link_reference_pattern(route, pattern) ...@@ -72,17 +72,4 @@ def link_reference_pattern(route, pattern)
}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 @@ def self.sort(method, excluded_labels: []) ...@@ -153,11 +153,7 @@ def self.sort(method, excluded_labels: [])
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 @@ def title=(value) ...@@ -146,7 +146,8 @@ def title=(value)
# #
# 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 @@ def to_reference(source_project = nil, target_project = nil, format: :id) ...@@ -154,8 +155,8 @@ def to_reference(source_project = nil, target_project = nil, format: :id)
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 @@ def as_json(options = {}) ...@@ -169,10 +170,6 @@ def as_json(options = {})
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 @@ def self.wip_title(title) ...@@ -176,11 +176,7 @@ def self.wip_title(title)
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 @@ def self.upcoming_ids_by_projects(projects) ...@@ -115,17 +115,14 @@ def self.upcoming_ids_by_projects(projects)
# #
# 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 @@ def sort(method) ...@@ -419,7 +419,11 @@ def sort(method)
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 @@ def to_param ...@@ -650,8 +654,20 @@ def to_param
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 @@ def environments_recently_updated_on_branch(branch) ...@@ -1327,10 +1343,21 @@ def environments_recently_updated_on_branch(branch)
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 @@ def self.link_reference_pattern ...@@ -67,12 +67,12 @@ def self.link_reference_pattern
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
...@@ -33,7 +33,7 @@ def self.object_sym ...@@ -33,7 +33,7 @@ def self.object_sym
# 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 @@ def call ...@@ -145,8 +145,9 @@ def call
# 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 @@ def references_per_project ...@@ -217,10 +218,9 @@ def references_per_project
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 @@ def current_project_path ...@@ -272,8 +272,19 @@ def current_project_path
@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 @@ def self.object_class ...@@ -12,7 +12,7 @@ def self.object_class
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 @@ def self.object_class ...@@ -12,7 +12,7 @@ def self.object_class
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 @@ def find_object(project, id) ...@@ -14,16 +14,18 @@ def find_object(project, id)
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 @@ def url_for_object(label, project) ...@@ -64,48 +66,12 @@ def url_for_object(label, project)
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 @@ def references_in(text, pattern = Milestone.reference_pattern) ...@@ -19,18 +19,20 @@ def references_in(text, pattern = Milestone.reference_pattern)
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)