diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 0f89f2e88ccce26ab6b5c225ad0b269993eb82f8..4612abcbae8f75c528e5f289007c1a75b5e6e8ca 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -55,7 +55,7 @@ class Projects::IssuesController < Projects::ApplicationController end def show - @participants = @issue.participants(current_user, @project) + @participants = @issue.participants(current_user) @note = @project.notes.new(noteable: @issue) @notes = @issue.notes.inc_author.fresh @noteable = @issue diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 7570934e7277c18c0cae84997e5754bc8bfa0c62..98df6984bf7d7879ca5c4a9b0aff34dce4b42b82 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -246,7 +246,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_show_vars - @participants = @merge_request.participants(current_user, @project) + @participants = @merge_request.participants(current_user) # Build a note object for comment form @note = @project.notes.new(noteable: @merge_request) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 5b0ae41164200ed37a0eedfb994cacecdbcd4378..b34def66d2e260955609e26e8a0251454da5eb0d 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -41,55 +41,49 @@ module Mentionable self end - # Determine whether or not a cross-reference Note has already been created between this Mentionable and - # the specified target. - def has_mentioned?(target) - SystemNoteService.cross_reference_exists?(target, local_reference) + def all_references(current_user = self.author, text = self.mentionable_text) + ext = Gitlab::ReferenceExtractor.new(self.project, current_user) + ext.analyze(text) + ext end def mentioned_users(current_user = nil) - return [] if mentionable_text.blank? - - ext = Gitlab::ReferenceExtractor.new(self.project, current_user) - ext.analyze(mentionable_text) - ext.users.uniq + all_references(current_user).users.uniq end # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. - def references(p = project, current_user = self.author, text = mentionable_text) + def referenced_mentionables(current_user = self.author, text = self.mentionable_text) return [] if text.blank? - ext = Gitlab::ReferenceExtractor.new(p, current_user) - ext.analyze(text) - - (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference] + refs = all_references(current_user, text) + (refs.issues + refs.merge_requests + refs.commits).uniq - [local_reference] end # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. - def create_cross_references!(p = project, a = author, without = []) - refs = references(p) - + def create_cross_references!(author = self.author, without = [], text = self.mentionable_text) + refs = referenced_mentionables(author, text) + # We're using this method instead of Array diffing because that requires # both of the object's `hash` values to be the same, which may not be the # case for otherwise identical Commit objects. - refs.reject! { |ref| without.include?(ref) } + refs.reject! { |ref| without.include?(ref) || cross_reference_exists?(ref) } refs.each do |ref| - SystemNoteService.cross_reference(ref, local_reference, a) + SystemNoteService.cross_reference(ref, local_reference, author) end end # When a mentionable field is changed, creates cross-reference notes that # don't already exist - def create_new_cross_references!(p = project, a = author) + def create_new_cross_references!(author = self.author) changes = detect_mentionable_changes return if changes.empty? original_text = changes.collect { |_, vals| vals.first }.join(' ') - preexisting = references(p, self.author, original_text) - create_cross_references!(p, a, preexisting) + preexisting = referenced_mentionables(author, original_text) + create_cross_references!(author, preexisting) end private @@ -111,4 +105,10 @@ module Mentionable # Only include changed fields that are mentionable source.select { |key, val| mentionable.include?(key) } end + + # Determine whether or not a cross-reference Note has already been created between this Mentionable and + # the specified target. + def cross_reference_exists?(target) + SystemNoteService.cross_reference_exists?(target, local_reference) + end end diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 7c9597333dd943269a8c83b0e4eeb9bd10930555..ffc874357fdf4b3c101f4e5a97cba5e7cab6f549 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -37,8 +37,8 @@ module Participable # Be aware that this method makes a lot of sql queries. # Save result into variable if you are going to reuse it inside same request - def participants(current_user = self.author, project = self.project) - participants = self.class.participant_attrs.flat_map do |attr| + def participants(current_user = self.author) + self.class.participant_attrs.flat_map do |attr| meth = method(attr) value = @@ -48,28 +48,22 @@ module Participable meth.call end - participants_for(value, current_user, project) - end.compact.uniq - - if project - participants.select! do |user| - user.can?(:read_project, project) - end + participants_for(value, current_user) + end.compact.uniq.select do |user| + user.can?(:read_project, self.project) end - - participants end private - def participants_for(value, current_user = nil, project = nil) + def participants_for(value, current_user = nil) case value when User [value] when Enumerable, ActiveRecord::Relation - value.flat_map { |v| participants_for(v, current_user, project) } + value.flat_map { |v| participants_for(v, current_user) } when Participable - value.participants(current_user, project) + value.participants(current_user) end end end diff --git a/app/models/note.rb b/app/models/note.rb index de3b6df88f7ce8ef07d6c4e8c7db267f2ca28fd3..ee0c14598f3aca97b1a9aaecce0922fc8d7e202e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -62,7 +62,6 @@ class Note < ActiveRecord::Base serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } - after_update :set_references class << self def discussions_from_notes(notes) @@ -333,15 +332,13 @@ class Note < ActiveRecord::Base end def noteable_type_name - if noteable_type.present? - noteable_type.downcase - end + noteable_type.downcase if noteable_type.present? end # FIXME: Hack for polymorphic associations with STI # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations - def noteable_type=(sType) - super(sType.to_s.classify.constantize.base_class.to_s) + def noteable_type=(noteable_type) + super(noteable_type.to_s.classify.constantize.base_class.to_s) end # Reset notes events cache @@ -357,10 +354,6 @@ class Note < ActiveRecord::Base Event.reset_event_cache_for(self) end - def set_references - create_new_cross_references!(project, author) - end - def system? read_attribute(:system) end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f9a8265d2d4d191a2cb86afcec4ec39dfca7d253..81d47602f13cd2fc8ed04c8d0481fdb4cc1fddcc 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -74,48 +74,30 @@ class GitPushService def process_commit_messages(ref) is_default_branch = is_default_branch?(ref) - @push_commits.each do |commit| - # Close issues if these commits were pushed to the project's default branch and the commit message matches the - # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to - # a different branch. - issues_to_close = commit.closes_issues(user) + authors = Hash.new do |hash, commit| + email = commit.author_email + return hash[email] if hash.has_key?(email) - # Load commit author only if needed. - # For push with 1k commits it prevents 900+ requests in database - author = nil + hash[email] = commit_user(commit) + end + @push_commits.each do |commit| # Keep track of the issues that will be actually closed because they are on a default branch. # Hence, when creating cross-reference notes, the not-closed issues (on non-default branches) # will also have cross-reference. - actually_closed_issues = [] - - if issues_to_close.present? && is_default_branch - author ||= commit_user(commit) - actually_closed_issues = issues_to_close - issues_to_close.each do |issue| - Issues::CloseService.new(project, author, {}).execute(issue, commit) + closed_issues = [] + + if is_default_branch + # Close issues if these commits were pushed to the project's default branch and the commit message matches the + # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to + # a different branch. + closed_issues = commit.closes_issues(user) + closed_issues.each do |issue| + Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit) end end - if project.default_issues_tracker? - create_cross_reference_notes(commit, actually_closed_issues) - end - end - end - - def create_cross_reference_notes(commit, issues_to_close) - # Create cross-reference notes for any other references than those given in issues_to_close. - # Omit any issues that were referenced in an issue-closing phrase, or have already been - # mentioned from this commit (probably from this commit being pushed to a different branch). - refs = commit.references(project, user) - issues_to_close - refs.reject! { |r| commit.has_mentioned?(r) } - - if refs.present? - author ||= commit_user(commit) - - refs.each do |r| - SystemNoteService.cross_reference(r, commit, author) - end + commit.create_cross_references!(authors[commit], closed_issues) end end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index 1ea4b72216c6a2e9edeaa682c43b4da8b1e21686..bcb380d3215da95c3b07a1f38105283a8b620be6 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -10,7 +10,7 @@ module Issues issue.update_attributes(label_ids: label_params) notification_service.new_issue(issue, current_user) event_service.open_issue(issue, current_user) - issue.create_cross_references!(issue.project, current_user) + issue.create_cross_references!(current_user) execute_hooks(issue, 'open') end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 2fc6ef7f35642913776680d7e9283bd1d9a21c65..2b5426ad4528f3d33b503840cc4b9c62c88e21be 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -35,7 +35,7 @@ module Issues create_title_change_note(issue, issue.previous_changes['title'].first) end - issue.create_new_cross_references!(issue.project, current_user) + issue.create_new_cross_references! execute_hooks(issue, 'update') end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 9651b16462cb61b2225111e1b1c6c6a99ac0b98d..009d5a6867efab83a9065e9fcc062683dfcc446a 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -18,7 +18,7 @@ module MergeRequests merge_request.update_attributes(label_ids: label_params) event_service.open_mr(merge_request, current_user) notification_service.new_merge_request(merge_request, current_user) - merge_request.create_cross_references!(merge_request.project, current_user) + merge_request.create_cross_references!(current_user) execute_hooks(merge_request) end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 25d79e22e395c9182f508621f7666d05ee63751b..ebbe0af803ba60bc069d7ce10874f18708180ab2 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -59,7 +59,7 @@ module MergeRequests merge_request.mark_as_unchecked end - merge_request.create_new_cross_references!(merge_request.project, current_user) + merge_request.create_new_cross_references! execute_hooks(merge_request, 'update') end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 482c0444049bdef61d8c8fab5e4b0fac8779a265..2001dc89c3332fbbf91770d2dc31aefb7d244eb9 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -11,13 +11,7 @@ module Notes # Skip system notes, like status changes and cross-references. unless note.system event_service.leave_note(note, note.author) - - # Create a cross-reference note if this Note contains GFM that names an - # issue, merge request, or commit. - note.references.each do |mentioned| - SystemNoteService.cross_reference(mentioned, note.noteable, note.author) - end - + note.create_cross_references! execute_hooks(note) end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index c22a9333ef6889d9f36a1992a9b675b5cb7d92da..6c2f08e5963c51e0e2c2f4b44490e7edb638f777 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -4,7 +4,7 @@ module Notes return note unless note.editable? note.update_attributes(params.merge(updated_by: current_user)) - + note.create_new_cross_references! note.reset_events_cache note diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 0961bd80421a966bdc575fcce10eaa055ce6ecfe..30497e274c251bb02aa9d53e293e018ba871b5ec 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -39,6 +39,8 @@ module Gitlab # # Returns the results Array for the requested filter type def pipeline_result(filter_type) + return [] if @text.blank? + klass = filter_type.to_s.camelize + 'ReferenceFilter' filter = Gitlab::Markdown.const_get(klass) diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index e303a97e6b55edd290ced7c3b79f93f5d6934cfd..90be93249511f5b8eef45d4b5d65587758c37553 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -89,9 +89,9 @@ eos end it_behaves_like 'a mentionable' do - subject { commit } + subject { create(:project).commit } - let(:author) { create(:user, email: commit.author_email) } + let(:author) { create(:user, email: subject.author_email) } let(:backref_text) { "commit #{subject.id}" } let(:set_mentionable_text) do ->(txt) { allow(subject).to receive(:safe_message).and_return(txt) } diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 2d6fe003215599acaf050a959299ab5d42ceadf6..6179882e93591f4556ddc9062b5c3ebba2dd14c0 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -25,7 +25,7 @@ describe Issue, "Mentionable" do it 'correctly removes already-mentioned Commits' do expect(SystemNoteService).not_to receive(:cross_reference) - issue.create_cross_references!(project, author, [commit2]) + issue.create_cross_references!(author, [commit2]) end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index cf336d829579ee7c4477bf06b27a9497ae65479d..623332cd2f95c16784f6958ad5443194cf4c78bf 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -69,7 +69,7 @@ describe Issue do end it_behaves_like 'an editable mentionable' do - subject { create(:issue, project: project) } + subject { create(:issue) } let(:backref_text) { "issue #{subject.to_reference}" } let(:set_mentionable_text) { ->(txt){ subject.description = txt } } diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 3a0b194ba1e236d49d4a87c54400d280a710c084..75564839dcfb6baac5c2b44357b1184cd5fc0b73 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -192,10 +192,9 @@ describe Note do end it_behaves_like 'an editable mentionable' do - subject { create :note, noteable: issue, project: project } + subject { create :note, noteable: issue, project: issue.project } - let(:project) { create(:project) } - let(:issue) { create :issue, project: project } + let(:issue) { create :issue } let(:backref_text) { issue.gfm_reference } let(:set_mentionable_text) { ->(txt) { subject.note = txt } } end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index c483060fd73994235eb2c069bb936b79ce78ad60..fd72905c331ab3f72ed61d66b3f3834bb6cec0ae 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -155,7 +155,7 @@ describe GitPushService do before do allow(commit).to receive_messages( - safe_message: "this commit \n mentions ##{issue.id}", + safe_message: "this commit \n mentions #{issue.to_reference}", references: [issue], author_name: commit_author.name, author_email: commit_author.email diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index e3de0afb44825a6b84a7bb9723e22754dfb7538b..f584904845e63c1034274328dff93a6f0814d6af 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -5,7 +5,7 @@ # - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } } def common_mentionable_setup - let(:project) { create :project } + let(:project) { subject.project } let(:author) { subject.author } let(:mentioned_issue) { create(:issue, project: project) } @@ -65,7 +65,7 @@ shared_examples 'a mentionable' do it "extracts references from its reference property" do # De-duplicate and omit itself - refs = subject.references(project) + refs = subject.referenced_mentionables expect(refs.size).to eq(6) expect(refs).to include(mentioned_issue) expect(refs).to include(mentioned_mr) @@ -84,14 +84,7 @@ shared_examples 'a mentionable' do with(referenced, subject.local_reference, author) end - subject.create_cross_references!(project, author) - end - - it 'detects existing cross-references' do - SystemNoteService.cross_reference(mentioned_issue, subject.local_reference, author) - - expect(subject).to have_mentioned(mentioned_issue) - expect(subject).not_to have_mentioned(mentioned_mr) + subject.create_cross_references! end end @@ -143,6 +136,6 @@ shared_examples 'an editable mentionable' do end set_mentionable_text.call(new_text) - subject.create_new_cross_references!(project, author) + subject.create_new_cross_references!(author) end end