From 8f43298d967d715628b9b17d357cb9169a3606bf Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 30 Nov 2015 21:09:36 +0100 Subject: Show local issues and MRs in "This X closes... / closed by..." sentence --- app/helpers/issues_helper.rb | 6 +++++- app/helpers/merge_requests_helper.rb | 6 +++++- app/models/merge_request.rb | 2 +- app/views/projects/issues/_closed_by_box.html.haml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 493f370d9a9..b45d4e1ea8e 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -84,7 +84,11 @@ module IssuesHelper end def merge_requests_sentence(merge_requests) - merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ') + # Sorting based on the `!123` or `group/project!123` reference will sort + # local merge requests first. + merge_requests.map do |merge_request| + merge_request.to_reference(@project) + end.sort.to_sentence(last_word_connector: ', or ') end def url_to_emoji(name) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index b804d4f4e3b..a6010721dff 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -39,7 +39,11 @@ module MergeRequestsHelper end def issues_sentence(issues) - issues.map(&:to_reference).to_sentence + # Sorting based on the `#123` or `group/project#123` reference will sort + # local issues first. + issues.map do |issue| + issue.to_reference(@project) + end.sort.to_sentence end def mr_change_branches_path(merge_request) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1b3d6079d2c..939df8cd8d1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -316,7 +316,7 @@ class MergeRequest < ActiveRecord::Base issues = commits.flat_map { |c| c.closes_issues(current_user) } issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). closed_by_message(description)) - issues.uniq.sort_by(&:id) + issues.uniq else [] end diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml index aef352029d0..917d5181689 100644 --- a/app/views/projects/issues/_closed_by_box.html.haml +++ b/app/views/projects/issues/_closed_by_box.html.haml @@ -1,3 +1,3 @@ .issue-closed-by-widget = icon('check') - This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted. + This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests))} is accepted. -- cgit v1.2.1 From a7be01cd07430a4302668224947b2ed135c2d7bb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 30 Nov 2015 21:10:52 +0100 Subject: Render commit range reference with short shas, link to full shas. --- app/models/commit_range.rb | 70 ++++++++----- .../markdown/commit_range_reference_filter_spec.rb | 7 +- spec/models/commit_range_spec.rb | 111 +++++++++------------ 3 files changed, 94 insertions(+), 94 deletions(-) diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 86fc9eb01a3..fd23e24aff6 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -2,12 +2,12 @@ # # Examples: # -# range = CommitRange.new('f3f85602...e86e1013') +# range = CommitRange.new('f3f85602...e86e1013', project) # range.exclude_start? # => false # range.reference_title # => "Commits f3f85602 through e86e1013" # range.to_s # => "f3f85602...e86e1013" # -# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae') +# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project) # range.exclude_start? # => true # range.reference_title # => "Commits f3f85602^ through e86e1013" # range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"} @@ -21,7 +21,7 @@ class CommitRange include ActiveModel::Conversion include Referable - attr_reader :sha_from, :notation, :sha_to + attr_reader :commit_from, :notation, :commit_to # Optional Project model attr_accessor :project @@ -53,17 +53,22 @@ class CommitRange # project - An optional Project model. # # Raises ArgumentError if `range_string` does not match `PATTERN`. - def initialize(range_string, project = nil) + def initialize(range_string, project) + @project = project + range_string.strip! unless range_string.match(/\A#{PATTERN}\z/) raise ArgumentError, "invalid CommitRange string format: #{range_string}" end - @exclude_start = !range_string.include?('...') - @sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2) + ref_from, @notation, ref_to = range_string.split(/(\.{2,3})/, 2) - @project = project + @exclude_start = @notation == '..' + if project.valid_repo? + @commit_from = project.commit(ref_from) + @commit_to = project.commit(ref_to) + end end def inspect @@ -71,15 +76,16 @@ class CommitRange end def to_s - "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}" + sha_from + notation + sha_to end + alias_method :id, :to_s + def to_reference(from_project = nil) - # Not using to_s because we want the full SHAs - reference = sha_from + notation + sha_to + reference = Commit.truncate_sha(sha_from) + notation + Commit.truncate_sha(sha_to) if cross_project_reference?(from_project) - reference = project.to_reference + '@' + reference + reference = project.to_reference + self.class.reference_prefix + reference end reference @@ -87,14 +93,14 @@ class CommitRange # Returns a String for use in a link's title attribute def reference_title - "Commits #{suffixed_sha_from} through #{sha_to}" + "Commits #{sha_start} through #{sha_to}" end # Return a Hash of parameters for passing to a URL helper # # See `namespace_project_compare_url` def to_param - { from: suffixed_sha_from, to: sha_to } + { from: sha_start, to: sha_to } end def exclude_start? @@ -105,28 +111,42 @@ class CommitRange # repository # # project - An optional Project to check (default: `project`) - def valid_commits?(project = project) - return nil unless project.present? - return false unless project.valid_repo? - - commit_from.present? && commit_to.present? + def valid_commits? + commit_start.present? && commit_end.present? end def persisted? true end - def commit_from - @commit_from ||= project.repository.commit(suffixed_sha_from) + def sha_from + return nil unless @commit_from + + @commit_from.id end - def commit_to - @commit_to ||= project.repository.commit(sha_to) + def sha_to + return nil unless @commit_to + + @commit_to.id + end + + def sha_start + return nil unless sha_from + + exclude_start? ? sha_from + '^' : sha_from end - private + def commit_start + return nil unless sha_start - def suffixed_sha_from - sha_from + (exclude_start? ? '^' : '') + if exclude_start? + @commit_start ||= project.commit(sha_start) + else + commit_from + end end + + alias_method :sha_end, :sha_to + alias_method :commit_end, :commit_to end diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index e5b8d723fe5..e078ff26814 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -8,8 +8,8 @@ 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}") } + let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) } + let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) } it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) @@ -53,7 +53,7 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("See (#{reference}.)") - exp = Regexp.escape(range.to_s) + exp = Regexp.escape(range.to_reference) expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) end @@ -62,6 +62,7 @@ module Gitlab::Markdown expect(project).to receive(:valid_repo?).and_return(true) expect(project.repository).to receive(:commit).with(commit1.id.reverse) + expect(project.repository).to receive(:commit).with(commit2.id) expect(filter(act).to_html).to eq exp end diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 1031af097bd..58283f06972 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -7,50 +7,56 @@ describe CommitRange do it { is_expected.to include_module(Referable) } end - let(:sha_from) { 'f3f85602' } - let(:sha_to) { 'e86e1013' } + let!(:project) { create(:project, :public) } + let!(:commit1) { project.commit("HEAD~2") } + let!(:commit2) { project.commit } - let(:range) { described_class.new("#{sha_from}...#{sha_to}") } - let(:range2) { described_class.new("#{sha_from}..#{sha_to}") } + let(:sha_from) { commit1.short_id } + let(:sha_to) { commit2.short_id } + + let(:full_sha_from) { commit1.id } + let(:full_sha_to) { commit2.id } + + let(:range) { described_class.new("#{sha_from}...#{sha_to}", project) } + let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) } it 'raises ArgumentError when given an invalid range string' do - expect { described_class.new("Foo") }.to raise_error(ArgumentError) + expect { described_class.new("Foo", project) }.to raise_error(ArgumentError) end describe '#to_s' do it 'is correct for three-dot syntax' do - expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}" + expect(range.to_s).to eq "#{full_sha_from}...#{full_sha_to}" end it 'is correct for two-dot syntax' do - expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}" + expect(range2.to_s).to eq "#{full_sha_from}..#{full_sha_to}" end end describe '#to_reference' do - let(:project) { double('project', to_reference: 'namespace1/project') } + let(:cross) { create(:project) } - before do - range.project = project + it 'returns a String reference to the object' do + expect(range.to_reference).to eq "#{sha_from}...#{sha_to}" end it 'returns a String reference to the object' do - expect(range.to_reference).to eq range.to_s + expect(range2.to_reference).to eq "#{sha_from}..#{sha_to}" end it 'supports a cross-project reference' do - cross = double('project') - expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}" + expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}" 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}" + expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}" end it 'returns the correct String for two-dot ranges' do - expect(range2.reference_title).to eq "Commits #{sha_from}^ through #{sha_to}" + expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}" end end @@ -60,11 +66,11 @@ describe CommitRange do end it 'includes the correct values for a three-dot range' do - expect(range.to_param).to eq({ from: sha_from, to: sha_to }) + expect(range.to_param).to eq({ from: full_sha_from, to: full_sha_to }) end it 'includes the correct values for a two-dot range' do - expect(range2.to_param).to eq({ from: sha_from + '^', to: sha_to }) + expect(range2.to_param).to eq({ from: full_sha_from + '^', to: full_sha_to }) end end @@ -79,64 +85,37 @@ describe CommitRange do end describe '#valid_commits?' do - context 'without a project' do - it 'returns nil' do - expect(range.valid_commits?).to be_nil + context 'with a valid repo' do + before do + expect(project).to receive(:valid_repo?).and_return(true) end - end - it 'accepts an optional project argument' do - project1 = double('project1').as_null_object - project2 = double('project2').as_null_object + it 'is false when `sha_from` is invalid' do + expect(project).to receive(:commit).with(sha_from).and_return(nil) + expect(project).to receive(:commit).with(sha_to).and_call_original - # project1 gets assigned through the accessor, but ignored when not given - # as an argument to `valid_commits?` - expect(project1).not_to receive(:present?) - range.project = project1 + expect(range).not_to be_valid_commits + end - # project2 gets passed to `valid_commits?` - expect(project2).to receive(:present?).and_return(false) + it 'is false when `sha_to` is invalid' do + expect(project).to receive(:commit).with(sha_from).and_call_original + expect(project).to receive(:commit).with(sha_to).and_return(nil) - range.valid_commits?(project2) - end + expect(range).not_to be_valid_commits + end - context 'with a project' do - let(:project) { double('project', repository: double('repository')) } - - context 'with a valid repo' do - before do - expect(project).to receive(:valid_repo?).and_return(true) - range.project = project - end - - it 'is false when `sha_from` is invalid' do - expect(project.repository).to receive(:commit).with(sha_from).and_return(false) - expect(project.repository).not_to receive(:commit).with(sha_to) - expect(range).not_to be_valid_commits - end - - it 'is false when `sha_to` is invalid' do - expect(project.repository).to receive(:commit).with(sha_from).and_return(true) - expect(project.repository).to receive(:commit).with(sha_to).and_return(false) - expect(range).not_to be_valid_commits - end - - it 'is true when both `sha_from` and `sha_to` are valid' do - expect(project.repository).to receive(:commit).with(sha_from).and_return(true) - expect(project.repository).to receive(:commit).with(sha_to).and_return(true) - expect(range).to be_valid_commits - end + it 'is true when both `sha_from` and `sha_to` are valid' do + expect(range).to be_valid_commits end + end - context 'without a valid repo' do - before do - expect(project).to receive(:valid_repo?).and_return(false) - range.project = project - end + context 'without a valid repo' do + before do + expect(project).to receive(:valid_repo?).and_return(false) + end - it 'returns false' do - expect(range).not_to be_valid_commits - end + it 'returns false' do + expect(range).not_to be_valid_commits end end end -- cgit v1.2.1 From d6a5b45c8ea5ec7a68e213636fde405c52bb90e4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 30 Nov 2015 21:14:46 +0100 Subject: Recognize issue/MR/snippet/commit links as references. --- app/models/commit.rb | 15 +++-- app/models/commit_range.rb | 11 +++- app/models/concerns/referable.rb | 19 ++++++ app/models/issue.rb | 11 +++- app/models/merge_request.rb | 11 +++- app/models/snippet.rb | 11 +++- lib/gitlab/markdown.rb | 5 +- lib/gitlab/markdown/abstract_reference_filter.rb | 31 +++++++--- .../markdown/commit_range_reference_filter.rb | 70 ++++++---------------- lib/gitlab/markdown/commit_reference_filter.rb | 68 +++++---------------- lib/gitlab/markdown/external_link_filter.rb | 3 + .../markdown/merge_request_reference_filter.rb | 10 ++++ lib/gitlab/markdown/redactor_filter.rb | 5 +- .../markdown/commit_range_reference_filter_spec.rb | 39 +++++++++++- .../markdown/commit_reference_filter_spec.rb | 31 ++++++++++ .../gitlab/markdown/issue_reference_filter_spec.rb | 32 ++++++++++ .../merge_request_reference_filter_spec.rb | 29 ++++++++- .../markdown/snippet_reference_filter_spec.rb | 30 ++++++++++ 18 files changed, 301 insertions(+), 130 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 492f6be1ce3..fc03d2580d7 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -73,16 +73,23 @@ class Commit # This pattern supports cross-project references. def self.reference_pattern %r{ - (?:#{Project.reference_pattern}#{reference_prefix})? - (?\h{6,40}) + #{link_reference_pattern} | + (?: + (?:#{Project.reference_pattern}#{reference_prefix})? + (?\h{6,40}) + ) }x end + def self.link_reference_pattern + super("commit", /(?\h{6,40})/) + end + def to_reference(from_project = nil) if cross_project_reference?(from_project) - "#{project.to_reference}@#{id}" + project.to_reference + self.class.reference_prefix + self.short_id else - id + self.short_id end end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index fd23e24aff6..98067771b71 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -42,11 +42,18 @@ class CommitRange # This pattern supports cross-project references. def self.reference_pattern %r{ - (?:#{Project.reference_pattern}#{reference_prefix})? - (?#{PATTERN}) + #{link_reference_pattern} | + (?: + (?:#{Project.reference_pattern}#{reference_prefix})? + (?#{PATTERN}) + ) }x end + def self.link_reference_pattern + super("compare", /(?#{PATTERN})/) + end + # Initialize a CommitRange # # range_string - The String commit range. diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index cced66cc1e4..16e4d054869 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -44,6 +44,25 @@ module Referable def reference_pattern raise NotImplementedError, "#{self} does not implement #{__method__}" end + + def link_reference_pattern(route, pattern) + %r{ + (? + #{Regexp.escape(Gitlab.config.gitlab.url)} + \/#{Project.reference_pattern} + \/#{Regexp.escape(route)} + \/#{pattern} + (? + (\/[a-z0-9_=-]+)* + )? + (? + \?[a-z0-9_=-]+ + (&[a-z0-9_=-]+)* + )? + (?\#[a-z0-9_-]+)? + ) + }x + end end private diff --git a/app/models/issue.rb b/app/models/issue.rb index 72183108033..e62acfdfd91 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -64,11 +64,18 @@ class Issue < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - (#{Project.reference_pattern})? - #{Regexp.escape(reference_prefix)}(?\d+) + #{link_reference_pattern} | + (?: + (#{Project.reference_pattern})? + #{Regexp.escape(reference_prefix)}(?\d+) + ) }x end + def self.link_reference_pattern + super("issues", /(?\d+)/) + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 939df8cd8d1..c1d3874adee 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -146,11 +146,18 @@ class MergeRequest < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - (#{Project.reference_pattern})? - #{Regexp.escape(reference_prefix)}(?\d+) + #{link_reference_pattern} | + (?: + (#{Project.reference_pattern})? + #{Regexp.escape(reference_prefix)}(?\d+) + ) }x end + def self.link_reference_pattern + super("merge_requests", /(?\d+)/) + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" diff --git a/app/models/snippet.rb b/app/models/snippet.rb index b0831982aa7..8ec12ddf6ef 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -60,11 +60,18 @@ class Snippet < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - (#{Project.reference_pattern})? - #{Regexp.escape(reference_prefix)}(?\d+) + #{link_reference_pattern} | + (?: + (#{Project.reference_pattern})? + #{Regexp.escape(reference_prefix)}(?\d+) + ) }x end + def self.link_reference_pattern + super("snippets", /(?\d+)/) + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{id}" diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index b082bfc434b..ee458eda025 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -181,8 +181,6 @@ module Gitlab Gitlab::Markdown::RelativeLinkFilter, Gitlab::Markdown::EmojiFilter, Gitlab::Markdown::TableOfContentsFilter, - Gitlab::Markdown::AutolinkFilter, - Gitlab::Markdown::ExternalLinkFilter, Gitlab::Markdown::UserReferenceFilter, Gitlab::Markdown::IssueReferenceFilter, @@ -193,6 +191,9 @@ module Gitlab Gitlab::Markdown::CommitReferenceFilter, Gitlab::Markdown::LabelReferenceFilter, + Gitlab::Markdown::AutolinkFilter, + Gitlab::Markdown::ExternalLinkFilter, + Gitlab::Markdown::TaskListFilter ] end diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb index fd5b7eb9332..4adc44361b7 100644 --- a/lib/gitlab/markdown/abstract_reference_filter.rb +++ b/lib/gitlab/markdown/abstract_reference_filter.rb @@ -40,7 +40,7 @@ module Gitlab # Returns a String replaced with the return of the block. def self.references_in(text) text.gsub(object_class.reference_pattern) do |match| - yield match, $~[object_sym].to_i, $~[:project] + yield match, $~[object_sym].to_i, $~[:project], $~ end end @@ -74,26 +74,41 @@ module Gitlab # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. def object_link_filter(text) - references_in(text) do |match, id, project_ref| + references_in(text) do |match, id, project_ref, matches| project = project_from_ref(project_ref) if project && object = find_object(project, id) - title = escape_once("#{object_title}: #{object.title}") + title = escape_once(object_link_title(object)) klass = reference_class(object_sym) - data = data_attribute(project: project.id, object_sym => object.id) - url = url_for_object(object, project) + data = data_attribute(project: project.id, object_sym => object.id, original: match) + url = matches[:url] || url_for_object(object, project) + + text = object.to_reference(context[:project]) + + extras = object_link_text_extras(object, matches) + text += " (#{extras.join(", ")})" if extras.any? %(#{match}) + class="#{klass}">#{text}) else match end end end - def object_title - object_class.name.titleize + def object_link_text_extras(object, matches) + extras = [] + + if matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/ + extras << "comment #{$1}" + end + + extras + end + + def object_link_title(object) + "#{object_class.name.titleize}: #{object.title}" end end end diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb index e070edae0a4..f24bed76193 100644 --- a/lib/gitlab/markdown/commit_range_reference_filter.rb +++ b/lib/gitlab/markdown/commit_range_reference_filter.rb @@ -5,24 +5,14 @@ module Gitlab # HTML filter that replaces commit range references with links. # # This filter supports cross-project references. - class CommitRangeReferenceFilter < ReferenceFilter - include CrossProjectReference + class CommitRangeReferenceFilter < AbstractReferenceFilter + def self.object_class + CommitRange + end - # Public: Find commit range references in text - # - # CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref| - # "#{commit_range}" - # end - # - # text - String text to search. - # - # Yields the String match, the String commit range, and an optional String - # of the external project reference. - # - # Returns a String replaced with the return of the block. def self.references_in(text) text.gsub(CommitRange.reference_pattern) do |match| - yield match, $~[:commit_range], $~[:project] + yield match, $~[:commit_range], $~[:project], $~ end end @@ -31,9 +21,9 @@ module Gitlab return unless project id = node.attr("data-commit-range") - range = CommitRange.new(id, project) + range = find_object(project, id) - return unless range.valid_commits? + return unless range { commit_range: range } end @@ -44,49 +34,25 @@ module Gitlab @commit_map = {} end - def call - replace_text_nodes_matching(CommitRange.reference_pattern) do |content| - commit_range_link_filter(content) - end - end - - # Replace commit range references in text with links to compare the commit - # ranges. - # - # text - String text to replace references in. - # - # Returns a String with commit range references replaced with links. All - # links have `gfm` and `gfm-commit_range` class names attached for - # styling. - def commit_range_link_filter(text) - self.class.references_in(text) do |match, id, project_ref| - project = self.project_from_ref(project_ref) - - range = CommitRange.new(id, project) - - if range.valid_commits? - url = url_for_commit_range(project, range) - - title = range.reference_title - klass = reference_class(:commit_range) - data = data_attribute(project: project.id, commit_range: id) + def self.find_object(project, id) + range = CommitRange.new(id, project) - project_ref += '@' if project_ref + range.valid_commits? ? range : nil + end - %(#{project_ref}#{range}) - else - match - end - end + def find_object(*args) + self.class.find_object(*args) end - def url_for_commit_range(project, range) + def url_for_object(range, project) h = Gitlab::Application.routes.url_helpers h.namespace_project_compare_url(project.namespace, project, range.to_param.merge(only_path: context[:only_path])) end + + def object_link_title(range) + range.reference_title + end end end end diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb index 8cdbeb1f9cf..cc7abc08c87 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -5,24 +5,14 @@ module Gitlab # HTML filter that replaces commit references with links. # # This filter supports cross-project references. - class CommitReferenceFilter < ReferenceFilter - include CrossProjectReference + class CommitReferenceFilter < AbstractReferenceFilter + def self.object_class + Commit + end - # Public: Find commit references in text - # - # CommitReferenceFilter.references_in(text) do |match, commit, project_ref| - # "#{commit}" - # end - # - # text - String text to search. - # - # Yields the String match, the String commit identifier, and an optional - # String of the external project reference. - # - # Returns a String replaced with the return of the block. def self.references_in(text) text.gsub(Commit.reference_pattern) do |match| - yield match, $~[:commit], $~[:project] + yield match, $~[:commit], $~[:project], $~ end end @@ -31,58 +21,32 @@ module Gitlab return unless project id = node.attr("data-commit") - commit = commit_from_ref(project, id) + commit = find_object(project, id) return unless commit { commit: commit } end - def call - replace_text_nodes_matching(Commit.reference_pattern) do |content| - commit_link_filter(content) - end - end - - # Replace commit references in text with links to the commit specified. - # - # text - String text to replace references in. - # - # Returns a String with commit references replaced with links. All links - # have `gfm` and `gfm-commit` class names attached for styling. - def commit_link_filter(text) - self.class.references_in(text) do |match, id, project_ref| - project = self.project_from_ref(project_ref) - - if commit = self.class.commit_from_ref(project, id) - url = url_for_commit(project, commit) - - title = escape_once(commit.link_title) - klass = reference_class(:commit) - data = data_attribute(project: project.id, commit: id) - - project_ref += '@' if project_ref - - %(#{project_ref}#{commit.short_id}) - else - match - end - end - end - - def self.commit_from_ref(project, id) + def self.find_object(project, id) if project && project.valid_repo? project.commit(id) end end - def url_for_commit(project, commit) + def find_object(*args) + self.class.find_object(*args) + end + + def url_for_object(commit, project) h = Gitlab::Application.routes.url_helpers h.namespace_project_commit_url(project.namespace, project, commit, only_path: context[:only_path]) end + + def object_link_title(commit) + commit.link_title + end end end end diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb index 29e51b6ade6..b6792932016 100644 --- a/lib/gitlab/markdown/external_link_filter.rb +++ b/lib/gitlab/markdown/external_link_filter.rb @@ -10,6 +10,9 @@ module Gitlab doc.search('a').each do |node| next unless node.has_attribute?('href') + klass = node.attribute('class') + next if klass && klass.include?('gfm') + link = node.attribute('href').value # Skip non-HTTP(S) links diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 1f47f03c94e..3780a14a130 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -20,6 +20,16 @@ module Gitlab h.namespace_project_merge_request_url(project.namespace, project, mr, only_path: context[:only_path]) end + + def object_link_text_extras(object, matches) + extras = super + + if matches[:path] && matches[:path] == '/diffs' + extras.unshift "diffs" + end + + extras + end end end end diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/redactor_filter.rb index a1f3a8a8ebf..2a58c798f9f 100644 --- a/lib/gitlab/markdown/redactor_filter.rb +++ b/lib/gitlab/markdown/redactor_filter.rb @@ -12,7 +12,10 @@ module Gitlab def call doc.css('a.gfm').each do |node| unless user_can_reference?(node) - node.replace(node.text) + # The reference should be replaced by the original text, + # which is not always the same as the rendered text. + text = node.attribute('data-original') || node.text + node.replace(text) end end diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index e078ff26814..753140d60e6 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -125,7 +125,44 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}") + exp = Regexp.escape("#{project2.to_reference}@#{range.to_reference}") + expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" + expect(filter(act).to_html).to eq exp + + exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" + expect(filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("See #{reference}") + expect(result[:references][:commit_range]).not_to be_empty + end + end + + context 'URL cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) } + + before do + range.project = project2 + end + + it 'links to a valid reference' do + doc = filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + 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(range.to_reference(project)) expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) end diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index d080efbf3d4..c1bcf29b1ba 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -131,5 +131,36 @@ module Gitlab::Markdown expect(result[:references][:commit]).not_to be_empty end end + + context 'URL cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:commit) { project2.commit } + let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } + + it 'links to a valid reference' do + doc = filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id) + end + + it 'links with adjacent text' do + doc = filter("Fixed (#{reference}.)") + + exp = Regexp.escape(project2.to_reference) + expect(doc.to_html).to match(/\(#{commit.to_reference(project)}<\/a>\.\)/) + end + + it 'ignores invalid commit IDs on the referenced project' do + exp = act = "Committed #{invalidate_reference(reference)}" + expect(filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("See #{reference}") + expect(result[:references][:commit]).not_to be_empty + end + end end end diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb index 94c80ae6611..296e8868f46 100644 --- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb @@ -135,5 +135,37 @@ module Gitlab::Markdown expect(result[:references][:issue]).to eq [issue] end end + + context 'URL cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { helper.url_for_issue(issue.iid, project2) + "#note_123" } + + it 'ignores valid references when cross-reference project uses external tracker' do + expect_any_instance_of(Project).to receive(:get_issue). + with(issue.iid).and_return(nil) + + exp = act = "Issue #{reference}" + expect(filter(act).to_html).to eq exp + end + + it 'links to a valid reference' do + doc = filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end end end diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb index 3ef6cdfff33..49d8a6e44da 100644 --- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb @@ -89,7 +89,7 @@ module Gitlab::Markdown context 'cross-project reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, :public, namespace: namespace) } - let(:merge) { create(:merge_request, source_project: project2) } + let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } let(:reference) { merge.to_reference(project) } it 'links to a valid reference' do @@ -97,7 +97,7 @@ module Gitlab::Markdown expect(doc.css('a').first.attr('href')). to eq urls.namespace_project_merge_request_url(project2.namespace, - project, merge) + project2, merge) end it 'links with adjacent text' do @@ -116,5 +116,30 @@ module Gitlab::Markdown expect(result[:references][:merge_request]).to eq [merge] end end + + context 'URL cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:project, :public, namespace: namespace) } + let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } + let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, + project2, merge) + '/diffs#note_123' } + + it 'links to a valid reference' do + doc = filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = filter("Merge (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Merge #{reference}") + expect(result[:references][:merge_request]).to eq [merge] + end + end end end diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb index 9d9652dba46..3080a8a3608 100644 --- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb @@ -114,5 +114,35 @@ module Gitlab::Markdown expect(result[:references][:snippet]).to eq [snippet] end end + + context 'URL cross-project reference' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:snippet) { create(:project_snippet, project: project2) } + let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) } + + it 'links to a valid reference' do + doc = filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) + end + + it 'links with adjacent text' do + doc = filter("See (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/) + end + + it 'ignores invalid snippet IDs on the referenced project' do + exp = act = "See #{invalidate_reference(reference)}" + + expect(filter(act).to_html).to eq exp + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Snippet #{reference}") + expect(result[:references][:snippet]).to eq [snippet] + end + end end end -- cgit v1.2.1 From f9017a2b0384eef3f99ec2f3f1f2ef156afff2b8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 30 Nov 2015 21:15:19 +0100 Subject: Have ClosingIssueExtractor recognize all referenced issues --- config/initializers/1_settings.rb | 2 +- lib/gitlab/closing_issue_extractor.rb | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 62619241001..63d8ae17436 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -164,7 +164,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled']. Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab['max_attachment_size'] ||= 10 diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb index aeec595782c..70b9943d7eb 100644 --- a/lib/gitlab/closing_issue_extractor.rb +++ b/lib/gitlab/closing_issue_extractor.rb @@ -1,6 +1,10 @@ module Gitlab class ClosingIssueExtractor - ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern) + ISSUE_CLOSING_REGEX = begin + pattern = Gitlab.config.gitlab.issue_closing_pattern + pattern = pattern.sub('%{issue_ref}', "(?:#{Issue.reference_pattern})") + Regexp.new(pattern).freeze + end def initialize(project, current_user = nil) @extractor = Gitlab::ReferenceExtractor.new(project, current_user) @@ -9,10 +13,12 @@ module Gitlab def closed_by_message(message) return [] if message.nil? - closing_statements = message.scan(ISSUE_CLOSING_REGEX). - map { |ref| ref[0] }.join(" ") + closing_statements = [] + message.scan(ISSUE_CLOSING_REGEX) do + closing_statements << Regexp.last_match[0] + end - @extractor.analyze(closing_statements) + @extractor.analyze(closing_statements.join(" ")) @extractor.issues end -- cgit v1.2.1 From 4a292aa6042f33d0b63bf95d223ae87bddcea2ce Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 30 Nov 2015 21:15:34 +0100 Subject: Proper cross-project references when MR is closed by issue --- app/services/system_note_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 7e2bc834176..09c159510cd 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -125,7 +125,7 @@ class SystemNoteService # Returns the created Note object def self.change_status(noteable, project, author, status, source) body = "Status changed to #{status}" - body += " by #{source.gfm_reference}" if source + body += " by #{source.gfm_reference(project)}" if source create_note(noteable: noteable, project: project, author: author, note: body) end -- cgit v1.2.1 From bf5e7252ee5ffb5198b7916d1ad8f3436723721a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 30 Nov 2015 21:36:13 +0100 Subject: Recognize commit range with named refs in compare URLs. --- app/models/commit_range.rb | 31 +++++++++++++--------- .../markdown/commit_range_reference_filter_spec.rb | 11 ++++---- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 98067771b71..7b1164b024c 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -22,16 +22,19 @@ class CommitRange include Referable attr_reader :commit_from, :notation, :commit_to + attr_reader :ref_from, :ref_to # Optional Project model attr_accessor :project - # See `exclude_start?` - attr_reader :exclude_start - - # The beginning and ending SHAs can be between 6 and 40 hex characters, and + # The beginning and ending refs can be named or SHAs, and # the range notation can be double- or triple-dot. - PATTERN = /\h{6,40}\.{2,3}\h{6,40}/ + REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/ + PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/ + + # In text references, the beginning and ending refs can only be SHAs + # between 6 and 40 hex characters. + STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/ def self.reference_prefix '@' @@ -45,7 +48,7 @@ class CommitRange #{link_reference_pattern} | (?: (?:#{Project.reference_pattern}#{reference_prefix})? - (?#{PATTERN}) + (?#{STRICT_PATTERN}) ) }x end @@ -69,12 +72,16 @@ class CommitRange raise ArgumentError, "invalid CommitRange string format: #{range_string}" end - ref_from, @notation, ref_to = range_string.split(/(\.{2,3})/, 2) + @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2) - @exclude_start = @notation == '..' if project.valid_repo? - @commit_from = project.commit(ref_from) - @commit_to = project.commit(ref_to) + @commit_from = project.commit(@ref_from) + @commit_to = project.commit(@ref_to) + end + + if valid_commits? + @ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from) + @ref_to = Commit.truncate_sha(sha_to) if sha_to.start_with?(@ref_to) end end @@ -89,7 +96,7 @@ class CommitRange alias_method :id, :to_s def to_reference(from_project = nil) - reference = Commit.truncate_sha(sha_from) + notation + Commit.truncate_sha(sha_to) + reference = ref_from + notation + ref_to if cross_project_reference?(from_project) reference = project.to_reference + self.class.reference_prefix + reference @@ -111,7 +118,7 @@ class CommitRange end def exclude_start? - exclude_start + @notation == '..' end # Check if both the starting and ending commit IDs exist in a project's diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index 753140d60e6..4b4c769a110 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -5,8 +5,8 @@ module Gitlab::Markdown include FilterSpecHelper let(:project) { create(:project, :public) } - let(:commit1) { project.commit } - let(:commit2) { project.commit("HEAD~2") } + let(:commit1) { project.commit("HEAD~2") } + let(:commit2) { project.commit } let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) } let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) } @@ -89,7 +89,7 @@ module Gitlab::Markdown link = doc.css('a').first expect(link).to have_attribute('data-commit-range') - expect(link.attr('data-commit-range')).to eq range.to_reference + expect(link.attr('data-commit-range')).to eq range.to_s end it 'supports an :only_path option' do @@ -146,7 +146,8 @@ module Gitlab::Markdown context 'URL cross-project reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, :public, namespace: namespace) } - let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) } + let(:range) { CommitRange.new("#{commit1.id}...master", project) } + let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') } before do range.project = project2 @@ -156,7 +157,7 @@ module Gitlab::Markdown doc = filter("See #{reference}") expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) + to eq reference end it 'links with adjacent text' do -- cgit v1.2.1 From 4a0ebccf6b96184888f2d28b6e97592c6cb239c7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 30 Nov 2015 22:43:54 +0100 Subject: Fix specs --- app/models/concerns/mentionable.rb | 11 ++++++++--- .../lib/gitlab/markdown/commit_reference_filter_spec.rb | 1 - .../markdown/merge_request_reference_filter_spec.rb | 3 +-- spec/models/commit_spec.rb | 16 ++++++---------- spec/support/mentionable_shared_examples.rb | 17 +++++++---------- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 193c91f1742..0d6cd86aade 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -62,13 +62,18 @@ module Mentionable return [] if text.blank? refs = all_references(current_user, text, load_lazy_references: load_lazy_references) - (refs.issues + refs.merge_requests + refs.commits) - [local_reference] + refs = (refs.issues + refs.merge_requests + refs.commits) + + # 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| ref == local_reference } end # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. 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. @@ -111,7 +116,7 @@ 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) diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index c1bcf29b1ba..a8c9c7efd56 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -148,7 +148,6 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - exp = Regexp.escape(project2.to_reference) expect(doc.to_html).to match(/\(#{commit.to_reference(project)}<\/a>\.\)/) end diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb index 49d8a6e44da..ca3e7151e02 100644 --- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb @@ -121,8 +121,7 @@ module Gitlab::Markdown let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, :public, namespace: namespace) } let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } - let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, - project2, merge) + '/diffs#note_123' } + let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' } it 'links to a valid reference' do doc = filter("See #{reference}") diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 90be9324951..1aaa927c216 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -15,12 +15,12 @@ describe Commit do describe '#to_reference' do it 'returns a String reference to the object' do - expect(commit.to_reference).to eq commit.id + expect(commit.to_reference).to eq commit.short_id end it 'supports a cross-project reference' do cross = double('project') - expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}" + expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.short_id}" end end @@ -77,14 +77,10 @@ eos let(:other_issue) { create :issue, project: other_project } it 'detects issues that this commit is marked as closing' do - allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid}") - expect(commit.closes_issues).to eq([issue]) - end - - it 'does not detect issues from other projects' do ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}" - allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}") - expect(commit.closes_issues).to be_empty + allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}") + expect(commit.closes_issues).to include(issue) + expect(commit.closes_issues).to include(other_issue) end end @@ -92,7 +88,7 @@ eos subject { create(:project).commit } let(:author) { create(:user, email: subject.author_email) } - let(:backref_text) { "commit #{subject.id}" } + let(:backref_text) { "commit #{subject.short_id}" } let(:set_mentionable_text) do ->(txt) { allow(subject).to receive(:safe_message).and_return(txt) } end diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 3bb568f4d49..33d2b14583c 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -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.commit } + let(:mentioned_commit) { project.commit("HEAD~1") } 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.commit } + let(:ext_commit) { ext_proj.commit("HEAD~2") } # Override to add known commits to the repository stub. let(:extra_commits) { [] } @@ -45,14 +45,11 @@ def common_mentionable_setup before do # Wire the project's repository to return the mentioned commit, and +nil+ # for any unrecognized commits. - commitmap = { - mentioned_commit.id => mentioned_commit - } - extra_commits.each { |c| commitmap[c.short_id] = c } - - allow(Project).to receive(:find).and_call_original - allow(Project).to receive(:find).with(project.id.to_s).and_return(project) - allow(project.repository).to receive(:commit) { |sha| commitmap[sha] } + allow_any_instance_of(::Repository).to receive(:commit).and_call_original + allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit) + extra_commits.each do |commit| + allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit) + end set_mentionable_text.call(ref_string) end -- cgit v1.2.1 From bd4ab21c07061e06166b08d86157e4004665ccbc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 1 Dec 2015 12:49:22 +0100 Subject: Fix code docs --- app/models/commit_range.rb | 7 ++----- lib/gitlab/markdown/abstract_reference_filter.rb | 15 +++++++-------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 7b1164b024c..449689faf65 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -13,8 +13,7 @@ # range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"} # range.to_s # => "f3f85602..e86e1013" # -# # Assuming `project` is a Project with a repository containing both commits: -# range.project = project +# # Assuming the specified project has a repository containing both commits: # range.valid_commits? # => true # class CommitRange @@ -68,7 +67,7 @@ class CommitRange range_string.strip! - unless range_string.match(/\A#{PATTERN}\z/) + unless range_string =~ /\A#{PATTERN}\z/ raise ArgumentError, "invalid CommitRange string format: #{range_string}" end @@ -123,8 +122,6 @@ class CommitRange # Check if both the starting and ending commit IDs exist in a project's # repository - # - # project - An optional Project to check (default: `project`) def valid_commits? commit_start.present? && commit_end.present? end diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb index 4adc44361b7..02a9e05a699 100644 --- a/lib/gitlab/markdown/abstract_reference_filter.rb +++ b/lib/gitlab/markdown/abstract_reference_filter.rb @@ -2,8 +2,8 @@ require 'gitlab/markdown' module Gitlab module Markdown - # Issues, Snippets and Merge Requests shares similar functionality in refernce filtering. - # All this functionality moved to this class + # Issues, Snippets, Merge Requests, Commits and Commit Ranges share + # similar functionality in refernce filtering. class AbstractReferenceFilter < ReferenceFilter include CrossProjectReference @@ -26,16 +26,15 @@ module Gitlab # Public: Find references in text (like `!123` for merge requests) # - # AnyReferenceFilter.references_in(text) do |match, object| - # "PREFIX#{object}" + # AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches| + # object = find_object(project_ref, id) + # "#{object.to_reference}" # end # - # PREFIX - symbol that detects reference (like ! for merge requests) - # object - reference object (snippet, merget request etc) # text - String text to search. # - # Yields the String match, the Integer referenced object ID, and an optional String - # of the external project reference. + # Yields the String match, the Integer referenced object ID, an optional String + # of the external project reference, and all of the matchdata. # # Returns a String replaced with the return of the block. def self.references_in(text) -- cgit v1.2.1 From 62c14ba2edf9ac4b4bb1e8c46c0c60f1b6574909 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 1 Dec 2015 12:58:45 +0100 Subject: Render commit reference using short sha, but include full sha in comment. --- app/models/commit.rb | 8 ++++++++ app/models/commit_range.rb | 8 ++++++++ app/models/concerns/referable.rb | 4 ++++ lib/gitlab/markdown/abstract_reference_filter.rb | 2 +- .../markdown/commit_range_reference_filter_spec.rb | 6 +++--- .../markdown/commit_reference_filter_spec.rb | 2 +- spec/models/commit_range_spec.rb | 22 +++++++++++++++++++--- spec/models/commit_spec.rb | 17 ++++++++++++++--- 8 files changed, 58 insertions(+), 11 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index fc03d2580d7..a5d041a49c8 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -86,6 +86,14 @@ class Commit end def to_reference(from_project = nil) + if cross_project_reference?(from_project) + project.to_reference + self.class.reference_prefix + self.id + else + self.id + end + end + + def reference_link_text(from_project = nil) if cross_project_reference?(from_project) project.to_reference + self.class.reference_prefix + self.short_id else diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 449689faf65..f786a749b8e 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -95,6 +95,14 @@ class CommitRange alias_method :id, :to_s def to_reference(from_project = nil) + if cross_project_reference?(from_project) + reference = project.to_reference + self.class.reference_prefix + self.id + else + self.id + end + end + + def reference_link_text(from_project = nil) reference = ref_from + notation + ref_to if cross_project_reference?(from_project) diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index 16e4d054869..ce064f675ae 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -21,6 +21,10 @@ module Referable '' end + def reference_link_text(from_project = nil) + to_reference(from_project) + end + module ClassMethods # The character that prefixes the actual reference identifier # diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb index 02a9e05a699..f6df9518fc6 100644 --- a/lib/gitlab/markdown/abstract_reference_filter.rb +++ b/lib/gitlab/markdown/abstract_reference_filter.rb @@ -82,7 +82,7 @@ module Gitlab data = data_attribute(project: project.id, object_sym => object.id, original: match) url = matches[:url] || url_for_object(object, project) - text = object.to_reference(context[:project]) + text = object.reference_link_text(context[:project]) extras = object_link_text_extras(object, matches) text += " (#{extras.join(", ")})" if extras.any? diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index 4b4c769a110..f65a3e8a0bd 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -53,7 +53,7 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("See (#{reference}.)") - exp = Regexp.escape(range.to_reference) + exp = Regexp.escape(range.reference_link_text) expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) end @@ -125,7 +125,7 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - exp = Regexp.escape("#{project2.to_reference}@#{range.to_reference}") + exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}") expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) end @@ -163,7 +163,7 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - exp = Regexp.escape(range.to_reference(project)) + exp = Regexp.escape(range.reference_link_text(project)) expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) end diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index a8c9c7efd56..4cc6bbbfe94 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -148,7 +148,7 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(#{commit.to_reference(project)}<\/a>\.\)/) + expect(doc.to_html).to match(/\(#{commit.reference_link_text(project)}<\/a>\.\)/) end it 'ignores invalid commit IDs on the referenced project' do diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 58283f06972..3c1009a2eb0 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -38,15 +38,31 @@ describe CommitRange do let(:cross) { create(:project) } it 'returns a String reference to the object' do - expect(range.to_reference).to eq "#{sha_from}...#{sha_to}" + expect(range.to_reference).to eq "#{full_sha_from}...#{full_sha_to}" end it 'returns a String reference to the object' do - expect(range2.to_reference).to eq "#{sha_from}..#{sha_to}" + expect(range2.to_reference).to eq "#{full_sha_from}..#{full_sha_to}" end it 'supports a cross-project reference' do - expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}" + expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}" + end + end + + describe '#reference_link_text' do + let(:cross) { create(:project) } + + it 'returns a String reference to the object' do + expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}" + end + + it 'returns a String reference to the object' do + expect(range2.reference_link_text).to eq "#{sha_from}..#{sha_to}" + end + + it 'supports a cross-project reference' do + expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}" end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 1aaa927c216..974b52c1833 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -15,12 +15,23 @@ describe Commit do describe '#to_reference' do it 'returns a String reference to the object' do - expect(commit.to_reference).to eq commit.short_id + 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.short_id}" + expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}" + end + end + + describe '#reference_link_text' do + it 'returns a String reference to the object' do + expect(commit.reference_link_text).to eq commit.short_id + end + + it 'supports a cross-project reference' do + cross = double('project') + expect(commit.reference_link_text(cross)).to eq "#{project.to_reference}@#{commit.short_id}" end end @@ -88,7 +99,7 @@ eos subject { create(:project).commit } let(:author) { create(:user, email: subject.author_email) } - let(:backref_text) { "commit #{subject.short_id}" } + let(:backref_text) { "commit #{subject.id}" } let(:set_mentionable_text) do ->(txt) { allow(subject).to receive(:safe_message).and_return(txt) } end -- cgit v1.2.1 From f3ea06eb7f8bef748be0882cb0b4fb58deed8eef Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 1 Dec 2015 15:51:27 +0100 Subject: Autolink first so we don't pick up numeric anchors as issue references. --- app/models/commit.rb | 7 ++-- app/models/commit_range.rb | 7 ++-- app/models/issue.rb | 7 ++-- app/models/merge_request.rb | 7 ++-- app/models/snippet.rb | 7 ++-- lib/gitlab/closing_issue_extractor.rb | 2 +- lib/gitlab/markdown.rb | 5 ++- lib/gitlab/markdown/abstract_reference_filter.rb | 20 +++++++---- .../markdown/commit_range_reference_filter.rb | 4 +-- lib/gitlab/markdown/commit_reference_filter.rb | 4 +-- lib/gitlab/markdown/external_link_filter.rb | 7 ++-- .../markdown/merge_request_reference_filter.rb | 2 +- lib/gitlab/markdown/redactor_filter.rb | 2 +- lib/gitlab/markdown/reference_filter.rb | 23 +++++++++++++ lib/gitlab/reference_extractor.rb | 14 ++++++-- .../markdown/commit_range_reference_filter_spec.rb | 40 +++++++++++----------- .../markdown/commit_reference_filter_spec.rb | 36 +++++++++---------- .../gitlab/markdown/issue_reference_filter_spec.rb | 36 +++++++++---------- .../merge_request_reference_filter_spec.rb | 30 ++++++++-------- .../markdown/snippet_reference_filter_spec.rb | 32 ++++++++--------- spec/support/filter_spec_helper.rb | 19 ++++++++-- 21 files changed, 171 insertions(+), 140 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index a5d041a49c8..c0998a45709 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -73,11 +73,8 @@ class Commit # This pattern supports cross-project references. def self.reference_pattern %r{ - #{link_reference_pattern} | - (?: - (?:#{Project.reference_pattern}#{reference_prefix})? - (?\h{6,40}) - ) + (?:#{Project.reference_pattern}#{reference_prefix})? + (?\h{6,40}) }x end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index f786a749b8e..b8bf36b32ce 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -44,11 +44,8 @@ class CommitRange # This pattern supports cross-project references. def self.reference_pattern %r{ - #{link_reference_pattern} | - (?: - (?:#{Project.reference_pattern}#{reference_prefix})? - (?#{STRICT_PATTERN}) - ) + (?:#{Project.reference_pattern}#{reference_prefix})? + (?#{STRICT_PATTERN}) }x end diff --git a/app/models/issue.rb b/app/models/issue.rb index e62acfdfd91..187b6482b6c 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -64,11 +64,8 @@ class Issue < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - #{link_reference_pattern} | - (?: - (#{Project.reference_pattern})? - #{Regexp.escape(reference_prefix)}(?\d+) - ) + (#{Project.reference_pattern})? + #{Regexp.escape(reference_prefix)}(?\d+) }x end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c1d3874adee..2a4aee7e5d9 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -146,11 +146,8 @@ class MergeRequest < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - #{link_reference_pattern} | - (?: - (#{Project.reference_pattern})? - #{Regexp.escape(reference_prefix)}(?\d+) - ) + (#{Project.reference_pattern})? + #{Regexp.escape(reference_prefix)}(?\d+) }x end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 8ec12ddf6ef..f876be7a4c8 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -60,11 +60,8 @@ class Snippet < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - #{link_reference_pattern} | - (?: - (#{Project.reference_pattern})? - #{Regexp.escape(reference_prefix)}(?\d+) - ) + (#{Project.reference_pattern})? + #{Regexp.escape(reference_prefix)}(?\d+) }x end diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb index 70b9943d7eb..0cf4c918736 100644 --- a/lib/gitlab/closing_issue_extractor.rb +++ b/lib/gitlab/closing_issue_extractor.rb @@ -2,7 +2,7 @@ module Gitlab class ClosingIssueExtractor ISSUE_CLOSING_REGEX = begin pattern = Gitlab.config.gitlab.issue_closing_pattern - pattern = pattern.sub('%{issue_ref}', "(?:#{Issue.reference_pattern})") + pattern = pattern.sub('%{issue_ref}', "(?:(?:#{Issue.link_reference_pattern})|(?:#{Issue.reference_pattern}))") Regexp.new(pattern).freeze end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index ee458eda025..b082bfc434b 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -181,6 +181,8 @@ module Gitlab Gitlab::Markdown::RelativeLinkFilter, Gitlab::Markdown::EmojiFilter, Gitlab::Markdown::TableOfContentsFilter, + Gitlab::Markdown::AutolinkFilter, + Gitlab::Markdown::ExternalLinkFilter, Gitlab::Markdown::UserReferenceFilter, Gitlab::Markdown::IssueReferenceFilter, @@ -191,9 +193,6 @@ module Gitlab Gitlab::Markdown::CommitReferenceFilter, Gitlab::Markdown::LabelReferenceFilter, - Gitlab::Markdown::AutolinkFilter, - Gitlab::Markdown::ExternalLinkFilter, - Gitlab::Markdown::TaskListFilter ] end diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb index f6df9518fc6..37ed423eeda 100644 --- a/lib/gitlab/markdown/abstract_reference_filter.rb +++ b/lib/gitlab/markdown/abstract_reference_filter.rb @@ -37,8 +37,8 @@ module Gitlab # of the external project reference, and all of the matchdata. # # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(object_class.reference_pattern) do |match| + def self.references_in(text, pattern = object_class.reference_pattern) + text.gsub(pattern) do |match| yield match, $~[object_sym].to_i, $~[:project], $~ end end @@ -61,7 +61,11 @@ module Gitlab def call replace_text_nodes_matching(object_class.reference_pattern) do |content| - object_link_filter(content) + object_link_filter(content, object_class.reference_pattern) + end + + replace_link_nodes_matching(object_class.link_reference_pattern) do |content| + object_link_filter(content, object_class.link_reference_pattern) end end @@ -72,15 +76,17 @@ module Gitlab # # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. - def object_link_filter(text) - references_in(text) do |match, id, project_ref, matches| + def object_link_filter(text, pattern) + references_in(text, pattern) do |match, id, project_ref, matches| project = project_from_ref(project_ref) if project && object = find_object(project, id) title = escape_once(object_link_title(object)) klass = reference_class(object_sym) data = data_attribute(project: project.id, object_sym => object.id, original: match) - url = matches[:url] || url_for_object(object, project) + + url = matches[:url] if matches.names.include?("url") + url ||= url_for_object(object, project) text = object.reference_link_text(context[:project]) @@ -99,7 +105,7 @@ module Gitlab def object_link_text_extras(object, matches) extras = [] - if matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/ + if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/ extras << "comment #{$1}" end diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb index f24bed76193..36b3258ef76 100644 --- a/lib/gitlab/markdown/commit_range_reference_filter.rb +++ b/lib/gitlab/markdown/commit_range_reference_filter.rb @@ -10,8 +10,8 @@ module Gitlab CommitRange end - def self.references_in(text) - text.gsub(CommitRange.reference_pattern) do |match| + def self.references_in(text, pattern = CommitRange.reference_pattern) + text.gsub(pattern) do |match| yield match, $~[:commit_range], $~[:project], $~ end end diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb index cc7abc08c87..b4036578e60 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -10,8 +10,8 @@ module Gitlab Commit end - def self.references_in(text) - text.gsub(Commit.reference_pattern) do |match| + def self.references_in(text, pattern = Commit.reference_pattern) + text.gsub(pattern) do |match| yield match, $~[:commit], $~[:project], $~ end end diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb index b6792932016..e09dfcb83c8 100644 --- a/lib/gitlab/markdown/external_link_filter.rb +++ b/lib/gitlab/markdown/external_link_filter.rb @@ -8,12 +8,9 @@ module Gitlab class ExternalLinkFilter < HTML::Pipeline::Filter def call doc.search('a').each do |node| - next unless node.has_attribute?('href') + link = node.attr('href') - klass = node.attribute('class') - next if klass && klass.include?('gfm') - - link = node.attribute('href').value + next unless link # Skip non-HTTP(S) links next unless link.start_with?('http') diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 3780a14a130..de71fc76a9b 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -24,7 +24,7 @@ module Gitlab def object_link_text_extras(object, matches) extras = super - if matches[:path] && matches[:path] == '/diffs' + if matches.names.include?("path") && matches[:path] && matches[:path] == '/diffs' extras.unshift "diffs" end diff --git a/lib/gitlab/markdown/redactor_filter.rb b/lib/gitlab/markdown/redactor_filter.rb index 2a58c798f9f..bea714a01e7 100644 --- a/lib/gitlab/markdown/redactor_filter.rb +++ b/lib/gitlab/markdown/redactor_filter.rb @@ -14,7 +14,7 @@ module Gitlab unless user_can_reference?(node) # The reference should be replaced by the original text, # which is not always the same as the rendered text. - text = node.attribute('data-original') || node.text + text = node.attr('data-original') || node.text node.replace(text) end end diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index a4c560f578c..e52633ee74c 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -122,6 +122,29 @@ module Gitlab doc end + def replace_link_nodes_matching(pattern) + return doc if project.nil? + + doc.search('a').each do |node| + klass = node.attr('class') + next if klass && klass.include?('gfm') + + link = node.attr('href') + text = node.text + + # Ignore ending punctionation like periods or commas + next unless link == text && text =~ /\A#{pattern}/ + + html = yield text + + next if html == text + + node.replace(html) + end + + doc + end + # Ensure that a :project key exists in context # # Note that while the key might exist, its value could be nil! diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index da8df8a3025..3c3478a1271 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -41,14 +41,14 @@ 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) context = { project: project, current_user: current_user, - + # We don't actually care about the links generated only_path: true, ignore_blockquotes: true, @@ -58,7 +58,15 @@ module Gitlab reference_filter: filter } - pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context) + # We need to autolink first to finds links to referables, and to prevent + # numeric anchors to be parsed as issue references. + filters = [ + Gitlab::Markdown::AutolinkFilter, + filter, + Gitlab::Markdown::ReferenceGathererFilter + ] + + pipeline = HTML::Pipeline.new(filters, context) result = pipeline.call(@text) values = result[:references][filter_type].uniq diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index f65a3e8a0bd..92158382790 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -18,7 +18,7 @@ module Gitlab::Markdown %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do exp = act = "<#{elem}>Commit Range #{range.to_reference}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end @@ -27,14 +27,14 @@ module Gitlab::Markdown let(:reference2) { range2.to_reference } it 'links to a valid two-dot reference' do - doc = filter("See #{reference2}") + doc = reference_filter("See #{reference2}") expect(doc.css('a').first.attr('href')). 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}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param) @@ -46,12 +46,12 @@ module Gitlab::Markdown exp = commit1.short_id + '...' + commit2.short_id - expect(filter("See #{reference}").css('a').first.text).to eq exp - expect(filter("See #{reference2}").css('a').first.text).to eq exp + expect(reference_filter("See #{reference}").css('a').first.text).to eq exp + expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp end it 'links with adjacent text' do - doc = filter("See (#{reference}.)") + doc = reference_filter("See (#{reference}.)") exp = Regexp.escape(range.reference_link_text) expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) @@ -63,21 +63,21 @@ module Gitlab::Markdown expect(project).to receive(:valid_repo?).and_return(true) expect(project.repository).to receive(:commit).with(commit1.id.reverse) expect(project.repository).to receive(:commit).with(commit2.id) - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'includes a title attribute' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('title')).to eq range.reference_title end it 'includes default classes' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range' end it 'includes a data-project attribute' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") link = doc.css('a').first expect(link).to have_attribute('data-project') @@ -85,7 +85,7 @@ module Gitlab::Markdown end it 'includes a data-commit-range attribute' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") link = doc.css('a').first expect(link).to have_attribute('data-commit-range') @@ -93,7 +93,7 @@ module Gitlab::Markdown end it 'supports an :only_path option' do - doc = filter("See #{reference}", only_path: true) + doc = reference_filter("See #{reference}", only_path: true) link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) @@ -116,14 +116,14 @@ module Gitlab::Markdown end it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) end it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") + doc = reference_filter("Fixed (#{reference}.)") exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}") expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) @@ -131,10 +131,10 @@ module Gitlab::Markdown it 'ignores invalid commit IDs on the referenced project' do exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'adds to the results hash' do @@ -154,14 +154,14 @@ module Gitlab::Markdown end it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). to eq reference end it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") + doc = reference_filter("Fixed (#{reference}.)") exp = Regexp.escape(range.reference_link_text(project)) expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) @@ -169,10 +169,10 @@ module Gitlab::Markdown it 'ignores invalid commit IDs on the referenced project' do exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'adds to the results hash' do diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index 4cc6bbbfe94..6fe9b165ff5 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -14,7 +14,7 @@ module Gitlab::Markdown %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do exp = act = "<#{elem}>Commit #{commit.id}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end @@ -24,7 +24,7 @@ module Gitlab::Markdown # Let's test a variety of commit SHA sizes just to be paranoid [6, 8, 12, 18, 20, 32, 40].each do |size| it "links to a valid reference of #{size} characters" do - doc = filter("See #{reference[0...size]}") + doc = reference_filter("See #{reference[0...size]}") expect(doc.css('a').first.text).to eq commit.short_id expect(doc.css('a').first.attr('href')). @@ -33,15 +33,15 @@ module Gitlab::Markdown end it 'always uses the short ID as the link text' do - doc = filter("See #{commit.id}") + doc = reference_filter("See #{commit.id}") expect(doc.text).to eq "See #{commit.short_id}" - doc = filter("See #{commit.id[0...6]}") + doc = reference_filter("See #{commit.id[0...6]}") expect(doc.text).to eq "See #{commit.short_id}" end it 'links with adjacent text' do - doc = filter("See (#{reference}.)") + doc = reference_filter("See (#{reference}.)") expect(doc.to_html).to match(/\(#{commit.short_id}<\/a>\.\)/) end @@ -51,28 +51,28 @@ module Gitlab::Markdown expect(project).to receive(:valid_repo?).and_return(true) expect(project.repository).to receive(:commit).with(invalid) - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'includes a title attribute' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('title')).to eq commit.link_title end it 'escapes the title attribute' do allow_any_instance_of(Commit).to receive(:title).and_return(%{">whatever#{exp}@#{commit.short_id}<\/a>\.\)/) @@ -123,7 +123,7 @@ module Gitlab::Markdown it 'ignores invalid commit IDs on the referenced project' do exp = act = "Committed #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'adds to the results hash' do @@ -139,21 +139,21 @@ module Gitlab::Markdown let(:reference) { urls.namespace_project_commit_url(project2.namespace, project2, commit.id) } it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id) end it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") + doc = reference_filter("Fixed (#{reference}.)") expect(doc.to_html).to match(/\(#{commit.reference_link_text(project)}<\/a>\.\)/) end it 'ignores invalid commit IDs on the referenced project' do exp = act = "Committed #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) end it 'adds to the results hash' do diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb index 296e8868f46..0a741688b82 100644 --- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb @@ -18,7 +18,7 @@ module Gitlab::Markdown %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do exp = act = "<#{elem}>Issue #{issue.to_reference}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end @@ -29,18 +29,18 @@ module Gitlab::Markdown expect(project).to receive(:get_issue).with(issue.iid).and_return(nil) exp = act = "Issue #{reference}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'links to a valid reference' do - doc = filter("Fixed #{reference}") + doc = reference_filter("Fixed #{reference}") expect(doc.css('a').first.attr('href')). to eq helper.url_for_issue(issue.iid, project) end it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") + doc = reference_filter("Fixed (#{reference}.)") expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) end @@ -48,28 +48,28 @@ module Gitlab::Markdown invalid = invalidate_reference(reference) exp = act = "Fixed #{invalid}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'includes a title attribute' do - doc = filter("Issue #{reference}") + doc = reference_filter("Issue #{reference}") expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}" end it 'escapes the title attribute' do issue.update_attribute(:title, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) end it 'ignores invalid issue IDs on the referenced project' do exp = act = "Fixed #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'adds to the results hash' do @@ -147,18 +147,18 @@ module Gitlab::Markdown with(issue.iid).and_return(nil) exp = act = "Issue #{reference}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to match(/#{Regexp.escape(reference)}<\/a>/) end it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). to eq reference end it 'links with adjacent text' do - doc = filter("Fixed (#{reference}.)") + doc = reference_filter("Fixed (#{reference}.)") expect(doc.to_html).to match(/\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/) end diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb index ca3e7151e02..cdb3390e793 100644 --- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb @@ -14,7 +14,7 @@ module Gitlab::Markdown %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do exp = act = "<#{elem}>Merge #{merge.to_reference}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end @@ -22,42 +22,42 @@ module Gitlab::Markdown let(:reference) { merge.to_reference } it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. namespace_project_merge_request_url(project.namespace, project, merge) end it 'links with adjacent text' do - doc = filter("Merge (#{reference}.)") + doc = reference_filter("Merge (#{reference}.)") expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) end it 'ignores invalid merge IDs' do exp = act = "Merge #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'includes a title attribute' do - doc = filter("Merge #{reference}") + doc = reference_filter("Merge #{reference}") expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}" end it 'escapes the title attribute' do merge.update_attribute(:title, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) end it 'ignores invalid merge IDs on the referenced project' do exp = act = "Merge #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'adds to the results hash' do @@ -124,14 +124,14 @@ module Gitlab::Markdown let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' } it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). to eq reference end it 'links with adjacent text' do - doc = filter("Merge (#{reference}.)") + doc = reference_filter("Merge (#{reference}.)") expect(doc.to_html).to match(/\(#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/) end diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb index 3080a8a3608..73d20957a56 100644 --- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb @@ -15,48 +15,48 @@ module Gitlab::Markdown %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do exp = act = "<#{elem}>Snippet #{reference}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end context 'internal reference' do it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. namespace_project_snippet_url(project.namespace, project, snippet) end it 'links with adjacent text' do - doc = filter("Snippet (#{reference}.)") + doc = reference_filter("Snippet (#{reference}.)") expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) end it 'ignores invalid snippet IDs' do exp = act = "Snippet #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'includes a title attribute' do - doc = filter("Snippet #{reference}") + doc = reference_filter("Snippet #{reference}") expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}" end it 'escapes the title attribute' do snippet.update_attribute(:title, %{">whatever#{Regexp.escape(reference)}<\/a>\.\)/) end it 'ignores invalid snippet IDs on the referenced project' do exp = act = "See #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end it 'adds to the results hash' do @@ -122,21 +122,21 @@ module Gitlab::Markdown let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) } it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) end it 'links with adjacent text' do - doc = filter("See (#{reference}.)") + doc = reference_filter("See (#{reference}.)") expect(doc.to_html).to match(/\(#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/) end it 'ignores invalid snippet IDs on the referenced project' do exp = act = "See #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) end it 'adds to the results hash' do diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb index 97e5c270a59..91e3bee13c1 100644 --- a/spec/support/filter_spec_helper.rb +++ b/spec/support/filter_spec_helper.rb @@ -35,11 +35,24 @@ module FilterSpecHelper pipeline.call(body) end - def reference_pipeline_result(body, contexts = {}) + def reference_pipeline(contexts = {}) contexts.reverse_merge!(project: project) if defined?(project) - pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts) - pipeline.call(body) + filters = [ + Gitlab::Markdown::AutolinkFilter, + described_class, + Gitlab::Markdown::ReferenceGathererFilter + ] + + HTML::Pipeline.new(filters, contexts) + end + + def reference_pipeline_result(body, contexts = {}) + reference_pipeline(contexts).call(body) + end + + def reference_filter(html, contexts = {}) + reference_pipeline(contexts).to_document(html) end # Modify a String reference to make it invalid -- cgit v1.2.1 From 6dad2bc6e67f47149e8981730cf3f08938794ceb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 1 Dec 2015 16:15:53 +0100 Subject: Fix referenced_mentionables method. --- app/models/concerns/mentionable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 0d6cd86aade..634a8d0f274 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -67,7 +67,7 @@ module Mentionable # 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| ref == local_reference } + refs.reject { |ref| ref == local_reference } end # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. -- cgit v1.2.1 From 1d6d757dbd563500671f57f45faa808510a612d1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 1 Dec 2015 16:25:56 +0100 Subject: Allow reference format as link href --- lib/gitlab/markdown.rb | 3 ++- lib/gitlab/markdown/abstract_reference_filter.rb | 22 ++++++++++------ .../markdown/external_issue_reference_filter.rb | 10 ++++++-- lib/gitlab/markdown/label_reference_filter.rb | 10 ++++++-- lib/gitlab/markdown/reference_filter.rb | 29 +++++++++++++++++++++- lib/gitlab/markdown/relative_link_filter.rb | 3 +++ lib/gitlab/markdown/user_reference_filter.rb | 28 ++++++++++++--------- 7 files changed, 80 insertions(+), 25 deletions(-) diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index b082bfc434b..886a09f52af 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -178,7 +178,6 @@ module Gitlab Gitlab::Markdown::SanitizationFilter, Gitlab::Markdown::UploadLinkFilter, - Gitlab::Markdown::RelativeLinkFilter, Gitlab::Markdown::EmojiFilter, Gitlab::Markdown::TableOfContentsFilter, Gitlab::Markdown::AutolinkFilter, @@ -193,6 +192,8 @@ module Gitlab Gitlab::Markdown::CommitReferenceFilter, Gitlab::Markdown::LabelReferenceFilter, + Gitlab::Markdown::RelativeLinkFilter, + Gitlab::Markdown::TaskListFilter ] end diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb index 37ed423eeda..b044a73ed17 100644 --- a/lib/gitlab/markdown/abstract_reference_filter.rb +++ b/lib/gitlab/markdown/abstract_reference_filter.rb @@ -2,7 +2,7 @@ require 'gitlab/markdown' module Gitlab module Markdown - # Issues, Snippets, Merge Requests, Commits and Commit Ranges share + # Issues, Merge Requests, Snippets, Commits and Commit Ranges share # similar functionality in refernce filtering. class AbstractReferenceFilter < ReferenceFilter include CrossProjectReference @@ -64,8 +64,13 @@ module Gitlab object_link_filter(content, object_class.reference_pattern) end - replace_link_nodes_matching(object_class.link_reference_pattern) do |content| - object_link_filter(content, object_class.link_reference_pattern) + replace_link_nodes_with_href(object_class.reference_pattern) do |link, text| + object_link_filter(link, object_class.reference_pattern, link_text: text) + end + + replace_link_nodes_with_text(object_class.link_reference_pattern) do |text| + object_link_filter(text, object_class.link_reference_pattern) + end end end @@ -76,7 +81,7 @@ module Gitlab # # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. - def object_link_filter(text, pattern) + def object_link_filter(text, pattern, link_text: nil) references_in(text, pattern) do |match, id, project_ref, matches| project = project_from_ref(project_ref) @@ -88,10 +93,13 @@ module Gitlab url = matches[:url] if matches.names.include?("url") url ||= url_for_object(object, project) - text = object.reference_link_text(context[:project]) + text = link_text + unless text + text = object.reference_link_text(context[:project]) - extras = object_link_text_extras(object, matches) - text += " (#{extras.join(", ")})" if extras.any? + extras = object_link_text_extras(object, matches) + text += " (#{extras.join(", ")})" if extras.any? + end %(#{match}) + class="#{klass}">#{text}) end end diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 618acb7a578..4d0507b607d 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -30,6 +30,10 @@ module Gitlab replace_text_nodes_matching(Label.reference_pattern) do |content| label_link_filter(content) end + + replace_link_nodes_with_href(Label.reference_pattern) do |link, text| + label_link_filter(link, link_text: text) + end end # Replace label references in text with links to the label specified. @@ -38,7 +42,7 @@ module Gitlab # # Returns a String with label references replaced with links. All links # have `gfm` and `gfm-label` class names attached for styling. - def label_link_filter(text) + def label_link_filter(text, link_text: nil) project = context[:project] self.class.references_in(text) do |match, id, name| @@ -49,8 +53,10 @@ module Gitlab klass = reference_class(:label) data = data_attribute(project: project.id, label: label.id) + text = link_text || render_colored_label(label) + %(#{render_colored_label(label)}) + class="#{klass}">#{text}) else match end diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index e52633ee74c..2597784c7ae 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -122,7 +122,7 @@ module Gitlab doc end - def replace_link_nodes_matching(pattern) + def replace_link_nodes_with_text(pattern) return doc if project.nil? doc.search('a').each do |node| @@ -132,6 +132,9 @@ module Gitlab link = node.attr('href') text = node.text + next unless link && text + + link = URI.decode(link) # Ignore ending punctionation like periods or commas next unless link == text && text =~ /\A#{pattern}/ @@ -145,6 +148,30 @@ module Gitlab doc end + def replace_link_nodes_with_href(pattern) + return doc if project.nil? + + doc.search('a').each do |node| + klass = node.attr('class') + next if klass && klass.include?('gfm') + + link = node.attr('href') + text = node.text + + next unless link && text + link = URI.decode(link) + next unless link && link =~ /\A#{pattern}\z/ + + html = yield link, text + + next if html == link + + node.replace(html) + end + + doc + end + # Ensure that a :project key exists in context # # Note that while the key might exist, its value could be nil! diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb index 632be4d7542..692c51fd324 100644 --- a/lib/gitlab/markdown/relative_link_filter.rb +++ b/lib/gitlab/markdown/relative_link_filter.rb @@ -17,6 +17,9 @@ module Gitlab return doc unless linkable_files? doc.search('a').each do |el| + klass = el.attr('class') + next if klass && klass.include?('gfm') + process_link_attr el.attribute('href') end diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb index ab5e1f6fe9e..0a20d9c0347 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -52,6 +52,10 @@ module Gitlab replace_text_nodes_matching(User.reference_pattern) do |content| user_link_filter(content) end + + replace_link_nodes_with_href(User.reference_pattern) do |link, text| + user_link_filter(link, link_text: text) + end end # Replace `@user` user references in text with links to the referenced @@ -61,12 +65,12 @@ module Gitlab # # Returns a String with `@user` references replaced with links. All links # have `gfm` and `gfm-project_member` class names attached for styling. - def user_link_filter(text) + def user_link_filter(text, link_text: nil) self.class.references_in(text) do |match, username| if username == 'all' - link_to_all + link_to_all(link_text: link_text) elsif namespace = Namespace.find_by(path: username) - link_to_namespace(namespace) || match + link_to_namespace(namespace, link_text: link_text) || match else match end @@ -83,36 +87,36 @@ module Gitlab reference_class(:project_member) end - def link_to_all + def link_to_all(link_text: nil) project = context[:project] url = urls.namespace_project_url(project.namespace, project, only_path: context[:only_path]) data = data_attribute(project: project.id) - text = User.reference_prefix + 'all' + text = link_text || User.reference_prefix + 'all' link_tag(url, data, text) end - def link_to_namespace(namespace) + def link_to_namespace(namespace, link_text: nil) if namespace.is_a?(Group) - link_to_group(namespace.path, namespace) + link_to_group(namespace.path, namespace, link_text: link_text) else - link_to_user(namespace.path, namespace) + link_to_user(namespace.path, namespace, link_text: link_text) end end - def link_to_group(group, namespace) + def link_to_group(group, namespace, link_text: nil) url = urls.group_url(group, only_path: context[:only_path]) data = data_attribute(group: namespace.id) - text = Group.reference_prefix + group + text = link_text || Group.reference_prefix + group link_tag(url, data, text) end - def link_to_user(user, namespace) + def link_to_user(user, namespace, link_text: nil) url = urls.user_url(user, only_path: context[:only_path]) data = data_attribute(user: namespace.owner_id) - text = User.reference_prefix + user + text = link_text || User.reference_prefix + user link_tag(url, data, text) end -- cgit v1.2.1 From d4030a845eebcb913a7aac1e8fd502706669d0cc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 1 Dec 2015 16:26:05 +0100 Subject: Pick up direct links to issues/MRs as references. --- lib/gitlab/markdown/abstract_reference_filter.rb | 17 +++++- .../markdown/commit_range_reference_filter_spec.rb | 2 +- .../markdown/commit_reference_filter_spec.rb | 2 +- .../gitlab/markdown/issue_reference_filter_spec.rb | 56 ++++++++++++++--- .../gitlab/markdown/label_reference_filter_spec.rb | 71 ++++++++++++++++------ .../merge_request_reference_filter_spec.rb | 2 +- .../markdown/snippet_reference_filter_spec.rb | 2 +- .../gitlab/markdown/user_reference_filter_spec.rb | 51 ++++++++++++---- 8 files changed, 161 insertions(+), 42 deletions(-) diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb index b044a73ed17..0ec55c0207e 100644 --- a/lib/gitlab/markdown/abstract_reference_filter.rb +++ b/lib/gitlab/markdown/abstract_reference_filter.rb @@ -60,17 +60,27 @@ module Gitlab end def call + # `#123` replace_text_nodes_matching(object_class.reference_pattern) do |content| object_link_filter(content, object_class.reference_pattern) end + # `[Issue](#123)`, which is turned into + # `Issue` replace_link_nodes_with_href(object_class.reference_pattern) do |link, text| object_link_filter(link, object_class.reference_pattern, link_text: text) end + # `http://gitlab.example.com/namespace/project/issues/123`, which is turned into + # `http://gitlab.example.com/namespace/project/issues/123` replace_link_nodes_with_text(object_class.link_reference_pattern) do |text| object_link_filter(text, object_class.link_reference_pattern) end + + # `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into + # `Issue` + replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text| + object_link_filter(link, object_class.link_reference_pattern, link_text: text) end end @@ -88,7 +98,12 @@ module Gitlab if project && object = find_object(project, id) title = escape_once(object_link_title(object)) klass = reference_class(object_sym) - data = data_attribute(project: project.id, object_sym => object.id, original: match) + + data = data_attribute( + original: link_text || match, + project: project.id, + object_sym => object.id + ) url = matches[:url] if matches.names.include?("url") url ||= url_for_object(object, project) diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index 92158382790..9ce63f9af46 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -143,7 +143,7 @@ module Gitlab::Markdown end end - context 'URL cross-project reference' do + context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, :public, namespace: namespace) } let(:range) { CommitRange.new("#{commit1.id}...master", project) } diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index 6fe9b165ff5..78a3603269c 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -132,7 +132,7 @@ module Gitlab::Markdown end end - context 'URL cross-project reference' do + context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, :public, namespace: namespace) } let(:commit) { project2.commit } diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb index 0a741688b82..078ff3ed4b2 100644 --- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb @@ -136,30 +136,70 @@ module Gitlab::Markdown end end - context 'URL cross-project reference' do + context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:issue) { create(:issue, project: project2) } let(:reference) { helper.url_for_issue(issue.iid, project2) + "#note_123" } - it 'ignores valid references when cross-reference project uses external tracker' do - expect_any_instance_of(Project).to receive(:get_issue). - with(issue.iid).and_return(nil) + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") - exp = act = "Issue #{reference}" - expect(reference_filter(act).to_html).to match(/#{Regexp.escape(reference)}<\/a>/) + expect(doc.css('a').first.attr('href')). + to eq reference + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/) + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end + + context 'cross-project reference in link href' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { %Q{Reference} } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')). + to eq helper.url_for_issue(issue.iid, project2) + end + + it 'links with adjacent text' do + doc = reference_filter("Fixed (#{reference}.)") + expect(doc.to_html).to match(/\(Reference<\/a>\.\)/) end + it 'adds to the results hash' do + result = reference_pipeline_result("Fixed #{reference}") + expect(result[:references][:issue]).to eq [issue] + end + end + + context 'cross-project URL in link href' do + let(:namespace) { create(:namespace, name: 'cross-reference') } + let(:project2) { create(:empty_project, :public, namespace: namespace) } + let(:issue) { create(:issue, project: project2) } + let(:reference) { %Q{Reference} } + it 'links to a valid reference' do doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')). - to eq reference + to eq helper.url_for_issue(issue.iid, project2) + "#note_123" end it 'links with adjacent text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/) + expect(doc.to_html).to match(/\(Reference<\/a>\.\)/) end it 'adds to the results hash' do diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb index fc21b65a843..ef6dd524aba 100644 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb @@ -16,17 +16,17 @@ module Gitlab::Markdown %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do exp = act = "<#{elem}>Label #{reference}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end it 'includes default classes' do - doc = filter("Label #{reference}") + doc = reference_filter("Label #{reference}") expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' end it 'includes a data-project attribute' do - doc = filter("Label #{reference}") + doc = reference_filter("Label #{reference}") link = doc.css('a').first expect(link).to have_attribute('data-project') @@ -34,7 +34,7 @@ module Gitlab::Markdown end it 'includes a data-label attribute' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") link = doc.css('a').first expect(link).to have_attribute('data-label') @@ -42,7 +42,7 @@ module Gitlab::Markdown end it 'supports an :only_path context' do - doc = filter("Label #{reference}", only_path: true) + doc = reference_filter("Label #{reference}", only_path: true) link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) @@ -56,33 +56,33 @@ module Gitlab::Markdown describe 'label span element' do it 'includes default classes' do - doc = filter("Label #{reference}") + doc = reference_filter("Label #{reference}") expect(doc.css('a span').first.attr('class')).to eq 'label color-label' end it 'includes a style attribute' do - doc = filter("Label #{reference}") + doc = reference_filter("Label #{reference}") expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/) end end context 'Integer-based references' do it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. namespace_project_issues_url(project.namespace, project, label_name: label.name) end it 'links with adjacent text' do - doc = filter("Label (#{reference}.)") + doc = reference_filter("Label (#{reference}.)") expect(doc.to_html).to match(%r(\(#{label.name}\.\))) end it 'ignores invalid label IDs' do exp = act = "Label #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end @@ -91,7 +91,7 @@ module Gitlab::Markdown let(:reference) { "#{Label.reference_prefix}#{label.name}" } it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. namespace_project_issues_url(project.namespace, project, label_name: label.name) @@ -99,14 +99,14 @@ module Gitlab::Markdown end it 'links with adjacent text' do - doc = filter("Label (#{reference}.)") + doc = reference_filter("Label (#{reference}.)") expect(doc.to_html).to match(%r(\(#{label.name}\.\))) end it 'ignores invalid label names' do exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end @@ -115,7 +115,7 @@ module Gitlab::Markdown let(:reference) { label.to_reference(:name) } it 'links to a valid reference' do - doc = filter("See #{reference}") + doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. namespace_project_issues_url(project.namespace, project, label_name: label.name) @@ -123,21 +123,58 @@ module Gitlab::Markdown end it 'links with adjacent text' do - doc = filter("Label (#{reference}.)") + doc = reference_filter("Label (#{reference}.)") expect(doc.to_html).to match(%r(\(#{label.name}\.\))) end it 'ignores invalid label names' do exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end describe 'edge cases' do it 'gracefully handles non-references matching the pattern' do exp = act = '(format nil "~0f" 3.0) ; 3.0' - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp + end + end + + describe 'referencing a label in a link href' do + let(:reference) { %Q{Label} } + + it 'links to a valid reference' do + doc = reference_filter("See #{reference}") + + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + end + + it 'links with adjacent text' do + doc = reference_filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(Label\.\))) + end + + it 'includes a data-project attribute' do + doc = reference_filter("Label #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-project') + expect(link.attr('data-project')).to eq project.id.to_s + end + + it 'includes a data-label attribute' do + doc = reference_filter("See #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-label') + expect(link.attr('data-label')).to eq label.id.to_s + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Label #{reference}") + expect(result[:references][:label]).to eq [label] end end end diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb index cdb3390e793..4a232051127 100644 --- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb @@ -117,7 +117,7 @@ module Gitlab::Markdown end end - context 'URL cross-project reference' do + context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, :public, namespace: namespace) } let(:merge) { create(:merge_request, source_project: project2, target_project: project2) } diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb index 73d20957a56..b6f05710c3b 100644 --- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb @@ -115,7 +115,7 @@ module Gitlab::Markdown end end - context 'URL cross-project reference' do + context 'cross-project URL reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, :public, namespace: namespace) } let(:snippet) { create(:project_snippet, project: project2) } diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb index d9e0d7c42db..25379f0670e 100644 --- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb @@ -14,13 +14,13 @@ module Gitlab::Markdown it 'ignores invalid users' do exp = act = "Hey #{invalidate_reference(reference)}" - expect(filter(act).to_html).to eq(exp) + expect(reference_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 #{reference}" - expect(filter(act).to_html).to eq exp + expect(reference_filter(act).to_html).to eq exp end end @@ -32,7 +32,7 @@ module Gitlab::Markdown end it 'supports a special @all mention' do - doc = filter("Hey #{reference}") + doc = reference_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) @@ -46,26 +46,26 @@ module Gitlab::Markdown context 'mentioning a user' do it 'links to a User' do - doc = filter("Hey #{reference}") + doc = reference_filter("Hey #{reference}") expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) end it 'links to a User with a period' do user = create(:user, name: 'alphA.Beta') - doc = filter("Hey #{user.to_reference}") + doc = reference_filter("Hey #{user.to_reference}") expect(doc.css('a').length).to eq 1 end it 'links to a User with an underscore' do user = create(:user, name: 'ping_pong_king') - doc = filter("Hey #{user.to_reference}") + doc = reference_filter("Hey #{user.to_reference}") expect(doc.css('a').length).to eq 1 end it 'includes a data-user attribute' do - doc = filter("Hey #{reference}") + doc = reference_filter("Hey #{reference}") link = doc.css('a').first expect(link).to have_attribute('data-user') @@ -83,12 +83,12 @@ module Gitlab::Markdown let(:reference) { group.to_reference } it 'links to the Group' do - doc = filter("Hey #{reference}") + doc = reference_filter("Hey #{reference}") expect(doc.css('a').first.attr('href')).to eq urls.group_url(group) end it 'includes a data-group attribute' do - doc = filter("Hey #{reference}") + doc = reference_filter("Hey #{reference}") link = doc.css('a').first expect(link).to have_attribute('data-group') @@ -102,21 +102,48 @@ module Gitlab::Markdown end it 'links with adjacent text' do - doc = filter("Mention me (#{reference}.)") + doc = reference_filter("Mention me (#{reference}.)") expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) end it 'includes default classes' do - doc = filter("Hey #{reference}") + doc = reference_filter("Hey #{reference}") expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member' end it 'supports an :only_path context' do - doc = filter("Hey #{reference}", only_path: true) + doc = reference_filter("Hey #{reference}", only_path: true) link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) expect(link).to eq urls.user_path(user) end + + context 'referencing a user in a link href' do + let(:reference) { %Q{User} } + + it 'links to a User' do + doc = reference_filter("Hey #{reference}") + expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) + end + + it 'links with adjacent text' do + doc = reference_filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(/\(User<\/a>\.\)/) + end + + it 'includes a data-user attribute' do + doc = reference_filter("Hey #{reference}") + link = doc.css('a').first + + expect(link).to have_attribute('data-user') + expect(link.attr('data-user')).to eq user.namespace.owner_id.to_s + end + + it 'adds to the results hash' do + result = reference_pipeline_result("Hey #{reference}") + expect(result[:references][:user]).to eq [user] + end + end end end -- cgit v1.2.1 From c07f0fa735ba0d4cd926e601ebb3a40cfa197e21 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 1 Dec 2015 17:04:32 +0100 Subject: Add new references to markdown feature spec. --- spec/fixtures/markdown.md.erb | 17 +++++++++++++++++ spec/support/matchers/markdown_matchers.rb | 14 +++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 41d12afa9ce..76e733165ca 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Ignores invalid: <%= User.reference_prefix %>fake_user - Ignored in code: `<%= user.to_reference %>` - Ignored in links: [Link to <%= user.to_reference %>](#user-link) +- Link to user by reference: [User](<%= user.to_reference %>) #### IssueReferenceFilter @@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Issue in another project: <%= xissue.to_reference(project) %> - Ignored in code: `<%= issue.to_reference %>` - Ignored in links: [Link to <%= issue.to_reference %>](#issue-link) +- Issue by URL: <%= Gitlab.config.gitlab.url %>/<%= issue.project.to_reference %>/issues/<%= issue.iid %> +- Link to issue by reference: [Issue](<%= issue.to_reference %>) +- Link to issue by URL: [Issue](<%= Gitlab.config.gitlab.url %>/<%= issue.project.to_reference %>/issues/<%= issue.iid %>) #### MergeRequestReferenceFilter @@ -167,6 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - 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) +- Merge request by URL: <%= Gitlab.config.gitlab.url %>/<%= merge_request.project.to_reference %>/merge_requests/<%= merge_request.iid %> +- Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>) +- Link to merge request by URL: [Merge request](<%= Gitlab.config.gitlab.url %>/<%= merge_request.project.to_reference %>/merge_requests/<%= merge_request.iid %>) #### SnippetReferenceFilter @@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Snippet in another project: <%= xsnippet.to_reference(project) %> - Ignored in code: `<%= snippet.to_reference %>` - Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link) +- Snippet by URL: <%= Gitlab.config.gitlab.url %>/<%= snippet.project.to_reference %>/snippets/<%= snippet.id %> +- Link to snippet by reference: [Snippet](<%= snippet.to_reference %>) +- Link to snippet by URL: [Snippet](<%= Gitlab.config.gitlab.url %>/<%= snippet.project.to_reference %>/snippets/<%= snippet.id %>) #### CommitRangeReferenceFilter @@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - 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) +- Range by URL: <%= Gitlab.config.gitlab.url %>/<%= commit_range.project.to_reference %>/compare/<%= commit_range.id %> +- Link to range by reference: [Range](<%= commit_range.to_reference %>) +- Link to range by URL: [Range](<%= Gitlab.config.gitlab.url %>/<%= commit_range.project.to_reference %>/compare/<%= commit_range.id %>) #### CommitReferenceFilter @@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Commit in another project: <%= xcommit.to_reference(project) %> - Ignored in code: `<%= commit.to_reference %>` - Ignored in links: [Link to <%= commit.to_reference %>](#commit-link) +- Commit by URL: <%= Gitlab.config.gitlab.url %>/<%= commit.project.to_reference %>/commit/<%= commit.id %> +- Link to commit by reference: [Commit](<%= commit.to_reference %>) +- Link to commit by URL: [Commit](<%= Gitlab.config.gitlab.url %>/<%= commit.project.to_reference %>/commit/<%= commit.id %>) #### LabelReferenceFilter @@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - 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) +- Link to label by reference: [Label](<%= label.to_reference %>) ### Task Lists diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 7500d0fdf80..7eadcd58c1f 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -71,7 +71,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3) + expect(actual).to have_selector('a.gfm.gfm-project_member', count: 4) end end @@ -80,7 +80,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-issue', count: 3) + expect(actual).to have_selector('a.gfm.gfm-issue', count: 6) end end @@ -89,7 +89,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3) + expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 6) expect(actual).to have_selector('em a.gfm-merge_request') end end @@ -99,7 +99,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2) + expect(actual).to have_selector('a.gfm.gfm-snippet', count: 5) end end @@ -108,7 +108,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2) + expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 5) end end @@ -117,7 +117,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-commit', count: 2) + expect(actual).to have_selector('a.gfm.gfm-commit', count: 5) end end @@ -126,7 +126,7 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('a.gfm.gfm-label', count: 3) + expect(actual).to have_selector('a.gfm.gfm-label', count: 4) end end -- cgit v1.2.1 From 2f5074dc395c784f91abb3bacd23e75ce080a547 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 1 Dec 2015 17:13:47 +0100 Subject: Expand inline docs. --- lib/gitlab/markdown/abstract_reference_filter.rb | 6 ++++-- lib/gitlab/markdown/reference_filter.rb | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb index 0ec55c0207e..9488e980c08 100644 --- a/lib/gitlab/markdown/abstract_reference_filter.rb +++ b/lib/gitlab/markdown/abstract_reference_filter.rb @@ -3,7 +3,7 @@ require 'gitlab/markdown' module Gitlab module Markdown # Issues, Merge Requests, Snippets, Commits and Commit Ranges share - # similar functionality in refernce filtering. + # similar functionality in reference filtering. class AbstractReferenceFilter < ReferenceFilter include CrossProjectReference @@ -88,6 +88,8 @@ module Gitlab # to the referenced object's details page. # # text - String text to replace references in. + # pattern - Reference pattern to match against. + # link_text - Original content of the link being replaced. # # Returns a String with references replaced with links. All links # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. @@ -98,7 +100,7 @@ module Gitlab if project && object = find_object(project, id) title = escape_once(object_link_title(object)) klass = reference_class(object_sym) - + data = data_attribute( original: link_text || match, project: project.id, diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index 2597784c7ae..b6d93e05ec7 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -122,6 +122,18 @@ module Gitlab doc end + # Iterate through the document's link nodes, yielding the current node's + # content if: + # + # * The `project` context value is present AND + # * The node's content matches `pattern` + # + # pattern - Regex pattern against which to match the node's content + # + # Yields the current node's String contents. The result of the block will + # replace the node and update the current document. + # + # Returns the updated Nokogiri::HTML::DocumentFragment object. def replace_link_nodes_with_text(pattern) return doc if project.nil? @@ -148,6 +160,18 @@ module Gitlab doc end + # Iterate through the document's link nodes, yielding the current node's + # content if: + # + # * The `project` context value is present AND + # * The node's HREF matches `pattern` + # + # pattern - Regex pattern against which to match the node's HREF + # + # Yields the current node's String HREF and String content. + # The result of the block will replace the node and update the current document. + # + # Returns the updated Nokogiri::HTML::DocumentFragment object. def replace_link_nodes_with_href(pattern) return doc if project.nil? -- cgit v1.2.1 From 9aac53bcee8f5fc99ec84036d688801d987959ea Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 10:54:24 +0100 Subject: Satisfy Rubocop --- app/models/commit_range.rb | 2 +- spec/lib/gitlab/markdown/commit_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index b8bf36b32ce..14e7971fa06 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -93,7 +93,7 @@ class CommitRange def to_reference(from_project = nil) if cross_project_reference?(from_project) - reference = project.to_reference + self.class.reference_prefix + self.id + project.to_reference + self.class.reference_prefix + self.id else self.id end diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index 78a3603269c..462a41b4756 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -152,7 +152,7 @@ module Gitlab::Markdown end it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Committed #{invalidate_reference(reference)}" + act = "Committed #{invalidate_reference(reference)}" expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) end diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb index b6f05710c3b..3a9acc9d6d4 100644 --- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb @@ -134,7 +134,7 @@ module Gitlab::Markdown end it 'ignores invalid snippet IDs on the referenced project' do - exp = act = "See #{invalidate_reference(reference)}" + act = "See #{invalidate_reference(reference)}" expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) end -- cgit v1.2.1 From edc37c25204d7a4446d15eac94e6e1d92d613ed9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 10:56:39 +0100 Subject: Add changelog item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 090b54f41a4..01e7e8d20bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 8.3.0 (unreleased) - Fix 500 error when update group member permission - Fix: Raw private snippets access workflow - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) + - Recognize issue/MR/snippet/commit links as references v 8.2.1 - Forcefully update builds that didn't want to update with state machine -- cgit v1.2.1 From 044e0e33dce9371430a3c91e63f9f687b536d35b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 18:48:39 +0100 Subject: Allow invalid URLs in closing pattern --- lib/gitlab/closing_issue_extractor.rb | 4 +- spec/lib/gitlab/closing_issue_extractor_spec.rb | 49 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb index 0cf4c918736..9bef9037ad6 100644 --- a/lib/gitlab/closing_issue_extractor.rb +++ b/lib/gitlab/closing_issue_extractor.rb @@ -1,8 +1,10 @@ module Gitlab class ClosingIssueExtractor ISSUE_CLOSING_REGEX = begin + link_pattern = URI.regexp(%w(http https)) + pattern = Gitlab.config.gitlab.issue_closing_pattern - pattern = pattern.sub('%{issue_ref}', "(?:(?:#{Issue.link_reference_pattern})|(?:#{Issue.reference_pattern}))") + pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))") Regexp.new(pattern).freeze end diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 21254f778d3..e9d7e8c1111 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -2,11 +2,18 @@ require 'spec_helper' describe Gitlab::ClosingIssueExtractor do let(:project) { create(:project) } + let(:project2) { create(:project) } let(:issue) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project2) } let(:reference) { issue.to_reference } + let(:cross_reference) { issue2.to_reference(project) } subject { described_class.new(project, project.creator) } + before do + project2.team << [project.creator, :master] + end + describe "#closed_by_message" do context 'with a single reference' do it do @@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do end end + context "with a cross-project reference" do + it do + message = "Closes #{cross_reference}" + expect(subject.closed_by_message(message)).to eq([issue2]) + end + end + + context "with a cross-project URL" do + it do + message = "Closes #{Gitlab.config.gitlab.url}/#{project2.to_reference}/issues/#{issue2.iid}" + expect(subject.closed_by_message(message)).to eq([issue2]) + end + end + + context "with an invalid URL" do + it do + message = "Closes https://google.com/#{project2.to_reference}/issues/#{issue2.iid}" + expect(subject.closed_by_message(message)).to eq([]) + end + end + context 'with multiple references' do let(:other_issue) { create(:issue, project: project) } let(:third_issue) { create(:issue, project: project) } @@ -171,6 +199,27 @@ describe Gitlab::ClosingIssueExtractor do expect(subject.closed_by_message(message)). to match_array([issue, other_issue, third_issue]) end + + it "fetches cross-project references" do + message = "Closes #{reference} and #{cross_reference}" + + expect(subject.closed_by_message(message)). + to match_array([issue, issue2]) + end + + it "fetches cross-project URL references" do + message = "Closes #{Gitlab.config.gitlab.url}/#{project2.to_reference}/issues/#{issue2.iid} and #{reference}" + + expect(subject.closed_by_message(message)). + to match_array([issue, issue2]) + end + + it "ignores invalid cross-project URL references" do + message = "Closes https://google.com/#{project2.to_reference}/issues/#{issue2.iid} and #{reference}" + + expect(subject.closed_by_message(message)). + to match_array([issue]) + end end end end -- cgit v1.2.1 From f1bbd8119db3e494e662cd219a9882e5e17bf4bc Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 2 Dec 2015 22:03:43 -0200 Subject: Style warning about mentioning many people in a comment --- CHANGELOG | 1 + app/views/projects/_md_preview.html.haml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index db812796b69..89d3f854ae1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.3.0 (unreleased) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Add ignore whitespace change option to commit view - Fire update hook from GitLab + - Style warning about mentioning many people in a comment v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 8218cf11201..c4d3c9cc30a 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -8,17 +8,17 @@ %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1") Preview - - if defined?(referenced_users) && referenced_users - %span.referenced-users.pull-left.hide - = icon('exclamation-triangle') - You are about to add - %strong - %span.js-referenced-users-count 0 - people - to the discussion. Proceed with caution. - %div .md-write-holder = yield .md.md-preview-holder.hide .js-md-preview{class: (preview_class if defined?(preview_class))} + + - if defined?(referenced_users) && referenced_users + %span.referenced-users.pull-left.hide + = icon('exclamation-triangle') + You are about to add + %strong + %span.js-referenced-users-count 0 + people + to the discussion. Proceed with caution. -- cgit v1.2.1 From 9cf67f72a581d4648b2b02b53403dd4318e4f1e9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 1 Dec 2015 12:00:11 +0100 Subject: Add validator for award-emoji note --- app/models/note.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/note.rb b/app/models/note.rb index 1c6345e735c..40b46b85da1 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -41,6 +41,7 @@ class Note < ActiveRecord::Base validates :note, :project, presence: true validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award } + validates :note, format: { with: /\A[-_+[:alnum:]]*\z/ }, if: -> (n){ n.is_award } validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true # Attachments are deprecated and are handled by Markdown uploader validates :attachment, file_size: { maximum: :max_attachment_size } -- cgit v1.2.1 From 22d87b74a9de5d603f101699d7a3665db9627037 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 1 Dec 2015 14:45:24 +0100 Subject: Support award-emoji notes only when it a comment for an issue --- app/services/notes/create_service.rb | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index dbff58dfb9c..20a3ba30755 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -5,9 +5,9 @@ module Notes note.author = current_user note.system = false - if contains_emoji_only?(params[:note]) + if award_emoji_note? note.is_award = true - note.note = emoji_name(params[:note]) + note.note = emoji_name end if note.save @@ -34,12 +34,23 @@ module Notes note.project.execute_services(note_data, :note_hooks) end - def contains_emoji_only?(note) - note =~ /\A:[-_+[:alnum:]]*:\s?\z/ + private + + def award_emoji_note? + # We support award-emojis only in issue discussion + issue_comment? && contains_emoji_only? + end + + def contains_emoji_only? + params[:note] =~ /\A:[-_+[:alnum:]]*:\s?\z/ + end + + def issue_comment? + params[:noteable_type] == 'Issue' end - def emoji_name(note) - note.match(/\A:([-_+[:alnum:]]*):\s?/)[1] + def emoji_name + params[:note].match(/\A:([-_+[:alnum:]]*):\s?/)[1] end end end -- cgit v1.2.1 From ba08811d07cd1804b027b1a37a5278723cdbeb2c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 2 Dec 2015 08:48:21 +0100 Subject: Move note emoji-award implementation to note model (feature envy) --- app/models/note.rb | 33 +++++++++++++++++++++++++++++++++ app/services/notes/create_service.rb | 24 ------------------------ 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 40b46b85da1..8acb2cf7582 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -72,6 +72,7 @@ class Note < ActiveRecord::Base serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } + before_validation :set_award! class << self def discussions_from_notes(notes) @@ -349,4 +350,36 @@ class Note < ActiveRecord::Base def editable? !system? end + + # Checks if note is an award added from an issue comment. + # + # If note is an award, this method sets is_award to true, + # and changes note content to award-emoji name. + # + # Awards are only supported for issue comments. + # + # Method is executed as a before_validation callback. + # + def set_award! + return unless issue_comment? && contains_emoji_only? + + self.is_award = true + self.note = award_emoji_name + end + + private + + def issue_comment? + noteable.kind_of?(Issue) + end + + def contains_emoji_only? + (note =~ /\A:[-_+[:alnum:]]*:\s?\z/) ? true : false + end + + def award_emoji_name + return nil unless contains_emoji_only? + + note.match(/\A:([-_+[:alnum:]]*):\s?/)[1] + end end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 20a3ba30755..a8486e6a5a1 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -5,11 +5,6 @@ module Notes note.author = current_user note.system = false - if award_emoji_note? - note.is_award = true - note.note = emoji_name - end - if note.save notification_service.new_note(note) @@ -33,24 +28,5 @@ module Notes note.project.execute_hooks(note_data, :note_hooks) note.project.execute_services(note_data, :note_hooks) end - - private - - def award_emoji_note? - # We support award-emojis only in issue discussion - issue_comment? && contains_emoji_only? - end - - def contains_emoji_only? - params[:note] =~ /\A:[-_+[:alnum:]]*:\s?\z/ - end - - def issue_comment? - params[:noteable_type] == 'Issue' - end - - def emoji_name - params[:note].match(/\A:([-_+[:alnum:]]*):\s?/)[1] - end end end -- cgit v1.2.1 From c92b6e66dc3a5bb1b07d1561fa09e8fd051c1956 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 2 Dec 2015 09:36:11 +0100 Subject: Scroll to awards after adding emoji-award comment This makes it more intuitive, as user can see that something actually happened after adding emoji-only comment in long discussions. --- app/assets/javascripts/awards_handler.coffee | 7 ++++++- app/assets/javascripts/notes.js.coffee | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 09b48fe5572..96fd8f8773e 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -88,4 +88,9 @@ class @AwardsHandler callback.call() findEmojiIcon: (emoji) -> - $(".icon[data-emoji='" + emoji + "']") \ No newline at end of file + $(".icon[data-emoji='" + emoji + "']") + + scrollToAwards: -> + $('body, html').animate({ + scrollTop: $('.awards').offset().top - 80 + }, 200) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 7de7632201d..797234e6d9c 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -122,6 +122,7 @@ class @Notes if note.award awards_handler.addAwardToEmojiBar(note.note, note.emoji_path) + awards_handler.scrollToAwards() ### Check if note does not exists on page -- cgit v1.2.1 From bfce5d716835f07b98b6d26ccc121d3ac8322aa9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 2 Dec 2015 10:13:29 +0100 Subject: Render json message with errors if note didn't pass validation --- app/controllers/projects/notes_controller.rb | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 5ac18446aa7..a7ff5fcd09a 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -131,16 +131,20 @@ class Projects::NotesController < Projects::ApplicationController end def render_note_json(note) - render json: { - id: note.id, - discussion_id: note.discussion_id, - html: note_to_html(note), - award: note.is_award, - emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "", - note: note.note, - discussion_html: note_to_discussion_html(note), - discussion_with_diff_html: note_to_discussion_with_diff_html(note) - } + if note.valid? + render json: { + id: note.id, + discussion_id: note.discussion_id, + html: note_to_html(note), + award: note.is_award, + emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "", + note: note.note, + discussion_html: note_to_discussion_html(note), + discussion_with_diff_html: note_to_discussion_with_diff_html(note) + } + else + render json: { invalid: true, errors: note.errors } + end end def authorize_admin_note! -- cgit v1.2.1 From a527f5c27ff92d2ee7e2d5e78dc20b6d1d982aa0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 2 Dec 2015 10:51:46 +0100 Subject: Notify user when award-emoji comment is invalid --- app/assets/javascripts/notes.js.coffee | 4 ++++ app/controllers/projects/notes_controller.rb | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 797234e6d9c..af0d62c8495 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -111,6 +111,10 @@ class @Notes Note: for rendering inline notes use renderDiscussionNote ### renderNote: (note) -> + unless note.valid + alert('You have already used this award emoji !') if note.award + return + # render note if it not present in loaded list # or skip if rendered if @isNewNote(note) && !note.award diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index a7ff5fcd09a..88b949a27ab 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -133,6 +133,7 @@ class Projects::NotesController < Projects::ApplicationController def render_note_json(note) if note.valid? render json: { + valid: true, id: note.id, discussion_id: note.discussion_id, html: note_to_html(note), @@ -143,7 +144,11 @@ class Projects::NotesController < Projects::ApplicationController discussion_with_diff_html: note_to_discussion_with_diff_html(note) } else - render json: { invalid: true, errors: note.errors } + render json: { + valid: false, + award: note.is_award, + errors: note.errors + } end end -- cgit v1.2.1 From 4676ba1f7c7b37498d26815c1fbe0e02c2ffaeb8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 2 Dec 2015 14:11:32 +0000 Subject: Add test for award-emoji being added as regular comment --- features/project/issues/award_emoji.feature | 6 +++++- features/steps/project/issues/award_emoji.rb | 31 +++++++++++++++++----------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature index a9bc8ffb9bb..2609f129d07 100644 --- a/features/project/issues/award_emoji.feature +++ b/features/project/issues/award_emoji.feature @@ -11,4 +11,8 @@ Feature: Award Emoji And I click to emoji in the picker Then I have award added And I can remove it by clicking to icon - \ No newline at end of file + + @javascript + Scenario: I add award emoji using regular comment + Given I leave comment with a single emoji + Then I have award added diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb index 8f7a45dec0e..325eaf2ea6a 100644 --- a/features/steps/project/issues/award_emoji.rb +++ b/features/steps/project/issues/award_emoji.rb @@ -9,33 +9,40 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps end step 'I click to emoji-picker' do - page.within ".awards-controls" do - page.find(".add-award").click + page.within '.awards-controls' do + page.find('.add-award').click end end step 'I click to emoji in the picker' do - page.within ".awards-menu" do - page.first("img").click + page.within '.awards-menu' do + page.first('img').click end end step 'I can remove it by clicking to icon' do - page.within ".awards" do - page.first(".award").click - expect(page).to_not have_selector ".award" + page.within '.awards' do + page.first('.award').click + expect(page).to_not have_selector '.award' end end step 'I have award added' do - page.within ".awards" do - expect(page).to have_selector ".award" - expect(page.find(".award .counter")).to have_content "1" + page.within '.awards' do + expect(page).to have_selector '.award' + expect(page.find('.award .counter')).to have_content '1' end end step 'project "Shop" has issue "Bugfix"' do - @project = Project.find_by(name: "Shop") - @issue = create(:issue, title: "Bugfix", project: project) + @project = Project.find_by(name: 'Shop') + @issue = create(:issue, title: 'Bugfix', project: project) + end + + step 'I leave comment with a single emoji' do + page.within('.js-main-target-form') do + fill_in 'note[note]', with: ':smile:' + click_button 'Add Comment' + end end end -- cgit v1.2.1 From 6fc1b948560b9e19ac5c1412dd2deb98987aefe8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 2 Dec 2015 18:39:12 +0100 Subject: Show flash message instead of alert when note is invalid --- app/assets/javascripts/notes.js.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index af0d62c8495..ea190fa8b76 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -112,7 +112,8 @@ class @Notes ### renderNote: (note) -> unless note.valid - alert('You have already used this award emoji !') if note.award + if note.award + new Flash('You have already used this award emoji !', 'alert') return # render note if it not present in loaded list -- cgit v1.2.1 From 70a076c059482e5c26cb723b722bc865142460ea Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 3 Dec 2015 08:51:14 +0100 Subject: Add new features to javascript flash message --- app/assets/javascripts/flash.js.coffee | 19 +++++++++++++------ app/assets/stylesheets/framework/flash.scss | 10 ++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index b39ab0c4475..5e34b49c20d 100644 --- a/app/assets/javascripts/flash.js.coffee +++ b/app/assets/javascripts/flash.js.coffee @@ -1,12 +1,19 @@ class @Flash constructor: (message, type)-> - flash = $(".flash-container") - flash.html("") + @flash = $(".flash-container") + @flash.html("") - $('
', + innerDiv = $('
', class: "flash-#{type}", text: message - ).appendTo(".flash-container") + ) + innerDiv.appendTo(".flash-container") - flash.click -> $(@).fadeOut() - flash.show() + @flash.click -> $(@).fadeOut() + @flash.show() + + pinToTop: -> + @flash.addClass('flash-pinned') + + raise: -> + @flash.addClass('flash-raised') diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 82eb50ad4be..1b723021d76 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -15,3 +15,13 @@ @extend .alert-danger; } } + +.flash-pinned { + position: fixed; + top: 80px; + width: 80%; +} + +.flash-raised { + z-index: 1000; +} -- cgit v1.2.1 From 554f4684622564fe496acb25cacb2daed3b9f3ac Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 3 Dec 2015 08:51:37 +0100 Subject: Pin flash message to top if award note is invalid --- app/assets/javascripts/notes.js.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index ea190fa8b76..8eacd05d050 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -113,7 +113,9 @@ class @Notes renderNote: (note) -> unless note.valid if note.award - new Flash('You have already used this award emoji !', 'alert') + flash = new Flash('You have already used this award emoji !', 'alert') + flash.pinToTop() + flash.raise() return # render note if it not present in loaded list -- cgit v1.2.1 From 2b577551177c631d229eca6844520e29478085f8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 3 Dec 2015 09:30:30 +0100 Subject: Add feature test for emoji-only diff notes This specs is related to bug described in #3734 (award-emojis). --- features/project/commits/diff_comments.feature | 6 ++++++ features/steps/shared/diff_note.rb | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature index 4a2b870e082..d6e0c84537e 100644 --- a/features/project/commits/diff_comments.feature +++ b/features/project/commits/diff_comments.feature @@ -13,6 +13,12 @@ Feature: Project Commits Diff Comments Given I leave a diff comment like "Typo, please fix" Then I should see a diff comment saying "Typo, please fix" + @javascript + Scenario: I can add a diff comment with a single emoji + Given I open a diff comment form + And I write a diff comment like ":smile:" + Then I should see a diff comment with an emoji image + @javascript Scenario: I get a temporary form for the first comment on a diff line Given I open a diff comment form diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 72621911a37..dd466cde28d 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -87,6 +87,17 @@ module SharedDiffNote end end + step 'I write a diff comment like ":smile:"' do + page.within(diff_file_selector) do + click_diff_line(sample_commit.line_code) + + page.within("form[rel$='#{sample_commit.line_code}']") do + fill_in 'note[note]', with: ':smile:' + click_button('Add Comment') + end + end + end + step 'I submit the diff comment' do page.within(diff_file_selector) do click_button("Add Comment") @@ -197,6 +208,12 @@ module SharedDiffNote end end + step 'I should see a diff comment with an emoji image' do + page.within("#{diff_file_selector} .note") do + expect(page).to have_xpath("//img[@alt=':smile:']") + end + end + step 'I click side-by-side diff button' do find('#parallel-diff-btn').trigger('click') end -- cgit v1.2.1 From f08f921d537c68ec0bbfb8a1ae7e905cded1bbad Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 3 Dec 2015 12:46:39 +0100 Subject: Support emoji awards also in merge requests --- app/models/note.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 8acb2cf7582..30d15d93fed 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -361,7 +361,7 @@ class Note < ActiveRecord::Base # Method is executed as a before_validation callback. # def set_award! - return unless issue_comment? && contains_emoji_only? + return unless supports_awards? && contains_emoji_only? self.is_award = true self.note = award_emoji_name @@ -369,8 +369,9 @@ class Note < ActiveRecord::Base private - def issue_comment? - noteable.kind_of?(Issue) + def supports_awards? + noteable.kind_of?(Issue) || + noteable.is_a?(MergeRequest) end def contains_emoji_only? -- cgit v1.2.1 From 7af67f619a9ce3dab0a66560bc57ccf606077779 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 3 Dec 2015 12:59:44 +0100 Subject: Combine new javascript Flash methods into one --- app/assets/javascripts/flash.js.coffee | 7 ++----- app/assets/javascripts/notes.js.coffee | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index 5e34b49c20d..9b59d4e57f7 100644 --- a/app/assets/javascripts/flash.js.coffee +++ b/app/assets/javascripts/flash.js.coffee @@ -12,8 +12,5 @@ class @Flash @flash.click -> $(@).fadeOut() @flash.show() - pinToTop: -> - @flash.addClass('flash-pinned') - - raise: -> - @flash.addClass('flash-raised') + pin: -> + @flash.addClass('flash-pinned flash-raised') diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 8eacd05d050..4f559e86378 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -114,8 +114,7 @@ class @Notes unless note.valid if note.award flash = new Flash('You have already used this award emoji !', 'alert') - flash.pinToTop() - flash.raise() + flash.pin() return # render note if it not present in loaded list -- cgit v1.2.1 From 83d8185f5afee7553bf5756ce61b6b855d48c1e5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 3 Dec 2015 13:03:50 +0100 Subject: Make method `supports_award?` public in `Note` --- app/models/note.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 30d15d93fed..03640be7c93 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -367,13 +367,13 @@ class Note < ActiveRecord::Base self.note = award_emoji_name end - private - def supports_awards? noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest) end + private + def contains_emoji_only? (note =~ /\A:[-_+[:alnum:]]*:\s?\z/) ? true : false end -- cgit v1.2.1 From f905fd239c79f391ce5a7cc2c5c6648b3d6380e4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 3 Dec 2015 14:00:00 +0100 Subject: Use URL helpers in specs --- lib/gitlab/markdown/label_reference_filter.rb | 4 ++-- spec/fixtures/markdown.md.erb | 20 ++++++++++---------- spec/lib/gitlab/closing_issue_extractor_spec.rb | 12 ++++++++---- .../gitlab/markdown/label_reference_filter_spec.rb | 6 +++--- spec/support/markdown_feature.rb | 4 ++++ 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index a23aa0adeec..ec2b179b7de 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -65,8 +65,8 @@ module Gitlab def url_for_label(project, label) h = Gitlab::Application.routes.url_helpers - h.namespace_project_issues_path(project.namespace, project, - label_name: label.name) + h.namespace_project_issues_url( project.namespace, project, label_name: label.name, + only_path: context[:only_path]) end def render_colored_label(label) diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 76e733165ca..e8dfc5c0eb1 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -161,9 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Issue in another project: <%= xissue.to_reference(project) %> - Ignored in code: `<%= issue.to_reference %>` - Ignored in links: [Link to <%= issue.to_reference %>](#issue-link) -- Issue by URL: <%= Gitlab.config.gitlab.url %>/<%= issue.project.to_reference %>/issues/<%= issue.iid %> +- Issue by URL: <%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %> - Link to issue by reference: [Issue](<%= issue.to_reference %>) -- Link to issue by URL: [Issue](<%= Gitlab.config.gitlab.url %>/<%= issue.project.to_reference %>/issues/<%= issue.iid %>) +- Link to issue by URL: [Issue](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>) #### MergeRequestReferenceFilter @@ -171,9 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - 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) -- Merge request by URL: <%= Gitlab.config.gitlab.url %>/<%= merge_request.project.to_reference %>/merge_requests/<%= merge_request.iid %> +- Merge request by URL: <%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %> - Link to merge request by reference: [Merge request](<%= merge_request.to_reference %>) -- Link to merge request by URL: [Merge request](<%= Gitlab.config.gitlab.url %>/<%= merge_request.project.to_reference %>/merge_requests/<%= merge_request.iid %>) +- Link to merge request by URL: [Merge request](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>) #### SnippetReferenceFilter @@ -181,9 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Snippet in another project: <%= xsnippet.to_reference(project) %> - Ignored in code: `<%= snippet.to_reference %>` - Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link) -- Snippet by URL: <%= Gitlab.config.gitlab.url %>/<%= snippet.project.to_reference %>/snippets/<%= snippet.id %> +- Snippet by URL: <%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %> - Link to snippet by reference: [Snippet](<%= snippet.to_reference %>) -- Link to snippet by URL: [Snippet](<%= Gitlab.config.gitlab.url %>/<%= snippet.project.to_reference %>/snippets/<%= snippet.id %>) +- Link to snippet by URL: [Snippet](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>) #### CommitRangeReferenceFilter @@ -191,9 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - 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) -- Range by URL: <%= Gitlab.config.gitlab.url %>/<%= commit_range.project.to_reference %>/compare/<%= commit_range.id %> +- Range by URL: <%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %> - Link to range by reference: [Range](<%= commit_range.to_reference %>) -- Link to range by URL: [Range](<%= Gitlab.config.gitlab.url %>/<%= commit_range.project.to_reference %>/compare/<%= commit_range.id %>) +- Link to range by URL: [Range](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>) #### CommitReferenceFilter @@ -201,9 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - Commit in another project: <%= xcommit.to_reference(project) %> - Ignored in code: `<%= commit.to_reference %>` - Ignored in links: [Link to <%= commit.to_reference %>](#commit-link) -- Commit by URL: <%= Gitlab.config.gitlab.url %>/<%= commit.project.to_reference %>/commit/<%= commit.id %> +- Commit by URL: <%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %> - Link to commit by reference: [Commit](<%= commit.to_reference %>) -- Link to commit by URL: [Commit](<%= Gitlab.config.gitlab.url %>/<%= commit.project.to_reference %>/commit/<%= commit.id %>) +- Link to commit by URL: [Commit](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>) #### LabelReferenceFilter diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index e9d7e8c1111..fe1b94a484e 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -146,14 +146,14 @@ describe Gitlab::ClosingIssueExtractor do context "with a cross-project URL" do it do - message = "Closes #{Gitlab.config.gitlab.url}/#{project2.to_reference}/issues/#{issue2.iid}" + message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)}" expect(subject.closed_by_message(message)).to eq([issue2]) end end context "with an invalid URL" do it do - message = "Closes https://google.com/#{project2.to_reference}/issues/#{issue2.iid}" + message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}" expect(subject.closed_by_message(message)).to eq([]) end end @@ -208,18 +208,22 @@ describe Gitlab::ClosingIssueExtractor do end it "fetches cross-project URL references" do - message = "Closes #{Gitlab.config.gitlab.url}/#{project2.to_reference}/issues/#{issue2.iid} and #{reference}" + message = "Closes #{urls.namespace_project_issue_url(issue2.project.namespace, issue2.project, issue2)} and #{reference}" expect(subject.closed_by_message(message)). to match_array([issue, issue2]) end it "ignores invalid cross-project URL references" do - message = "Closes https://google.com/#{project2.to_reference}/issues/#{issue2.iid} and #{reference}" + message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)} and #{reference}" expect(subject.closed_by_message(message)). to match_array([issue]) end end end + + def urls + Gitlab::Application.routes.url_helpers + end end diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb index f5a6d67f772..ef6dd524aba 100644 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb @@ -71,7 +71,7 @@ module Gitlab::Markdown doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_path(project.namespace, project, label_name: label.name) + namespace_project_issues_url(project.namespace, project, label_name: label.name) end it 'links with adjacent text' do @@ -94,7 +94,7 @@ module Gitlab::Markdown doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_path(project.namespace, project, label_name: label.name) + namespace_project_issues_url(project.namespace, project, label_name: label.name) expect(doc.text).to eq 'See gfm' end @@ -118,7 +118,7 @@ module Gitlab::Markdown doc = reference_filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_path(project.namespace, project, label_name: label.name) + namespace_project_issues_url(project.namespace, project, label_name: label.name) expect(doc.text).to eq 'See gfm references' end diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb index bedc1a7f1db..d6d3062a197 100644 --- a/spec/support/markdown_feature.rb +++ b/spec/support/markdown_feature.rb @@ -93,6 +93,10 @@ class MarkdownFeature end end + def urls + Gitlab::Application.routes.url_helpers + end + def raw_markdown markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb')) ERB.new(markdown).result(binding) -- cgit v1.2.1 From 0de8e260662a62603dbee5efb133cd4f1e0f0ed7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 3 Dec 2015 14:00:41 +0100 Subject: Pass original text along with label reference filter. --- lib/gitlab/markdown/label_reference_filter.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index ec2b179b7de..36cf7a8f4ce 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -51,7 +51,11 @@ module Gitlab if label = project.labels.find_by(params) url = url_for_label(project, label) klass = reference_class(:label) - data = data_attribute(project: project.id, label: label.id) + data = data_attribute( + original: link_text || match, + project: project.id, + label: label.id + ) text = link_text || render_colored_label(label) -- cgit v1.2.1 From 5145706c82613d64462fe736850d09799224cd77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Wed, 25 Nov 2015 19:20:40 -0500 Subject: Run custom Git hooks when creating or deleting branches through the UI. #1156 --- CHANGELOG | 1 + app/models/repository.rb | 54 +++++++++-------- app/services/create_branch_service.rb | 5 +- app/services/delete_branch_service.rb | 4 +- app/services/files/base_service.rb | 2 +- app/services/git_hooks_service.rb | 32 ++++++++++ spec/models/repository_spec.rb | 100 ++++++++++++++++++++++++++++++++ spec/services/git_hooks_service_spec.rb | 38 ++++++++++++ 8 files changed, 207 insertions(+), 29 deletions(-) create mode 100644 app/services/git_hooks_service.rb create mode 100644 spec/services/git_hooks_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index db812796b69..882648b36a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.3.0 (unreleased) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Add ignore whitespace change option to commit view - Fire update hook from GitLab + - Run custom Git hooks when branch is created or deleted. #1156 v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) diff --git a/app/models/repository.rb b/app/models/repository.rb index d247b0f5012..c304955b0b3 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1,7 +1,6 @@ require 'securerandom' class Repository - class PreReceiveError < StandardError; end class CommitError < StandardError; end include Gitlab::ShellAdapter @@ -108,10 +107,19 @@ class Repository tags.find { |tag| tag.name == name } end - def add_branch(branch_name, ref) - expire_branches_cache + def add_branch(user, branch_name, target) + oldrev = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + target = commit(target).try(:id) + + return false unless target + + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do + rugged.branches.create(branch_name, target) + end - gitlab_shell.add_branch(path_with_namespace, branch_name, ref) + expire_branches_cache + find_branch(branch_name) end def add_tag(tag_name, ref, message = nil) @@ -120,10 +128,20 @@ class Repository gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) end - def rm_branch(branch_name) + def rm_branch(user, branch_name) expire_branches_cache - gitlab_shell.rm_branch(path_with_namespace, branch_name) + branch = find_branch(branch_name) + oldrev = branch.try(:target) + newrev = Gitlab::Git::BLANK_SHA + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + + GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do + rugged.branches.delete(branch_name) + end + + expire_branches_cache + true end def rm_tag(tag_name) @@ -550,7 +568,6 @@ class Repository def commit_with_hooks(current_user, branch) oldrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch - gl_id = Gitlab::ShellEnv.gl_id(current_user) was_empty = empty? # Create temporary ref @@ -569,15 +586,7 @@ class Repository raise CommitError.new('Failed to create commit') end - # Run GitLab pre-receive hook - pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo) - pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref) - - # Run GitLab update hook - update_hook = Gitlab::Git::Hook.new('update', path_to_repo) - update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref) - - if pre_receive_hook_status && update_hook_status + GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do if was_empty # Create branch rugged.references.create(ref, newrev) @@ -592,16 +601,11 @@ class Repository raise CommitError.new('Commit was rejected because branch received new push') end end - - # Run GitLab post receive hook - post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo) - post_receive_hook.trigger(gl_id, oldrev, newrev, ref) - else - # Remove tmp ref and return error to user - rugged.references.delete(tmp_ref) - - raise PreReceiveError.new('Commit was rejected by git hook') end + rescue GitHooksService::PreReceiveError + # Remove tmp ref and return error to user + rugged.references.delete(tmp_ref) + raise end private diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index cf7ae4345f3..de18f3bc556 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -13,8 +13,7 @@ class CreateBranchService < BaseService return error('Branch already exists') end - repository.add_branch(branch_name, ref) - new_branch = repository.find_branch(branch_name) + new_branch = repository.add_branch(current_user, branch_name, ref) if new_branch push_data = build_push_data(project, current_user, new_branch) @@ -27,6 +26,8 @@ class CreateBranchService < BaseService else error('Invalid reference name') end + rescue GitHooksService::PreReceiveError + error('Branch creation was rejected by Git hook') end def success(branch) diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index b19b112a0c4..22bf9dd935e 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -24,7 +24,7 @@ class DeleteBranchService < BaseService return error('You dont have push access to repo', 405) end - if repository.rm_branch(branch_name) + if repository.rm_branch(current_user, branch_name) push_data = build_push_data(branch) EventCreateService.new.push(project, current_user, push_data) @@ -35,6 +35,8 @@ class DeleteBranchService < BaseService else error('Failed to remove branch') end + rescue GitHooksService::PreReceiveError + error('Branch deletion was rejected by Git hook') end def error(message, return_code = 400) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 008833eed80..f50aaf2eb52 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -26,7 +26,7 @@ module Files else error("Something went wrong. Your changes were not committed") end - rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex + rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex error(ex.message) end diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb new file mode 100644 index 00000000000..53f1fdef796 --- /dev/null +++ b/app/services/git_hooks_service.rb @@ -0,0 +1,32 @@ +class GitHooksService + PreReceiveError = Class.new(StandardError) + + def execute(user, repo_path, oldrev, newrev, ref) + @repo_path = repo_path + @user = Gitlab::ShellEnv.gl_id(user) + @oldrev = oldrev + @newrev = newrev + @ref = ref + + pre_status = run_hook('pre-receive') + + if pre_status + yield + + run_hook('post-receive') + end + end + + private + + def run_hook(name) + hook = Gitlab::Git::Hook.new(name, @repo_path) + status = hook.trigger(@user, @oldrev, @newrev, @ref) + + if !status && (name != 'post-receive') + raise PreReceiveError.new("Git operation was rejected by #{name} hook") + end + + status + end +end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 319fa0a7c8d..c746b8db621 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -4,6 +4,7 @@ describe Repository do include RepoHelpers let(:repository) { create(:project).repository } + let(:user) { create(:user) } describe :branch_names_contains do subject { repository.branch_names_contains(sample_commit.id) } @@ -99,5 +100,104 @@ describe Repository do it { expect(subject.startline).to eq(186) } it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") } end + end + + describe :add_branch do + context 'when pre hooks were successful' do + it 'should run without errors' do + hook = double(trigger: true) + expect(Gitlab::Git::Hook).to receive(:new).twice.and_return(hook) + + expect { repository.add_branch(user, 'new_feature', 'master') }.not_to raise_error + end + + it 'should create the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true) + + branch = repository.add_branch(user, 'new_feature', 'master') + + expect(branch.name).to eq('new_feature') + end + end + + context 'when pre hooks failed' do + it 'should get an error' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.add_branch(user, 'new_feature', 'master') + end.to raise_error(GitHooksService::PreReceiveError) + end + + it 'should not create the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.add_branch(user, 'new_feature', 'master') + end.to raise_error(GitHooksService::PreReceiveError) + expect(repository.find_branch('new_feature')).to be_nil + end + end + end + + describe :rm_branch do + context 'when pre hooks were successful' do + it 'should run without errors' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true) + + expect { repository.rm_branch(user, 'feature') }.not_to raise_error + end + + it 'should delete the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true) + + expect { repository.rm_branch(user, 'feature') }.not_to raise_error + + expect(repository.find_branch('feature')).to be_nil + end + end + + context 'when pre hooks failed' do + it 'should get an error' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.rm_branch(user, 'new_feature') + end.to raise_error(GitHooksService::PreReceiveError) + end + + it 'should not delete the branch' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.rm_branch(user, 'feature') + end.to raise_error(GitHooksService::PreReceiveError) + expect(repository.find_branch('feature')).not_to be_nil + end + end + end + + describe :commit_with_hooks do + context 'when pre hooks were successful' do + it 'should run without errors' do + expect_any_instance_of(GitHooksService).to receive(:execute).and_return(true) + + expect do + repository.commit_with_hooks(user, 'feature') { sample_commit.id } + end.not_to raise_error + end + end + + context 'when pre hooks failed' do + it 'should get an error' do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(false) + + expect do + repository.commit_with_hooks(user, 'feature') { sample_commit.id } + end.to raise_error(GitHooksService::PreReceiveError) + end + end + end + end diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb new file mode 100644 index 00000000000..21585cc4629 --- /dev/null +++ b/spec/services/git_hooks_service_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe GitHooksService do + include RepoHelpers + + let(:user) { create :user } + let(:project) { create :project } + let(:service) { GitHooksService.new } + + before do + @blankrev = Gitlab::Git::BLANK_SHA + @oldrev = sample_commit.parent_id + @newrev = sample_commit.id + @ref = 'refs/heads/feature' + @repo_path = project.repository.path_to_repo + end + + describe '#execute' do + + context 'when pre hooks were successful' do + it 'should call post hooks' do + expect(service).to receive(:run_hook).with('pre-receive').and_return(true) + expect(service).to receive(:run_hook).with('post-receive').and_return(true) + expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq(true) + end + end + + context 'when pre hooks failed' do + it 'should not call post hooks' do + expect(service).to receive(:run_hook).with('pre-receive').and_return(false) + expect(service).not_to receive(:run_hook).with('post-receive') + + service.execute(user, @repo_path, @blankrev, @newrev, @ref) + end + end + + end +end -- cgit v1.2.1 From 338eb2c41ea766779d6bb7798079a1dd3a50e11d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Tue, 1 Dec 2015 00:22:45 -0500 Subject: Call update hook from new GitHooksService class. #3069 --- app/services/git_hooks_service.rb | 4 +--- spec/models/repository_spec.rb | 2 +- spec/services/git_hooks_service_spec.rb | 23 +++++++++++++++++------ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index 53f1fdef796..b7804ed472f 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -8,9 +8,7 @@ class GitHooksService @newrev = newrev @ref = ref - pre_status = run_hook('pre-receive') - - if pre_status + if run_hook('pre-receive') && run_hook('update') yield run_hook('post-receive') diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index c746b8db621..fa261e64c35 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -107,7 +107,7 @@ describe Repository do context 'when pre hooks were successful' do it 'should run without errors' do hook = double(trigger: true) - expect(Gitlab::Git::Hook).to receive(:new).twice.and_return(hook) + expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) expect { repository.add_branch(user, 'new_feature', 'master') }.not_to raise_error end diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index 21585cc4629..bb639a5ae23 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -17,16 +17,17 @@ describe GitHooksService do describe '#execute' do - context 'when pre hooks were successful' do - it 'should call post hooks' do - expect(service).to receive(:run_hook).with('pre-receive').and_return(true) - expect(service).to receive(:run_hook).with('post-receive').and_return(true) + context 'when receive hooks were successful' do + it 'should call post-receive hook' do + hook = double(trigger: true) + expect(Gitlab::Git::Hook).to receive(:new).exactly(3).times.and_return(hook) + expect(service.execute(user, @repo_path, @blankrev, @newrev, @ref) { }).to eq(true) end end - context 'when pre hooks failed' do - it 'should not call post hooks' do + context 'when pre-receive hook failed' do + it 'should not call post-receive hook' do expect(service).to receive(:run_hook).with('pre-receive').and_return(false) expect(service).not_to receive(:run_hook).with('post-receive') @@ -34,5 +35,15 @@ describe GitHooksService do end end + context 'when update hook failed' do + it 'should not call post-receive hook' do + expect(service).to receive(:run_hook).with('pre-receive').and_return(true) + expect(service).to receive(:run_hook).with('update').and_return(false) + expect(service).not_to receive(:run_hook).with('post-receive') + + service.execute(user, @repo_path, @blankrev, @newrev, @ref) + end + end + end end -- cgit v1.2.1 From 5e6a5270d52ea2f13ca9967913012691e257439b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Tue, 1 Dec 2015 12:31:44 -0500 Subject: Raise the exception from #execute instead of #run_hook. #1156 #3069 --- app/services/git_hooks_service.rb | 20 +++++++++----------- spec/services/git_hooks_service_spec.rb | 8 ++++++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index b7804ed472f..8f5c3393dfc 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -8,23 +8,21 @@ class GitHooksService @newrev = newrev @ref = ref - if run_hook('pre-receive') && run_hook('update') - yield - - run_hook('post-receive') + %w(pre-receive update).each do |hook_name| + unless run_hook(hook_name) + raise PreReceiveError.new("Git operation was rejected by #{hook_name} hook") + end end + + yield + + run_hook('post-receive') end private def run_hook(name) hook = Gitlab::Git::Hook.new(name, @repo_path) - status = hook.trigger(@user, @oldrev, @newrev, @ref) - - if !status && (name != 'post-receive') - raise PreReceiveError.new("Git operation was rejected by #{name} hook") - end - - status + hook.trigger(@user, @oldrev, @newrev, @ref) end end diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb index bb639a5ae23..7e018d3c9fe 100644 --- a/spec/services/git_hooks_service_spec.rb +++ b/spec/services/git_hooks_service_spec.rb @@ -31,7 +31,9 @@ describe GitHooksService do expect(service).to receive(:run_hook).with('pre-receive').and_return(false) expect(service).not_to receive(:run_hook).with('post-receive') - service.execute(user, @repo_path, @blankrev, @newrev, @ref) + expect do + service.execute(user, @repo_path, @blankrev, @newrev, @ref) + end.to raise_error(GitHooksService::PreReceiveError) end end @@ -41,7 +43,9 @@ describe GitHooksService do expect(service).to receive(:run_hook).with('update').and_return(false) expect(service).not_to receive(:run_hook).with('post-receive') - service.execute(user, @repo_path, @blankrev, @newrev, @ref) + expect do + service.execute(user, @repo_path, @blankrev, @newrev, @ref) + end.to raise_error(GitHooksService::PreReceiveError) end end -- cgit v1.2.1 From dbbd2b863b402e460ac1dc90f852fcae617a2351 Mon Sep 17 00:00:00 2001 From: Greg Smethells Date: Mon, 30 Nov 2015 14:47:44 -0600 Subject: sort milestones by due_date --- CHANGELOG | 1 + app/controllers/concerns/global_milestones.rb | 2 ++ app/finders/milestones_finder.rb | 2 +- app/helpers/milestones_helper.rb | 2 ++ app/models/global_milestone.rb | 31 +++++++++++++++++++++- .../dashboard/milestones/_milestone.html.haml | 11 +++++--- app/views/projects/milestones/_milestone.html.haml | 6 +---- app/views/shared/_milestone_expired.html.haml | 5 ++++ 8 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 app/views/shared/_milestone_expired.html.haml diff --git a/CHANGELOG b/CHANGELOG index db812796b69..aad58af6678 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.3.0 (unreleased) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Add ignore whitespace change option to commit view - Fire update hook from GitLab + - Fix: sort milestones by due date once again (Greg Smethells) v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb index b428249acd3..3e4c0e63601 100644 --- a/app/controllers/concerns/global_milestones.rb +++ b/app/controllers/concerns/global_milestones.rb @@ -2,8 +2,10 @@ module GlobalMilestones extend ActiveSupport::Concern def milestones + epoch = DateTime.parse('1970-01-01') @milestones = MilestonesFinder.new.execute(@projects, params) @milestones = GlobalMilestone.build_collection(@milestones) + @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } @milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE) end diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb index b704e878903..630c73c2a94 100644 --- a/app/finders/milestones_finder.rb +++ b/app/finders/milestones_finder.rb @@ -1,7 +1,7 @@ class MilestonesFinder def execute(projects, params) milestones = Milestone.of_projects(projects) - milestones = milestones.order("due_date ASC") + milestones = milestones.reorder("due_date ASC") case params[:state] when 'closed' then milestones.closed diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index ad43892b639..a42cbcff182 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -28,7 +28,9 @@ module MilestonesHelper Milestone.where(project_id: @projects) end.active + epoch = DateTime.parse('1970-01-01') grouped_milestones = GlobalMilestone.build_collection(milestones) + grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } grouped_milestones.unshift(Milestone::None) grouped_milestones.unshift(Milestone::Any) diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index 1321ccd963f..33ddb265fba 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -19,6 +19,14 @@ class GlobalMilestone @title.parameterize end + def expired? + if due_date + due_date.past? + else + false + end + end + def projects milestones.map { |milestone| milestone.project } end @@ -98,4 +106,25 @@ class GlobalMilestone def complete? total_items_count == closed_items_count end -end + + def due_date + return @due_date if defined?(@due_date) + + @due_date = + if @milestones.all? { |x| x.due_date == @milestones.first.due_date } + @milestones.first.due_date + else + nil + end + end + + def expires_at + if due_date + if due_date.past? + "expired at #{due_date.stamp("Aug 21, 2011")}" + else + "expires at #{due_date.stamp("Aug 21, 2011")}" + end + end + end +end \ No newline at end of file diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml index 55080d6b3fe..7c882a32702 100644 --- a/app/views/dashboard/milestones/_milestone.html.haml +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -16,7 +16,10 @@ = milestone_progress_bar(milestone) .row .col-sm-6 - - milestone.milestones.each do |milestone| - = link_to milestone_path(milestone) do - %span.label.label-gray - = milestone.project.name_with_namespace + .expiration + = render 'shared/milestone_expired', milestone: milestone + .projects + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name_with_namespace diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 334172b976f..d6a44c9f0a1 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -18,11 +18,7 @@ .row .col-sm-6 - - if milestone.expired? and not milestone.closed? - %span.cred (Expired) - - if milestone.expires_at - %span - = milestone.expires_at + = render 'shared/milestone_expired', milestone: milestone .col-sm-6 - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do diff --git a/app/views/shared/_milestone_expired.html.haml b/app/views/shared/_milestone_expired.html.haml new file mode 100644 index 00000000000..b8eef15fbec --- /dev/null +++ b/app/views/shared/_milestone_expired.html.haml @@ -0,0 +1,5 @@ +- if milestone.expired? and not milestone.closed? + %span.cred (Expired) +- if milestone.expires_at + %span + = milestone.expires_at -- cgit v1.2.1 From c204aca83b0c8308080a9f53b692f509a80ddffc Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 3 Dec 2015 15:30:10 -0200 Subject: Improve style of the warning when mentioning many people in a comment --- app/assets/stylesheets/framework/markdown_area.scss | 8 +++----- app/views/projects/_md_preview.html.haml | 15 ++++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index cc660529cb4..11b609f3b79 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -73,11 +73,9 @@ } .referenced-users { - padding: 10px 0; - color: #999; - margin-left: 10px; - margin-top: 1px; - margin-right: 130px; + color: #4c4e54; + display: inline-block; + padding-top: 10px; } .md-preview-holder { diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index c4d3c9cc30a..5bdceb9523a 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -15,10 +15,11 @@ .js-md-preview{class: (preview_class if defined?(preview_class))} - if defined?(referenced_users) && referenced_users - %span.referenced-users.pull-left.hide - = icon('exclamation-triangle') - You are about to add - %strong - %span.js-referenced-users-count 0 - people - to the discussion. Proceed with caution. + %div.clearfix + %span.referenced-users.hide + = icon('exclamation-triangle') + You are about to add + %strong + %span.js-referenced-users-count 0 + people + to the discussion. Proceed with caution. -- cgit v1.2.1 From f9d954fae71d40a314a5812c1a7eec5a601d1575 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 3 Dec 2015 19:02:56 +0100 Subject: Satisfy rubocop --- lib/gitlab/markdown/label_reference_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 36cf7a8f4ce..a2026eecaeb 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -70,7 +70,7 @@ module Gitlab def url_for_label(project, label) h = Gitlab::Application.routes.url_helpers h.namespace_project_issues_url( project.namespace, project, label_name: label.name, - only_path: context[:only_path]) + only_path: context[:only_path]) end def render_colored_label(label) -- cgit v1.2.1 From 0decc7f941b96bf53e172de0340847d3b7d714b7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 3 Dec 2015 19:40:31 -0200 Subject: Fix specs --- app/assets/stylesheets/framework/markdown_area.scss | 1 - app/views/projects/_md_preview.html.haml | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 11b609f3b79..2b044786738 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -74,7 +74,6 @@ .referenced-users { color: #4c4e54; - display: inline-block; padding-top: 10px; } diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 5bdceb9523a..54c818baaf4 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -15,8 +15,8 @@ .js-md-preview{class: (preview_class if defined?(preview_class))} - if defined?(referenced_users) && referenced_users - %div.clearfix - %span.referenced-users.hide + %div.referenced-users.hide + %span = icon('exclamation-triangle') You are about to add %strong -- cgit v1.2.1 From 5c1b49f494f07bf37ba3c60f3b9f70d1842d8b60 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 4 Dec 2015 16:23:21 +0200 Subject: Add added, modified and removed properties to commit object in webhook --- CHANGELOG | 3 +++ app/models/commit.rb | 24 ++++++++++++++++++++++- doc/web_hooks/web_hooks.md | 14 +++++++++----- lib/gitlab/push_data_builder.rb | 32 ++----------------------------- spec/lib/gitlab/push_data_builder_spec.rb | 9 +++------ spec/models/commit_spec.rb | 11 +++++++++++ 6 files changed, 51 insertions(+), 42 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 58a0a3c8944..d8d878c45c9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,9 @@ v 8.3.0 (unreleased) - Add API endpoint to fetch merge request commits list - Expose events API with comment information and author info +v 8.2.3 + - Webhook payload has an added, modified and removed properties for each commit + v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) - Ensure cached application settings are refreshed at startup (Stan Hu) diff --git a/app/models/commit.rb b/app/models/commit.rb index 492f6be1ce3..912b4dedf51 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -146,7 +146,10 @@ class Commit author: { name: author_name, email: author_email - } + }, + added: repo_changes[:added], + modified: repo_changes[:modified], + removed: repo_changes[:removed] } end @@ -196,4 +199,23 @@ class Commit def status ci_commit.try(:status) || :not_found end + + private + + def repo_changes + changes = { added: [], modified: [], removed: [] } + + diffs.each do |diff| + case true + when diff.deleted_file + changes[:removed] << diff.old_path + when diff.renamed_file, diff.new_file + changes[:added] << diff.new_path + else + changes[:modified] << diff.new_path + end + end + + changes + end end diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 7d838187a26..03746dd9df3 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -57,6 +57,9 @@ X-Gitlab-Event: Push Hook "name": "Jordi Mallach", "email": "jordi@softcatala.org" } + "added": ["CHANGELOG"], + "modified": ["app/controller/application.rb"], + "removed": [] }, { "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", @@ -66,13 +69,14 @@ X-Gitlab-Event: Push Hook "author": { "name": "GitLab dev user", "email": "gitlabdev@dv6700.(none)" - } + }, + "added": ["CHANGELOG"], + "modified": ["app/controller/application.rb"], + "removed": [] } ], - "total_commits_count": 4, - "added": ["CHANGELOG"], - "modified": ["app/controller/application.rb"], - "removed": [] + "total_commits_count": 4 + } ``` diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb index fa068d50763..cdcdb02a052 100644 --- a/lib/gitlab/push_data_builder.rb +++ b/lib/gitlab/push_data_builder.rb @@ -18,10 +18,7 @@ module Gitlab # homepage: String, # }, # commits: Array, - # total_commits_count: Fixnum, - # added: ["CHANGELOG"], - # modified: [], - # removed: ["tmp/file.txt"] + # total_commits_count: Fixnum # } # def build(project, user, oldrev, newrev, ref, commits = [], message = nil) @@ -37,7 +34,6 @@ module Gitlab type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push" - repo_changes = repo_changes(project, newrev, oldrev) # Hash to be passed as post_receive_data data = { object_kind: type, @@ -60,10 +56,7 @@ module Gitlab visibility_level: project.visibility_level }, commits: commit_attrs, - total_commits_count: commits_count, - added: repo_changes[:added], - modified: repo_changes[:modified], - removed: repo_changes[:removed] + total_commits_count: commits_count } data @@ -94,27 +87,6 @@ module Gitlab newrev end end - - def repo_changes(project, newrev, oldrev) - changes = { added: [], modified: [], removed: [] } - compare_result = CompareService.new. - execute(project, newrev, project, oldrev) - - if compare_result - compare_result.diffs.each do |diff| - case true - when diff.deleted_file - changes[:removed] << diff.old_path - when diff.renamed_file, diff.new_file - changes[:added] << diff.new_path - else - changes[:modified] << diff.new_path - end - end - end - - changes - end end end end diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb index 02710742625..2170399ab5c 100644 --- a/spec/lib/gitlab/push_data_builder_spec.rb +++ b/spec/lib/gitlab/push_data_builder_spec.rb @@ -17,9 +17,9 @@ describe 'Gitlab::PushDataBuilder' do it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) } it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) } it { expect(data[:total_commits_count]).to eq(3) } - it { expect(data[:added]).to eq(["gitlab-grack"]) } - it { expect(data[:modified]).to eq([".gitmodules", "files/ruby/popen.rb", "files/ruby/regex.rb"]) } - it { expect(data[:removed]).to eq([]) } + it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) } + it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) } + it { expect(data[:commits].first[:removed]).to eq([]) } end describe :build do @@ -38,8 +38,5 @@ describe 'Gitlab::PushDataBuilder' do it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } it { expect(data[:commits]).to be_empty } it { expect(data[:total_commits_count]).to be_zero } - it { expect(data[:added]).to eq([]) } - it { expect(data[:modified]).to eq([]) } - it { expect(data[:removed]).to eq([]) } end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 90be9324951..b417bc98fa7 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -100,4 +100,15 @@ eos # Include the subject in the repository stub. let(:extra_commits) { [subject] } end + + describe '#hook_attrs' do + let(:data) { commit.hook_attrs } + + it { expect(data).to be_a(Hash) } + it { expect(data[:message]).to include('Add submodule from gitlab.com') } + it { expect(data[:timestamp]).to eq('2014-02-27T11:01:38+02:00') } + it { expect(data[:added]).to eq(["gitlab-grack"]) } + it { expect(data[:modified]).to eq([".gitmodules"]) } + it { expect(data[:removed]).to eq([]) } + end end -- cgit v1.2.1 From f1fd4880d9bbb7c34e910b357bc52874d2e6188e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 2 Dec 2015 20:11:58 +0000 Subject: Check GitLab Workhorse status in init.d script when reporting all components are up and running Closes https://github.com/gitlabhq/gitlabhq/issues/9869 --- lib/support/init.d/gitlab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index f0a6c2b30e9..43fda6fa92e 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -327,7 +327,7 @@ print_status() { printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" fi fi - if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then + if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } -- cgit v1.2.1 From 3c8051776b25add2e2845344a328328db33d1671 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 4 Dec 2015 14:35:29 -0500 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 6b61cbc11e9..ad15ed43b74 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) - - Fix application settings cache not expiring after changes (Stan Hu) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix 500 error when update group member permission - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) @@ -12,6 +11,9 @@ v 8.3.0 (unreleased) - Expose events API with comment information and author info - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583 +v 8.2.3 + - Fix application settings cache not expiring after changes (Stan Hu) + v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) - Ensure cached application settings are refreshed at startup (Stan Hu) -- cgit v1.2.1 From d800a949d2d5497e8aff3ae28ec8520e5b99cdb8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 3 Dec 2015 23:33:52 -0800 Subject: Fix Error 500 when creating global milestones with Unicode characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues: 1. The constraints in the resources were incorrect. Here's what it was before: ``` group_milestone GET /groups/:group_id/milestones/:id(.:format) groups/milestones#show {:id=>/[a-zA-Z.0-9_\-]+(?/[a-zA-Z.0-9_\-]+(?/[^\/]+/, :group_id=>/[a-zA-Z.0-9_\-]+(?"show", :controller=>"groups/milestones", :group_id=>#, :id=>"", :title=>"肯定不是中文的问题"} missing required keys: [:id]): This change uses the babosa library to create a better slug, which surprisingly isn't actually used by the global milestone controllers. Instead, they use the title passed as a query string for some reason. Closes https://github.com/gitlabhq/gitlabhq/issues/9881 Fix constraints --- CHANGELOG | 1 + Gemfile | 1 + Gemfile.lock | 2 ++ app/controllers/groups/milestones_controller.rb | 2 +- app/models/global_milestone.rb | 2 +- config/routes.rb | 2 +- .../groups/milestones_controller_spec.rb | 27 ++++++++++++++++++++++ 7 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 spec/controllers/groups/milestones_controller_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 7b2f1528656..228848c13f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.3.0 (unreleased) v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) + - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) diff --git a/Gemfile b/Gemfile index 67640bb9ae0..860e6bee47d 100644 --- a/Gemfile +++ b/Gemfile @@ -171,6 +171,7 @@ gem "underscore-rails", "~> 1.4.4" # Sanitize user input gem "sanitize", '~> 2.0' +gem 'babosa', '~> 1.0.2' # Protect against bruteforcing gem "rack-attack", '~> 4.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index cd1855758b2..be3e39d8e84 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -73,6 +73,7 @@ GEM descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) + babosa (1.0.2) bcrypt (3.1.10) benchmark-ips (2.3.0) better_errors (1.0.1) @@ -823,6 +824,7 @@ DEPENDENCIES asciidoctor (~> 1.5.2) attr_encrypted (~> 1.3.4) awesome_print (~> 1.2.0) + babosa (~> 1.0.2) benchmark-ips better_errors (~> 1.0.1) binding_of_caller (~> 0.7.2) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 10233222ee1..0c2a350bc39 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController end def milestone_path(title) - group_milestone_path(@group, title.parameterize, title: title) + group_milestone_path(@group, title.to_slug.to_s, title: title) end def projects diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index 1321ccd963f..85aa71662fe 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -16,7 +16,7 @@ class GlobalMilestone end def safe_title - @title.parameterize + @title.to_slug.to_s end def projects diff --git a/config/routes.rb b/config/routes.rb index 5c114452a3f..fdd387fd184 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -368,7 +368,7 @@ Rails.application.routes.draw do end resource :avatar, only: [:destroy] - resources :milestones, only: [:index, :show, :update, :new, :create] + resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] end end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb new file mode 100644 index 00000000000..eb0c6ac6d80 --- /dev/null +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Groups::MilestonesController do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:project2) { create(:empty_project, group: group) } + let(:user) { create(:user) } + let(:title) { '肯定不是中文的问题' } + + before do + sign_in(user) + group.add_owner(user) + project.team << [user, :master] + controller.instance_variable_set(:@group, group) + end + + describe "#create" do + it "should create group milestone with Chinese title" do + post :create, + group_id: group.id, + milestone: { project_ids: [project.id, project2.id], title: title } + + expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title)) + expect(Milestone.where(title: title).count).to eq(2) + end + end +end -- cgit v1.2.1 From 4fab178850f512ad15715b6f723ace6fce7882fc Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 4 Dec 2015 22:56:10 -0800 Subject: Fix spec that broke due to fact that iid is needed, not id, for MilestonesController --- spec/controllers/projects/milestones_controller_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 8127efabe6e..d173bb350f1 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::MilestonesController do let(:user) { create(:user) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project, milestone: milestone) } - let(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) } + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) } before do sign_in(user) @@ -15,10 +15,9 @@ describe Projects::MilestonesController do describe "#destroy" do it "should remove milestone" do - merge_request.reload expect(issue.milestone_id).to eq(milestone.id) - delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js + delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js expect(response).to be_success expect(Event.first.action).to eq(Event::DESTROYED) -- cgit v1.2.1 From 1c53dc28b505f2853750ed4ea8b954385c5bf598 Mon Sep 17 00:00:00 2001 From: Andrew Tomaka Date: Wed, 2 Dec 2015 19:02:15 -0500 Subject: Notify user if they cannot create projects --- app/assets/javascripts/user.js.coffee | 6 ++++++ app/assets/stylesheets/pages/projects.scss | 2 +- app/controllers/profiles_controller.rb | 1 + app/views/dashboard/_projects_head.html.haml | 3 +++ app/views/shared/_project_limit.html.haml | 8 ++++++++ db/migrate/20151203162133_add_hide_project_limit_to_users.rb | 5 +++++ db/schema.rb | 3 ++- 7 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 app/views/shared/_project_limit.html.haml create mode 100644 db/migrate/20151203162133_add_hide_project_limit_to_users.rb diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee index d0d81f96921..ec4271b092c 100644 --- a/app/assets/javascripts/user.js.coffee +++ b/app/assets/javascripts/user.js.coffee @@ -2,3 +2,9 @@ class @User constructor: -> $('.profile-groups-avatars').tooltip("placement": "top") new ProjectsList() + + $('.hide-project-limit-message').on 'click', (e) -> + path = '/' + $.cookie('hide_project_limit_message', 'false', { path: path }) + $(@).parents('.project-limit-message').remove() + e.preventDefault() diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 352f0ba2781..2ded32dba12 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -5,7 +5,7 @@ font-weight: normal; } } -.no-ssh-key-message { +.no-ssh-key-message, .project-limit-message { background-color: #f28d35; margin-bottom: 16px; } diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 8da7b4d50ea..28803164fcf 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController :email, :hide_no_password, :hide_no_ssh_key, + :hide_project_limit, :linkedin, :location, :name, diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index ed480b8caf8..991e67b1cd3 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,3 +1,6 @@ += content_for :flash_message do + = render 'shared/project_limit' + %ul.center-top-menu = nav_link(path: ['projects#index', 'root#index']) do = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do diff --git a/app/views/shared/_project_limit.html.haml b/app/views/shared/_project_limit.html.haml new file mode 100644 index 00000000000..960ff00b49d --- /dev/null +++ b/app/views/shared/_project_limit.html.haml @@ -0,0 +1,8 @@ +- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project? + .project-limit-message.alert.alert-warning.hidden-xs + You won't be able to create new projects because you have reached your project limit. + + .pull-right + = link_to "Don't show again", profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link' + | + = link_to 'Remind later', '#', class: 'hide-project-limit-message alert-link' diff --git a/db/migrate/20151203162133_add_hide_project_limit_to_users.rb b/db/migrate/20151203162133_add_hide_project_limit_to_users.rb new file mode 100644 index 00000000000..6ffadfa1894 --- /dev/null +++ b/db/migrate/20151203162133_add_hide_project_limit_to_users.rb @@ -0,0 +1,5 @@ +class AddHideProjectLimitToUsers < ActiveRecord::Migration + def change + add_column :users, :hide_project_limit, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index fbcb711e569..fb59e187625 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151118162244) do +ActiveRecord::Schema.define(version: 20151203162133) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -814,6 +814,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do t.integer "project_view", default: 0 t.integer "consumed_timestep" t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree -- cgit v1.2.1 From 176d6e2a8ff97a33d533495aa3a2775dbb87284f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 5 Dec 2015 22:09:52 +0100 Subject: Refactor note awards to reuse `emoji_pattern` and improve validator --- app/models/note.rb | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 03640be7c93..2bee19479c5 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -39,9 +39,11 @@ class Note < ActiveRecord::Base delegate :name, to: :project, prefix: true delegate :name, :email, to: :author, prefix: true + before_validation :set_award! + validates :note, :project, presence: true validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award } - validates :note, format: { with: /\A[-_+[:alnum:]]*\z/ }, if: -> (n){ n.is_award } + validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award } validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true # Attachments are deprecated and are handled by Markdown uploader validates :attachment, file_size: { maximum: :max_attachment_size } @@ -72,7 +74,6 @@ class Note < ActiveRecord::Base serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } - before_validation :set_award! class << self def discussions_from_notes(notes) @@ -351,36 +352,31 @@ class Note < ActiveRecord::Base !system? end - # Checks if note is an award added from an issue comment. + # Checks if note is an award added as a comment # - # If note is an award, this method sets is_award to true, - # and changes note content to award-emoji name. - # - # Awards are only supported for issue comments. + # If note is an award, this method sets is_award to true + # and changes content of the note to award name. # # Method is executed as a before_validation callback. # def set_award! - return unless supports_awards? && contains_emoji_only? - + return unless awards_supported? && contains_emoji_only? self.is_award = true self.note = award_emoji_name end - def supports_awards? - noteable.kind_of?(Issue) || - noteable.is_a?(MergeRequest) - end - private + def awards_supported? + noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest) + end + def contains_emoji_only? - (note =~ /\A:[-_+[:alnum:]]*:\s?\z/) ? true : false + emoji_only_pattern = /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/ + (note =~ emoji_only_pattern) ? true : false end def award_emoji_name - return nil unless contains_emoji_only? - - note.match(/\A:([-_+[:alnum:]]*):\s?/)[1] + note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1] end end -- cgit v1.2.1 From bfe91b692a89f7a5ee8a0b044fabf5ec397b2904 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 5 Dec 2015 22:18:13 +0100 Subject: Remove space before exclamation mark in award alert [ci skip] --- app/assets/javascripts/notes.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 4f559e86378..dd6cbcfc70b 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -113,7 +113,7 @@ class @Notes renderNote: (note) -> unless note.valid if note.award - flash = new Flash('You have already used this award emoji !', 'alert') + flash = new Flash('You have already used this award emoji!', 'alert') flash.pin() return -- cgit v1.2.1 From caa6851bf5a65e454b702104a2895e63e368a21a Mon Sep 17 00:00:00 2001 From: Anton Baklanov Date: Sun, 29 Nov 2015 22:42:54 +0200 Subject: Fixed duplicated issue note email notifications. Fixes #2560 --- CHANGELOG | 1 + app/services/notification_service.rb | 1 + spec/services/notification_service_spec.rb | 9 ++++++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7b2f1528656..99c5fdd4d07 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.2.2 - Prevent "413 Request entity too large" errors when pushing large files with LFS - Fix invalid links within projects dashboard header - Make current user the first user in assignee dropdown in issues detail page (Stan Hu) + - Fix: duplicate email notifications on issue comments v 8.2.1 - Forcefully update builds that didn't want to update with state machine diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 388a4defb26..bdf7b3ad2bb 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -145,6 +145,7 @@ class NotificationService recipients = reject_unsubscribed_users(recipients, note.noteable) recipients.delete(note.author) + recipients = recipients.uniq # build notify method like 'note_commit_email' notify_method = "note_#{note.noteable_type.underscore}_email".to_sym diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index a4e2b2953cc..35fa412ed80 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -45,6 +45,7 @@ describe NotificationService do project.team << [issue.author, :master] project.team << [issue.assignee, :master] project.team << [note.author, :master] + create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy') end describe :new_note do @@ -60,6 +61,7 @@ describe NotificationService do should_email(note.noteable.assignee) should_email(@u_mentioned) should_email(@subscriber) + should_email(@subscribed_participant) should_not_email(note.author) should_not_email(@u_participating) should_not_email(@u_disabled) @@ -381,18 +383,19 @@ describe NotificationService do def add_users_with_subscription(project, issuable) @subscriber = create :user @unsubscriber = create :user + @subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING) + project.team << [@subscribed_participant, :master] project.team << [@subscriber, :master] project.team << [@unsubscriber, :master] issuable.subscriptions.create(user: @subscriber, subscribed: true) + issuable.subscriptions.create(user: @subscribed_participant, subscribed: true) issuable.subscriptions.create(user: @unsubscriber, subscribed: false) end def sent_to_user?(user) - ActionMailer::Base.deliveries.any? do |message| - message.to.include?(user.email) - end + ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1 end def should_email(user) -- cgit v1.2.1 From 1c4213acd5dde6ce44a70b79dd766e9e7f8b59b4 Mon Sep 17 00:00:00 2001 From: Vyacheslav Stetskevych Date: Sun, 6 Dec 2015 03:10:29 +0200 Subject: Fix gitlab-ssl nginx config to work when multiple server_names are served over https --- lib/support/nginx/gitlab-ssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 016f7a536fb..79fe1474821 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -56,7 +56,7 @@ server { listen [::]:80 ipv6only=on default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice - return 301 https://$server_name$request_uri; + return 301 https://$http_host$request_uri; access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; } -- cgit v1.2.1 From 893d08c0dc6a1eba14db7694636707f30b28a7f4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 7 Dec 2015 11:00:03 +0100 Subject: Simplify `contains_emoji_only?` method in `Note` --- app/models/note.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 2bee19479c5..239a0f77f8e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -372,8 +372,7 @@ class Note < ActiveRecord::Base end def contains_emoji_only? - emoji_only_pattern = /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/ - (note =~ emoji_only_pattern) ? true : false + note =~ /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/ end def award_emoji_name -- cgit v1.2.1 From f5ec1ebe2caa0d8b4ccccb671ae6a4cc99cddbe0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 7 Dec 2015 12:07:13 +0100 Subject: Remove changelog entry issue number --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 882648b36a2..00a24d4317b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,7 +6,7 @@ v 8.3.0 (unreleased) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Add ignore whitespace change option to commit view - Fire update hook from GitLab - - Run custom Git hooks when branch is created or deleted. #1156 + - Run custom Git hooks when branch is created or deleted. v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) -- cgit v1.2.1 From 839a8b924972d87e37085333adf1b74b41bbd26c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 7 Dec 2015 12:07:40 +0100 Subject: Move changelog item --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9c00aa7a657..2cb341d882d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,10 +11,10 @@ v 8.3.0 (unreleased) - Add API endpoint to fetch merge request commits list - Expose events API with comment information and author info - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583 + - Run custom Git hooks when branch is created or deleted. v 8.2.3 - Fix application settings cache not expiring after changes (Stan Hu) - - Run custom Git hooks when branch is created or deleted. v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) -- cgit v1.2.1 From 5df2c4419c5019b5003ddfa6adb59c84c3d9910c Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 7 Dec 2015 14:11:15 +0200 Subject: fox specs --- app/models/commit.rb | 37 +++++++++++++++++++++++-------------- app/models/merge_request.rb | 2 +- lib/gitlab/push_data_builder.rb | 4 +++- spec/models/commit_spec.rb | 2 +- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 912b4dedf51..fecadfeec8e 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -135,10 +135,10 @@ class Commit description.present? end - def hook_attrs + def hook_attrs(with_changed_files = false) path_with_namespace = project.path_with_namespace - { + data = { id: id, message: safe_message, timestamp: committed_date.xmlschema, @@ -146,11 +146,18 @@ class Commit author: { name: author_name, email: author_email - }, - added: repo_changes[:added], - modified: repo_changes[:modified], - removed: repo_changes[:removed] + } } + + if with_changed_files + data.merge!({ + added: repo_changes[:added], + modified: repo_changes[:modified], + removed: repo_changes[:removed] + }) + end + + data end # Discover issues should be closed when this commit is pushed to a project's @@ -205,14 +212,16 @@ class Commit def repo_changes changes = { added: [], modified: [], removed: [] } - diffs.each do |diff| - case true - when diff.deleted_file - changes[:removed] << diff.old_path - when diff.renamed_file, diff.new_file - changes[:added] << diff.new_path - else - changes[:modified] << diff.new_path + if diffs.any? + diffs.each do |diff| + case true + when diff.deleted_file + changes[:removed] << diff.old_path + when diff.renamed_file, diff.new_file + changes[:added] << diff.new_path + else + changes[:modified] << diff.new_path + end end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1b3d6079d2c..92a82d44c76 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -291,7 +291,7 @@ class MergeRequest < ActiveRecord::Base work_in_progress: work_in_progress? } - unless last_commit.nil? + if last_commit attrs.merge!(last_commit: last_commit.hook_attrs) end diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb index cdcdb02a052..5842b740e8e 100644 --- a/lib/gitlab/push_data_builder.rb +++ b/lib/gitlab/push_data_builder.rb @@ -30,7 +30,9 @@ module Gitlab # For performance purposes maximum 20 latest commits # will be passed as post receive hook data. - commit_attrs = commits_limited.map(&:hook_attrs) + commit_attrs = commits_limited.map do |commit| + commit.hook_attrs(true) + end type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push" diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index b417bc98fa7..6728722b503 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -102,7 +102,7 @@ eos end describe '#hook_attrs' do - let(:data) { commit.hook_attrs } + let(:data) { commit.hook_attrs(true) } it { expect(data).to be_a(Hash) } it { expect(data[:message]).to include('Add submodule from gitlab.com') } -- cgit v1.2.1 From ff08ce9ca4bef1a4f81f7a4b323614a639efe959 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 7 Dec 2015 13:45:00 +0100 Subject: Satisfy Rubocop --- app/models/global_milestone.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index 33ddb265fba..dd9f88704a6 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -127,4 +127,4 @@ class GlobalMilestone end end end -end \ No newline at end of file +end -- cgit v1.2.1 From 3c97cbc74cf87856ed7b1af197358d4e3adb1240 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 7 Dec 2015 15:13:06 +0200 Subject: fixes after review --- app/models/commit.rb | 25 +++++++++---------------- lib/gitlab/push_data_builder.rb | 2 +- spec/models/commit_spec.rb | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index fecadfeec8e..14883c96f5f 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -135,7 +135,7 @@ class Commit description.present? end - def hook_attrs(with_changed_files = false) + def hook_attrs(with_changed_files: false) path_with_namespace = project.path_with_namespace data = { @@ -150,11 +150,7 @@ class Commit } if with_changed_files - data.merge!({ - added: repo_changes[:added], - modified: repo_changes[:modified], - removed: repo_changes[:removed] - }) + data.merge!(repo_changes) end data @@ -212,16 +208,13 @@ class Commit def repo_changes changes = { added: [], modified: [], removed: [] } - if diffs.any? - diffs.each do |diff| - case true - when diff.deleted_file - changes[:removed] << diff.old_path - when diff.renamed_file, diff.new_file - changes[:added] << diff.new_path - else - changes[:modified] << diff.new_path - end + diffs.each do |diff| + if diff.deleted_file + changes[:removed] << diff.old_path + elsif diff.renamed_file || diff.new_file + changes[:added] << diff.new_path + else + changes[:modified] << diff.new_path end end diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb index 5842b740e8e..4f9cdef3869 100644 --- a/lib/gitlab/push_data_builder.rb +++ b/lib/gitlab/push_data_builder.rb @@ -31,7 +31,7 @@ module Gitlab # For performance purposes maximum 20 latest commits # will be passed as post receive hook data. commit_attrs = commits_limited.map do |commit| - commit.hook_attrs(true) + commit.hook_attrs(with_changed_files: true) end type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push" diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 6728722b503..0b1e2bf74d0 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -102,7 +102,7 @@ eos end describe '#hook_attrs' do - let(:data) { commit.hook_attrs(true) } + let(:data) { commit.hook_attrs(with_changed_files: true) } it { expect(data).to be_a(Hash) } it { expect(data[:message]).to include('Add submodule from gitlab.com') } -- cgit v1.2.1