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,21 +90,24 @@ def to_s ...@@ -90,21 +90,24 @@ 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)
reference = ref_from + notation + ref_to project_reference = project.to_reference(from_project)
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
else
reference
end end
reference
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)
......
...@@ -144,9 +144,10 @@ def title=(value) ...@@ -144,9 +144,10 @@ def title=(value)
# #
# Examples: # Examples:
# #
# 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
......
...@@ -113,19 +113,16 @@ def self.upcoming_ids_by_projects(projects) ...@@ -113,19 +113,16 @@ def self.upcoming_ids_by_projects(projects)
# #
# Examples: # Examples:
# #
# 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)
path_with_namespace if cross_namespace_reference?(from_project)
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,11 +67,11 @@ def self.link_reference_pattern ...@@ -67,11 +67,11 @@ 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}"
else
reference
end end
reference
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