summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarin Jankovski <maxlazio@gmail.com>2014-10-05 21:04:16 +0200
committerMarin Jankovski <maxlazio@gmail.com>2014-10-05 21:04:16 +0200
commitbb929c2117c8a45620eb37b55d43f5cb8a215572 (patch)
treee3d57450efa28c0f860d4472285fa8167dd1ea1e
parent43be3fcb833fe522721a7192fffd8d7348b01ffb (diff)
parent8dce0cd215b657d11b3e183e361fc86ae9314ecd (diff)
downloadgitlab-ce-bb929c2117c8a45620eb37b55d43f5cb8a215572.tar.gz
Merge pull request #7933 from mr-vinn/cross-project-markdown
Implement cross-project Markdown references
-rw-r--r--CHANGELOG1
-rw-r--r--app/models/concerns/mentionable.rb6
-rw-r--r--app/models/note.rb73
-rw-r--r--doc/markdown/markdown.md6
-rw-r--r--lib/gitlab/closing_issue_extractor.rb2
-rw-r--r--lib/gitlab/markdown.rb98
-rw-r--r--lib/gitlab/reference_extractor.rb52
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb102
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb52
-rw-r--r--spec/models/commit_spec.rb14
-rw-r--r--spec/models/note_spec.rb4
-rw-r--r--spec/support/mentionable_shared_examples.rb40
12 files changed, 356 insertions, 94 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 14d2572f742..857a5bc9234 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,6 +17,7 @@ v 7.4.0
- Font Awesome 4.2 integration (Sullivan Senechal)
- Add Pushover service integration (Sullivan Senechal)
- Add select field type for services options (Sullivan Senechal)
+ - Add cross-project references to the Markdown parser (Vinnie Okada)
v 7.3.2
- Fix creating new file via web editor
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 71dd2f8c697..5938d9cb28e 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -67,8 +67,10 @@ module Mentionable
def references(p = project, text = mentionable_text)
return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new
- ext.analyze(text)
- (ext.issues_for(p) + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference]
+ ext.analyze(text, p)
+ (ext.issues_for +
+ ext.merge_requests_for +
+ ext.commits_for).uniq - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
diff --git a/app/models/note.rb b/app/models/note.rb
index fa5fdea4eb0..0c1d792ca9a 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -70,13 +70,17 @@ class Note < ActiveRecord::Base
)
end
- # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note.
- # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+.
+ # +noteable+ was referenced from +mentioner+, by including GFM in either
+ # +mentioner+'s description or an associated Note.
+ # Create a system Note associated with +noteable+ with a GFM back-reference
+ # to +mentioner+.
def create_cross_reference_note(noteable, mentioner, author, project)
+ gfm_reference = mentioner_gfm_ref(noteable, mentioner, project)
+
note_options = {
project: project,
author: author,
- note: "_mentioned in #{mentioner.gfm_reference}_",
+ note: "_mentioned in #{gfm_reference}_",
system: true
}
@@ -163,12 +167,73 @@ class Note < ActiveRecord::Base
# Determine whether or not a cross-reference note already exists.
def cross_reference_exists?(noteable, mentioner)
- where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any?
+ gfm_reference = mentioner_gfm_ref(noteable, mentioner)
+
+ where(['noteable_id = ? and system = ? and note like ?',
+ noteable.id, true, "_mentioned in #{gfm_reference}_"]).any?
end
def search(query)
where("note like :query", query: "%#{query}%")
end
+
+ private
+
+ # 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 mentioner_gfm_ref(noteable, mentioner, project = nil)
+ if mentioner.is_a?(Commit)
+ if project.nil?
+ return mentioner.gfm_reference.sub('commit ', 'commit %')
+ else
+ mentioning_project = project
+ end
+ else
+ mentioning_project = mentioner.project
+ end
+
+ noteable_project_id = noteable_project_id(noteable, mentioning_project)
+
+ full_gfm_reference(mentioning_project, noteable_project_id, mentioner)
+ end
+
+ # Return the ID of the project that +noteable+ belongs to, or nil if
+ # +noteable+ is a commit and is not part of the project that owns
+ # +mentioner+.
+ def noteable_project_id(noteable, mentioning_project)
+ if noteable.is_a?(Commit)
+ if mentioning_project.repository.commit(noteable.id)
+ # The noteable commit belongs to the mentioner's project
+ mentioning_project.id
+ else
+ nil
+ end
+ else
+ noteable.project.id
+ end
+ 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 full_gfm_reference(mentioning_project, noteable_project_id, mentioner)
+ if mentioning_project.id == noteable_project_id
+ 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
end
def commit_author
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 5627fd0659f..5c095ed1487 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -177,6 +177,12 @@ GFM will recognize the following:
- 1234567 : for commits
- \[file\](path/to/file) : for file references
+GFM also recognizes references to commits, issues, and merge requests in other projects:
+
+- namespace/project#123 : for issues
+- namespace/project!123 : for merge requests
+- namespace/project@1234567 : for commits
+
# Standard Markdown
## Headers
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index 90f1370c209..401e6e047b1 100644
--- a/lib/gitlab/closing_issue_extractor.rb
+++ b/lib/gitlab/closing_issue_extractor.rb
@@ -6,7 +6,7 @@ module Gitlab
md = ISSUE_CLOSING_REGEX.match(message)
if md
extractor = Gitlab::ReferenceExtractor.new
- extractor.analyze(md[0])
+ extractor.analyze(md[0], project)
extractor.issues_for(project)
else
[]
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index d346acf0d32..709a74fe21e 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -108,15 +108,18 @@ module Gitlab
text
end
+ NAME_STR = '[a-zA-Z][a-zA-Z0-9_\-\.]*'
+ PROJ_STR = "(?<project>#{NAME_STR}/#{NAME_STR})"
+
REFERENCE_PATTERN = %r{
(?<prefix>\W)? # Prefix
( # Reference
- @(?<user>[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name
+ @(?<user>#{NAME_STR}) # User name
|(?<issue>([A-Z\-]+-)\d+) # JIRA Issue ID
- |\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
- |!(?<merge_request>\d+) # MR ID
+ |#{PROJ_STR}?\#(?<issue>([a-zA-Z\-]+-)?\d+) # Issue ID
+ |#{PROJ_STR}?!(?<merge_request>\d+) # MR ID
|\$(?<snippet>\d+) # Snippet ID
- |(?<commit>[\h]{6,40}) # Commit ID
+ |(#{PROJ_STR}@)?(?<commit>[\h]{6,40}) # Commit ID
|(?<skip>gfm-extraction-[\h]{6,40}) # Skip gfm extractions. Otherwise will be parsed as commit
)
(?<suffix>\W)? # Suffix
@@ -127,38 +130,59 @@ module Gitlab
def parse_references(text, project = @project)
# parse reference links
text.gsub!(REFERENCE_PATTERN) do |match|
- prefix = $~[:prefix]
- suffix = $~[:suffix]
type = TYPES.select{|t| !$~[t].nil?}.first
- if type
- identifier = $~[type]
-
- # Avoid HTML entities
- if prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
- match
- elsif ref_link = reference_link(type, identifier, project)
- "#{prefix}#{ref_link}#{suffix}"
- else
- match
- end
- else
- match
+ actual_project = project
+ project_prefix = nil
+ project_path = $LAST_MATCH_INFO[:project]
+ if project_path
+ actual_project = ::Project.find_with_namespace(project_path)
+ project_prefix = project_path
end
+
+ parse_result($LAST_MATCH_INFO, type,
+ actual_project, project_prefix) || match
+ end
+ end
+
+ # Called from #parse_references. Attempts to build a gitlab reference
+ # link. Returns nil if +type+ is nil, if the match string is an HTML
+ # entity, if the reference is invalid, or if the matched text includes an
+ # invalid project path.
+ def parse_result(match_info, type, project, project_prefix)
+ prefix = match_info[:prefix]
+ suffix = match_info[:suffix]
+
+ return nil if html_entity?(prefix, suffix) || type.nil?
+ return nil if project.nil? && !project_prefix.nil?
+
+ identifier = match_info[type]
+ ref_link = reference_link(type, identifier, project, project_prefix)
+
+ if ref_link
+ "#{prefix}#{ref_link}#{suffix}"
+ else
+ nil
end
end
+ # Return true if the +prefix+ and +suffix+ indicate that the matched string
+ # is an HTML entity like &amp;
+ def html_entity?(prefix, suffix)
+ prefix && suffix && prefix[0] == '&' && suffix[-1] == ';'
+ end
+
# Private: Dispatches to a dedicated processing method based on reference
#
# reference - Object reference ("@1234", "!567", etc.)
# identifier - Object identifier (Issue ID, SHA hash, etc.)
#
# Returns string rendered by the processing method
- def reference_link(type, identifier, project = @project)
- send("reference_#{type}", identifier, project)
+ def reference_link(type, identifier, project = @project, prefix_text = nil)
+ send("reference_#{type}", identifier, project, prefix_text)
end
- def reference_user(identifier, project = @project)
+ def reference_user(identifier, project = @project, _ = nil)
options = html_options.merge(
class: "gfm gfm-team_member #{html_options[:class]}"
)
@@ -170,39 +194,41 @@ module Gitlab
end
end
- def reference_issue(identifier, project = @project)
+ def reference_issue(identifier, project = @project, prefix_text = nil)
if project.used_default_issues_tracker? || !external_issues_tracker_enabled?
if project.issue_exists? identifier
url = url_for_issue(identifier, project)
- title = title_for_issue(identifier)
+ title = title_for_issue(identifier, project)
options = html_options.merge(
title: "Issue: #{title}",
class: "gfm gfm-issue #{html_options[:class]}"
)
- link_to("##{identifier}", url, options)
+ link_to("#{prefix_text}##{identifier}", url, options)
end
else
config = Gitlab.config
external_issue_tracker = config.issues_tracker[project.issues_tracker]
if external_issue_tracker.present?
- reference_external_issue(identifier, external_issue_tracker, project)
+ reference_external_issue(identifier, external_issue_tracker, project,
+ prefix_text)
end
end
end
- def reference_merge_request(identifier, project = @project)
+ def reference_merge_request(identifier, project = @project,
+ prefix_text = nil)
if merge_request = project.merge_requests.find_by(iid: identifier)
options = html_options.merge(
title: "Merge Request: #{merge_request.title}",
class: "gfm gfm-merge_request #{html_options[:class]}"
)
url = project_merge_request_url(project, merge_request)
- link_to("!#{identifier}", url, options)
+ link_to("#{prefix_text}!#{identifier}", url, options)
end
end
- def reference_snippet(identifier, project = @project)
+ def reference_snippet(identifier, project = @project, _ = nil)
if snippet = project.snippets.find_by(id: identifier)
options = html_options.merge(
title: "Snippet: #{snippet.title}",
@@ -213,17 +239,23 @@ module Gitlab
end
end
- def reference_commit(identifier, project = @project)
+ def reference_commit(identifier, project = @project, prefix_text = nil)
if project.valid_repo? && commit = project.repository.commit(identifier)
options = html_options.merge(
title: commit.link_title,
class: "gfm gfm-commit #{html_options[:class]}"
)
- link_to(identifier, project_commit_url(project, commit), options)
+ prefix_text = "#{prefix_text}@" if prefix_text
+ link_to(
+ "#{prefix_text}#{identifier}",
+ project_commit_url(project, commit),
+ options
+ )
end
end
- def reference_external_issue(identifier, issue_tracker, project = @project)
+ def reference_external_issue(identifier, issue_tracker, project = @project,
+ prefix_text = nil)
url = url_for_issue(identifier, project)
title = issue_tracker['title']
@@ -231,7 +263,7 @@ module Gitlab
title: "Issue in #{title}",
class: "gfm gfm-issue #{html_options[:class]}"
)
- link_to("##{identifier}", url, options)
+ link_to("#{prefix_text}##{identifier}", url, options)
end
end
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 73b19ad55d5..99165950aef 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -9,51 +9,63 @@ module Gitlab
@users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], []
end
- def analyze(string)
- parse_references(string.dup)
+ def analyze(string, project)
+ parse_references(string.dup, project)
end
# Given a valid project, resolve the extracted identifiers of the requested type to
# model objects.
def users_for(project)
- users.map do |identifier|
- project.users.where(username: identifier).first
+ users.map do |entry|
+ project.users.where(username: entry[:id]).first
end.reject(&:nil?)
end
- def issues_for(project)
- issues.map do |identifier|
- project.issues.where(iid: identifier).first
+ def issues_for(project = nil)
+ issues.map do |entry|
+ if should_lookup?(project, entry[:project])
+ entry[:project].issues.where(iid: entry[:id]).first
+ end
end.reject(&:nil?)
end
- def merge_requests_for(project)
- merge_requests.map do |identifier|
- project.merge_requests.where(iid: identifier).first
+ def merge_requests_for(project = nil)
+ merge_requests.map do |entry|
+ if should_lookup?(project, entry[:project])
+ entry[:project].merge_requests.where(iid: entry[:id]).first
+ end
end.reject(&:nil?)
end
def snippets_for(project)
- snippets.map do |identifier|
- project.snippets.where(id: identifier).first
+ snippets.map do |entry|
+ project.snippets.where(id: entry[:id]).first
end.reject(&:nil?)
end
- def commits_for(project)
- repo = project.repository
- return [] if repo.nil?
-
- commits.map do |identifier|
- repo.commit(identifier)
+ def commits_for(project = nil)
+ commits.map do |entry|
+ repo = entry[:project].repository if entry[:project]
+ if should_lookup?(project, entry[:project])
+ repo.commit(entry[:id]) if repo
+ end
end.reject(&:nil?)
end
private
- def reference_link(type, identifier, project)
+ def reference_link(type, identifier, project, _)
# Append identifier to the appropriate collection.
- send("#{type}s") << identifier
+ send("#{type}s") << { project: project, id: identifier }
+ end
+
+ def should_lookup?(project, entry_project)
+ if entry_project.nil?
+ false
+ else
+ project.nil? || project.id == entry_project.id
+ end
end
end
end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 6c865b1e079..73b3d91e96e 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -181,6 +181,76 @@ describe GitlabMarkdownHelper do
end
end
+ # Shared examples for referencing an object in a different project
+ #
+ # Expects the following attributes to be available in the example group:
+ #
+ # - object - The object itself
+ # - reference - The object reference string (e.g., #1234, $1234, !1234)
+ # - other_project - The project that owns the target object
+ #
+ # Currently limited to Snippets, Issues and MergeRequests
+ shared_examples 'cross-project referenced object' do
+ let(:project_path) { @other_project.path_with_namespace }
+ let(:full_reference) { "#{project_path}#{reference}" }
+ let(:actual) { "Reference to #{full_reference}" }
+ let(:expected) do
+ if object.is_a?(Commit)
+ project_commit_path(@other_project, object)
+ else
+ polymorphic_path([@other_project, object])
+ end
+ end
+
+ it 'should link using a valid id' do
+ gfm(actual).should match(
+ /#{expected}.*#{Regexp.escape(full_reference)}/
+ )
+ end
+
+ it 'should link with adjacent text' do
+ # Wrap the reference in parenthesis
+ gfm(actual.gsub(full_reference, "(#{full_reference})")).should(
+ match(expected)
+ )
+
+ # Append some text to the end of the reference
+ gfm(actual.gsub(full_reference, "#{full_reference}, right?")).should(
+ match(expected)
+ )
+ end
+
+ it 'should keep whitespace intact' do
+ actual = "Referenced #{full_reference} already."
+ expected = /Referenced <a.+>[^\s]+<\/a> already/
+ gfm(actual).should match(expected)
+ end
+
+ it 'should not link with an invalid id' do
+ # Modify the reference string so it's still parsed, but is invalid
+ if object.is_a?(Commit)
+ reference.gsub!(/^(.).+$/, '\1' + '12345abcd')
+ else
+ reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2))
+ end
+ gfm(actual).should == actual
+ end
+
+ it 'should include a title attribute' do
+ if object.is_a?(Commit)
+ title = object.link_title
+ else
+ title = "#{object.class.to_s.titlecase}: #{object.title}"
+ end
+ gfm(actual).should match(/title="#{title}"/)
+ end
+
+ it 'should include standard gfm classes' do
+ css = object.class.to_s.underscore
+ gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/)
+ end
+ end
+
describe "referencing an issue" do
let(:object) { issue }
let(:reference) { "##{issue.iid}" }
@@ -188,6 +258,38 @@ describe GitlabMarkdownHelper do
include_examples 'referenced object'
end
+ context 'cross-repo references' do
+ before(:all) do
+ @other_project = create(:project, :public)
+ @commit2 = @other_project.repository.commit
+ @issue2 = create(:issue, project: @other_project)
+ @merge_request2 = create(:merge_request,
+ source_project: @other_project,
+ target_project: @other_project)
+ end
+
+ describe 'referencing an issue in another project' do
+ let(:object) { @issue2 }
+ let(:reference) { "##{@issue2.iid}" }
+
+ include_examples 'cross-project referenced object'
+ end
+
+ describe 'referencing an merge request in another project' do
+ let(:object) { @merge_request2 }
+ let(:reference) { "!#{@merge_request2.iid}" }
+
+ include_examples 'cross-project referenced object'
+ end
+
+ describe 'referencing a commit in another project' do
+ let(:object) { @commit2 }
+ let(:reference) { "@#{@commit2.id}" }
+
+ include_examples 'cross-project referenced object'
+ end
+ end
+
describe "referencing a Jira issue" do
let(:actual) { "Reference to JIRA-#{issue.iid}" }
let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" }
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 99fed27c796..23867df39dd 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -2,45 +2,48 @@ require 'spec_helper'
describe Gitlab::ReferenceExtractor do
it 'extracts username references' do
- subject.analyze "this contains a @user reference"
- subject.users.should == ["user"]
+ subject.analyze('this contains a @user reference', nil)
+ subject.users.should == [{ project: nil, id: 'user' }]
end
it 'extracts issue references' do
- subject.analyze "this one talks about issue #1234"
- subject.issues.should == ["1234"]
+ subject.analyze('this one talks about issue #1234', nil)
+ subject.issues.should == [{ project: nil, id: '1234' }]
end
it 'extracts JIRA issue references' do
- Gitlab.config.gitlab.stub(:issues_tracker).and_return("jira")
- subject.analyze "this one talks about issue JIRA-1234"
- subject.issues.should == ["JIRA-1234"]
+ Gitlab.config.gitlab.stub(:issues_tracker).and_return('jira')
+ subject.analyze('this one talks about issue JIRA-1234', nil)
+ subject.issues.should == [{ project: nil, id: 'JIRA-1234' }]
end
it 'extracts merge request references' do
- subject.analyze "and here's !43, a merge request"
- subject.merge_requests.should == ["43"]
+ subject.analyze("and here's !43, a merge request", nil)
+ subject.merge_requests.should == [{ project: nil, id: '43' }]
end
it 'extracts snippet ids' do
- subject.analyze "snippets like $12 get extracted as well"
- subject.snippets.should == ["12"]
+ subject.analyze('snippets like $12 get extracted as well', nil)
+ subject.snippets.should == [{ project: nil, id: '12' }]
end
it 'extracts commit shas' do
- subject.analyze "commit shas 98cf0ae3 are pulled out as Strings"
- subject.commits.should == ["98cf0ae3"]
+ subject.analyze('commit shas 98cf0ae3 are pulled out as Strings', nil)
+ subject.commits.should == [{ project: nil, id: '98cf0ae3' }]
end
it 'extracts multiple references and preserves their order' do
- subject.analyze "@me and @you both care about this"
- subject.users.should == ["me", "you"]
+ subject.analyze('@me and @you both care about this', nil)
+ subject.users.should == [
+ { project: nil, id: 'me' },
+ { project: nil, id: 'you' }
+ ]
end
it 'leaves the original note unmodified' do
- text = "issue #123 is just the worst, @user"
- subject.analyze text
- text.should == "issue #123 is just the worst, @user"
+ text = 'issue #123 is just the worst, @user'
+ subject.analyze(text, nil)
+ text.should == 'issue #123 is just the worst, @user'
end
it 'handles all possible kinds of references' do
@@ -59,7 +62,7 @@ describe Gitlab::ReferenceExtractor do
project.team << [@u_foo, :reporter]
project.team << [@u_bar, :guest]
- subject.analyze "@foo, @baduser, @bar, and @offteam"
+ subject.analyze('@foo, @baduser, @bar, and @offteam', project)
subject.users_for(project).should == [@u_foo, @u_bar]
end
@@ -67,7 +70,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.iid}, ##{@i1.iid}, and #999.", project)
subject.issues_for(project).should == [@i0, @i1]
end
@@ -75,7 +78,7 @@ describe Gitlab::ReferenceExtractor do
@m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa')
@m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb')
- subject.analyze "!999, !#{@m1.iid}, and !#{@m0.iid}."
+ subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.", project)
subject.merge_requests_for(project).should == [@m1, @m0]
end
@@ -84,14 +87,15 @@ describe Gitlab::ReferenceExtractor do
@s1 = create(:project_snippet, project: project)
@s2 = create(:project_snippet)
- subject.analyze "$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}"
+ subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}", project)
subject.snippets_for(project).should == [@s0, @s1]
end
it 'accesses valid commits' do
- commit = project.repository.commit("master")
+ commit = project.repository.commit('master')
- subject.analyze "this references commits #{commit.sha[0..6]} and 012345"
+ subject.analyze("this references commits #{commit.sha[0..6]} and 012345",
+ project)
extracted = subject.commits_for(project)
extracted.should have(1).item
extracted[0].sha.should == commit.sha
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 1673184cbe4..6f201adc4e8 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -53,11 +53,23 @@ eos
describe '#closes_issues' do
let(:issue) { create :issue, project: project }
+ let(:other_project) { create :project, :public }
+ let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do
- commit.stub(issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/, safe_message: "Fixes ##{issue.iid}")
+ stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX',
+ /Fixes #\d+/)
+ commit.stub(safe_message: "Fixes ##{issue.iid}")
commit.closes_issues(project).should == [issue]
end
+
+ it 'does not detect issues from other projects' do
+ ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
+ stub_const('Gitlab::ClosingIssueExtractor::ISSUE_CLOSING_REGEX',
+ /^([Cc]loses|[Ff]ixes)/)
+ commit.stub(safe_message: "Fixes #{ext_ref}")
+ commit.closes_issues(project).should be_empty
+ end
end
it_behaves_like 'a mentionable' do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index da51100e0d7..c88a03beb0c 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -264,8 +264,8 @@ describe Note do
let(:project) { create :project }
let(:author) { create :user }
let(:issue) { create :issue }
- let(:commit0) { double 'commit0', gfm_reference: 'commit 123456' }
- let(:commit1) { double 'commit1', gfm_reference: 'commit 654321' }
+ let(:commit0) { project.repository.commit }
+ let(:commit1) { project.repository.commit('HEAD~2') }
before do
Note.create_cross_reference_note(issue, commit0, author, project)
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 0d67e7ee4e6..692834c9f29 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -14,13 +14,23 @@ def common_mentionable_setup
let(:mentioned_mr) { create :merge_request, :simple, source_project: mproject }
let(:mentioned_commit) { double('commit', sha: '1234567890abcdef').as_null_object }
+ let(:ext_proj) { create :project, :public }
+ let(:ext_issue) { create :issue, project: ext_proj }
+ let(:other_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 }
+
# Override to add known commits to the repository stub.
let(:extra_commits) { [] }
# 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
- "mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, !#{mentioned_mr.iid}, " +
+ "mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, " +
+ "!#{mentioned_mr.iid}, " +
+ "#{ext_proj.path_with_namespace}##{ext_issue.iid}, " +
+ "#{ext_proj.path_with_namespace}!#{ext_mr.iid}, " +
+ "#{ext_proj.path_with_namespace}@#{ext_commit.id[0..5]}, " +
"#{mentioned_commit.sha[0..5]} and itself as #{backref_text}"
end
@@ -45,14 +55,20 @@ shared_examples 'a mentionable' do
# De-duplicate and omit itself
refs = subject.references(mproject)
- refs.should have(3).items
+ refs.should have(6).items
refs.should include(mentioned_issue)
refs.should include(mentioned_mr)
refs.should include(mentioned_commit)
+ refs.should include(ext_issue)
+ refs.should include(ext_mr)
+ refs.should include(ext_commit)
end
it 'creates cross-reference notes' do
- [mentioned_issue, mentioned_mr, mentioned_commit].each do |referenced|
+ mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit,
+ ext_issue, ext_mr, ext_commit]
+
+ mentioned_objects.each do |referenced|
Note.should_receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject)
end
@@ -73,15 +89,25 @@ shared_examples 'an editable mentionable' do
it_behaves_like 'a mentionable'
it 'creates new cross-reference notes when the mentionable text is edited' do
- new_text = "this text still mentions ##{mentioned_issue.iid} and #{mentioned_commit.sha[0..5]}, " +
- "but now it mentions ##{other_issue.iid}, too."
+ new_text = "still mentions ##{mentioned_issue.iid}, " +
+ "#{mentioned_commit.sha[0..5]}, " +
+ "#{ext_issue.iid}, " +
+ "new refs: ##{other_issue.iid}, " +
+ "#{ext_proj.path_with_namespace}##{other_ext_issue.iid}"
- [mentioned_issue, mentioned_commit].each do |oldref|
+ [mentioned_issue, mentioned_commit, ext_issue].each do |oldref|
Note.should_not_receive(:create_cross_reference_note).with(oldref, subject.local_reference,
mauthor, mproject)
end
- Note.should_receive(:create_cross_reference_note).with(other_issue, subject.local_reference, mauthor, mproject)
+ [other_issue, other_ext_issue].each do |newref|
+ Note.should_receive(:create_cross_reference_note).with(
+ newref,
+ subject.local_reference,
+ mauthor,
+ mproject
+ )
+ end
subject.save
set_mentionable_text.call(new_text)