Commit 7d836a0c authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'master' into fix-merge-request-that-removes-submodule

parents 12fdc13a bd5fb1b4
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased) 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: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Fix 500 error when update group member permission - Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references
- Add ignore whitespace change option to commit view - Add ignore whitespace change option to commit view
- Fire update hook from GitLab - Fire update hook from GitLab
- Style warning about mentioning many people in a comment
- Fix: sort milestones by due date once again (Greg Smethells)
- Don't show project fork event as "imported" - Don't show project fork event as "imported"
- Add API endpoint to fetch merge request commits list - Add API endpoint to fetch merge request commits list
- Expose events API with comment information and author info - Expose events API with comment information and author info
- Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583 - Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
- Fix 500 error when creating a merge request that removes a submodule - Fix 500 error when creating a merge request that removes a submodule
- Run custom Git hooks when branch is created or deleted.
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.3
- Webhook payload has an added, modified and removed properties for each commit
v 8.2.2 v 8.2.2
- Fix 404 in redirection after removing a project (Stan Hu) - Fix 404 in redirection after removing a project (Stan Hu)
...@@ -21,6 +31,7 @@ v 8.2.2 ...@@ -21,6 +31,7 @@ v 8.2.2
- Prevent "413 Request entity too large" errors when pushing large files with LFS - Prevent "413 Request entity too large" errors when pushing large files with LFS
- Fix invalid links within projects dashboard header - Fix invalid links within projects dashboard header
- Make current user the first user in assignee dropdown in issues detail page (Stan Hu) - 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 v 8.2.1
- Forcefully update builds that didn't want to update with state machine - Forcefully update builds that didn't want to update with state machine
......
...@@ -171,6 +171,7 @@ gem "underscore-rails", "~> 1.4.4" ...@@ -171,6 +171,7 @@ gem "underscore-rails", "~> 1.4.4"
# Sanitize user input # Sanitize user input
gem "sanitize", '~> 2.0' gem "sanitize", '~> 2.0'
gem 'babosa', '~> 1.0.2'
# Protect against bruteforcing # Protect against bruteforcing
gem "rack-attack", '~> 4.3.0' gem "rack-attack", '~> 4.3.0'
......
...@@ -73,6 +73,7 @@ GEM ...@@ -73,6 +73,7 @@ GEM
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
bcrypt (3.1.10) bcrypt (3.1.10)
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
better_errors (1.0.1) better_errors (1.0.1)
...@@ -823,6 +824,7 @@ DEPENDENCIES ...@@ -823,6 +824,7 @@ DEPENDENCIES
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4) attr_encrypted (~> 1.3.4)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
benchmark-ips benchmark-ips
better_errors (~> 1.0.1) better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
......
...@@ -88,4 +88,9 @@ class @AwardsHandler ...@@ -88,4 +88,9 @@ class @AwardsHandler
callback.call() callback.call()
findEmojiIcon: (emoji) -> findEmojiIcon: (emoji) ->
$(".icon[data-emoji='" + emoji + "']") $(".icon[data-emoji='" + emoji + "']")
\ No newline at end of file
scrollToAwards: ->
$('body, html').animate({
scrollTop: $('.awards').offset().top - 80
}, 200)
class @Flash class @Flash
constructor: (message, type)-> constructor: (message, type)->
flash = $(".flash-container") @flash = $(".flash-container")
flash.html("") @flash.html("")
$('<div/>', innerDiv = $('<div/>',
class: "flash-#{type}", class: "flash-#{type}",
text: message text: message
).appendTo(".flash-container") )
innerDiv.appendTo(".flash-container")
flash.click -> $(@).fadeOut() @flash.click -> $(@).fadeOut()
flash.show() @flash.show()
pin: ->
@flash.addClass('flash-pinned flash-raised')
...@@ -111,6 +111,12 @@ class @Notes ...@@ -111,6 +111,12 @@ class @Notes
Note: for rendering inline notes use renderDiscussionNote Note: for rendering inline notes use renderDiscussionNote
### ###
renderNote: (note) -> renderNote: (note) ->
unless note.valid
if note.award
flash = new Flash('You have already used this award emoji!', 'alert')
flash.pin()
return
# render note if it not present in loaded list # render note if it not present in loaded list
# or skip if rendered # or skip if rendered
if @isNewNote(note) && !note.award if @isNewNote(note) && !note.award
...@@ -122,6 +128,7 @@ class @Notes ...@@ -122,6 +128,7 @@ class @Notes
if note.award if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path) awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
awards_handler.scrollToAwards()
### ###
Check if note does not exists on page Check if note does not exists on page
......
...@@ -2,3 +2,9 @@ class @User ...@@ -2,3 +2,9 @@ class @User
constructor: -> constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top") $('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList() 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()
...@@ -15,3 +15,13 @@ ...@@ -15,3 +15,13 @@
@extend .alert-danger; @extend .alert-danger;
} }
} }
.flash-pinned {
position: fixed;
top: 80px;
width: 80%;
}
.flash-raised {
z-index: 1000;
}
...@@ -73,11 +73,8 @@ ...@@ -73,11 +73,8 @@
} }
.referenced-users { .referenced-users {
padding: 10px 0; color: #4c4e54;
color: #999; padding-top: 10px;
margin-left: 10px;
margin-top: 1px;
margin-right: 130px;
} }
.md-preview-holder { .md-preview-holder {
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
font-weight: normal; font-weight: normal;
} }
} }
.no-ssh-key-message { .no-ssh-key-message, .project-limit-message {
background-color: #f28d35; background-color: #f28d35;
margin-bottom: 16px; margin-bottom: 16px;
} }
......
...@@ -2,8 +2,10 @@ module GlobalMilestones ...@@ -2,8 +2,10 @@ module GlobalMilestones
extend ActiveSupport::Concern extend ActiveSupport::Concern
def milestones def milestones
epoch = DateTime.parse('1970-01-01')
@milestones = MilestonesFinder.new.execute(@projects, params) @milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones) @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) @milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
end end
......
...@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end end
def milestone_path(title) def milestone_path(title)
group_milestone_path(@group, title.parameterize, title: title) group_milestone_path(@group, title.to_slug.to_s, title: title)
end end
def projects def projects
......
...@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
:email, :email,
:hide_no_password, :hide_no_password,
:hide_no_ssh_key, :hide_no_ssh_key,
:hide_project_limit,
:linkedin, :linkedin,
:location, :location,
:name, :name,
......
...@@ -131,16 +131,25 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -131,16 +131,25 @@ class Projects::NotesController < Projects::ApplicationController
end end
def render_note_json(note) def render_note_json(note)
render json: { if note.valid?
id: note.id, render json: {
discussion_id: note.discussion_id, valid: true,
html: note_to_html(note), id: note.id,
award: note.is_award, discussion_id: note.discussion_id,
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "", html: note_to_html(note),
note: note.note, award: note.is_award,
discussion_html: note_to_discussion_html(note), emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
discussion_with_diff_html: note_to_discussion_with_diff_html(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: {
valid: false,
award: note.is_award,
errors: note.errors
}
end
end end
def authorize_admin_note! def authorize_admin_note!
......
class MilestonesFinder class MilestonesFinder
def execute(projects, params) def execute(projects, params)
milestones = Milestone.of_projects(projects) milestones = Milestone.of_projects(projects)
milestones = milestones.order("due_date ASC") milestones = milestones.reorder("due_date ASC")
case params[:state] case params[:state]
when 'closed' then milestones.closed when 'closed' then milestones.closed
......
...@@ -87,7 +87,11 @@ module IssuesHelper ...@@ -87,7 +87,11 @@ module IssuesHelper
end end
def merge_requests_sentence(merge_requests) 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 end
def url_to_emoji(name) def url_to_emoji(name)
......
...@@ -39,7 +39,11 @@ module MergeRequestsHelper ...@@ -39,7 +39,11 @@ module MergeRequestsHelper
end end
def issues_sentence(issues) 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 end
def mr_change_branches_path(merge_request) def mr_change_branches_path(merge_request)
......
...@@ -28,7 +28,9 @@ module MilestonesHelper ...@@ -28,7 +28,9 @@ module MilestonesHelper
Milestone.where(project_id: @projects) Milestone.where(project_id: @projects)
end.active end.active
epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones) 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::None)
grouped_milestones.unshift(Milestone::Any) grouped_milestones.unshift(Milestone::Any)
......
...@@ -78,11 +78,23 @@ class Commit ...@@ -78,11 +78,23 @@ class Commit
}x }x
end end
def self.link_reference_pattern
super("commit", /(?<commit>\h{6,40})/)
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
if cross_project_reference?(from_project) if cross_project_reference?(from_project)
"#{project.to_reference}@#{id}" project.to_reference + self.class.reference_prefix + self.id
else else
id 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
self.short_id
end end
end end
...@@ -135,10 +147,10 @@ class Commit ...@@ -135,10 +147,10 @@ class Commit
description.present? description.present?
end end
def hook_attrs def hook_attrs(with_changed_files: false)
path_with_namespace = project.path_with_namespace path_with_namespace = project.path_with_namespace
{ data = {
id: id, id: id,
message: safe_message, message: safe_message,
timestamp: committed_date.xmlschema, timestamp: committed_date.xmlschema,
...@@ -148,6 +160,12 @@ class Commit ...@@ -148,6 +160,12 @@ class Commit
email: author_email email: author_email
} }
} }
if with_changed_files
data.merge!(repo_changes)
end
data
end end
# Discover issues should be closed when this commit is pushed to a project's # Discover issues should be closed when this commit is pushed to a project's
...@@ -196,4 +214,22 @@ class Commit ...@@ -196,4 +214,22 @@ class Commit
def status def status
ci_commit.try(:status) || :not_found ci_commit.try(:status) || :not_found
end end
private
def repo_changes
changes = { added: [], modified: [], removed: [] }
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
changes
end
end end
...@@ -2,36 +2,38 @@ ...@@ -2,36 +2,38 @@
# #
# Examples: # Examples:
# #
# range = CommitRange.new('f3f85602...e86e1013') # range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false # range.exclude_start? # => false
# range.reference_title # => "Commits f3f85602 through e86e1013" # range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013" # range.to_s # => "f3f85602...e86e1013"
# #
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae') # range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true # range.exclude_start? # => true
# range.reference_title # => "Commits f3f85602^ through e86e1013" # range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"} # range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013" # range.to_s # => "f3f85602..e86e1013"
# #
# # Assuming `project` is a Project with a repository containing both commits: # # Assuming the specified project has a repository containing both commits:
# range.project = project
# range.valid_commits? # => true # range.valid_commits? # => true
# #
class CommitRange class CommitRange
include ActiveModel::Conversion include ActiveModel::Conversion
include Referable include Referable
attr_reader :sha_from, :notation, :sha_to attr_reader :commit_from, :notation, :commit_to
attr_reader :ref_from, :ref_to
# Optional Project model # Optional Project model
attr_accessor :project attr_accessor :project
# See `exclude_start?` # The beginning and ending refs can be named or SHAs, and
attr_reader :exclude_start
# The beginning and ending SHAs can be between 6 and 40 hex characters, and
# the range notation can be double- or triple-dot. # 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 def self.reference_prefix
'@' '@'
...@@ -43,27 +45,40 @@ class CommitRange ...@@ -43,27 +45,40 @@ class CommitRange
def self.reference_pattern def self.reference_pattern
%r{ %r{
(?:#{Project.reference_pattern}#{reference_prefix})? (?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{PATTERN}) (?<commit_range>#{STRICT_PATTERN})
}x }x
end end
def self.link_reference_pattern
super("compare", /(?<commit_range>#{PATTERN})/)
end
# Initialize a CommitRange # Initialize a CommitRange
# #
# range_string - The String commit range. # range_string - The String commit range.
# project - An optional Project model. # project - An optional Project model.
# #
# Raises ArgumentError if `range_string` does not match `PATTERN`. # 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! range_string.strip!
unless range_string.match(/\A#{PATTERN}\z/) unless range_string =~ /\A#{PATTERN}\z/
raise ArgumentError, "invalid CommitRange string format: #{range_string}" raise ArgumentError, "invalid CommitRange string format: #{range_string}"
end end
@exclude_start = !range_string.include?('...') @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
@sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
@project = project if project.valid_repo?
@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 end
def inspect def inspect
...@@ -71,15 +86,24 @@ class CommitRange ...@@ -71,15 +86,24 @@ class CommitRange
end end
def to_s def to_s
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}" sha_from + notation + sha_to
end end
alias_method :id, :to_s
def to_reference(from_project = nil) def to_reference(from_project = nil)
# Not using to_s because we want the full SHAs if cross_project_reference?(from_project)
reference = sha_from + notation + sha_to 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) if cross_project_reference?(from_project)
reference = project.to_reference + '@' + reference reference = project.to_reference + self.class.reference_prefix + reference
end end
reference reference
...@@ -87,46 +111,58 @@ class CommitRange ...@@ -87,46 +111,58 @@ class CommitRange
# Returns a String for use in a link's title attribute # Returns a String for use in a link's title attribute
def reference_title def reference_title
"Commits #{suffixed_sha_from} through #{sha_to}" "Commits #{sha_start} through #{sha_to}"
end end
# Return a Hash of parameters for passing to a URL helper # Return a Hash of parameters for passing to a URL helper
# #
# See `namespace_project_compare_url` # See `namespace_project_compare_url`
def to_param def to_param
{ from: suffixed_sha_from, to: sha_to } { from: sha_start, to: sha_to }
end end
def exclude_start? def exclude_start?
exclude_start @notation == '..'
end end
# Check if both the starting and ending commit IDs exist in a project's # Check if both the starting and ending commit IDs exist in a project's
# repository # repository
# def valid_commits?
# project - An optional Project to check (default: `project`) commit_start.present? && commit_end.present?
def valid_commits?(project = project)
return nil unless project.present?
return false unless project.valid_repo?
commit_from.present? && commit_to.present?
end end
def persisted? def persisted?
true true
end end
def commit_from def sha_from
@commit_from ||= project.repository.commit(suffixed_sha_from) return nil unless @commit_from
@commit_from.id
end
def sha_to
return nil unless @commit_to
@commit_to.id
end end
def commit_to def sha_start
@commit_to ||= project.repository.commit(sha_to) return nil unless sha_from
exclude_start? ? sha_from + '^' : sha_from
end end
private def commit_start
return nil unless sha_start
def suffixed_sha_from if exclude_start?
sha_from + (exclude_start? ? '^' : '') @commit_start ||= project.commit(sha_start)
else
commit_from
end
end end
alias_method :sha_end, :sha_to
alias_method :commit_end, :commit_to
end end
...@@ -62,13 +62,18 @@ module Mentionable ...@@ -62,13 +62,18 @@ module Mentionable
return [] if text.blank? return [] if text.blank?
refs = all_references(current_user, text, load_lazy_references: load_lazy_references) 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 end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. # 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) def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
refs = referenced_mentionables(author, text) refs = referenced_mentionables(author, text)
# We're using this method instead of Array diffing because that requires # 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 # both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects. # case for otherwise identical Commit objects.
...@@ -111,7 +116,7 @@ module Mentionable ...@@ -111,7 +116,7 @@ module Mentionable
# Only include changed fields that are mentionable # Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) } source.select { |key, val| mentionable.include?(key) }
end end
# Determine whether or not a cross-reference Note has already been created between this Mentionable and # Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target. # the specified target.
def cross_reference_exists?(target) def cross_reference_exists?(target)
......
...@@ -21,6 +21,10 @@ module Referable ...@@ -21,6 +21,10 @@ module Referable
'' ''
end end
def reference_link_text(from_project = nil)
to_reference(from_project)
end
module ClassMethods module ClassMethods
# The character that prefixes the actual reference identifier # The character that prefixes the actual reference identifier
# #
...@@ -44,6 +48,25 @@ module Referable ...@@ -44,6 +48,25 @@ module Referable
def reference_pattern def reference_pattern
raise NotImplementedError, "#{self} does not implement #{__method__}" raise NotImplementedError, "#{self} does not implement #{__method__}"
end end
def link_reference_pattern(route, pattern)
%r{
(?<url>
#{Regexp.escape(Gitlab.config.gitlab.url)}
\/#{Project.reference_pattern}
\/#{Regexp.escape(route)}
\/#{pattern}
(?<path>
(\/[a-z0-9_=-]+)*
)?
(?<query>
\?[a-z0-9_=-]+
(&[a-z0-9_=-]+)*
)?
(?<anchor>\#[a-z0-9_-]+)?
)
}x
end
end end
private private
......
...@@ -16,7 +16,15 @@ class GlobalMilestone ...@@ -16,7 +16,15 @@ class GlobalMilestone
end end
def safe_title def safe_title
@title.parameterize @title.to_slug.to_s
end
def expired?
if due_date
due_date.past?
else
false
end
end end
def projects def projects
...@@ -98,4 +106,25 @@ class GlobalMilestone ...@@ -98,4 +106,25 @@ class GlobalMilestone
def complete? def complete?
total_items_count == closed_items_count 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 end
...@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base ...@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
}x }x
end end
def self.link_reference_pattern
super("issues", /(?<issue>\d+)/)
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
......
...@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base
}x }x
end end
def self.link_reference_pattern
super("merge_requests", /(?<merge_request>\d+)/)
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}" reference = "#{self.class.reference_prefix}#{iid}"
...@@ -291,7 +295,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -291,7 +295,7 @@ class MergeRequest < ActiveRecord::Base
work_in_progress: work_in_progress? work_in_progress: work_in_progress?
} }
unless last_commit.nil? if last_commit
attrs.merge!(last_commit: last_commit.hook_attrs) attrs.merge!(last_commit: last_commit.hook_attrs)
end end
...@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) } issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description)) closed_by_message(description))
issues.uniq.sort_by(&:id) issues.uniq
else else
[] []
end end
......
...@@ -39,8 +39,11 @@ class Note < ActiveRecord::Base ...@@ -39,8 +39,11 @@ class Note < ActiveRecord::Base
delegate :name, to: :project, prefix: true delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true delegate :name, :email, to: :author, prefix: true
before_validation :set_award!
validates :note, :project, presence: true validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award } validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, 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 validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader # Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size } validates :attachment, file_size: { maximum: :max_attachment_size }
...@@ -348,4 +351,31 @@ class Note < ActiveRecord::Base ...@@ -348,4 +351,31 @@ class Note < ActiveRecord::Base
def editable? def editable?
!system? !system?
end end
# Checks if note is an award added as a comment
#
# 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 awards_supported? && contains_emoji_only?
self.is_award = true
self.note = award_emoji_name
end
private
def awards_supported?
noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
end
def contains_emoji_only?
note =~ /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/
end
def award_emoji_name
note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
end
end end
require 'securerandom' require 'securerandom'
class Repository class Repository
class PreReceiveError < StandardError; end
class CommitError < StandardError; end class CommitError < StandardError; end
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
...@@ -108,10 +107,19 @@ class Repository ...@@ -108,10 +107,19 @@ class Repository
tags.find { |tag| tag.name == name } tags.find { |tag| tag.name == name }
end end
def add_branch(branch_name, ref) def add_branch(user, branch_name, target)
expire_branches_cache 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 end
def add_tag(tag_name, ref, message = nil) def add_tag(tag_name, ref, message = nil)
...@@ -120,10 +128,20 @@ class Repository ...@@ -120,10 +128,20 @@ class Repository
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end end
def rm_branch(branch_name) def rm_branch(user, branch_name)
expire_branches_cache 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 end
def rm_tag(tag_name) def rm_tag(tag_name)
...@@ -550,7 +568,6 @@ class Repository ...@@ -550,7 +568,6 @@ class Repository
def commit_with_hooks(current_user, branch) def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
gl_id = Gitlab::ShellEnv.gl_id(current_user)
was_empty = empty? was_empty = empty?
# Create temporary ref # Create temporary ref
...@@ -569,15 +586,7 @@ class Repository ...@@ -569,15 +586,7 @@ class Repository
raise CommitError.new('Failed to create commit') raise CommitError.new('Failed to create commit')
end end
# Run GitLab pre-receive hook GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
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
if was_empty if was_empty
# Create branch # Create branch
rugged.references.create(ref, newrev) rugged.references.create(ref, newrev)
...@@ -592,16 +601,11 @@ class Repository ...@@ -592,16 +601,11 @@ class Repository
raise CommitError.new('Commit was rejected because branch received new push') raise CommitError.new('Commit was rejected because branch received new push')
end end
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 end
rescue GitHooksService::PreReceiveError
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
raise
end end
private private
......
...@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base ...@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
}x }x
end end
def self.link_reference_pattern
super("snippets", /(?<snippet>\d+)/)
end
def to_reference(from_project = nil) def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}" reference = "#{self.class.reference_prefix}#{id}"
......
...@@ -13,8 +13,7 @@ class CreateBranchService < BaseService ...@@ -13,8 +13,7 @@ class CreateBranchService < BaseService
return error('Branch already exists') return error('Branch already exists')
end end
repository.add_branch(branch_name, ref) new_branch = repository.add_branch(current_user, branch_name, ref)
new_branch = repository.find_branch(branch_name)
if new_branch if new_branch
push_data = build_push_data(project, current_user, new_branch) push_data = build_push_data(project, current_user, new_branch)
...@@ -27,6 +26,8 @@ class CreateBranchService < BaseService ...@@ -27,6 +26,8 @@ class CreateBranchService < BaseService
else else
error('Invalid reference name') error('Invalid reference name')
end end
rescue GitHooksService::PreReceiveError
error('Branch creation was rejected by Git hook')
end end
def success(branch) def success(branch)
......
...@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService ...@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
return error('You dont have push access to repo', 405) return error('You dont have push access to repo', 405)
end end
if repository.rm_branch(branch_name) if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch) push_data = build_push_data(branch)
EventCreateService.new.push(project, current_user, push_data) EventCreateService.new.push(project, current_user, push_data)
...@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService ...@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
else else
error('Failed to remove branch') error('Failed to remove branch')
end end
rescue GitHooksService::PreReceiveError
error('Branch deletion was rejected by Git hook')
end end
def error(message, return_code = 400) def error(message, return_code = 400)
......
...@@ -26,7 +26,7 @@ module Files ...@@ -26,7 +26,7 @@ module Files
else else
error("Something went wrong. Your changes were not committed") error("Something went wrong. Your changes were not committed")
end end
rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message) error(ex.message)
end end
......
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
%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)
hook.trigger(@user, @oldrev, @newrev, @ref)
end
end
...@@ -5,11 +5,6 @@ module Notes ...@@ -5,11 +5,6 @@ module Notes
note.author = current_user note.author = current_user
note.system = false note.system = false
if contains_emoji_only?(params[:note])
note.is_award = true
note.note = emoji_name(params[:note])
end
if note.save if note.save
notification_service.new_note(note) notification_service.new_note(note)
...@@ -33,13 +28,5 @@ module Notes ...@@ -33,13 +28,5 @@ module Notes
note.project.execute_hooks(note_data, :note_hooks) note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks) note.project.execute_services(note_data, :note_hooks)
end end
def contains_emoji_only?(note)
note =~ /\A:[-_+[:alnum:]]*:\s?\z/
end
def emoji_name(note)
note.match(/\A:([-_+[:alnum:]]*):\s?/)[1]
end
end end
end end
...@@ -145,6 +145,7 @@ class NotificationService ...@@ -145,6 +145,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable) recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients.delete(note.author) recipients.delete(note.author)
recipients = recipients.uniq
# build notify method like 'note_commit_email' # build notify method like 'note_commit_email'
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
......
...@@ -125,7 +125,7 @@ class SystemNoteService ...@@ -125,7 +125,7 @@ class SystemNoteService
# Returns the created Note object # Returns the created Note object
def self.change_status(noteable, project, author, status, source) def self.change_status(noteable, project, author, status, source)
body = "Status changed to #{status}" 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) create_note(noteable: noteable, project: project, author: author, note: body)
end end
......
= content_for :flash_message do
= render 'shared/project_limit'
%ul.center-top-menu %ul.center-top-menu
= nav_link(path: ['projects#index', 'root#index']) do = nav_link(path: ['projects#index', 'root#index']) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
......
...@@ -16,7 +16,10 @@ ...@@ -16,7 +16,10 @@
= milestone_progress_bar(milestone) = milestone_progress_bar(milestone)
.row .row
.col-sm-6 .col-sm-6
- milestone.milestones.each do |milestone| .expiration
= link_to milestone_path(milestone) do = render 'shared/milestone_expired', milestone: milestone
%span.label.label-gray .projects
= milestone.project.name_with_namespace - milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= milestone.project.name_with_namespace
...@@ -8,17 +8,18 @@ ...@@ -8,17 +8,18 @@
%a.js-md-preview-button(href="#md-preview-holder" tabindex="-1") %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
Preview Preview
- if defined?(referenced_users) && referenced_users %div
%span.referenced-users.pull-left.hide .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
%div.referenced-users.hide
%span
= icon('exclamation-triangle') = icon('exclamation-triangle')
You are about to add You are about to add
%strong %strong
%span.js-referenced-users-count 0 %span.js-referenced-users-count 0
people people
to the discussion. Proceed with caution. 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))}
.issue-closed-by-widget .issue-closed-by-widget
= icon('check') = 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.
...@@ -18,11 +18,7 @@ ...@@ -18,11 +18,7 @@
.row .row
.col-sm-6 .col-sm-6
- if milestone.expired? and not milestone.closed? = render 'shared/milestone_expired', milestone: milestone
%span.cred (Expired)
- if milestone.expires_at
%span
= milestone.expires_at
.col-sm-6 .col-sm-6
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - 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 = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
......
- if milestone.expired? and not milestone.closed?
%span.cred (Expired)
- if milestone.expires_at
%span
= milestone.expires_at
- 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'
...@@ -164,7 +164,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled']. ...@@ -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['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['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['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['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10 Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10 Settings.gitlab['max_attachment_size'] ||= 10
......
...@@ -368,7 +368,7 @@ Rails.application.routes.draw do ...@@ -368,7 +368,7 @@ Rails.application.routes.draw do
end end
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
resources :milestones, only: [:index, :show, :update, :new, :create] resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
end end
end end
......
class AddHideProjectLimitToUsers < ActiveRecord::Migration
def change
add_column :users, :hide_project_limit, :boolean, default: false
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -814,6 +814,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do ...@@ -814,6 +814,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
t.integer "project_view", default: 0 t.integer "project_view", default: 0
t.integer "consumed_timestep" t.integer "consumed_timestep"
t.integer "layout", default: 0 t.integer "layout", default: 0
t.boolean "hide_project_limit", default: false
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
...@@ -57,6 +57,9 @@ X-Gitlab-Event: Push Hook ...@@ -57,6 +57,9 @@ X-Gitlab-Event: Push Hook
"name": "Jordi Mallach", "name": "Jordi Mallach",
"email": "jordi@softcatala.org" "email": "jordi@softcatala.org"
} }
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
}, },
{ {
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
...@@ -66,13 +69,14 @@ X-Gitlab-Event: Push Hook ...@@ -66,13 +69,14 @@ X-Gitlab-Event: Push Hook
"author": { "author": {
"name": "GitLab dev user", "name": "GitLab dev user",
"email": "gitlabdev@dv6700.(none)" "email": "gitlabdev@dv6700.(none)"
} },
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
} }
], ],
"total_commits_count": 4, "total_commits_count": 4
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
} }
``` ```
......
...@@ -13,6 +13,12 @@ Feature: Project Commits Diff Comments ...@@ -13,6 +13,12 @@ Feature: Project Commits Diff Comments
Given I leave a diff comment like "Typo, please fix" Given I leave a diff comment like "Typo, please fix"
Then I should see a diff comment saying "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 @javascript
Scenario: I get a temporary form for the first comment on a diff line Scenario: I get a temporary form for the first comment on a diff line
Given I open a diff comment form Given I open a diff comment form
......
...@@ -11,4 +11,8 @@ Feature: Award Emoji ...@@ -11,4 +11,8 @@ Feature: Award Emoji
And I click to emoji in the picker And I click to emoji in the picker
Then I have award added Then I have award added
And I can remove it by clicking to icon 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
...@@ -9,33 +9,40 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps ...@@ -9,33 +9,40 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end end
step 'I click to emoji-picker' do step 'I click to emoji-picker' do
page.within ".awards-controls" do page.within '.awards-controls' do
page.find(".add-award").click page.find('.add-award').click
end end
end end
step 'I click to emoji in the picker' do step 'I click to emoji in the picker' do
page.within ".awards-menu" do page.within '.awards-menu' do
page.first("img").click page.first('img').click
end end
end end
step 'I can remove it by clicking to icon' do step 'I can remove it by clicking to icon' do
page.within ".awards" do page.within '.awards' do
page.first(".award").click page.first('.award').click
expect(page).to_not have_selector ".award" expect(page).to_not have_selector '.award'
end end
end end
step 'I have award added' do step 'I have award added' do
page.within ".awards" do page.within '.awards' do
expect(page).to have_selector ".award" expect(page).to have_selector '.award'
expect(page.find(".award .counter")).to have_content "1" expect(page.find('.award .counter')).to have_content '1'
end end
end end
step 'project "Shop" has issue "Bugfix"' do step 'project "Shop" has issue "Bugfix"' do
@project = Project.find_by(name: "Shop") @project = Project.find_by(name: 'Shop')
@issue = create(:issue, title: "Bugfix", project: project) @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
end end
...@@ -87,6 +87,17 @@ module SharedDiffNote ...@@ -87,6 +87,17 @@ module SharedDiffNote
end end
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 step 'I submit the diff comment' do
page.within(diff_file_selector) do page.within(diff_file_selector) do
click_button("Add Comment") click_button("Add Comment")
...@@ -197,6 +208,12 @@ module SharedDiffNote ...@@ -197,6 +208,12 @@ module SharedDiffNote
end end
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 step 'I click side-by-side diff button' do
find('#parallel-diff-btn').trigger('click') find('#parallel-diff-btn').trigger('click')
end end
......
module Gitlab module Gitlab
class ClosingIssueExtractor class ClosingIssueExtractor
ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern) ISSUE_CLOSING_REGEX = begin
link_pattern = URI.regexp(%w(http https))
pattern = Gitlab.config.gitlab.issue_closing_pattern
pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))")
Regexp.new(pattern).freeze
end
def initialize(project, current_user = nil) def initialize(project, current_user = nil)
@extractor = Gitlab::ReferenceExtractor.new(project, current_user) @extractor = Gitlab::ReferenceExtractor.new(project, current_user)
...@@ -9,10 +15,12 @@ module Gitlab ...@@ -9,10 +15,12 @@ module Gitlab
def closed_by_message(message) def closed_by_message(message)
return [] if message.nil? return [] if message.nil?
closing_statements = message.scan(ISSUE_CLOSING_REGEX). closing_statements = []
map { |ref| ref[0] }.join(" ") message.scan(ISSUE_CLOSING_REGEX) do
closing_statements << Regexp.last_match[0]
end
@extractor.analyze(closing_statements) @extractor.analyze(closing_statements.join(" "))
@extractor.issues @extractor.issues
end end
......
...@@ -178,7 +178,6 @@ module Gitlab ...@@ -178,7 +178,6 @@ module Gitlab
Gitlab::Markdown::SanitizationFilter, Gitlab::Markdown::SanitizationFilter,
Gitlab::Markdown::UploadLinkFilter, Gitlab::Markdown::UploadLinkFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::EmojiFilter, Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter, Gitlab::Markdown::TableOfContentsFilter,
Gitlab::Markdown::AutolinkFilter, Gitlab::Markdown::AutolinkFilter,
...@@ -193,6 +192,8 @@ module Gitlab ...@@ -193,6 +192,8 @@ module Gitlab
Gitlab::Markdown::CommitReferenceFilter, Gitlab::Markdown::CommitReferenceFilter,
Gitlab::Markdown::LabelReferenceFilter, Gitlab::Markdown::LabelReferenceFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::TaskListFilter Gitlab::Markdown::TaskListFilter
] ]
end end
......
...@@ -2,8 +2,8 @@ require 'gitlab/markdown' ...@@ -2,8 +2,8 @@ require 'gitlab/markdown'
module Gitlab module Gitlab
module Markdown module Markdown
# Issues, Snippets and Merge Requests shares similar functionality in refernce filtering. # Issues, Merge Requests, Snippets, Commits and Commit Ranges share
# All this functionality moved to this class # similar functionality in reference filtering.
class AbstractReferenceFilter < ReferenceFilter class AbstractReferenceFilter < ReferenceFilter
include CrossProjectReference include CrossProjectReference
...@@ -26,21 +26,20 @@ module Gitlab ...@@ -26,21 +26,20 @@ module Gitlab
# Public: Find references in text (like `!123` for merge requests) # Public: Find references in text (like `!123` for merge requests)
# #
# AnyReferenceFilter.references_in(text) do |match, object| # AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
# "<a href=...>PREFIX#{object}</a>" # object = find_object(project_ref, id)
# "<a href=...>#{object.to_reference}</a>"
# end # end
# #
# PREFIX - symbol that detects reference (like ! for merge requests)
# object - reference object (snippet, merget request etc)
# text - String text to search. # text - String text to search.
# #
# Yields the String match, the Integer referenced object ID, and an optional String # Yields the String match, the Integer referenced object ID, an optional String
# of the external project reference. # of the external project reference, and all of the matchdata.
# #
# Returns a String replaced with the return of the block. # Returns a String replaced with the return of the block.
def self.references_in(text) def self.references_in(text, pattern = object_class.reference_pattern)
text.gsub(object_class.reference_pattern) do |match| text.gsub(pattern) do |match|
yield match, $~[object_sym].to_i, $~[:project] yield match, $~[object_sym].to_i, $~[:project], $~
end end
end end
...@@ -61,8 +60,27 @@ module Gitlab ...@@ -61,8 +60,27 @@ module Gitlab
end end
def call def call
# `#123`
replace_text_nodes_matching(object_class.reference_pattern) do |content| replace_text_nodes_matching(object_class.reference_pattern) do |content|
object_link_filter(content) object_link_filter(content, object_class.reference_pattern)
end
# `[Issue](#123)`, which is turned into
# `<a href="#123">Issue</a>`
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
# `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
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
# `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
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
end end
...@@ -70,30 +88,57 @@ module Gitlab ...@@ -70,30 +88,57 @@ module Gitlab
# to the referenced object's details page. # to the referenced object's details page.
# #
# text - String text to replace references in. # 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 # Returns a String with references replaced with links. All links
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text) def object_link_filter(text, pattern, link_text: nil)
references_in(text) do |match, id, project_ref| references_in(text, pattern) do |match, id, project_ref, matches|
project = project_from_ref(project_ref) project = project_from_ref(project_ref)
if project && object = find_object(project, id) 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) klass = reference_class(object_sym)
data = data_attribute(project: project.id, object_sym => object.id)
url = url_for_object(object, project) 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)
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?
end
%(<a href="#{url}" #{data} %(<a href="#{url}" #{data}
title="#{title}" title="#{title}"
class="#{klass}">#{match}</a>) class="#{klass}">#{text}</a>)
else else
match match
end end
end end
end end
def object_title def object_link_text_extras(object, matches)
object_class.name.titleize extras = []
if matches.names.include?("anchor") && 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 end
end end
......
...@@ -5,24 +5,14 @@ module Gitlab ...@@ -5,24 +5,14 @@ module Gitlab
# HTML filter that replaces commit range references with links. # HTML filter that replaces commit range references with links.
# #
# This filter supports cross-project references. # This filter supports cross-project references.
class CommitRangeReferenceFilter < ReferenceFilter class CommitRangeReferenceFilter < AbstractReferenceFilter
include CrossProjectReference def self.object_class
CommitRange
end
# Public: Find commit range references in text def self.references_in(text, pattern = CommitRange.reference_pattern)
# text.gsub(pattern) do |match|
# CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref| yield match, $~[:commit_range], $~[:project], $~
# "<a href=...>#{commit_range}</a>"
# 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]
end end
end end
...@@ -31,9 +21,9 @@ module Gitlab ...@@ -31,9 +21,9 @@ module Gitlab
return unless project return unless project
id = node.attr("data-commit-range") 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 } { commit_range: range }
end end
...@@ -44,49 +34,25 @@ module Gitlab ...@@ -44,49 +34,25 @@ module Gitlab
@commit_map = {} @commit_map = {}
end end
def call def self.find_object(project, id)
replace_text_nodes_matching(CommitRange.reference_pattern) do |content| range = CommitRange.new(id, project)
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)
project_ref += '@' if project_ref range.valid_commits? ? range : nil
end
%(<a href="#{url}" #{data} def find_object(*args)
title="#{title}" self.class.find_object(*args)
class="#{klass}">#{project_ref}#{range}</a>)
else
match
end
end
end end
def url_for_commit_range(project, range) def url_for_object(range, project)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Application.routes.url_helpers
h.namespace_project_compare_url(project.namespace, project, h.namespace_project_compare_url(project.namespace, project,
range.to_param.merge(only_path: context[:only_path])) range.to_param.merge(only_path: context[:only_path]))
end end
def object_link_title(range)
range.reference_title
end
end end
end end
end end
...@@ -5,24 +5,14 @@ module Gitlab ...@@ -5,24 +5,14 @@ module Gitlab
# HTML filter that replaces commit references with links. # HTML filter that replaces commit references with links.
# #
# This filter supports cross-project references. # This filter supports cross-project references.
class CommitReferenceFilter < ReferenceFilter class CommitReferenceFilter < AbstractReferenceFilter
include CrossProjectReference def self.object_class
Commit
end
# Public: Find commit references in text def self.references_in(text, pattern = Commit.reference_pattern)
# text.gsub(pattern) do |match|
# CommitReferenceFilter.references_in(text) do |match, commit, project_ref| yield match, $~[:commit], $~[:project], $~
# "<a href=...>#{commit}</a>"
# 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]
end end
end end
...@@ -31,58 +21,32 @@ module Gitlab ...@@ -31,58 +21,32 @@ module Gitlab
return unless project return unless project
id = node.attr("data-commit") id = node.attr("data-commit")
commit = commit_from_ref(project, id) commit = find_object(project, id)
return unless commit return unless commit
{ commit: commit } { commit: commit }
end end
def call def self.find_object(project, id)
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
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{project_ref}#{commit.short_id}</a>)
else
match
end
end
end
def self.commit_from_ref(project, id)
if project && project.valid_repo? if project && project.valid_repo?
project.commit(id) project.commit(id)
end end
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 = Gitlab::Application.routes.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit, h.namespace_project_commit_url(project.namespace, project, commit,
only_path: context[:only_path]) only_path: context[:only_path])
end end
def object_link_title(commit)
commit.link_title
end
end end
end end
end end
...@@ -30,6 +30,10 @@ module Gitlab ...@@ -30,6 +30,10 @@ module Gitlab
replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content| replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
issue_link_filter(content) issue_link_filter(content)
end end
replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
issue_link_filter(link, link_text: text)
end
end end
# Replace `JIRA-123` issue references in text with links to the referenced # Replace `JIRA-123` issue references in text with links to the referenced
...@@ -39,7 +43,7 @@ module Gitlab ...@@ -39,7 +43,7 @@ module Gitlab
# #
# Returns a String with `JIRA-123` references replaced with links. All # Returns a String with `JIRA-123` references replaced with links. All
# links have `gfm` and `gfm-issue` class names attached for styling. # links have `gfm` and `gfm-issue` class names attached for styling.
def issue_link_filter(text) def issue_link_filter(text, link_text: nil)
project = context[:project] project = context[:project]
self.class.references_in(text) do |match, issue| self.class.references_in(text) do |match, issue|
...@@ -49,9 +53,11 @@ module Gitlab ...@@ -49,9 +53,11 @@ module Gitlab
klass = reference_class(:issue) klass = reference_class(:issue)
data = data_attribute(project: project.id) data = data_attribute(project: project.id)
text = link_text || match
%(<a href="#{url}" #{data} %(<a href="#{url}" #{data}
title="#{title}" title="#{title}"
class="#{klass}">#{match}</a>) class="#{klass}">#{text}</a>)
end end
end end
......
...@@ -8,9 +8,9 @@ module Gitlab ...@@ -8,9 +8,9 @@ module Gitlab
class ExternalLinkFilter < HTML::Pipeline::Filter class ExternalLinkFilter < HTML::Pipeline::Filter
def call def call
doc.search('a').each do |node| doc.search('a').each do |node|
next unless node.has_attribute?('href') link = node.attr('href')
link = node.attribute('href').value next unless link
# Skip non-HTTP(S) links # Skip non-HTTP(S) links
next unless link.start_with?('http') next unless link.start_with?('http')
......
...@@ -30,6 +30,10 @@ module Gitlab ...@@ -30,6 +30,10 @@ module Gitlab
replace_text_nodes_matching(Label.reference_pattern) do |content| replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content) label_link_filter(content)
end end
replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
label_link_filter(link, link_text: text)
end
end end
# Replace label references in text with links to the label specified. # Replace label references in text with links to the label specified.
...@@ -38,7 +42,7 @@ module Gitlab ...@@ -38,7 +42,7 @@ module Gitlab
# #
# Returns a String with label references replaced with links. All links # Returns a String with label references replaced with links. All links
# have `gfm` and `gfm-label` class names attached for styling. # 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] project = context[:project]
self.class.references_in(text) do |match, id, name| self.class.references_in(text) do |match, id, name|
...@@ -47,10 +51,16 @@ module Gitlab ...@@ -47,10 +51,16 @@ module Gitlab
if label = project.labels.find_by(params) if label = project.labels.find_by(params)
url = url_for_label(project, label) url = url_for_label(project, label)
klass = reference_class(: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)
%(<a href="#{url}" #{data} %(<a href="#{url}" #{data}
class="#{klass}">#{render_colored_label(label)}</a>) class="#{klass}">#{text}</a>)
else else
match match
end end
...@@ -59,8 +69,8 @@ module Gitlab ...@@ -59,8 +69,8 @@ module Gitlab
def url_for_label(project, label) def url_for_label(project, label)
h = Gitlab::Application.routes.url_helpers h = Gitlab::Application.routes.url_helpers
h.namespace_project_issues_path(project.namespace, project, h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
label_name: label.name) only_path: context[:only_path])
end end
def render_colored_label(label) def render_colored_label(label)
......
...@@ -20,6 +20,16 @@ module Gitlab ...@@ -20,6 +20,16 @@ module Gitlab
h.namespace_project_merge_request_url(project.namespace, project, mr, h.namespace_project_merge_request_url(project.namespace, project, mr,
only_path: context[:only_path]) only_path: context[:only_path])
end end
def object_link_text_extras(object, matches)
extras = super
if matches.names.include?("path") && matches[:path] && matches[:path] == '/diffs'
extras.unshift "diffs"
end
extras
end
end end
end end
end end
...@@ -12,7 +12,10 @@ module Gitlab ...@@ -12,7 +12,10 @@ module Gitlab
def call def call
doc.css('a.gfm').each do |node| doc.css('a.gfm').each do |node|
unless user_can_reference?(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.attr('data-original') || node.text
node.replace(text)
end end
end end
......
...@@ -122,6 +122,80 @@ module Gitlab ...@@ -122,6 +122,80 @@ module Gitlab
doc doc
end 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?
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)
# 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
# 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?
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 # Ensure that a :project key exists in context
# #
# Note that while the key might exist, its value could be nil! # Note that while the key might exist, its value could be nil!
......
...@@ -17,6 +17,9 @@ module Gitlab ...@@ -17,6 +17,9 @@ module Gitlab
return doc unless linkable_files? return doc unless linkable_files?
doc.search('a').each do |el| doc.search('a').each do |el|
klass = el.attr('class')
next if klass && klass.include?('gfm')
process_link_attr el.attribute('href') process_link_attr el.attribute('href')
end end
......
...@@ -52,6 +52,10 @@ module Gitlab ...@@ -52,6 +52,10 @@ module Gitlab
replace_text_nodes_matching(User.reference_pattern) do |content| replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content) user_link_filter(content)
end end
replace_link_nodes_with_href(User.reference_pattern) do |link, text|
user_link_filter(link, link_text: text)
end
end end
# Replace `@user` user references in text with links to the referenced # Replace `@user` user references in text with links to the referenced
...@@ -61,12 +65,12 @@ module Gitlab ...@@ -61,12 +65,12 @@ module Gitlab
# #
# Returns a String with `@user` references replaced with links. All links # Returns a String with `@user` references replaced with links. All links
# have `gfm` and `gfm-project_member` class names attached for styling. # 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| self.class.references_in(text) do |match, username|
if username == 'all' if username == 'all'
link_to_all link_to_all(link_text: link_text)
elsif namespace = Namespace.find_by(path: username) elsif namespace = Namespace.find_by(path: username)
link_to_namespace(namespace) || match link_to_namespace(namespace, link_text: link_text) || match
else else
match match
end end
...@@ -83,36 +87,36 @@ module Gitlab ...@@ -83,36 +87,36 @@ module Gitlab
reference_class(:project_member) reference_class(:project_member)
end end
def link_to_all def link_to_all(link_text: nil)
project = context[:project] project = context[:project]
url = urls.namespace_project_url(project.namespace, project, url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path]) only_path: context[:only_path])
data = data_attribute(project: project.id) data = data_attribute(project: project.id)
text = User.reference_prefix + 'all' text = link_text || User.reference_prefix + 'all'
link_tag(url, data, text) link_tag(url, data, text)
end end
def link_to_namespace(namespace) def link_to_namespace(namespace, link_text: nil)
if namespace.is_a?(Group) if namespace.is_a?(Group)
link_to_group(namespace.path, namespace) link_to_group(namespace.path, namespace, link_text: link_text)
else else
link_to_user(namespace.path, namespace) link_to_user(namespace.path, namespace, link_text: link_text)
end end
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]) url = urls.group_url(group, only_path: context[:only_path])
data = data_attribute(group: namespace.id) data = data_attribute(group: namespace.id)
text = Group.reference_prefix + group text = link_text || Group.reference_prefix + group
link_tag(url, data, text) link_tag(url, data, text)
end 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]) url = urls.user_url(user, only_path: context[:only_path])
data = data_attribute(user: namespace.owner_id) data = data_attribute(user: namespace.owner_id)
text = User.reference_prefix + user text = link_text || User.reference_prefix + user
link_tag(url, data, text) link_tag(url, data, text)
end end
......
...@@ -18,10 +18,7 @@ module Gitlab ...@@ -18,10 +18,7 @@ module Gitlab
# homepage: String, # homepage: String,
# }, # },
# commits: Array, # commits: Array,
# total_commits_count: Fixnum, # total_commits_count: Fixnum
# added: ["CHANGELOG"],
# modified: [],
# removed: ["tmp/file.txt"]
# } # }
# #
def build(project, user, oldrev, newrev, ref, commits = [], message = nil) def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
...@@ -33,11 +30,12 @@ module Gitlab ...@@ -33,11 +30,12 @@ module Gitlab
# For performance purposes maximum 20 latest commits # For performance purposes maximum 20 latest commits
# will be passed as post receive hook data. # 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(with_changed_files: true)
end
type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push" type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
repo_changes = repo_changes(project, newrev, oldrev)
# Hash to be passed as post_receive_data # Hash to be passed as post_receive_data
data = { data = {
object_kind: type, object_kind: type,
...@@ -60,10 +58,7 @@ module Gitlab ...@@ -60,10 +58,7 @@ module Gitlab
visibility_level: project.visibility_level visibility_level: project.visibility_level
}, },
commits: commit_attrs, commits: commit_attrs,
total_commits_count: commits_count, total_commits_count: commits_count
added: repo_changes[:added],
modified: repo_changes[:modified],
removed: repo_changes[:removed]
} }
data data
...@@ -94,27 +89,6 @@ module Gitlab ...@@ -94,27 +89,6 @@ module Gitlab
newrev newrev
end end
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 end
end end
...@@ -41,14 +41,14 @@ module Gitlab ...@@ -41,14 +41,14 @@ module Gitlab
# Returns the results Array for the requested filter type # Returns the results Array for the requested filter type
def pipeline_result(filter_type) def pipeline_result(filter_type)
return [] if @text.blank? return [] if @text.blank?
klass = "#{filter_type.to_s.camelize}ReferenceFilter" klass = "#{filter_type.to_s.camelize}ReferenceFilter"
filter = Gitlab::Markdown.const_get(klass) filter = Gitlab::Markdown.const_get(klass)
context = { context = {
project: project, project: project,
current_user: current_user, current_user: current_user,
# We don't actually care about the links generated # We don't actually care about the links generated
only_path: true, only_path: true,
ignore_blockquotes: true, ignore_blockquotes: true,
...@@ -58,7 +58,15 @@ module Gitlab ...@@ -58,7 +58,15 @@ module Gitlab
reference_filter: filter 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) result = pipeline.call(@text)
values = result[:references][filter_type].uniq values = result[:references][filter_type].uniq
......
...@@ -327,7 +327,7 @@ print_status() { ...@@ -327,7 +327,7 @@ print_status() {
printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n" printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n"
fi fi
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" printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
fi fi
} }
......
...@@ -56,7 +56,7 @@ server { ...@@ -56,7 +56,7 @@ server {
listen [::]:80 ipv6only=on default_server; listen [::]:80 ipv6only=on default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com 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 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; access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log; error_log /var/log/nginx/gitlab_error.log;
} }
......
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
...@@ -5,7 +5,7 @@ describe Projects::MilestonesController do ...@@ -5,7 +5,7 @@ describe Projects::MilestonesController do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
let(:issue) { create(:issue, project: project, milestone: milestone) } 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 before do
sign_in(user) sign_in(user)
...@@ -15,10 +15,9 @@ describe Projects::MilestonesController do ...@@ -15,10 +15,9 @@ describe Projects::MilestonesController do
describe "#destroy" do describe "#destroy" do
it "should remove milestone" do it "should remove milestone" do
merge_request.reload
expect(issue.milestone_id).to eq(milestone.id) 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(response).to be_success
expect(Event.first.action).to eq(Event::DESTROYED) expect(Event.first.action).to eq(Event::DESTROYED)
......
...@@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e ...@@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Ignores invalid: <%= User.reference_prefix %>fake_user - Ignores invalid: <%= User.reference_prefix %>fake_user
- Ignored in code: `<%= user.to_reference %>` - Ignored in code: `<%= user.to_reference %>`
- Ignored in links: [Link to <%= user.to_reference %>](#user-link) - Ignored in links: [Link to <%= user.to_reference %>](#user-link)
- Link to user by reference: [User](<%= user.to_reference %>)
#### IssueReferenceFilter #### IssueReferenceFilter
...@@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e ...@@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Issue in another project: <%= xissue.to_reference(project) %> - Issue in another project: <%= xissue.to_reference(project) %>
- Ignored in code: `<%= issue.to_reference %>` - Ignored in code: `<%= issue.to_reference %>`
- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link) - Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
- 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](<%= urls.namespace_project_issue_url(issue.project.namespace, issue.project, issue) %>)
#### MergeRequestReferenceFilter #### MergeRequestReferenceFilter
...@@ -167,6 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e ...@@ -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) %> - Merge request in another project: <%= xmerge_request.to_reference(project) %>
- Ignored in code: `<%= merge_request.to_reference %>` - Ignored in code: `<%= merge_request.to_reference %>`
- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link) - Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
- 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](<%= urls.namespace_project_merge_request_url(merge_request.project.namespace, merge_request.project, merge_request) %>)
#### SnippetReferenceFilter #### SnippetReferenceFilter
...@@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e ...@@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Snippet in another project: <%= xsnippet.to_reference(project) %> - Snippet in another project: <%= xsnippet.to_reference(project) %>
- Ignored in code: `<%= snippet.to_reference %>` - Ignored in code: `<%= snippet.to_reference %>`
- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link) - Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
- 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](<%= urls.namespace_project_snippet_url(snippet.project.namespace, snippet.project, snippet) %>)
#### CommitRangeReferenceFilter #### CommitRangeReferenceFilter
...@@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e ...@@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Range in another project: <%= xcommit_range.to_reference(project) %> - Range in another project: <%= xcommit_range.to_reference(project) %>
- Ignored in code: `<%= commit_range.to_reference %>` - Ignored in code: `<%= commit_range.to_reference %>`
- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link) - Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
- 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](<%= urls.namespace_project_compare_url(commit_range.project.namespace, commit_range.project, commit_range.to_param) %>)
#### CommitReferenceFilter #### CommitReferenceFilter
...@@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e ...@@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Commit in another project: <%= xcommit.to_reference(project) %> - Commit in another project: <%= xcommit.to_reference(project) %>
- Ignored in code: `<%= commit.to_reference %>` - Ignored in code: `<%= commit.to_reference %>`
- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link) - Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
- 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](<%= urls.namespace_project_commit_url(commit.project.namespace, commit.project, commit) %>)
#### LabelReferenceFilter #### LabelReferenceFilter
...@@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e ...@@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Label by name in quotes: <%= label.to_reference(:name) %> - Label by name in quotes: <%= label.to_reference(:name) %>
- Ignored in code: `<%= simple_label.to_reference %>` - Ignored in code: `<%= simple_label.to_reference %>`
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link) - Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
- Link to label by reference: [Label](<%= label.to_reference %>)
### Task Lists ### Task Lists
......
...@@ -2,11 +2,18 @@ require 'spec_helper' ...@@ -2,11 +2,18 @@ require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:project2) { create(:project) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project2) }
let(:reference) { issue.to_reference } let(:reference) { issue.to_reference }
let(:cross_reference) { issue2.to_reference(project) }
subject { described_class.new(project, project.creator) } subject { described_class.new(project, project.creator) }
before do
project2.team << [project.creator, :master]
end
describe "#closed_by_message" do describe "#closed_by_message" do
context 'with a single reference' do context 'with a single reference' do
it do it do
...@@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do ...@@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do
end end
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 #{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#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}"
expect(subject.closed_by_message(message)).to eq([])
end
end
context 'with multiple references' do context 'with multiple references' do
let(:other_issue) { create(:issue, project: project) } let(:other_issue) { create(:issue, project: project) }
let(:third_issue) { create(:issue, project: project) } let(:third_issue) { create(:issue, project: project) }
...@@ -171,6 +199,31 @@ describe Gitlab::ClosingIssueExtractor do ...@@ -171,6 +199,31 @@ describe Gitlab::ClosingIssueExtractor do
expect(subject.closed_by_message(message)). expect(subject.closed_by_message(message)).
to match_array([issue, other_issue, third_issue]) to match_array([issue, other_issue, third_issue])
end 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 #{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#{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
end end
def urls
Gitlab::Application.routes.url_helpers
end
end end
...@@ -5,11 +5,11 @@ module Gitlab::Markdown ...@@ -5,11 +5,11 @@ module Gitlab::Markdown
include FilterSpecHelper include FilterSpecHelper
let(:project) { create(:project, :public) } let(:project) { create(:project, :public) }
let(:commit1) { project.commit } let(:commit1) { project.commit("HEAD~2") }
let(:commit2) { project.commit("HEAD~2") } let(:commit2) { project.commit }
let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") } let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) }
let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") } let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) }
it 'requires project context' do it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
...@@ -18,7 +18,7 @@ module Gitlab::Markdown ...@@ -18,7 +18,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem| %w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>" exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
...@@ -27,14 +27,14 @@ module Gitlab::Markdown ...@@ -27,14 +27,14 @@ module Gitlab::Markdown
let(:reference2) { range2.to_reference } let(:reference2) { range2.to_reference }
it 'links to a valid two-dot reference' do 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')). expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param) to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
end end
it 'links to a valid three-dot reference' do 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')). expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param) to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
...@@ -46,14 +46,14 @@ module Gitlab::Markdown ...@@ -46,14 +46,14 @@ module Gitlab::Markdown
exp = commit1.short_id + '...' + commit2.short_id exp = commit1.short_id + '...' + commit2.short_id
expect(filter("See #{reference}").css('a').first.text).to eq exp expect(reference_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 #{reference2}").css('a').first.text).to eq exp
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("See (#{reference}.)") doc = reference_filter("See (#{reference}.)")
exp = Regexp.escape(range.to_s) exp = Regexp.escape(range.reference_link_text)
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end end
...@@ -62,21 +62,22 @@ module Gitlab::Markdown ...@@ -62,21 +62,22 @@ module Gitlab::Markdown
expect(project).to receive(:valid_repo?).and_return(true) 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(commit1.id.reverse)
expect(filter(act).to_html).to eq exp expect(project.repository).to receive(:commit).with(commit2.id)
expect(reference_filter(act).to_html).to eq exp
end end
it 'includes a title attribute' do 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 expect(doc.css('a').first.attr('title')).to eq range.reference_title
end end
it 'includes default classes' do 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' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project') expect(link).to have_attribute('data-project')
...@@ -84,15 +85,15 @@ module Gitlab::Markdown ...@@ -84,15 +85,15 @@ module Gitlab::Markdown
end end
it 'includes a data-commit-range attribute' do it 'includes a data-commit-range attribute' do
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-commit-range') 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 end
it 'supports an :only_path option' do 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') link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://) expect(link).not_to match %r(https?://)
...@@ -115,25 +116,63 @@ module Gitlab::Markdown ...@@ -115,25 +116,63 @@ module Gitlab::Markdown
end end
it 'links to a valid 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')). expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)") doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}") exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}")
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end end
it 'ignores invalid commit IDs on the referenced project' do it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" 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}" 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
result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit_range]).not_to be_empty
end
end
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) }
let(:reference) { urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: 'master') }
before do
range.project = project2
end
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq reference
end
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape(range.reference_link_text(project))
expect(doc.to_html).to match(/\(<a.+>#{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(reference_filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
expect(reference_filter(act).to_html).to eq exp
end end
it 'adds to the results hash' do it 'adds to the results hash' do
......
...@@ -14,7 +14,7 @@ module Gitlab::Markdown ...@@ -14,7 +14,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem| %w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>" exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
...@@ -24,7 +24,7 @@ module Gitlab::Markdown ...@@ -24,7 +24,7 @@ module Gitlab::Markdown
# Let's test a variety of commit SHA sizes just to be paranoid # Let's test a variety of commit SHA sizes just to be paranoid
[6, 8, 12, 18, 20, 32, 40].each do |size| [6, 8, 12, 18, 20, 32, 40].each do |size|
it "links to a valid reference of #{size} characters" do 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.text).to eq commit.short_id
expect(doc.css('a').first.attr('href')). expect(doc.css('a').first.attr('href')).
...@@ -33,15 +33,15 @@ module Gitlab::Markdown ...@@ -33,15 +33,15 @@ module Gitlab::Markdown
end end
it 'always uses the short ID as the link text' do 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}" 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}" expect(doc.text).to eq "See #{commit.short_id}"
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("See (#{reference}.)") doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
end end
...@@ -51,28 +51,28 @@ module Gitlab::Markdown ...@@ -51,28 +51,28 @@ module Gitlab::Markdown
expect(project).to receive(:valid_repo?).and_return(true) expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(invalid) 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 end
it 'includes a title attribute' do 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 expect(doc.css('a').first.attr('title')).to eq commit.link_title
end end
it 'escapes the title attribute' do it 'escapes the title attribute' do
allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="}) allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
expect(doc.text).to eq "See #{commit.short_id}" expect(doc.text).to eq "See #{commit.short_id}"
end end
it 'includes default classes' do 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' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project') expect(link).to have_attribute('data-project')
...@@ -80,7 +80,7 @@ module Gitlab::Markdown ...@@ -80,7 +80,7 @@ module Gitlab::Markdown
end end
it 'includes a data-commit attribute' do it 'includes a data-commit attribute' do
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-commit') expect(link).to have_attribute('data-commit')
...@@ -88,7 +88,7 @@ module Gitlab::Markdown ...@@ -88,7 +88,7 @@ module Gitlab::Markdown
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
doc = filter("See #{reference}", only_path: true) doc = reference_filter("See #{reference}", only_path: true)
link = doc.css('a').first.attr('href') link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://) expect(link).not_to match %r(https?://)
...@@ -108,14 +108,14 @@ module Gitlab::Markdown ...@@ -108,14 +108,14 @@ module Gitlab::Markdown
let(:reference) { commit.to_reference(project) } let(:reference) { commit.to_reference(project) }
it 'links to a valid 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')). expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id) to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)") doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape(project2.to_reference) exp = Regexp.escape(project2.to_reference)
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
...@@ -123,7 +123,37 @@ module Gitlab::Markdown ...@@ -123,7 +123,37 @@ module Gitlab::Markdown
it 'ignores invalid commit IDs on the referenced project' do it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Committed #{invalidate_reference(reference)}" 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
result = reference_pipeline_result("See #{reference}")
expect(result[:references][:commit]).not_to be_empty
end
end
context 'cross-project URL 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 = 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 = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{commit.reference_link_text(project)}<\/a>\.\)/)
end
it 'ignores invalid commit IDs on the referenced project' do
act = "Committed #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end end
it 'adds to the results hash' do it 'adds to the results hash' do
......
...@@ -18,7 +18,7 @@ module Gitlab::Markdown ...@@ -18,7 +18,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem| %w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>" exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
...@@ -29,18 +29,18 @@ module Gitlab::Markdown ...@@ -29,18 +29,18 @@ module Gitlab::Markdown
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil) expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}" exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
it 'links to a valid reference' do it 'links to a valid reference' do
doc = filter("Fixed #{reference}") doc = reference_filter("Fixed #{reference}")
expect(doc.css('a').first.attr('href')). expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project) to eq helper.url_for_issue(issue.iid, project)
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)") doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end end
...@@ -48,28 +48,28 @@ module Gitlab::Markdown ...@@ -48,28 +48,28 @@ module Gitlab::Markdown
invalid = invalidate_reference(reference) invalid = invalidate_reference(reference)
exp = act = "Fixed #{invalid}" exp = act = "Fixed #{invalid}"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
it 'includes a title attribute' do 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}" expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
end end
it 'escapes the title attribute' do it 'escapes the title attribute' do
issue.update_attribute(:title, %{"></a>whatever<a title="}) issue.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Issue #{reference}") doc = reference_filter("Issue #{reference}")
expect(doc.text).to eq "Issue #{reference}" expect(doc.text).to eq "Issue #{reference}"
end end
it 'includes default classes' do it 'includes default classes' do
doc = filter("Issue #{reference}") doc = reference_filter("Issue #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
doc = filter("Issue #{reference}") doc = reference_filter("Issue #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project') expect(link).to have_attribute('data-project')
...@@ -77,7 +77,7 @@ module Gitlab::Markdown ...@@ -77,7 +77,7 @@ module Gitlab::Markdown
end end
it 'includes a data-issue attribute' do it 'includes a data-issue attribute' do
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-issue') expect(link).to have_attribute('data-issue')
...@@ -85,7 +85,7 @@ module Gitlab::Markdown ...@@ -85,7 +85,7 @@ module Gitlab::Markdown
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
doc = filter("Issue #{reference}", only_path: true) doc = reference_filter("Issue #{reference}", only_path: true)
link = doc.css('a').first.attr('href') link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://) expect(link).not_to match %r(https?://)
...@@ -109,25 +109,97 @@ module Gitlab::Markdown ...@@ -109,25 +109,97 @@ module Gitlab::Markdown
with(issue.iid).and_return(nil) with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}" exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
it 'links to a valid 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')). expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2) to eq helper.url_for_issue(issue.iid, project2)
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)") doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end end
it 'ignores invalid issue IDs on the referenced project' do it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}" 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
result = reference_pipeline_result("Fixed #{reference}")
expect(result[:references][:issue]).to eq [issue]
end
end
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 'links to a valid reference' do
doc = reference_filter("See #{reference}")
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(/\(<a.+>#{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{<a href="#{issue.to_reference(project)}">Reference</a>} }
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(/\(<a.+>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{<a href="#{helper.url_for_issue(issue.iid, project2) + "#note_123"}">Reference</a>} }
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) + "#note_123"
end
it 'links with adjacent text' do
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
end end
it 'adds to the results hash' do it 'adds to the results hash' do
......
...@@ -16,17 +16,17 @@ module Gitlab::Markdown ...@@ -16,17 +16,17 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem| %w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Label #{reference}</#{elem}>" exp = act = "<#{elem}>Label #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
it 'includes default classes' do 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' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
doc = filter("Label #{reference}") doc = reference_filter("Label #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project') expect(link).to have_attribute('data-project')
...@@ -34,7 +34,7 @@ module Gitlab::Markdown ...@@ -34,7 +34,7 @@ module Gitlab::Markdown
end end
it 'includes a data-label attribute' do it 'includes a data-label attribute' do
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-label') expect(link).to have_attribute('data-label')
...@@ -42,7 +42,7 @@ module Gitlab::Markdown ...@@ -42,7 +42,7 @@ module Gitlab::Markdown
end end
it 'supports an :only_path context' do 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') link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://) expect(link).not_to match %r(https?://)
...@@ -56,33 +56,33 @@ module Gitlab::Markdown ...@@ -56,33 +56,33 @@ module Gitlab::Markdown
describe 'label span element' do describe 'label span element' do
it 'includes default classes' 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' expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
end end
it 'includes a style attribute' do 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/) expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
end end
end end
context 'Integer-based references' do context 'Integer-based references' do
it 'links to a valid 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. 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 end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Label (#{reference}.)") doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end end
it 'ignores invalid label IDs' do it 'ignores invalid label IDs' do
exp = act = "Label #{invalidate_reference(reference)}" 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
end end
...@@ -91,22 +91,22 @@ module Gitlab::Markdown ...@@ -91,22 +91,22 @@ module Gitlab::Markdown
let(:reference) { "#{Label.reference_prefix}#{label.name}" } let(:reference) { "#{Label.reference_prefix}#{label.name}" }
it 'links to a valid 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. 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' expect(doc.text).to eq 'See gfm'
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Label (#{reference}.)") doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end end
it 'ignores invalid label names' do it 'ignores invalid label names' do
exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}" 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
end end
...@@ -115,29 +115,66 @@ module Gitlab::Markdown ...@@ -115,29 +115,66 @@ module Gitlab::Markdown
let(:reference) { label.to_reference(:name) } let(:reference) { label.to_reference(:name) }
it 'links to a valid 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. 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' expect(doc.text).to eq 'See gfm references'
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Label (#{reference}.)") doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\))) expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end end
it 'ignores invalid label names' do it 'ignores invalid label names' do
exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") 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
end end
describe 'edge cases' do describe 'edge cases' do
it 'gracefully handles non-references matching the pattern' do it 'gracefully handles non-references matching the pattern' do
exp = act = '(format nil "~0f" 3.0) ; 3.0' 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{<a href="#{label.to_reference}">Label</a>} }
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(\(<a.+>Label</a>\.\)))
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 end
end end
......
...@@ -14,7 +14,7 @@ module Gitlab::Markdown ...@@ -14,7 +14,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem| %w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>" exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
...@@ -22,42 +22,42 @@ module Gitlab::Markdown ...@@ -22,42 +22,42 @@ module Gitlab::Markdown
let(:reference) { merge.to_reference } let(:reference) { merge.to_reference }
it 'links to a valid 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. expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_merge_request_url(project.namespace, project, merge) namespace_project_merge_request_url(project.namespace, project, merge)
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)") doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end end
it 'ignores invalid merge IDs' do it 'ignores invalid merge IDs' do
exp = act = "Merge #{invalidate_reference(reference)}" exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
it 'includes a title attribute' do 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}" expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
end end
it 'escapes the title attribute' do it 'escapes the title attribute' do
merge.update_attribute(:title, %{"></a>whatever<a title="}) merge.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Merge #{reference}") doc = reference_filter("Merge #{reference}")
expect(doc.text).to eq "Merge #{reference}" expect(doc.text).to eq "Merge #{reference}"
end end
it 'includes default classes' do it 'includes default classes' do
doc = filter("Merge #{reference}") doc = reference_filter("Merge #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
doc = filter("Merge #{reference}") doc = reference_filter("Merge #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project') expect(link).to have_attribute('data-project')
...@@ -65,7 +65,7 @@ module Gitlab::Markdown ...@@ -65,7 +65,7 @@ module Gitlab::Markdown
end end
it 'includes a data-merge-request attribute' do it 'includes a data-merge-request attribute' do
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-merge-request') expect(link).to have_attribute('data-merge-request')
...@@ -73,7 +73,7 @@ module Gitlab::Markdown ...@@ -73,7 +73,7 @@ module Gitlab::Markdown
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
doc = filter("Merge #{reference}", only_path: true) doc = reference_filter("Merge #{reference}", only_path: true)
link = doc.css('a').first.attr('href') link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://) expect(link).not_to match %r(https?://)
...@@ -89,26 +89,50 @@ module Gitlab::Markdown ...@@ -89,26 +89,50 @@ module Gitlab::Markdown
context 'cross-project reference' do context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') } let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, :public, namespace: namespace) } 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) } let(:reference) { merge.to_reference(project) }
it 'links to a valid 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')). expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace, to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge) project2, merge)
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)") doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end end
it 'ignores invalid merge IDs on the referenced project' do it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{invalidate_reference(reference)}" 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
result = reference_pipeline_result("Merge #{reference}")
expect(result[:references][:merge_request]).to eq [merge]
end
end
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) }
let(:reference) { urls.namespace_project_merge_request_url(project2.namespace, project2, merge) + '/diffs#note_123' }
it 'links to a valid reference' do
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq reference
end
it 'links with adjacent text' do
doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/)
end end
it 'adds to the results hash' do it 'adds to the results hash' do
......
...@@ -15,48 +15,48 @@ module Gitlab::Markdown ...@@ -15,48 +15,48 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem| %w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Snippet #{reference}</#{elem}>" exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
context 'internal reference' do context 'internal reference' do
it 'links to a valid 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. expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_snippet_url(project.namespace, project, snippet) namespace_project_snippet_url(project.namespace, project, snippet)
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Snippet (#{reference}.)") doc = reference_filter("Snippet (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end end
it 'ignores invalid snippet IDs' do it 'ignores invalid snippet IDs' do
exp = act = "Snippet #{invalidate_reference(reference)}" exp = act = "Snippet #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
it 'includes a title attribute' do 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}" expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
end end
it 'escapes the title attribute' do it 'escapes the title attribute' do
snippet.update_attribute(:title, %{"></a>whatever<a title="}) snippet.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Snippet #{reference}") doc = reference_filter("Snippet #{reference}")
expect(doc.text).to eq "Snippet #{reference}" expect(doc.text).to eq "Snippet #{reference}"
end end
it 'includes default classes' do it 'includes default classes' do
doc = filter("Snippet #{reference}") doc = reference_filter("Snippet #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
doc = filter("Snippet #{reference}") doc = reference_filter("Snippet #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-project') expect(link).to have_attribute('data-project')
...@@ -64,7 +64,7 @@ module Gitlab::Markdown ...@@ -64,7 +64,7 @@ module Gitlab::Markdown
end end
it 'includes a data-snippet attribute' do it 'includes a data-snippet attribute' do
doc = filter("See #{reference}") doc = reference_filter("See #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-snippet') expect(link).to have_attribute('data-snippet')
...@@ -72,7 +72,7 @@ module Gitlab::Markdown ...@@ -72,7 +72,7 @@ module Gitlab::Markdown
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
doc = filter("Snippet #{reference}", only_path: true) doc = reference_filter("Snippet #{reference}", only_path: true)
link = doc.css('a').first.attr('href') link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://) expect(link).not_to match %r(https?://)
...@@ -92,21 +92,51 @@ module Gitlab::Markdown ...@@ -92,21 +92,51 @@ module Gitlab::Markdown
let(:reference) { snippet.to_reference(project) } let(:reference) { snippet.to_reference(project) }
it 'links to a valid 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')). expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet) to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("See (#{reference}.)") doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end end
it 'ignores invalid snippet IDs on the referenced project' do it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{invalidate_reference(reference)}" 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
result = reference_pipeline_result("Snippet #{reference}")
expect(result[:references][:snippet]).to eq [snippet]
end
end
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) }
let(:reference) { urls.namespace_project_snippet_url(project2.namespace, project2, snippet) }
it 'links to a valid reference' do
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 = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs on the referenced project' do
act = "See #{invalidate_reference(reference)}"
expect(reference_filter(act).to_html).to match(/<a.+>#{Regexp.escape(invalidate_reference(reference))}<\/a>/)
end end
it 'adds to the results hash' do it 'adds to the results hash' do
......
...@@ -14,13 +14,13 @@ module Gitlab::Markdown ...@@ -14,13 +14,13 @@ module Gitlab::Markdown
it 'ignores invalid users' do it 'ignores invalid users' do
exp = act = "Hey #{invalidate_reference(reference)}" exp = act = "Hey #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq(exp) expect(reference_filter(act).to_html).to eq(exp)
end end
%w(pre code a style).each do |elem| %w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Hey #{reference}</#{elem}>" exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
end end
...@@ -32,7 +32,7 @@ module Gitlab::Markdown ...@@ -32,7 +32,7 @@ module Gitlab::Markdown
end end
it 'supports a special @all mention' do 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').length).to eq 1
expect(doc.css('a').first.attr('href')) expect(doc.css('a').first.attr('href'))
.to eq urls.namespace_project_url(project.namespace, project) .to eq urls.namespace_project_url(project.namespace, project)
...@@ -46,26 +46,26 @@ module Gitlab::Markdown ...@@ -46,26 +46,26 @@ module Gitlab::Markdown
context 'mentioning a user' do context 'mentioning a user' do
it 'links to 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) expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end end
it 'links to a User with a period' do it 'links to a User with a period' do
user = create(:user, name: 'alphA.Beta') 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 expect(doc.css('a').length).to eq 1
end end
it 'links to a User with an underscore' do it 'links to a User with an underscore' do
user = create(:user, name: 'ping_pong_king') 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 expect(doc.css('a').length).to eq 1
end end
it 'includes a data-user attribute' do it 'includes a data-user attribute' do
doc = filter("Hey #{reference}") doc = reference_filter("Hey #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-user') expect(link).to have_attribute('data-user')
...@@ -83,12 +83,12 @@ module Gitlab::Markdown ...@@ -83,12 +83,12 @@ module Gitlab::Markdown
let(:reference) { group.to_reference } let(:reference) { group.to_reference }
it 'links to the Group' do 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) expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end end
it 'includes a data-group attribute' do it 'includes a data-group attribute' do
doc = filter("Hey #{reference}") doc = reference_filter("Hey #{reference}")
link = doc.css('a').first link = doc.css('a').first
expect(link).to have_attribute('data-group') expect(link).to have_attribute('data-group')
...@@ -102,21 +102,48 @@ module Gitlab::Markdown ...@@ -102,21 +102,48 @@ module Gitlab::Markdown
end end
it 'links with adjacent text' do it 'links with adjacent text' do
doc = filter("Mention me (#{reference}.)") doc = reference_filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/) expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end end
it 'includes default classes' do 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' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
end end
it 'supports an :only_path context' do 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') link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://) expect(link).not_to match %r(https?://)
expect(link).to eq urls.user_path(user) expect(link).to eq urls.user_path(user)
end end
context 'referencing a user in a link href' do
let(:reference) { %Q{<a href="#{user.to_reference}">User</a>} }
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(/\(<a.+>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
end end
...@@ -17,9 +17,9 @@ describe 'Gitlab::PushDataBuilder' do ...@@ -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][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) } it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
it { expect(data[:total_commits_count]).to eq(3) } it { expect(data[:total_commits_count]).to eq(3) }
it { expect(data[:added]).to eq(["gitlab-grack"]) } it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
it { expect(data[:modified]).to eq([".gitmodules", "files/ruby/popen.rb", "files/ruby/regex.rb"]) } it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
it { expect(data[:removed]).to eq([]) } it { expect(data[:commits].first[:removed]).to eq([]) }
end end
describe :build do describe :build do
...@@ -38,8 +38,5 @@ describe 'Gitlab::PushDataBuilder' do ...@@ -38,8 +38,5 @@ describe 'Gitlab::PushDataBuilder' do
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:commits]).to be_empty } it { expect(data[:commits]).to be_empty }
it { expect(data[:total_commits_count]).to be_zero } 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
end end
...@@ -7,50 +7,72 @@ describe CommitRange do ...@@ -7,50 +7,72 @@ describe CommitRange do
it { is_expected.to include_module(Referable) } it { is_expected.to include_module(Referable) }
end end
let(:sha_from) { 'f3f85602' } let!(:project) { create(:project, :public) }
let(:sha_to) { 'e86e1013' } let!(:commit1) { project.commit("HEAD~2") }
let!(:commit2) { project.commit }
let(:range) { described_class.new("#{sha_from}...#{sha_to}") } let(:sha_from) { commit1.short_id }
let(:range2) { described_class.new("#{sha_from}..#{sha_to}") } 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 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 end
describe '#to_s' do describe '#to_s' do
it 'is correct for three-dot syntax' 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 end
it 'is correct for two-dot syntax' do 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
end end
describe '#to_reference' do describe '#to_reference' do
let(:project) { double('project', to_reference: 'namespace1/project') } let(:cross) { create(:project) }
it 'returns a String reference to the object' do
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 "#{full_sha_from}..#{full_sha_to}"
end
it 'supports a cross-project reference' do
expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{full_sha_from}...#{full_sha_to}"
end
end
before do describe '#reference_link_text' do
range.project = project 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 end
it 'returns a String reference to the object' do it 'returns a String reference to the object' do
expect(range.to_reference).to eq range.to_s expect(range2.reference_link_text).to eq "#{sha_from}..#{sha_to}"
end end
it 'supports a cross-project reference' do it 'supports a cross-project reference' do
cross = double('project') expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}"
expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
end end
end end
describe '#reference_title' do describe '#reference_title' do
it 'returns the correct String for three-dot ranges' 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 end
it 'returns the correct String for two-dot ranges' do 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
end end
...@@ -60,11 +82,11 @@ describe CommitRange do ...@@ -60,11 +82,11 @@ describe CommitRange do
end end
it 'includes the correct values for a three-dot range' do 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 end
it 'includes the correct values for a two-dot range' do 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
end end
...@@ -79,64 +101,37 @@ describe CommitRange do ...@@ -79,64 +101,37 @@ describe CommitRange do
end end
describe '#valid_commits?' do describe '#valid_commits?' do
context 'without a project' do context 'with a valid repo' do
it 'returns nil' do before do
expect(range.valid_commits?).to be_nil expect(project).to receive(:valid_repo?).and_return(true)
end end
end
it 'accepts an optional project argument' do
project1 = double('project1').as_null_object
project2 = double('project2').as_null_object
# 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
# project2 gets passed to `valid_commits?`
expect(project2).to receive(:present?).and_return(false)
range.valid_commits?(project2) it 'is false when `sha_from` is invalid' do
end expect(project).to receive(:commit).with(sha_from).and_return(nil)
expect(project).to receive(:commit).with(sha_to).and_call_original
context 'with a project' do
let(:project) { double('project', repository: double('repository')) }
context 'with a valid repo' do expect(range).not_to be_valid_commits
before do end
expect(project).to receive(:valid_repo?).and_return(true)
range.project = project
end
it 'is false when `sha_from` is invalid' do it 'is false when `sha_to` is invalid' do
expect(project.repository).to receive(:commit).with(sha_from).and_return(false) expect(project).to receive(:commit).with(sha_from).and_call_original
expect(project.repository).not_to receive(:commit).with(sha_to) expect(project).to receive(:commit).with(sha_to).and_return(nil)
expect(range).not_to be_valid_commits
end
it 'is false when `sha_to` is invalid' do expect(range).not_to be_valid_commits
expect(project.repository).to receive(:commit).with(sha_from).and_return(true) end
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 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(range).to be_valid_commits
expect(project.repository).to receive(:commit).with(sha_to).and_return(true)
expect(range).to be_valid_commits
end
end end
end
context 'without a valid repo' do context 'without a valid repo' do
before do before do
expect(project).to receive(:valid_repo?).and_return(false) expect(project).to receive(:valid_repo?).and_return(false)
range.project = project end
end
it 'returns false' do it 'returns false' do
expect(range).not_to be_valid_commits expect(range).not_to be_valid_commits
end
end end
end end
end end
......
...@@ -24,6 +24,17 @@ describe Commit do ...@@ -24,6 +24,17 @@ describe Commit do
end end
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
describe '#title' do describe '#title' do
it "returns no_commit_message when safe_message is blank" do it "returns no_commit_message when safe_message is blank" do
allow(commit).to receive(:safe_message).and_return('') allow(commit).to receive(:safe_message).and_return('')
...@@ -77,14 +88,10 @@ eos ...@@ -77,14 +88,10 @@ eos
let(:other_issue) { create :issue, project: other_project } let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do 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}" ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}") allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
expect(commit.closes_issues).to be_empty expect(commit.closes_issues).to include(issue)
expect(commit.closes_issues).to include(other_issue)
end end
end end
...@@ -100,4 +107,15 @@ eos ...@@ -100,4 +107,15 @@ eos
# Include the subject in the repository stub. # Include the subject in the repository stub.
let(:extra_commits) { [subject] } let(:extra_commits) { [subject] }
end end
describe '#hook_attrs' do
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') }
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 end
...@@ -4,6 +4,7 @@ describe Repository do ...@@ -4,6 +4,7 @@ describe Repository do
include RepoHelpers include RepoHelpers
let(:repository) { create(:project).repository } let(:repository) { create(:project).repository }
let(:user) { create(:user) }
describe :branch_names_contains do describe :branch_names_contains do
subject { repository.branch_names_contains(sample_commit.id) } subject { repository.branch_names_contains(sample_commit.id) }
...@@ -99,5 +100,104 @@ describe Repository do ...@@ -99,5 +100,104 @@ describe Repository do
it { expect(subject.startline).to eq(186) } it { expect(subject.startline).to eq(186) }
it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") } it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
end end
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).exactly(3).times.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 end
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 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-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')
expect do
service.execute(user, @repo_path, @blankrev, @newrev, @ref)
end.to raise_error(GitHooksService::PreReceiveError)
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')
expect do
service.execute(user, @repo_path, @blankrev, @newrev, @ref)
end.to raise_error(GitHooksService::PreReceiveError)
end
end
end
end
...@@ -45,6 +45,7 @@ describe NotificationService do ...@@ -45,6 +45,7 @@ describe NotificationService do
project.team << [issue.author, :master] project.team << [issue.author, :master]
project.team << [issue.assignee, :master] project.team << [issue.assignee, :master]
project.team << [note.author, :master] project.team << [note.author, :master]
create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy')
end end
describe :new_note do describe :new_note do
...@@ -60,6 +61,7 @@ describe NotificationService do ...@@ -60,6 +61,7 @@ describe NotificationService do
should_email(note.noteable.assignee) should_email(note.noteable.assignee)
should_email(@u_mentioned) should_email(@u_mentioned)
should_email(@subscriber) should_email(@subscriber)
should_email(@subscribed_participant)
should_not_email(note.author) should_not_email(note.author)
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
...@@ -381,18 +383,19 @@ describe NotificationService do ...@@ -381,18 +383,19 @@ describe NotificationService do
def add_users_with_subscription(project, issuable) def add_users_with_subscription(project, issuable)
@subscriber = create :user @subscriber = create :user
@unsubscriber = 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 << [@subscriber, :master]
project.team << [@unsubscriber, :master] project.team << [@unsubscriber, :master]
issuable.subscriptions.create(user: @subscriber, subscribed: true) issuable.subscriptions.create(user: @subscriber, subscribed: true)
issuable.subscriptions.create(user: @subscribed_participant, subscribed: true)
issuable.subscriptions.create(user: @unsubscriber, subscribed: false) issuable.subscriptions.create(user: @unsubscriber, subscribed: false)
end end
def sent_to_user?(user) def sent_to_user?(user)
ActionMailer::Base.deliveries.any? do |message| ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
message.to.include?(user.email)
end
end end
def should_email(user) def should_email(user)
......
...@@ -35,11 +35,24 @@ module FilterSpecHelper ...@@ -35,11 +35,24 @@ module FilterSpecHelper
pipeline.call(body) pipeline.call(body)
end end
def reference_pipeline_result(body, contexts = {}) def reference_pipeline(contexts = {})
contexts.reverse_merge!(project: project) if defined?(project) contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts) filters = [
pipeline.call(body) 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 end
# Modify a String reference to make it invalid # Modify a String reference to make it invalid
......
...@@ -93,6 +93,10 @@ class MarkdownFeature ...@@ -93,6 +93,10 @@ class MarkdownFeature
end end
end end
def urls
Gitlab::Application.routes.url_helpers
end
def raw_markdown def raw_markdown
markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb')) markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
ERB.new(markdown).result(binding) ERB.new(markdown).result(binding)
......
...@@ -71,7 +71,7 @@ module MarkdownMatchers ...@@ -71,7 +71,7 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| 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
end end
...@@ -80,7 +80,7 @@ module MarkdownMatchers ...@@ -80,7 +80,7 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| 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
end end
...@@ -89,7 +89,7 @@ module MarkdownMatchers ...@@ -89,7 +89,7 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| 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') expect(actual).to have_selector('em a.gfm-merge_request')
end end
end end
...@@ -99,7 +99,7 @@ module MarkdownMatchers ...@@ -99,7 +99,7 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| 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
end end
...@@ -108,7 +108,7 @@ module MarkdownMatchers ...@@ -108,7 +108,7 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| 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
end end
...@@ -117,7 +117,7 @@ module MarkdownMatchers ...@@ -117,7 +117,7 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| 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
end end
...@@ -126,7 +126,7 @@ module MarkdownMatchers ...@@ -126,7 +126,7 @@ module MarkdownMatchers
set_default_markdown_messages set_default_markdown_messages
match do |actual| 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
end end
......
...@@ -10,12 +10,12 @@ def common_mentionable_setup ...@@ -10,12 +10,12 @@ def common_mentionable_setup
let(:mentioned_issue) { create(:issue, project: project) } let(:mentioned_issue) { create(:issue, project: project) }
let!(:mentioned_mr) { create(:merge_request, :simple, source_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_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) } let(:ext_issue) { create(:issue, project: ext_proj) }
let(:ext_mr) { create(:merge_request, :simple, source_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. # Override to add known commits to the repository stub.
let(:extra_commits) { [] } let(:extra_commits) { [] }
...@@ -45,14 +45,11 @@ def common_mentionable_setup ...@@ -45,14 +45,11 @@ def common_mentionable_setup
before do before do
# Wire the project's repository to return the mentioned commit, and +nil+ # Wire the project's repository to return the mentioned commit, and +nil+
# for any unrecognized commits. # for any unrecognized commits.
commitmap = { allow_any_instance_of(::Repository).to receive(:commit).and_call_original
mentioned_commit.id => mentioned_commit allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit)
} extra_commits.each do |commit|
extra_commits.each { |c| commitmap[c.short_id] = c } allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit)
end
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] }
set_mentionable_text.call(ref_string) set_mentionable_text.call(ref_string)
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment