Commit 701c2e9a authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'rs-to_reference' into 'master'

Add to_reference method to referable models

Now there is a single source of information for which attribute a model uses to be referenced, and its special character.

See merge request !641
parents 3ed05b21 3cb6a338
......@@ -135,15 +135,25 @@ module GitlabMarkdownHelper
end
end
# Returns the text necessary to reference `entity` across projects
#
# project - Project to reference
# entity - Object that responds to `to_reference`
#
# Examples:
#
# cross_project_reference(project, project.issues.first)
# # => 'namespace1/project1#123'
#
# cross_project_reference(project, project.merge_requests.first)
# # => 'namespace1/project1!345'
#
# Returns a String
def cross_project_reference(project, entity)
path = project.path_with_namespace
if entity.kind_of?(Issue)
[path, entity.iid].join('#')
elsif entity.kind_of?(MergeRequest)
[path, entity.iid].join('!')
if entity.respond_to?(:to_reference)
"#{project.to_reference}#{entity.to_reference}"
else
raise 'Not supported type'
''
end
end
end
class Commit
include ActiveModel::Conversion
include StaticModel
extend ActiveModel::Naming
include ActiveModel::Conversion
include Mentionable
include Participable
include Referable
include StaticModel
attr_mentionable :safe_message
participant :author, :committer, :notes, :mentioned_users
......@@ -56,6 +58,34 @@ class Commit
@raw.id
end
def ==(other)
(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})
}x
end
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
"#{project.to_reference}@#{id}"
else
id
end
end
def diff_line_count
@diff_line_count ||= Commit::diff_line_count(self.diffs)
@diff_line_count
......@@ -126,11 +156,6 @@ class Commit
Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message)
end
# Mentionable override.
def gfm_reference
"commit #{id}"
end
def author
User.find_for_commit(author_email, author_name)
end
......
......@@ -19,6 +19,7 @@
#
class CommitRange
include ActiveModel::Conversion
include Referable
attr_reader :sha_from, :notation, :sha_to
......@@ -28,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.
......@@ -59,6 +74,17 @@ class CommitRange
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
end
def to_reference(from_project = nil)
# Not using to_s because we want the full SHAs
reference = sha_from + notation + sha_to
if cross_project_reference?(from_project)
reference = project.to_reference + '@' + reference
end
reference
end
# Returns a String for use in a link's title attribute
def reference_title
"Commits #{suffixed_sha_from} through #{sha_to}"
......
......@@ -20,10 +20,15 @@ module Mentionable
end
end
# Generate a GFM back-reference that will construct a link back to this Mentionable when rendered. Must
# be overridden if this model object can be referenced directly by GFM notation.
def gfm_reference
raise NotImplementedError.new("#{self.class} does not implement #gfm_reference")
# Returns the text used as the body of a Note when this object is referenced
#
# By default this will be the class name and the result of calling
# `to_reference` on the object.
def gfm_reference(from_project = nil)
# "MergeRequest" > "merge_request" > "Merge request" > "merge request"
friendly_name = self.class.to_s.underscore.humanize.downcase
"#{friendly_name} #{to_reference(from_project)}"
end
# Construct a String that contains possible GFM references.
......
# == Referable concern
#
# Contains functionality related to making a model referable in Markdown, such
# as "#1", "!2", "~3", etc.
module Referable
extend ActiveSupport::Concern
# Returns the String necessary to reference this object in Markdown
#
# from_project - Refering Project object
#
# This should be overridden by the including class.
#
# Examples:
#
# Issue.first.to_reference # => "#1"
# Issue.last.to_reference(other_project) # => "cross-project#1"
#
# Returns a String
def to_reference(_from_project = nil)
''
end
module ClassMethods
# The character that prefixes the actual reference identifier
#
# This should be overridden by the including class.
#
# Examples:
#
# Issue.reference_prefix # => '#'
# MergeRequest.reference_prefix # => '!'
#
# Returns a String
def reference_prefix
''
end
# Regexp pattern used to match references to this object
#
# This must be overridden by the including class.
#
# Returns a Regexp
def reference_pattern
raise NotImplementedError, "#{self} does not implement #{__method__}"
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
class ExternalIssue
include Referable
def initialize(issue_identifier, project)
@issue_identifier, @project = issue_identifier, project
end
......@@ -26,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
......@@ -17,6 +17,8 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Group < Namespace
include Referable
has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember'
has_many :users, through: :group_members
......@@ -36,6 +38,18 @@ class Group < Namespace
def sort(method)
order_by(method)
end
def reference_prefix
User.reference_prefix
end
def reference_pattern
User.reference_pattern
end
end
def to_reference(_from_project = nil)
"#{self.class.reference_prefix}#{name}"
end
def human_name
......
......@@ -21,10 +21,11 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Issue < ActiveRecord::Base
include Issuable
include InternalId
include Taskable
include Issuable
include Referable
include Sortable
include Taskable
ActsAsTaggableOn.strict_case_match = true
......@@ -53,10 +54,28 @@ class Issue < ActiveRecord::Base
attributes
end
# Mentionable overrides.
def self.reference_prefix
'#'
end
# 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)
reference = "#{self.class.reference_prefix}#{iid}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
end
def gfm_reference
"issue ##{iid}"
reference
end
# Reset issue events cache
......
......@@ -11,6 +11,8 @@
#
class Label < ActiveRecord::Base
include Referable
DEFAULT_COLOR = '#428BCA'
default_value_for :color, DEFAULT_COLOR
......@@ -34,6 +36,45 @@ class Label < ActiveRecord::Base
alias_attribute :name, :title
def self.reference_prefix
'~'
end
# Pattern used to extract label references from text
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, or
"[^&\?,]+" # 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)
#
# Note that its argument differs from other objects implementing Referable. If
# a non-Symbol argument is given (such as a Project), it will default to :id.
#
# Examples:
#
# Label.first.to_reference # => "~1"
# Label.first.to_reference(:name) # => "~\"bug\""
#
# Returns a String
def to_reference(format = :id)
if format == :name && !name.include?('"')
%(#{self.class.reference_prefix}"#{name}")
else
"#{self.class.reference_prefix}#{id}"
end
end
def open_issues_count
issues.opened.count
end
......
......@@ -25,10 +25,11 @@ require Rails.root.join("app/models/commit")
require Rails.root.join("lib/static_model")
class MergeRequest < ActiveRecord::Base
include Issuable
include Taskable
include InternalId
include Issuable
include Referable
include Sortable
include Taskable
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
......@@ -135,6 +136,30 @@ class MergeRequest < ActiveRecord::Base
scope :closed, -> { with_states(:closed, :merged) }
scope :declined, -> { with_states(:closed) }
def self.reference_prefix
'!'
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}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
end
reference
end
def validate_branches
if target_project == source_project && target_branch == source_branch
errors.add :branch_conflict, "You can not use same project/branch for source and target"
......@@ -289,11 +314,6 @@ class MergeRequest < ActiveRecord::Base
end
end
# Mentionable override.
def gfm_reference
"merge request !#{iid}"
end
def target_project_path
if target_project
target_project.path_with_namespace
......
......@@ -326,8 +326,8 @@ class Note < ActiveRecord::Base
end
# Mentionable override.
def gfm_reference
noteable.gfm_reference
def gfm_reference(from_project = nil)
noteable.gfm_reference(from_project)
end
# Mentionable override.
......
......@@ -33,11 +33,12 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class Project < ActiveRecord::Base
include Sortable
include Gitlab::ConfigHelper
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
include Gitlab::ConfigHelper
include Rails.application.routes.url_helpers
include Referable
include Sortable
extend Gitlab::ConfigHelper
extend Enumerize
......@@ -247,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
......@@ -305,6 +311,10 @@ class Project < ActiveRecord::Base
path
end
def to_reference(_from_project = nil)
path_with_namespace
end
def web_url
[gitlab_config.url, path_with_namespace].join('/')
end
......
......@@ -16,14 +16,16 @@
#
class Snippet < ActiveRecord::Base
include Sortable
include Linguist::BlobHelper
include Gitlab::VisibilityLevel
include Linguist::BlobHelper
include Participable
include Referable
include Sortable
default_value_for :visibility_level, Snippet::PRIVATE
belongs_to :author, class_name: "User"
belongs_to :author, class_name: 'User'
belongs_to :project
has_many :notes, as: :noteable, dependent: :destroy
......@@ -50,6 +52,30 @@ class Snippet < ActiveRecord::Base
participant :author, :notes
def self.reference_prefix
'$'
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}"
if cross_project_reference?(from_project)
reference = project.to_reference + reference
end
reference
end
def self.content_types
[
".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java",
......
......@@ -62,11 +62,13 @@ require 'carrierwave/orm/activerecord'
require 'file_size_validator'
class User < ActiveRecord::Base
include Sortable
include Gitlab::ConfigHelper
include TokenAuthenticatable
extend Gitlab::ConfigHelper
include Gitlab::ConfigHelper
include Gitlab::CurrentSettings
include Referable
include Sortable
include TokenAuthenticatable
default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group
......@@ -247,6 +249,18 @@ class User < ActiveRecord::Base
def build_user(attrs = {})
User.new(attrs)
end
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
#
......@@ -257,6 +271,10 @@ class User < ActiveRecord::Base
username
end
def to_reference(_from_project = nil)
"#{self.class.reference_prefix}#{username}"
end
def notification
@notification ||= Notification.new(self)
end
......
......@@ -10,7 +10,7 @@ class SystemNoteService
# author - User performing the change
# new_commits - Array of Commits added since last push
# existing_commits - Array of Commits added in a previous push
# oldrev - TODO (rspeicher): I have no idea what this actually does
# oldrev - Optional String SHA of a previous Commit
#
# See new_commit_summary and existing_commit_summary.
#
......@@ -157,11 +157,11 @@ class SystemNoteService
#
# Example Note text:
#
# "Mentioned in #1"
# "mentioned in #1"
#
# "Mentioned in !2"
# "mentioned in !2"
#
# "Mentioned in 54f7727c"
# "mentioned in 54f7727c"
#
# See cross_reference_note_content.
#
......@@ -169,7 +169,7 @@ class SystemNoteService
def self.cross_reference(noteable, mentioner, author)
return if cross_reference_disallowed?(noteable, mentioner)
gfm_reference = mentioner_gfm_ref(noteable, mentioner)
gfm_reference = mentioner.gfm_reference(noteable.project)
note_options = {
project: noteable.project,
......@@ -200,12 +200,21 @@ class SystemNoteService
#
# Returns Boolean
def self.cross_reference_disallowed?(noteable, mentioner)
return false unless MergeRequest === mentioner
return false unless Commit === noteable
return false unless mentioner.is_a?(MergeRequest)
return false unless noteable.is_a?(Commit)
mentioner.commits.include?(noteable)
end
# Check if a cross reference to a noteable from a mentioner already exists
#
# This method is used to prevent multiple notes being created for a mention
# when a issue is updated, for example.
#
# noteable - Noteable object being referenced
# mentioner - Mentionable object
#
# Returns Boolean
def self.cross_reference_exists?(noteable, mentioner)
# Initial scope should be system notes of this noteable type
notes = Note.system.where(noteable_type: noteable.class)
......@@ -217,7 +226,7 @@ class SystemNoteService
notes = notes.where(noteable_id: noteable.id)
end
gfm_reference = mentioner_gfm_ref(noteable, mentioner, true)
gfm_reference = mentioner.gfm_reference(noteable.project)
notes = notes.where(note: cross_reference_note_content(gfm_reference))
notes.count > 0
......@@ -229,39 +238,6 @@ class SystemNoteService
Note.create(args.merge(system: true))
end
# Prepend the mentioner's namespaced project path to the GFM reference for
# cross-project references. For same-project references, return the
# unmodified GFM reference.
def self.mentioner_gfm_ref(noteable, mentioner, cross_reference = false)
# FIXME (rspeicher): This was breaking things.
# if mentioner.is_a?(Commit) && cross_reference
# return mentioner.gfm_reference.sub('commit ', 'commit %')
# end
full_gfm_reference(mentioner.project, noteable.project, mentioner)
end
# Return the +mentioner+ GFM reference. If the mentioner and noteable
# projects are not the same, add the mentioning project's path to the
# returned value.
def self.full_gfm_reference(mentioning_project, noteable_project, mentioner)
if mentioning_project == noteable_project
mentioner.gfm_reference
else
if mentioner.is_a?(Commit)
mentioner.gfm_reference.sub(
/(commit )/,
"\\1#{mentioning_project.path_with_namespace}@"
)
else
mentioner.gfm_reference.sub(
/(issue |merge request )/,
"\\1#{mentioning_project.path_with_namespace}"
)
end
end
end
def self.cross_reference_note_prefix
'mentioned in '
end
......@@ -286,7 +262,7 @@ class SystemNoteService
#
# noteable - MergeRequest object
# existing_commits - Array of existing Commit objects
# oldrev - Optional String SHA of ... TODO (rspeicher): I have no idea what this actually does.
# oldrev - Optional String SHA of a previous Commit
#
# Examples:
#
......
......@@ -29,7 +29,7 @@
- else
= f.submit 'Save', class: "btn-save btn"
- if @snippet.respond_to?(:project)
- if @snippet.project_id
= link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel"
- else
= link_to "Cancel", snippets_path(@project), class: "btn btn-cancel"
......
......@@ -8,7 +8,7 @@ module Gitlab
def closed_by_message(message)
return [] if message.nil?
closing_statements = message.scan(ISSUE_CLOSING_REGEX).
map { |ref| ref[0] }.join(" ")
......
......@@ -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
......
......@@ -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
......
......@@ -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:
......
......@@ -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
......@@ -51,7 +48,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{issue}</a>)
class="#{klass}">#{match}</a>)
end
end
......
......@@ -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
......@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{project_ref}##{id}</a>)
class="#{klass}">#{match}</a>)
else
match
end
......
......@@ -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
......@@ -85,8 +72,7 @@ module Gitlab
# Returns a Hash.
def label_params(id, name)
if name
# TODO (rspeicher): Don't strip single quotes if we decide to only use double quotes for surrounding.
{ name: name.tr('\'"', '') }
{ name: name.tr('"', '') }
else
{ id: id }
end
......
......@@ -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
......@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{project_ref}!#{id}</a>)
class="#{klass}">#{match}</a>)
else
match
end
......
require 'active_support/core_ext/string/output_safety'
require 'html/pipeline'
require 'html/pipeline/filter'
module Gitlab
module Markdown
......
......@@ -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
......@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
class="#{klass}">#{project_ref}$#{id}</a>)
class="#{klass}">#{match}</a>)
else
match
end
......
......@@ -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
......@@ -68,7 +65,8 @@ module Gitlab
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
%(<a href="#{url}" class="#{link_class}">@all</a>)
text = User.reference_prefix + 'all'
%(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def link_to_namespace(namespace)
......@@ -86,7 +84,8 @@ module Gitlab
url = urls.group_url(group, only_path: context[:only_path])
%(<a href="#{url}" class="#{link_class}">@#{group}</a>)
text = Group.reference_prefix + group
%(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def link_to_user(user, namespace)
......@@ -94,7 +93,8 @@ module Gitlab
url = urls.user_url(user, only_path: context[:only_path])
%(<a href="#{url}" class="#{link_class}">@#{user}</a>)
text = User.reference_prefix + user
%(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def user_can_reference_group?(group)
......
......@@ -11,7 +11,7 @@ describe "GitLab Flavored Markdown", feature: true do
end
before do
Commit.any_instance.stub(title: "fix ##{issue.iid}\n\nask @#{fred.username} for details")
Commit.any_instance.stub(title: "fix #{issue.to_reference}\n\nask #{fred.to_reference} for details")
end
let(:commit) { project.commit }
......@@ -25,25 +25,25 @@ describe "GitLab Flavored Markdown", feature: true do
it "should render title in commits#index" do
visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1)
expect(page).to have_link("##{issue.iid}")
expect(page).to have_link(issue.to_reference)
end
it "should render title in commits#show" do
visit namespace_project_commit_path(project.namespace, project, commit)
expect(page).to have_link("##{issue.iid}")
expect(page).to have_link(issue.to_reference)
end
it "should render description in commits#show" do
visit namespace_project_commit_path(project.namespace, project, commit)
expect(page).to have_link("@#{fred.username}")
expect(page).to have_link(fred.to_reference)
end
it "should render title in repositories#branches" do
visit namespace_project_branches_path(project.namespace, project)
expect(page).to have_link("##{issue.iid}")
expect(page).to have_link(issue.to_reference)
end
end
......@@ -57,20 +57,20 @@ describe "GitLab Flavored Markdown", feature: true do
author: @user,
assignee: @user,
project: project,
title: "fix ##{@other_issue.iid}",
description: "ask @#{fred.username} for details")
title: "fix #{@other_issue.to_reference}",
description: "ask #{fred.to_reference} for details")
end
it "should render subject in issues#index" do
visit namespace_project_issues_path(project.namespace, project)
expect(page).to have_link("##{@other_issue.iid}")
expect(page).to have_link(@other_issue.to_reference)
end
it "should render subject in issues#show" do
visit namespace_project_issue_path(project.namespace, project, @issue)
expect(page).to have_link("##{@other_issue.iid}")
expect(page).to have_link(@other_issue.to_reference)
end
it "should render details in issues#show" do
......@@ -83,19 +83,19 @@ describe "GitLab Flavored Markdown", feature: true do
describe "for merge requests" do
before do
@merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix ##{issue.iid}")
@merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix #{issue.to_reference}")
end
it "should render title in merge_requests#index" do
visit namespace_project_merge_requests_path(project.namespace, project)
expect(page).to have_link("##{issue.iid}")
expect(page).to have_link(issue.to_reference)
end
it "should render title in merge_requests#show" do
visit namespace_project_merge_request_path(project.namespace, project, @merge_request)
expect(page).to have_link("##{issue.iid}")
expect(page).to have_link(issue.to_reference)
end
end
......@@ -104,26 +104,26 @@ describe "GitLab Flavored Markdown", feature: true do
before do
@milestone = create(:milestone,
project: project,
title: "fix ##{issue.iid}",
description: "ask @#{fred.username} for details")
title: "fix #{issue.to_reference}",
description: "ask #{fred.to_reference} for details")
end
it "should render title in milestones#index" do
visit namespace_project_milestones_path(project.namespace, project)
expect(page).to have_link("##{issue.iid}")
expect(page).to have_link(issue.to_reference)
end
it "should render title in milestones#show" do
visit namespace_project_milestone_path(project.namespace, project, @milestone)
expect(page).to have_link("##{issue.iid}")
expect(page).to have_link(issue.to_reference)
end
it "should render description in milestones#show" do
visit namespace_project_milestone_path(project.namespace, project, @milestone)
expect(page).to have_link("@#{fred.username}")
expect(page).to have_link(fred.to_reference)
end
end
end
......@@ -66,6 +66,10 @@ describe 'GitLab Markdown' do
@doc.at_css("##{id}").parent.next_element
end
# Sometimes it can be useful to see the parsed output of the Markdown document
# for debugging. Uncomment this block to write the output to
# tmp/capybara/markdown_spec.html.
#
# it 'writes to a file' do
# File.open(Rails.root.join('tmp/capybara/markdown_spec.html'), 'w') do |file|
# file.puts @md
......@@ -344,13 +348,13 @@ class MarkdownFeature
end
def commit
@commit ||= project.repository.commit
@commit ||= project.commit
end
def commit_range
unless @commit_range
commit2 = project.repository.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}")
commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
end
@commit_range
......@@ -376,11 +380,6 @@ class MarkdownFeature
@xproject
end
# Shortcut to "cross-reference/project"
def xref
xproject.path_with_namespace
end
def xissue
@xissue ||= create(:issue, project: xproject)
end
......@@ -394,13 +393,13 @@ class MarkdownFeature
end
def xcommit
@xcommit ||= xproject.repository.commit
@xcommit ||= xproject.commit
end
def xcommit_range
unless @xcommit_range
xcommit2 = xproject.repository.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}")
xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end
@xcommit_range
......
......@@ -127,61 +127,61 @@ But it shouldn't autolink text inside certain tags:
- <a>http://about.gitlab.com/</a>
- <kbd>http://about.gitlab.com/</kbd>
### Reference Filters (e.g., #<%= issue.iid %>)
### Reference Filters (e.g., <%= issue.to_reference %>)
References should be parseable even inside _!<%= merge_request.iid %>_ emphasis.
References should be parseable even inside _<%= merge_request.to_reference %>_ emphasis.
#### UserReferenceFilter
- All: @all
- User: @<%= user.username %>
- Group: @<%= group.name %>
- Ignores invalid: @fake_user
- Ignored in code: `@<%= user.username %>`
- Ignored in links: [Link to @<%= user.username %>](#user-link)
- User: <%= user.to_reference %>
- Group: <%= group.to_reference %>
- Ignores invalid: <%= User.reference_prefix %>fake_user
- Ignored in code: `<%= user.to_reference %>`
- Ignored in links: [Link to <%= user.to_reference %>](#user-link)
#### IssueReferenceFilter
- Issue: #<%= issue.iid %>
- Issue in another project: <%= xref %>#<%= xissue.iid %>
- Ignored in code: `#<%= issue.iid %>`
- Ignored in links: [Link to #<%= issue.iid %>](#issue-link)
- Issue: <%= issue.to_reference %>
- Issue in another project: <%= xissue.to_reference(project) %>
- Ignored in code: `<%= issue.to_reference %>`
- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
#### MergeRequestReferenceFilter
- Merge request: !<%= merge_request.iid %>
- Merge request in another project: <%= xref %>!<%= xmerge_request.iid %>
- Ignored in code: `!<%= merge_request.iid %>`
- Ignored in links: [Link to !<%= merge_request.iid %>](#merge-request-link)
- Merge request: <%= merge_request.to_reference %>
- Merge request in another project: <%= xmerge_request.to_reference(project) %>
- Ignored in code: `<%= merge_request.to_reference %>`
- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
#### SnippetReferenceFilter
- Snippet: $<%= snippet.id %>
- Snippet in another project: <%= xref %>$<%= xsnippet.id %>
- Ignored in code: `$<%= snippet.id %>`
- Ignored in links: [Link to $<%= snippet.id %>](#snippet-link)
- Snippet: <%= snippet.to_reference %>
- Snippet in another project: <%= xsnippet.to_reference(project) %>
- Ignored in code: `<%= snippet.to_reference %>`
- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
#### CommitRangeReferenceFilter
- Range: <%= commit_range %>
- Range in another project: <%= xref %>@<%= xcommit_range %>
- Ignored in code: `<%= commit_range %>`
- Ignored in links: [Link to <%= commit_range %>](#commit-range-link)
- Range: <%= commit_range.to_reference %>
- Range in another project: <%= xcommit_range.to_reference(project) %>
- Ignored in code: `<%= commit_range.to_reference %>`
- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
#### CommitReferenceFilter
- Commit: <%= commit.id %>
- Commit in another project: <%= xref %>@<%= xcommit.id %>
- Ignored in code: `<%= commit.id %>`
- Ignored in links: [Link to <%= commit.id %>](#commit-link)
- Commit: <%= commit.to_reference %>
- Commit in another project: <%= xcommit.to_reference(project) %>
- Ignored in code: `<%= commit.to_reference %>`
- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
#### LabelReferenceFilter
- Label by ID: ~<%= simple_label.id %>
- Label by name: ~<%= simple_label.name %>
- Label by name in quotes: ~"<%= label.name %>"
- Ignored in code: `~<%= simple_label.name %>`
- Ignored in links: [Link to ~<%= simple_label.id %>](#label-link)
- Label by ID: <%= simple_label.to_reference %>
- Label by name: <%= Label.reference_prefix %><%= simple_label.name %>
- Label by name in quotes: <%= label.to_reference(:name) %>
- Ignored in code: `<%= simple_label.to_reference %>`
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
### Task Lists
......
......@@ -26,7 +26,7 @@ describe GitlabMarkdownHelper do
end
describe "referencing multiple objects" do
let(:actual) { "!#{merge_request.iid} -> #{commit.id} -> ##{issue.iid}" }
let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" }
it "should link to the merge request" do
expected = namespace_project_merge_request_path(project.namespace, project, merge_request)
......@@ -50,7 +50,7 @@ describe GitlabMarkdownHelper do
let(:issues) { create_list(:issue, 2, project: project) }
it 'should handle references nested in links with all the text' do
actual = link_to_gfm("This should finally fix ##{issues[0].iid} and ##{issues[1].iid} for real", commit_path)
actual = link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path)
doc = Nokogiri::HTML.parse(actual)
# Make sure we didn't create invalid markup
......@@ -63,7 +63,7 @@ describe GitlabMarkdownHelper do
# First issue link
expect(doc.css('a')[1].attr('href')).
to eq namespace_project_issue_path(project.namespace, project, issues[0])
expect(doc.css('a')[1].text).to eq "##{issues[0].iid}"
expect(doc.css('a')[1].text).to eq issues[0].to_reference
# Internal commit link
expect(doc.css('a')[2].attr('href')).to eq commit_path
......@@ -72,7 +72,7 @@ describe GitlabMarkdownHelper do
# Second issue link
expect(doc.css('a')[3].attr('href')).
to eq namespace_project_issue_path(project.namespace, project, issues[1])
expect(doc.css('a')[3].text).to eq "##{issues[1].iid}"
expect(doc.css('a')[3].text).to eq issues[1].to_reference
# Trailing commit link
expect(doc.css('a')[4].attr('href')).to eq commit_path
......@@ -90,7 +90,7 @@ describe GitlabMarkdownHelper do
end
it "escapes HTML passed in as the body" do
actual = "This is a <h1>test</h1> - see ##{issues[0].iid}"
actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
expect(link_to_gfm(actual, commit_path)).
to match('&lt;h1&gt;test&lt;/h1&gt;')
end
......
require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:iid1) { issue.iid }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:reference) { issue.to_reference }
subject { described_class.new(project, project.creator) }
describe "#closed_by_message" do
context 'with a single reference' do
it do
message = "Awesome commit (Closes ##{iid1})"
message = "Awesome commit (Closes #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Awesome commit (closes ##{iid1})"
message = "Awesome commit (closes #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Closed ##{iid1}"
message = "Closed #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "closed ##{iid1}"
message = "closed #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Closing ##{iid1}"
message = "Closing #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "closing ##{iid1}"
message = "closing #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Close ##{iid1}"
message = "Close #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "close ##{iid1}"
message = "close #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Awesome commit (Fixes ##{iid1})"
message = "Awesome commit (Fixes #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Awesome commit (fixes ##{iid1})"
message = "Awesome commit (fixes #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Fixed ##{iid1}"
message = "Fixed #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "fixed ##{iid1}"
message = "fixed #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Fixing ##{iid1}"
message = "Fixing #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "fixing ##{iid1}"
message = "fixing #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Fix ##{iid1}"
message = "Fix #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "fix ##{iid1}"
message = "fix #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Awesome commit (Resolves ##{iid1})"
message = "Awesome commit (Resolves #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Awesome commit (resolves ##{iid1})"
message = "Awesome commit (resolves #{reference})"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Resolved ##{iid1}"
message = "Resolved #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "resolved ##{iid1}"
message = "resolved #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Resolving ##{iid1}"
message = "Resolving #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "resolving ##{iid1}"
message = "resolving #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "Resolve ##{iid1}"
message = "Resolve #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
it do
message = "resolve ##{iid1}"
message = "resolve #{reference}"
expect(subject.closed_by_message(message)).to eq([issue])
end
end
......@@ -133,40 +133,40 @@ describe Gitlab::ClosingIssueExtractor do
context 'with multiple references' do
let(:other_issue) { create(:issue, project: project) }
let(:third_issue) { create(:issue, project: project) }
let(:iid2) { other_issue.iid }
let(:iid3) { third_issue.iid }
let(:reference2) { other_issue.to_reference }
let(:reference3) { third_issue.to_reference }
it 'fetches issues in single line message' do
message = "Closes ##{iid1} and fix ##{iid2}"
message = "Closes #{reference} and fix #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
end
it 'fetches comma-separated issues references in single line message' do
message = "Closes ##{iid1}, closes ##{iid2}"
message = "Closes #{reference}, closes #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
end
it 'fetches comma-separated issues numbers in single line message' do
message = "Closes ##{iid1}, ##{iid2} and ##{iid3}"
message = "Closes #{reference}, #{reference2} and #{reference3}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue])
end
it 'fetches issues in multi-line message' do
message = "Awesome commit (closes ##{iid1})\nAlso fixes ##{iid2}"
message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue])
end
it 'fetches issues in hybrid message' do
message = "Awesome commit (closes ##{iid1})\n"\
"Also fixing issues ##{iid2}, ##{iid3} and #4"
message = "Awesome commit (closes #{reference})\n"\
"Also fixing issues #{reference2}, #{reference3} and #4"
expect(subject.closed_by_message(message)).
to eq([issue, other_issue, third_issue])
......
......@@ -8,34 +8,36 @@ module Gitlab::Markdown
let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") }
let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") }
let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
it 'requires project context' do
expect { described_class.call('Commit Range 1c002d..d200c1', {}) }.
to raise_error(ArgumentError, /:project/)
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit Range #{commit1.id}..#{commit2.id}</#{elem}>"
exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
let(:reference) { "#{commit1.id}...#{commit2.id}" }
let(:reference2) { "#{commit1.id}..#{commit2.id}" }
let(:reference) { range.to_reference }
let(:reference2) { range2.to_reference }
it 'links to a valid two-dot reference' do
doc = filter("See #{reference2}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, from: "#{commit1.id}^", to: commit2.id)
to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
end
it 'links to a valid three-dot reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id)
to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
end
it 'links to a valid short ID' do
......@@ -51,7 +53,7 @@ module Gitlab::Markdown
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
exp = Regexp.escape("#{commit1.short_id}...#{commit2.short_id}")
exp = Regexp.escape(range.to_s)
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
......@@ -65,7 +67,7 @@ module Gitlab::Markdown
it 'includes a title attribute' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Commits #{commit1.id} through #{commit2.id}"
expect(doc.css('a').first.attr('title')).to eq range.reference_title
end
it 'includes default classes' do
......@@ -95,9 +97,11 @@ module Gitlab::Markdown
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") }
let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" }
let(:reference) { range.to_reference(project) }
before do
range.project = project2
end
context 'when user can access reference' do
before { allow_cross_reference! }
......@@ -106,21 +110,21 @@ module Gitlab::Markdown
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: commit2.id)
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
exp = Regexp.escape("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}")
exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id.reverse}...#{commit2.id}"
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}"
expect(filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id}...#{commit2.id.reverse}"
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
expect(filter(act).to_html).to eq exp
end
......
......@@ -8,8 +8,7 @@ module Gitlab::Markdown
let(:commit) { project.commit }
it 'requires project context' do
expect { described_class.call('Commit 1c002d', {}) }.
to raise_error(ArgumentError, /:project/)
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
......@@ -47,10 +46,11 @@ module Gitlab::Markdown
end
it 'ignores invalid commit IDs' do
exp = act = "See #{reference.reverse}"
invalid = invalidate_reference(reference)
exp = act = "See #{invalid}"
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(reference.reverse)
expect(project.repository).to receive(:commit).with(invalid)
expect(filter(act).to_html).to eq exp
end
......@@ -93,8 +93,8 @@ module Gitlab::Markdown
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:commit) { project.commit }
let(:reference) { "#{project2.path_with_namespace}@#{commit.id}" }
let(:commit) { project2.commit }
let(:reference) { commit.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
......@@ -109,12 +109,12 @@ module Gitlab::Markdown
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
exp = Regexp.escape(project2.path_with_namespace)
exp = Regexp.escape(project2.to_reference)
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Committed #{project2.path_with_namespace}##{commit.id.reverse}"
exp = act = "Committed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
......
......@@ -9,19 +9,18 @@ module Gitlab::Markdown
end
let(:project) { create(:jira_project) }
let(:issue) { double('issue', iid: 123) }
context 'JIRA issue references' do
let(:reference) { "JIRA-#{issue.iid}" }
let(:issue) { ExternalIssue.new('JIRA-123', project) }
let(:reference) { issue.to_reference }
it 'requires project context' do
expect { described_class.call('Issue JIRA-123', {}) }.
to raise_error(ArgumentError, /:project/)
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue JIRA-#{issue.iid}</#{elem}>"
exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
......@@ -33,13 +32,6 @@ module Gitlab::Markdown
expect(filter(act).to_html).to eq exp
end
%w(pre code a style).each do |elem|
it "ignores references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
it 'links to a valid reference' do
doc = filter("Issue #{reference}")
expect(doc.css('a').first.attr('href'))
......
......@@ -12,24 +12,23 @@ module Gitlab::Markdown
let(:issue) { create(:issue, project: project) }
it 'requires project context' do
expect { described_class.call('Issue #123', {}) }.
to raise_error(ArgumentError, /:project/)
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue ##{issue.iid}</#{elem}>"
exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
let(:reference) { "##{issue.iid}" }
let(:reference) { issue.to_reference }
it 'ignores valid references when using non-default tracker' do
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
exp = act = "Issue ##{issue.iid}"
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
end
......@@ -46,9 +45,9 @@ module Gitlab::Markdown
end
it 'ignores invalid issue IDs' do
exp = act = "Fixed ##{issue.iid + 1}"
invalid = invalidate_reference(reference)
exp = act = "Fixed #{invalid}"
expect(project).to receive(:get_issue).with(issue.iid + 1).and_return(nil)
expect(filter(act).to_html).to eq exp
end
......@@ -92,7 +91,7 @@ module Gitlab::Markdown
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) }
let(:issue) { create(:issue, project: project2) }
let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" }
let(:reference) { issue.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
......@@ -101,7 +100,7 @@ module Gitlab::Markdown
expect_any_instance_of(Project).to receive(:get_issue).
with(issue.iid).and_return(nil)
exp = act = "Issue ##{issue.iid}"
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
end
......@@ -118,7 +117,7 @@ module Gitlab::Markdown
end
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{project2.path_with_namespace}##{issue.iid + 1}"
exp = act = "Fixed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
......
......@@ -7,11 +7,10 @@ module Gitlab::Markdown
let(:project) { create(:empty_project) }
let(:label) { create(:label, project: project) }
let(:reference) { "~#{label.id}" }
let(:reference) { label.to_reference }
it 'requires project context' do
expect { described_class.call('Label ~123', {}) }.
to raise_error(ArgumentError, /:project/)
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
......@@ -36,7 +35,7 @@ module Gitlab::Markdown
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq urls.namespace_project_issues_url(project.namespace, project, label_name: label.name, only_path: true)
expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name)
end
it 'adds to the results hash' do
......@@ -70,7 +69,7 @@ module Gitlab::Markdown
end
it 'ignores invalid label IDs' do
exp = act = "Label ~#{label.id + 1}"
exp = act = "Label #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
......@@ -78,7 +77,7 @@ module Gitlab::Markdown
context 'String-based single-word references' do
let(:label) { create(:label, name: 'gfm', project: project) }
let(:reference) { "~#{label.name}" }
let(:reference) { "#{Label.reference_prefix}#{label.name}" }
it 'links to a valid reference' do
doc = filter("See #{reference}")
......@@ -94,59 +93,33 @@ module Gitlab::Markdown
end
it 'ignores invalid label names' do
exp = act = "Label ~#{label.name.reverse}"
exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
expect(filter(act).to_html).to eq exp
end
end
context 'String-based multi-word references in quotes' do
let(:label) { create(:label, name: 'gfm references', project: project) }
let(:label) { create(:label, name: 'gfm references', project: project) }
let(:reference) { label.to_reference(:name) }
context 'in single quotes' do
let(:reference) { "~'#{label.name}'" }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = "Label ~'#{label.name.reverse}'"
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(filter(act).to_html).to eq exp
end
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
context 'in double quotes' do
let(:reference) { %(~"#{label.name}") }
it 'links to a valid reference' do
doc = filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label names' do
exp = act = %(Label ~"#{label.name.reverse}")
it 'ignores invalid label names' do
exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
expect(filter(act).to_html).to eq exp
end
expect(filter(act).to_html).to eq exp
end
end
......
......@@ -8,19 +8,18 @@ module Gitlab::Markdown
let(:merge) { create(:merge_request, source_project: project) }
it 'requires project context' do
expect { described_class.call('MergeRequest !123', {}) }.
to raise_error(ArgumentError, /:project/)
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Merge !#{merge.iid}</#{elem}>"
exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'internal reference' do
let(:reference) { "!#{merge.iid}" }
let(:reference) { merge.to_reference }
it 'links to a valid reference' do
doc = filter("See #{reference}")
......@@ -35,7 +34,7 @@ module Gitlab::Markdown
end
it 'ignores invalid merge IDs' do
exp = act = "Merge !#{merge.iid + 1}"
exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
......@@ -80,7 +79,7 @@ module Gitlab::Markdown
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) }
let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" }
let(:reference) { merge.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
......@@ -99,7 +98,7 @@ module Gitlab::Markdown
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{project2.path_with_namespace}!#{merge.iid + 1}"
exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
......
......@@ -6,11 +6,10 @@ module Gitlab::Markdown
let(:project) { create(:empty_project) }
let(:snippet) { create(:project_snippet, project: project) }
let(:reference) { "$#{snippet.id}" }
let(:reference) { snippet.to_reference }
it 'requires project context' do
expect { described_class.call('Snippet $123', {}) }.
to raise_error(ArgumentError, /:project/)
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
%w(pre code a style).each do |elem|
......@@ -34,7 +33,7 @@ module Gitlab::Markdown
end
it 'ignores invalid snippet IDs' do
exp = act = "Snippet $#{snippet.id + 1}"
exp = act = "Snippet #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
......@@ -79,7 +78,7 @@ module Gitlab::Markdown
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:empty_project, namespace: namespace) }
let(:snippet) { create(:project_snippet, project: project2) }
let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" }
let(:reference) { snippet.to_reference(project) }
context 'when user can access reference' do
before { allow_cross_reference! }
......@@ -97,7 +96,7 @@ module Gitlab::Markdown
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{project2.path_with_namespace}$#{snippet.id + 1}"
exp = act = "See #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
end
......
......@@ -4,65 +4,63 @@ module Gitlab::Markdown
describe UserReferenceFilter do
include ReferenceFilterSpecHelper
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:reference) { user.to_reference }
it 'requires project context' do
expect { described_class.call('Example @mention', {}) }.
to raise_error(ArgumentError, /:project/)
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
end
it 'ignores invalid users' do
exp = act = 'Hey @somebody'
exp = act = "Hey #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq(exp)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Hey @#{user.username}</#{elem}>"
exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
end
end
context 'mentioning @all' do
let(:reference) { User.reference_prefix + 'all' }
before do
project.team << [project.creator, :developer]
end
it 'supports a special @all mention' do
doc = filter("Hey @all")
doc = filter("Hey #{reference}")
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
.to eq urls.namespace_project_url(project.namespace, project)
end
it 'adds to the results hash' do
result = pipeline_result('Hey @all')
result = pipeline_result("Hey #{reference}")
expect(result[:references][:user]).to eq [project.creator]
end
end
context 'mentioning a user' do
let(:reference) { "@#{user.username}" }
it 'links to a User' do
doc = filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end
# TODO (rspeicher): This test might be overkill
it 'links to a User with a period' do
user = create(:user, name: 'alphA.Beta')
doc = filter("Hey @#{user.username}")
doc = filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
# TODO (rspeicher): This test might be overkill
it 'links to a User with an underscore' do
user = create(:user, name: 'ping_pong_king')
doc = filter("Hey @#{user.username}")
doc = filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
......@@ -73,10 +71,9 @@ module Gitlab::Markdown
end
context 'mentioning a group' do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:reference) { "@#{group.name}" }
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:reference) { group.to_reference }
context 'that the current user can read' do
before do
......@@ -108,23 +105,23 @@ module Gitlab::Markdown
end
it 'links with adjacent text' do
skip 'TODO (rspeicher): Re-enable when usernames can\'t end in periods.'
doc = filter("Mention me (@#{user.username}.)")
expect(doc.to_html).to match(/\(<a.+>@#{user.username}<\/a>\.\)/)
skip "TODO (rspeicher): Re-enable when usernames can't end in periods."
doc = filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end
it 'includes default classes' do
doc = filter("Hey @#{user.username}")
doc = filter("Hey #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
end
it 'includes an optional custom class' do
doc = filter("Hey @#{user.username}", reference_class: 'custom')
doc = filter("Hey #{reference}", reference_class: 'custom')
expect(doc.css('a').first.attr('class')).to include 'custom'
end
it 'supports an :only_path context' do
doc = filter("Hey @#{user.username}", only_path: true)
doc = filter("Hey #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......
......@@ -20,7 +20,7 @@ describe Gitlab::ReferenceExtractor do
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.")
subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.")
expect(subject.issues).to eq([@i0, @i1])
end
......@@ -82,7 +82,7 @@ describe Gitlab::ReferenceExtractor do
end
it 'handles project issue references' do
subject.analyze("this refers issue #{other_project.path_with_namespace}##{issue.iid}")
subject.analyze("this refers issue #{issue.to_reference(project)}")
extracted = subject.issues
expect(extracted.size).to eq(1)
expect(extracted).to eq([issue])
......
require 'spec_helper'
describe CommitRange do
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Referable) }
end
let(:sha_from) { 'f3f85602' }
let(:sha_to) { 'e86e1013' }
......@@ -21,6 +27,23 @@ describe CommitRange do
end
end
describe '#to_reference' do
let(:project) { double('project', to_reference: 'namespace1/project') }
before do
range.project = project
end
it 'returns a String reference to the object' do
expect(range.to_reference).to eq range.to_s
end
it 'supports a cross-project reference' do
cross = double('project')
expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
end
end
describe '#reference_title' do
it 'returns the correct String for three-dot ranges' do
expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}"
......
require 'spec_helper'
describe Commit do
let(:project) { create :project }
let(:commit) { project.commit }
let(:project) { create(:project) }
let(:commit) { project.commit }
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Mentionable) }
it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(StaticModel) }
end
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(commit.to_reference).to eq commit.id
end
it 'supports a cross-project reference' do
cross = double('project')
expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}"
end
end
describe '#title' do
it "returns no_commit_message when safe_message is blank" do
......
require 'spec_helper'
describe ExternalIssue do
let(:project) { double('project', to_reference: 'namespace1/project1') }
let(:issue) { described_class.new('EXT-1234', project) }
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Referable) }
end
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(issue.to_reference).to eq issue.id
end
end
describe '#title' do
it 'returns a title' do
expect(issue.title).to eq "External Issue #{issue}"
end
end
end
......@@ -18,16 +18,30 @@ require 'spec_helper'
describe Group do
let!(:group) { create(:group) }
describe "Associations" do
describe 'associations' do
it { is_expected.to have_many :projects }
it { is_expected.to have_many :group_members }
end
it { is_expected.to validate_presence_of :name }
it { is_expected.to validate_uniqueness_of(:name) }
it { is_expected.to validate_presence_of :path }
it { is_expected.to validate_uniqueness_of(:path) }
it { is_expected.not_to validate_presence_of :owner }
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Referable) }
end
describe 'validations' do
it { is_expected.to validate_presence_of :name }
it { is_expected.to validate_uniqueness_of(:name) }
it { is_expected.to validate_presence_of :path }
it { is_expected.to validate_uniqueness_of(:path) }
it { is_expected.not_to validate_presence_of :owner }
end
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(group.to_reference).to eq "@#{group.name}"
end
end
describe :users do
it { expect(group.users).to eq(group.owners) }
......
......@@ -24,15 +24,30 @@ describe Issue do
it { is_expected.to belong_to(:milestone) }
end
describe "Mass assignment" do
end
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(InternalId) }
it { is_expected.to include_module(Issuable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(Taskable) }
end
subject { create(:issue) }
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "##{subject.iid}"
end
it 'supports a cross-project reference' do
cross = double('project')
expect(subject.to_reference(cross)).
to eq "#{subject.project.to_reference}##{subject.iid}"
end
end
describe '#is_being_reassigned?' do
it 'returns true if the issue assignee has changed' do
subject.assignee = create(:user)
......@@ -45,11 +60,8 @@ describe Issue do
describe '#is_being_reassigned?' do
it 'returns issues assigned to user' do
user = create :user
2.times do
issue = create :issue, assignee: user
end
user = create(:user)
create_list(:issue, 2, assignee: user)
expect(Issue.open_for(user).count).to eq 2
end
......
......@@ -14,30 +14,63 @@ require 'spec_helper'
describe Label do
let(:label) { create(:label) }
it { expect(label).to be_valid }
it { is_expected.to belong_to(:project) }
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:label_links).dependent(:destroy) }
it { is_expected.to have_many(:issues).through(:label_links).source(:target) }
end
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Referable) }
end
describe 'validation' do
it { is_expected.to validate_presence_of(:project) }
describe 'Validation' do
it 'should validate color code' do
expect(build(:label, color: 'G-ITLAB')).not_to be_valid
expect(build(:label, color: 'AABBCC')).not_to be_valid
expect(build(:label, color: '#AABBCCEE')).not_to be_valid
expect(build(:label, color: '#GGHHII')).not_to be_valid
expect(build(:label, color: '#')).not_to be_valid
expect(build(:label, color: '')).not_to be_valid
expect(build(:label, color: '#AABBCC')).to be_valid
expect(label).not_to allow_value('G-ITLAB').for(:color)
expect(label).not_to allow_value('AABBCC').for(:color)
expect(label).not_to allow_value('#AABBCCEE').for(:color)
expect(label).not_to allow_value('GGHHII').for(:color)
expect(label).not_to allow_value('#').for(:color)
expect(label).not_to allow_value('').for(:color)
expect(label).to allow_value('#AABBCC').for(:color)
expect(label).to allow_value('#abcdef').for(:color)
end
it 'should validate title' do
expect(build(:label, title: 'G,ITLAB')).not_to be_valid
expect(build(:label, title: 'G?ITLAB')).not_to be_valid
expect(build(:label, title: 'G&ITLAB')).not_to be_valid
expect(build(:label, title: '')).not_to be_valid
expect(label).not_to allow_value('G,ITLAB').for(:title)
expect(label).not_to allow_value('G?ITLAB').for(:title)
expect(label).not_to allow_value('G&ITLAB').for(:title)
expect(label).not_to allow_value('').for(:title)
expect(label).to allow_value('GITLAB').for(:title)
expect(label).to allow_value('gitlab').for(:title)
expect(label).to allow_value("customer's request").for(:title)
end
end
describe '#to_reference' do
context 'using id' do
it 'returns a String reference to the object' do
expect(label.to_reference).to eq "~#{label.id}"
expect(label.to_reference(double('project'))).to eq "~#{label.id}"
end
end
context 'using name' do
it 'returns a String reference to the object' do
expect(label.to_reference(:name)).to eq %(~"#{label.name}")
end
expect(build(:label, title: 'GITLAB')).to be_valid
expect(build(:label, title: 'gitlab')).to be_valid
it 'uses id when name contains double quote' do
label = create(:label, name: %q{"irony"})
expect(label.to_reference(:name)).to eq "~#{label.id}"
end
end
end
end
......@@ -24,22 +24,45 @@
require 'spec_helper'
describe MergeRequest do
describe "Validation" do
it { is_expected.to validate_presence_of(:target_branch) }
it { is_expected.to validate_presence_of(:source_branch) }
subject { create(:merge_request) }
describe 'associations' do
it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') }
it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') }
it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) }
end
describe "Mass assignment" do
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(InternalId) }
it { is_expected.to include_module(Issuable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(Taskable) }
end
describe "Respond to" do
describe 'validation' do
it { is_expected.to validate_presence_of(:target_branch) }
it { is_expected.to validate_presence_of(:source_branch) }
end
describe 'respond to' do
it { is_expected.to respond_to(:unchecked?) }
it { is_expected.to respond_to(:can_be_merged?) }
it { is_expected.to respond_to(:cannot_be_merged?) }
end
describe 'modules' do
it { is_expected.to include_module(Issuable) }
describe '#to_reference' do
it 'returns a String reference to the object' do
expect(subject.to_reference).to eq "!#{subject.iid}"
end
it 'supports a cross-project reference' do
cross = double('project')
expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}"
end
end
describe "#mr_and_commit_notes" do
......@@ -57,8 +80,6 @@ describe MergeRequest do
end
end
subject { create(:merge_request) }
describe '#is_being_reassigned?' do
it 'returns true if the merge_request assignee has changed' do
subject.assignee = create(:user)
......@@ -108,7 +129,7 @@ describe MergeRequest do
it 'detects issues mentioned in the description' do
issue2 = create(:issue, project: subject.project)
subject.description = "Closes ##{issue2.iid}"
subject.description = "Closes #{issue2.to_reference}"
subject.project.stub(default_branch: subject.target_branch)
expect(subject.closes_issues).to include(issue2)
......
......@@ -32,7 +32,7 @@
require 'spec_helper'
describe Project do
describe 'Associations' do
describe 'associations' do
it { is_expected.to belong_to(:group) }
it { is_expected.to belong_to(:namespace) }
it { is_expected.to belong_to(:creator).class_name('User') }
......@@ -54,10 +54,17 @@ describe Project do
it { is_expected.to have_one(:asana_service).dependent(:destroy) }
end
describe 'Mass assignment' do
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Gitlab::ConfigHelper) }
it { is_expected.to include_module(Gitlab::ShellAdapter) }
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
end
describe 'Validation' do
describe 'validation' do
let!(:project) { create(:project) }
it { is_expected.to validate_presence_of(:name) }
......@@ -91,6 +98,14 @@ describe Project do
it { is_expected.to respond_to(:path_with_namespace) }
end
describe '#to_reference' do
let(:project) { create(:empty_project) }
it 'returns a String reference to the object' do
expect(project.to_reference).to eq project.path_with_namespace
end
end
it 'should return valid url to repo' do
project = Project.new(path: 'somewhere')
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
......
......@@ -18,23 +18,47 @@
require 'spec_helper'
describe Snippet do
describe "Associations" do
it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to have_many(:notes).dependent(:destroy) }
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
it { is_expected.to include_module(Linguist::BlobHelper) }
it { is_expected.to include_module(Participable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
end
describe "Mass assignment" do
describe 'associations' do
it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:notes).dependent(:destroy) }
end
describe "Validation" do
describe 'validation' do
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to ensure_length_of(:title).is_within(0..255) }
it { is_expected.to validate_presence_of(:file_name) }
it { is_expected.to ensure_length_of(:title).is_within(0..255) }
it { is_expected.to ensure_length_of(:file_name).is_within(0..255) }
it { is_expected.to validate_presence_of(:content) }
it { is_expected.to validate_inclusion_of(:visibility_level).in_array(Gitlab::VisibilityLevel.values) }
end
describe '#to_reference' do
let(:project) { create(:empty_project) }
let(:snippet) { create(:snippet, project: project) }
it 'returns a String reference to the object' do
expect(snippet.to_reference).to eq "$#{snippet.id}"
end
it 'supports a cross-project reference' do
cross = double('project')
expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}"
end
end
end
......@@ -63,7 +63,17 @@ require 'spec_helper'
describe User do
include Gitlab::CurrentSettings
describe "Associations" do
describe 'modules' do
subject { described_class }
it { is_expected.to include_module(Gitlab::ConfigHelper) }
it { is_expected.to include_module(Gitlab::CurrentSettings) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(TokenAuthenticatable) }
end
describe 'associations' do
it { is_expected.to have_one(:namespace) }
it { is_expected.to have_many(:snippets).class_name('Snippet').dependent(:destroy) }
it { is_expected.to have_many(:project_members).dependent(:destroy) }
......@@ -79,9 +89,6 @@ describe User do
it { is_expected.to have_many(:identities).dependent(:destroy) }
end
describe "Mass assignment" do
end
describe 'validations' do
it { is_expected.to validate_presence_of(:username) }
it { is_expected.to validate_presence_of(:projects_limit) }
......@@ -175,6 +182,14 @@ describe User do
it { is_expected.to respond_to(:private_token) }
end
describe '#to_reference' do
let(:user) { create(:user) }
it 'returns a String reference to the object' do
expect(user.to_reference).to eq "@#{user.username}"
end
end
describe '#generate_password' do
it "should execute callback when force_random_password specified" do
user = build(:user, force_random_password: true)
......
......@@ -259,13 +259,13 @@ describe SystemNoteService do
let(:mentioner) { project2.repository.commit }
it 'references the mentioning commit' do
expect(subject.note).to eq "mentioned in commit #{project2.path_with_namespace}@#{mentioner.id}"
expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}"
end
end
context 'from non-Commit' do
it 'references the mentioning object' do
expect(subject.note).to eq "mentioned in issue #{project2.path_with_namespace}##{mentioner.iid}"
expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}"
end
end
end
......@@ -275,13 +275,13 @@ describe SystemNoteService do
let(:mentioner) { project.repository.commit }
it 'references the mentioning commit' do
expect(subject.note).to eq "mentioned in commit #{mentioner.id}"
expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}"
end
end
context 'from non-Commit' do
it 'references the mentioning object' do
expect(subject.note).to eq "mentioned in issue ##{mentioner.iid}"
expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}"
end
end
end
......@@ -291,7 +291,7 @@ describe SystemNoteService do
describe '.cross_reference?' do
it 'is truthy when text begins with expected text' do
expect(described_class.cross_reference?('mentioned in issue #1')).to be_truthy
expect(described_class.cross_reference?('mentioned in something')).to be_truthy
end
it 'is falsey when text does not begin with expected text' do
......
......@@ -10,12 +10,12 @@ def common_mentionable_setup
let(:mentioned_issue) { create(:issue, project: project) }
let(:mentioned_mr) { create(:merge_request, :simple, source_project: project) }
let(:mentioned_commit) { project.repository.commit }
let(:mentioned_commit) { project.commit }
let(:ext_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) }
let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) }
let(:ext_commit) { ext_proj.repository.commit }
let(:ext_commit) { ext_proj.commit }
# Override to add known commits to the repository stub.
let(:extra_commits) { [] }
......@@ -23,21 +23,19 @@ def common_mentionable_setup
# A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference
# to this string and place it in their +mentionable_text+.
let(:ref_string) do
cross = ext_proj.path_with_namespace
<<-MSG.strip_heredoc
These references are new:
Issue: ##{mentioned_issue.iid}
Merge: !#{mentioned_mr.iid}
Commit: #{mentioned_commit.id}
Issue: #{mentioned_issue.to_reference}
Merge: #{mentioned_mr.to_reference}
Commit: #{mentioned_commit.to_reference}
This reference is a repeat and should only be mentioned once:
Repeat: ##{mentioned_issue.iid}
Repeat: #{mentioned_issue.to_reference}
These references are cross-referenced:
Issue: #{cross}##{ext_issue.iid}
Merge: #{cross}!#{ext_mr.iid}
Commit: #{cross}@#{ext_commit.short_id}
Issue: #{ext_issue.to_reference(project)}
Merge: #{ext_mr.to_reference(project)}
Commit: #{ext_commit.to_reference(project)}
This is a self-reference and should not be mentioned at all:
Self: #{backref_text}
......@@ -109,19 +107,17 @@ shared_examples 'an editable mentionable' do
it 'creates new cross-reference notes when the mentionable text is edited' do
subject.save
cross = ext_proj.path_with_namespace
new_text = <<-MSG
These references already existed:
Issue: ##{mentioned_issue.iid}
Commit: #{mentioned_commit.id}
Issue: #{mentioned_issue.to_reference}
Commit: #{mentioned_commit.to_reference}
This cross-project reference already existed:
Issue: #{cross}##{ext_issue.iid}
Issue: #{ext_issue.to_reference(project)}
These two references are introduced in an edit:
Issue: ##{new_issues[0].iid}
Cross: #{cross}##{new_issues[1].iid}
Issue: #{new_issues[0].to_reference}
Cross: #{new_issues[1].to_reference(project)}
MSG
# These three objects were already referenced, and should not receive new
......
......@@ -10,6 +10,26 @@ module ReferenceFilterSpecHelper
Rails.application.routes.url_helpers
end
# Modify a String reference to make it invalid
#
# Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
# their word characters reversed.
#
# reference - String reference to modify
#
# Returns a String
def invalidate_reference(reference)
if reference =~ /\A(.+)?.\d+\z/
# Integer-based reference with optional project prefix
reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
# SHA-based reference with optional prefix
reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
else
reference.gsub(/\w+\z/) { |v| v.reverse }
end
end
# Perform `call` on the described class
#
# Automatically passes the current `project` value to the context if none is
......
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