Make Mentionables work for cross-project refs
Add a note to merge requests and issues when they're mentioned by a merge request, issue, or commit in another project.
This commit is contained in:
parent
2c46c4523f
commit
088987e2dd
|
@ -68,7 +68,9 @@ module Mentionable
|
|||
return [] if text.blank?
|
||||
ext = Gitlab::ReferenceExtractor.new
|
||||
ext.analyze(text, p)
|
||||
(ext.issues_for(p) + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference]
|
||||
(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+.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue