diff --git a/app/models/commit.rb b/app/models/commit.rb index 085f4e6398f97803842a7ae57d3f3d6f7b3e44a2..2c244fc0410f959c409535bc9b14318ca529a8b7 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -62,6 +62,19 @@ class Commit (self.class === other) && (raw == other.raw) end + def self.reference_prefix + '@' + end + + # Pattern used to extract commit references from text + # + # The SHA can be between 6 and 40 hex characters. + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{(?:#{Project.reference_pattern}#{reference_prefix})?(?<commit>\h{6,40})} + end + def to_reference(from_project = nil) if cross_project_reference?(from_project) "#{project.to_reference}@#{id}" diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index fb1f6d09be6817d750f4089bf079fceadcb07bd1..86fc9eb01a3e0077ce1a20a289c1a37474c109db 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -29,10 +29,24 @@ class CommitRange # See `exclude_start?` attr_reader :exclude_start - # The beginning and ending SHA sums can be between 6 and 40 hex characters, - # and the range selection can be double- or triple-dot. + # The beginning and ending SHAs can be between 6 and 40 hex characters, and + # the range notation can be double- or triple-dot. PATTERN = /\h{6,40}\.{2,3}\h{6,40}/ + def self.reference_prefix + '@' + end + + # Pattern used to extract commit range references from text + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{ + (?:#{Project.reference_pattern}#{reference_prefix})? + (?<commit_range>#{PATTERN}) + }x + end + # Initialize a CommitRange # # range_string - The String commit range. diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index b41df301c3f4f7d8d9d7c54e4505065820905e01..e3c1c6d268efe483a2cc2ce1c86d008d60704ab5 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -35,6 +35,16 @@ module Referable def reference_prefix '' end + + # Regexp pattern used to match references to this object + # + # This must be overridden by the including class. + # + # Returns Regexp + def reference_pattern + raise NotImplementedError, + %Q{#{self} does not implement "reference_pattern"} + end end private diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index 6fda4a2ab775e35ec414b4e0d7229d0450acab10..49f6c95e04586bf2e2d82a8861ed83ed72e9f43b 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -9,10 +9,6 @@ class ExternalIssue @issue_identifier.to_s end - def to_reference(_from_project = nil) - id - end - def id @issue_identifier.to_s end @@ -32,4 +28,13 @@ class ExternalIssue def project @project end + + # Pattern used to extract `JIRA-123` issue references from text + def self.reference_pattern + %r{(?<issue>([A-Z\-]+-)\d+)} + end + + def to_reference(_from_project = nil) + id + end end diff --git a/app/models/group.rb b/app/models/group.rb index 33d72e0d9ee2654604b7102be1eee8d84ee0bbc2..b4e908c5602d23b577ee4ac1c9362627c4f06b70 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -40,7 +40,11 @@ class Group < Namespace end def reference_prefix - '@' + User.reference_prefix + end + + def reference_pattern + User.reference_pattern end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 31803b57b3f31058cd7a60ee240bc2afd79c9a0b..ea6b9329b07431592de768a2c9a916fb4f62ada6 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -50,12 +50,22 @@ class Issue < ActiveRecord::Base state :closed end + def hook_attrs + attributes + end + def self.reference_prefix '#' end - def hook_attrs - attributes + # Pattern used to extract `#123` issue references from text + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{ + #{Project.reference_pattern}? + #{Regexp.escape(reference_prefix)}(?<issue>\d+) + }x end def to_reference(from_project = nil) diff --git a/app/models/label.rb b/app/models/label.rb index 013e6bf5978aa62bae4cde92cd281e6c0f9d671b..8980049cef88f1cf699714cb2938c4c693ec244b 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -40,6 +40,22 @@ class Label < ActiveRecord::Base '~' end + # Pattern used to extract label references from text + # + # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad). + def self.reference_pattern + %r{ + #{reference_prefix} + (?: + (?<label_id>\d+) | # Integer-based label ID, or + (?<label_name> + [A-Za-z0-9_-]+ | # String-based single-word label title + ['"][^&\?,]+['"] # String-based multi-word label surrounded in quotes + ) + ) + }x + end + # Returns the String necessary to reference this Label in Markdown # # format - Symbol format to use (default: :id, optional: :name) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 60b0ce6c018166c9d8372f3598b1839e1c35a34f..6c90d09b8662adfa83481393384c455aa9a14b92 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -140,6 +140,16 @@ class MergeRequest < ActiveRecord::Base '!' end + # Pattern used to extract `!123` merge request references from text + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{ + #{Project.reference_pattern}? + #{Regexp.escape(reference_prefix)}(?<merge_request>\d+) + }x + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" diff --git a/app/models/project.rb b/app/models/project.rb index c943114449aa051a21c076a2056809f8e9a8cbbe..3c9f0dad28bc550dc152cc524c273657bbbd3ffe 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -248,6 +248,11 @@ class Project < ActiveRecord::Base order_by(method) end end + + def reference_pattern + name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR + %r{(?<project>#{name_pattern}/#{name_pattern})} + end end def team diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 8c3167833aaeb6314f95f7b858c9ac240f300fed..d1619071f4908f6defa8845c7fe063ee0de2693b 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -56,6 +56,16 @@ class Snippet < ActiveRecord::Base '$' end + # Pattern used to extract `$123` snippet references from text + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{ + #{Project.reference_pattern}? + #{Regexp.escape(reference_prefix)}(?<snippet>\d+) + }x + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{id}" diff --git a/app/models/user.rb b/app/models/user.rb index f546dc015c2c2db7c95c8e34f201c56977f9b648..50ca4bc5acc5941eb5fc914fc94cf39c4cb5f2fb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -253,6 +253,14 @@ class User < ActiveRecord::Base def reference_prefix '@' end + + # Pattern used to extract `@user` user references from text + def reference_pattern + %r{ + #{Regexp.escape(reference_prefix)} + (?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR}) + }x + end end # diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb index 8764f7e474f4111730b227172c6b06f69c0aea29..61591a9914bdb66db9962c4fa6c1a85c9a828b2b 100644 --- a/lib/gitlab/markdown/commit_range_reference_filter.rb +++ b/lib/gitlab/markdown/commit_range_reference_filter.rb @@ -19,7 +19,7 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(COMMIT_RANGE_PATTERN) do |match| + text.gsub(CommitRange.reference_pattern) do |match| yield match, $~[:commit_range], $~[:project] end end @@ -30,13 +30,8 @@ module Gitlab @commit_map = {} end - # Pattern used to extract commit range references from text - # - # This pattern supports cross-project references. - COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit_range>#{CommitRange::PATTERN})/ - def call - replace_text_nodes_matching(COMMIT_RANGE_PATTERN) do |content| + replace_text_nodes_matching(CommitRange.reference_pattern) do |content| commit_range_link_filter(content) end end diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb index b20b29f5d0ca07b7736242cf8659d926c9c16b00..f6932e76e7023eb35cfcd78ca45ba49de25f2239 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -19,20 +19,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(COMMIT_PATTERN) do |match| + text.gsub(Commit.reference_pattern) do |match| yield match, $~[:commit], $~[:project] end end - # Pattern used to extract commit references from text - # - # The SHA1 sum can be between 6 and 40 hex characters. - # - # This pattern supports cross-project references. - COMMIT_PATTERN = /(#{PROJECT_PATTERN}@)?(?<commit>\h{6,40})/ - def call - replace_text_nodes_matching(COMMIT_PATTERN) do |content| + replace_text_nodes_matching(Commit.reference_pattern) do |content| commit_link_filter(content) end end diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb index c436fabd658397e1e786655bb64636044b43f604..66c256c5104ccd90d3707874aaf856da1d8eb932 100644 --- a/lib/gitlab/markdown/cross_project_reference.rb +++ b/lib/gitlab/markdown/cross_project_reference.rb @@ -3,9 +3,6 @@ module Gitlab # Common methods for ReferenceFilters that support an optional cross-project # reference. module CrossProjectReference - NAMING_PATTERN = Gitlab::Regex::NAMESPACE_REGEX_STR - PROJECT_PATTERN = "(?<project>#{NAMING_PATTERN}/#{NAMING_PATTERN})" - # Given a cross-project reference string, get the Project record # # Defaults to value of `context[:project]` if: diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb index 0fc3f4cca06915bf835548bf6b1789d53ff39b1f..2e74c6e45e21e4c52ea9720e86c8ceed53da1494 100644 --- a/lib/gitlab/markdown/external_issue_reference_filter.rb +++ b/lib/gitlab/markdown/external_issue_reference_filter.rb @@ -16,19 +16,16 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(ISSUE_PATTERN) do |match| + text.gsub(ExternalIssue.reference_pattern) do |match| yield match, $~[:issue] end end - # Pattern used to extract `JIRA-123` issue references from text - ISSUE_PATTERN = /(?<issue>([A-Z\-]+-)\d+)/ - def call # Early return if the project isn't using an external tracker return doc if project.nil? || project.default_issues_tracker? - replace_text_nodes_matching(ISSUE_PATTERN) do |content| + replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content| issue_link_filter(content) end end diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb index 1e885615163092b9756417e544bad8af59ee8ea5..2815626e2476e5e0ff9418c71f4ffa92373058fe 100644 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ b/lib/gitlab/markdown/issue_reference_filter.rb @@ -20,18 +20,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(ISSUE_PATTERN) do |match| + text.gsub(Issue.reference_pattern) do |match| yield match, $~[:issue].to_i, $~[:project] end end - # Pattern used to extract `#123` issue references from text - # - # This pattern supports cross-project references. - ISSUE_PATTERN = /#{PROJECT_PATTERN}?\#(?<issue>([a-zA-Z\-]+-)?\d+)/ - def call - replace_text_nodes_matching(ISSUE_PATTERN) do |content| + replace_text_nodes_matching(Issue.reference_pattern) do |content| issue_link_filter(content) end end diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 1a77becee89f3ecaf4c1107c419406eafbc389e8..9f8c85b70127e3f5998c41cfc59502955060d3ad 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -15,26 +15,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(LABEL_PATTERN) do |match| + text.gsub(Label.reference_pattern) do |match| yield match, $~[:label_id].to_i, $~[:label_name] end end - # Pattern used to extract label references from text - # - # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad). - LABEL_PATTERN = %r{ - ~( - (?<label_id>\d+) | # Integer-based label ID, or - (?<label_name> - [A-Za-z0-9_-]+ | # String-based single-word label title - ['"][^&\?,]+['"] # String-based multi-word label surrounded in quotes - ) - ) - }x - def call - replace_text_nodes_matching(LABEL_PATTERN) do |content| + replace_text_nodes_matching(Label.reference_pattern) do |content| label_link_filter(content) end end diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 740d72abb3699025c6cdaf25fbb01c5764784d60..fddc050635f4abbc9ce7a2ba517d3dafa7027e2d 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -20,18 +20,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(MERGE_REQUEST_PATTERN) do |match| + text.gsub(MergeRequest.reference_pattern) do |match| yield match, $~[:merge_request].to_i, $~[:project] end end - # Pattern used to extract `!123` merge request references from text - # - # This pattern supports cross-project references. - MERGE_REQUEST_PATTERN = /#{PROJECT_PATTERN}?!(?<merge_request>\d+)/ - def call - replace_text_nodes_matching(MERGE_REQUEST_PATTERN) do |content| + replace_text_nodes_matching(MergeRequest.reference_pattern) do |content| merge_request_link_filter(content) end end diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb index 64a0a2696f7587867fc4d06024695d64223d252e..f22f08de27c1985a4a6986df0e2da72eb83dc70b 100644 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ b/lib/gitlab/markdown/snippet_reference_filter.rb @@ -20,18 +20,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(SNIPPET_PATTERN) do |match| + text.gsub(Snippet.reference_pattern) do |match| yield match, $~[:snippet].to_i, $~[:project] end end - # Pattern used to extract `$123` snippet references from text - # - # This pattern supports cross-project references. - SNIPPET_PATTERN = /#{PROJECT_PATTERN}?\$(?<snippet>\d+)/ - def call - replace_text_nodes_matching(SNIPPET_PATTERN) do |content| + replace_text_nodes_matching(Snippet.reference_pattern) do |content| snippet_link_filter(content) end end diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb index 28ec041b1d49735855f5380853474a393e6693af..ca7fd7b03389cff3a7164a9e40782b28e56ac635 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -16,16 +16,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(USER_PATTERN) do |match| + text.gsub(User.reference_pattern) do |match| yield match, $~[:user] end end - # Pattern used to extract `@user` user references from text - USER_PATTERN = /@(?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})/ - def call - replace_text_nodes_matching(USER_PATTERN) do |content| + replace_text_nodes_matching(User.reference_pattern) do |content| user_link_filter(content) end end