Commit bf0af030 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'master' into fix-admin-should-be-able-to-add-himself-to-group

parents 23c1c70b f5430e48
Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased)
- Fix application settings cache not expiring after changes (Stan Hu)
- Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
- Handle and report SSL errors in Web hook test (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references
- Add ignore whitespace change option to commit view
- 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"
- Add API endpoint to fetch merge request commits list
- Expose events API with comment information and author info
- Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
- Fix 500 error when creating a merge request that removes a submodule
- Run custom Git hooks when branch is created or deleted.
- Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch
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
- Fix 404 in redirection after removing a project (Stan Hu)
......@@ -21,6 +35,7 @@ v 8.2.2
- Fix: As an admin, cannot add oneself as a member to a group/project
- Fix invalid links within projects dashboard header
- Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
- Fix: duplicate email notifications on issue comments
v 8.2.1
- Forcefully update builds that didn't want to update with state machine
......
......@@ -99,7 +99,7 @@ gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
gem 'net-ssh', '~> 3.0.1'
gem 'rouge', '~> 1.10.1'
# Diffs
gem 'diffy', '~> 3.0.3'
......@@ -120,8 +120,8 @@ gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs
gem 'sinatra', '~> 1.4.4', require: nil
gem 'sidekiq', '3.3.0'
gem 'sidetiq', '~> 0.6.3'
gem 'sidekiq', '~> 3.5.0'
gem 'sidekiq-cron', '~> 0.3.0'
# HTTP requests
gem "httparty", '~> 0.13.3'
......@@ -171,6 +171,7 @@ gem "underscore-rails", "~> 1.4.4"
# Sanitize user input
gem "sanitize", '~> 2.0'
gem 'babosa', '~> 1.0.2'
# Protect against bruteforcing
gem "rack-attack", '~> 4.3.0'
......@@ -204,6 +205,7 @@ gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.2.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
group :development do
gem "foreman"
......
......@@ -73,6 +73,7 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2)
bcrypt (3.1.10)
benchmark-ips (2.3.0)
better_errors (1.0.1)
......@@ -116,8 +117,23 @@ GEM
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
celluloid (0.16.0)
timers (~> 4.0.0)
celluloid (0.17.2)
celluloid-essentials
celluloid-extras
celluloid-fsm
celluloid-pool
celluloid-supervision
timers (>= 4.1.1)
celluloid-essentials (0.20.5)
timers (>= 4.1.1)
celluloid-extras (0.20.5)
timers (>= 4.1.1)
celluloid-fsm (0.20.5)
timers (>= 4.1.1)
celluloid-pool (0.20.5)
timers (>= 4.1.1)
celluloid-supervision (0.20.5)
timers (>= 4.1.1)
charlock_holmes (0.7.3)
chunky_png (1.3.5)
cliver (0.3.2)
......@@ -369,7 +385,6 @@ GEM
multi_xml (>= 0.5.2)
httpclient (2.7.0.1)
i18n (0.7.0)
ice_cube (0.11.1)
ice_nine (0.11.1)
inflecto (0.0.2)
ipaddress (0.8.0)
......@@ -640,6 +655,7 @@ GEM
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
rufus-scheduler (3.1.10)
rugged (0.23.3)
safe_yaml (1.0.4)
sanitize (2.1.0)
......@@ -667,16 +683,15 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (3.3.0)
celluloid (>= 0.16.0)
connection_pool (>= 2.0.0)
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
sidetiq (0.6.3)
celluloid (>= 0.14.1)
ice_cube (= 0.11.1)
sidekiq (>= 3.0.0)
sidekiq (3.5.3)
celluloid (~> 0.17.2)
connection_pool (~> 2.2, >= 2.2.0)
json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
redis-namespace (~> 1.5, >= 1.5.2)
sidekiq-cron (0.3.1)
rufus-scheduler (>= 2.0.24)
sidekiq (>= 2.17.3)
simple_oauth (0.1.9)
simplecov (0.10.0)
docile (~> 1.1.0)
......@@ -742,7 +757,7 @@ GEM
thor (0.19.1)
thread_safe (0.3.5)
tilt (1.4.1)
timers (4.0.4)
timers (4.1.1)
hitimes
timfel-krb5-auth (0.8.3)
tinder (1.10.1)
......@@ -823,6 +838,7 @@ DEPENDENCIES
asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4)
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
benchmark-ips
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
......@@ -924,6 +940,7 @@ DEPENDENCIES
request_store (~> 1.2.0)
rerun (~> 0.10.0)
responders (~> 2.0)
rouge (~> 1.10.1)
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0)
rubocop (~> 0.28.0)
......@@ -936,8 +953,8 @@ DEPENDENCIES
settingslogic (~> 2.0.9)
sham_rack
shoulda-matchers (~> 2.8.0)
sidekiq (= 3.3.0)
sidetiq (~> 0.6.3)
sidekiq (~> 3.5.0)
sidekiq-cron (~> 0.3.0)
simplecov (~> 0.10.0)
sinatra (~> 1.4.4)
six (~> 0.2.0)
......
......@@ -80,7 +80,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
## GitLab release cycle
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/).
## Upgrading
......
......@@ -88,4 +88,9 @@ class @AwardsHandler
callback.call()
findEmojiIcon: (emoji) ->
$(".icon[data-emoji='" + emoji + "']")
\ No newline at end of file
$(".icon[data-emoji='" + emoji + "']")
scrollToAwards: ->
$('body, html').animate({
scrollTop: $('.awards').offset().top - 80
}, 200)
class @Flash
constructor: (message, type)->
flash = $(".flash-container")
flash.html("")
@flash = $(".flash-container")
@flash.html("")
$('<div/>',
innerDiv = $('<div/>',
class: "flash-#{type}",
text: message
).appendTo(".flash-container")
)
innerDiv.appendTo(".flash-container")
flash.click -> $(@).fadeOut()
flash.show()
@flash.click -> $(@).fadeOut()
@flash.show()
pin: ->
@flash.addClass('flash-pinned flash-raised')
......@@ -111,6 +111,12 @@ class @Notes
Note: for rendering inline notes use renderDiscussionNote
###
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
# or skip if rendered
if @isNewNote(note) && !note.award
......@@ -122,6 +128,7 @@ class @Notes
if note.award
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
awards_handler.scrollToAwards()
###
Check if note does not exists on page
......@@ -362,8 +369,8 @@ class @Notes
note = $(this).closest(".note")
note.find(".note-attachment").remove()
note.find(".note-body > .note-text").show()
note.find(".js-note-attachment-delete").hide()
note.find(".note-edit-form").hide()
note.find(".note-header").show()
note.find(".current-note-edit-form").remove()
###
Called when clicking on the "reply" button for a diff line.
......
......@@ -2,3 +2,9 @@ class @User
constructor: ->
$('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList()
$('.hide-project-limit-message').on 'click', (e) ->
path = '/'
$.cookie('hide_project_limit_message', 'false', { path: path })
$(@).parents('.project-limit-message').remove()
e.preventDefault()
......@@ -15,3 +15,13 @@
@extend .alert-danger;
}
}
.flash-pinned {
position: fixed;
top: 80px;
width: 80%;
}
.flash-raised {
z-index: 1000;
}
......@@ -73,11 +73,8 @@
}
.referenced-users {
padding: 10px 0;
color: #999;
margin-left: 10px;
margin-top: 1px;
margin-right: 130px;
color: #4c4e54;
padding-top: 10px;
}
.md-preview-holder {
......
......@@ -5,7 +5,7 @@
font-weight: normal;
}
}
.no-ssh-key-message {
.no-ssh-key-message, .project-limit-message {
background-color: #f28d35;
margin-bottom: 16px;
}
......
......@@ -2,8 +2,10 @@ module GlobalMilestones
extend ActiveSupport::Concern
def milestones
epoch = DateTime.parse('1970-01-01')
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
@milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
end
......
......@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def milestone_path(title)
group_milestone_path(@group, title.parameterize, title: title)
group_milestone_path(@group, title.to_slug.to_s, title: title)
end
def projects
......
......@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
:email,
:hide_no_password,
:hide_no_ssh_key,
:hide_project_limit,
:linkedin,
:location,
:name,
......
......@@ -25,13 +25,12 @@ class Projects::HooksController < Projects::ApplicationController
def test
if !@project.empty_repo?
status = TestHookService.new.execute(hook, current_user)
status, message = TestHookService.new.execute(hook, current_user)
if status
flash[:notice] = 'Hook successfully executed.'
else
flash[:alert] = 'Hook execution failed. '\
'Ensure hook URL is correct and service is up.'
flash[:alert] = "Hook execution failed: #{message}"
end
else
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
......
......@@ -131,16 +131,25 @@ class Projects::NotesController < Projects::ApplicationController
end
def render_note_json(note)
render json: {
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
award: note.is_award,
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
note: note.note,
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
if note.valid?
render json: {
valid: true,
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
award: note.is_award,
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
note: note.note,
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
else
render json: {
valid: false,
award: note.is_award,
errors: note.errors
}
end
end
def authorize_admin_note!
......
class MilestonesFinder
def execute(projects, params)
milestones = Milestone.of_projects(projects)
milestones = milestones.order("due_date ASC")
milestones = milestones.reorder("due_date ASC")
case params[:state]
when 'closed' then milestones.closed
......
......@@ -209,7 +209,7 @@ module ApplicationHelper
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
element += javascript_tag "$('.js-timeago').last().timeago()" unless skip_js
element
end
......
......@@ -87,7 +87,11 @@ module IssuesHelper
end
def merge_requests_sentence(merge_requests)
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
# Sorting based on the `!123` or `group/project!123` reference will sort
# local merge requests first.
merge_requests.map do |merge_request|
merge_request.to_reference(@project)
end.sort.to_sentence(last_word_connector: ', or ')
end
def url_to_emoji(name)
......
......@@ -39,7 +39,11 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
issues.map(&:to_reference).to_sentence
# Sorting based on the `#123` or `group/project#123` reference will sort
# local issues first.
issues.map do |issue|
issue.to_reference(@project)
end.sort.to_sentence
end
def mr_change_branches_path(merge_request)
......
......@@ -28,7 +28,9 @@ module MilestonesHelper
Milestone.where(project_id: @projects)
end.active
epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones)
grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any)
......
......@@ -43,12 +43,12 @@ class ApplicationSetting < ActiveRecord::Base
validates :home_page_url,
allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
url: true,
if: :home_page_url_column_exist
validates :after_sign_out_path,
allow_blank: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
url: true
validates :admin_notification_email,
allow_blank: true,
......
......@@ -16,12 +16,12 @@
class BroadcastMessage < ActiveRecord::Base
include Sortable
validates :message, presence: true
validates :message, presence: true
validates :starts_at, presence: true
validates :ends_at, presence: true
validates :ends_at, presence: true
validates :color, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
validates :font, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
validates :color, allow_blank: true, color: true
validates :font, allow_blank: true, color: true
def self.current
where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last
......
......@@ -20,8 +20,7 @@ module Ci
# HTTParty timeout
default_timeout 10
validates :url, presence: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
validates :url, presence: true, url: true
def execute(data)
parsed_url = URI.parse(url)
......
......@@ -78,11 +78,23 @@ class Commit
}x
end
def self.link_reference_pattern
super("commit", /(?<commit>\h{6,40})/)
end
def to_reference(from_project = nil)
if cross_project_reference?(from_project)
"#{project.to_reference}@#{id}"
project.to_reference + self.class.reference_prefix + self.id
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
......@@ -135,10 +147,10 @@ class Commit
description.present?
end
def hook_attrs
def hook_attrs(with_changed_files: false)
path_with_namespace = project.path_with_namespace
{
data = {
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
......@@ -148,6 +160,12 @@ class Commit
email: author_email
}
}
if with_changed_files
data.merge!(repo_changes)
end
data
end
# Discover issues should be closed when this commit is pushed to a project's
......@@ -196,4 +214,22 @@ class Commit
def status
ci_commit.try(:status) || :not_found
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
......@@ -2,36 +2,38 @@
#
# Examples:
#
# range = CommitRange.new('f3f85602...e86e1013')
# range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false
# range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013"
#
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae')
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true
# range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013"
#
# # Assuming `project` is a Project with a repository containing both commits:
# range.project = project
# # Assuming the specified project has a repository containing both commits:
# range.valid_commits? # => true
#
class CommitRange
include ActiveModel::Conversion
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
attr_accessor :project
# See `exclude_start?`
attr_reader :exclude_start
# The beginning and ending SHAs can be between 6 and 40 hex characters, and
# The beginning and ending refs can be named or SHAs, and
# the range notation can be double- or triple-dot.
PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be SHAs
# between 6 and 40 hex characters.
STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
def self.reference_prefix
'@'
......@@ -43,27 +45,40 @@ class CommitRange
def self.reference_pattern
%r{
(?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit_range>#{PATTERN})
(?<commit_range>#{STRICT_PATTERN})
}x
end
def self.link_reference_pattern
super("compare", /(?<commit_range>#{PATTERN})/)
end
# Initialize a CommitRange
#
# range_string - The String commit range.
# project - An optional Project model.
#
# Raises ArgumentError if `range_string` does not match `PATTERN`.
def initialize(range_string, project = nil)
def initialize(range_string, project)
@project = project
range_string.strip!
unless range_string.match(/\A#{PATTERN}\z/)
unless range_string =~ /\A#{PATTERN}\z/
raise ArgumentError, "invalid CommitRange string format: #{range_string}"
end
@exclude_start = !range_string.include?('...')
@sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
@ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
@project = project
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
def inspect
......@@ -71,15 +86,24 @@ class CommitRange
end
def to_s
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
sha_from + notation + sha_to
end
alias_method :id, :to_s
def to_reference(from_project = nil)
# Not using to_s because we want the full SHAs
reference = sha_from + notation + sha_to
if cross_project_reference?(from_project)
project.to_reference + self.class.reference_prefix + self.id
else
self.id
end
end
def reference_link_text(from_project = nil)
reference = ref_from + notation + ref_to
if cross_project_reference?(from_project)
reference = project.to_reference + '@' + reference
reference = project.to_reference + self.class.reference_prefix + reference
end
reference
......@@ -87,46 +111,58 @@ class CommitRange
# Returns a String for use in a link's title attribute
def reference_title
"Commits #{suffixed_sha_from} through #{sha_to}"
"Commits #{sha_start} through #{sha_to}"
end
# Return a Hash of parameters for passing to a URL helper
#
# See `namespace_project_compare_url`
def to_param
{ from: suffixed_sha_from, to: sha_to }
{ from: sha_start, to: sha_to }
end
def exclude_start?
exclude_start
@notation == '..'
end
# Check if both the starting and ending commit IDs exist in a project's
# repository
#
# project - An optional Project to check (default: `project`)
def valid_commits?(project = project)
return nil unless project.present?
return false unless project.valid_repo?
commit_from.present? && commit_to.present?
def valid_commits?
commit_start.present? && commit_end.present?
end
def persisted?
true
end
def commit_from
@commit_from ||= project.repository.commit(suffixed_sha_from)
def sha_from
return nil unless @commit_from
@commit_from.id
end
def sha_to
return nil unless @commit_to
@commit_to.id
end
def commit_to
@commit_to ||= project.repository.commit(sha_to)
def sha_start
return nil unless sha_from
exclude_start? ? sha_from + '^' : sha_from
end
private
def commit_start
return nil unless sha_start
def suffixed_sha_from
sha_from + (exclude_start? ? '^' : '')
if exclude_start?
@commit_start ||= project.commit(sha_start)
else
commit_from
end
end
alias_method :sha_end, :sha_to
alias_method :commit_end, :commit_to
end
......@@ -62,13 +62,18 @@ module Mentionable
return [] if text.blank?
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
(refs.issues + refs.merge_requests + refs.commits) - [local_reference]
refs = (refs.issues + refs.merge_requests + refs.commits)
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
refs.reject { |ref| ref == local_reference }
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
refs = referenced_mentionables(author, text)
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
......@@ -111,7 +116,7 @@ module Mentionable
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
end
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def cross_reference_exists?(target)
......
......@@ -21,6 +21,10 @@ module Referable
''
end
def reference_link_text(from_project = nil)
to_reference(from_project)
end
module ClassMethods
# The character that prefixes the actual reference identifier
#
......@@ -44,6 +48,25 @@ module Referable
def reference_pattern
raise NotImplementedError, "#{self} does not implement #{__method__}"
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
private
......
......@@ -16,7 +16,15 @@ class GlobalMilestone
end
def safe_title
@title.parameterize
@title.to_slug.to_s
end
def expired?
if due_date
due_date.past?
else
false
end
end
def projects
......@@ -98,4 +106,25 @@ class GlobalMilestone
def complete?
total_items_count == closed_items_count
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
......@@ -31,37 +31,38 @@ class WebHook < ActiveRecord::Base
# HTTParty timeout
default_timeout Gitlab.config.gitlab.webhook_timeout
validates :url, presence: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
validates :url, presence: true, url: true
def execute(data, hook_name)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
WebHook.post(url,
body: data.to_json,
headers: {
"Content-Type" => "application/json",
"X-Gitlab-Event" => hook_name.singularize.titleize
},
verify: enable_ssl_verification)
response = WebHook.post(url,
body: data.to_json,
headers: {
"Content-Type" => "application/json",
"X-Gitlab-Event" => hook_name.singularize.titleize
},
verify: enable_ssl_verification)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
username: URI.decode(parsed_url.user),
password: URI.decode(parsed_url.password),
}
WebHook.post(post_url,
body: data.to_json,
headers: {
"Content-Type" => "application/json",
"X-Gitlab-Event" => hook_name.singularize.titleize
},
verify: enable_ssl_verification,
basic_auth: auth)
response = WebHook.post(post_url,
body: data.to_json,
headers: {
"Content-Type" => "application/json",
"X-Gitlab-Event" => hook_name.singularize.titleize
},
verify: enable_ssl_verification,
basic_auth: auth)
end
rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
[response.code == 200, ActionView::Base.full_sanitizer.sanitize(response.to_s)]
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}")
false
[false, e.to_s]
end
def async_execute(data, hook_name)
......
......@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("issues", /(?<issue>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
......
......@@ -27,9 +27,7 @@ class Label < ActiveRecord::Base
has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
validates :color,
format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
allow_blank: false
validates :color, color: true, allow_blank: false
validates :project, presence: true, unless: Proc.new { |service| service.template? }
# Don't allow '?', '&', and ',' for label titles
......
......@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("merge_requests", /(?<merge_request>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{iid}"
......@@ -291,7 +295,7 @@ class MergeRequest < ActiveRecord::Base
work_in_progress: work_in_progress?
}
unless last_commit.nil?
if last_commit
attrs.merge!(last_commit: last_commit.hook_attrs)
end
......@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description))
issues.uniq.sort_by(&:id)
issues.uniq
else
[]
end
......
......@@ -23,19 +23,17 @@ class Namespace < ActiveRecord::Base
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
presence: true, uniqueness: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.namespace_name_regex,
message: Gitlab::Regex.namespace_name_regex_message }
namespace_name: true,
presence: true,
uniqueness: true
validates :description, length: { within: 0..255 }
validates :path,
uniqueness: { case_sensitive: false },
presence: true,
length: { within: 1..255 },
exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.namespace_regex,
message: Gitlab::Regex.namespace_regex_message }
namespace: true,
presence: true,
uniqueness: { case_sensitive: false }
delegate :name, to: :owner, allow_nil: true, prefix: true
......
......@@ -39,9 +39,12 @@ class Note < ActiveRecord::Base
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
before_validation :set_award!
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
validates :line_code, line_code: true, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
......@@ -348,4 +351,31 @@ class Note < ActiveRecord::Base
def editable?
!system?
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
......@@ -152,7 +152,7 @@ class Project < ActiveRecord::Base
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url,
format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' },
url: { protocols: %w(ssh git http https) },
if: :external_import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
......
......@@ -23,10 +23,7 @@ class BambooService < CiService
prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url,
presence: true,
format: { with: /\A#{URI.regexp}\z/ },
if: :activated?
validates :bamboo_url, presence: true, url: true, if: :activated?
validates :build_key, presence: true, if: :activated?
validates :username,
presence: true,
......@@ -84,7 +81,7 @@ class BambooService < CiService
def supported_events
%w(push)
end
def build_info(sha)
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
......
......@@ -19,14 +19,11 @@
#
class DroneCiService < CiService
prop_accessor :drone_url, :token, :enable_ssl_verification
validates :drone_url,
presence: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
validates :token,
presence: true,
if: :activated?
validates :drone_url, presence: true, url: true, if: :activated?
validates :token, presence: true, if: :activated?
after_save :compose_service_hook, if: :activated?
......@@ -58,16 +55,16 @@ class DroneCiService < CiService
end
def merge_request_status_path(iid, sha = nil, ref = nil)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
"?access_token=#{token}"]
URI.join(*url).to_s
end
def commit_status_path(sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
URI.join(*url).to_s
......@@ -114,15 +111,15 @@ class DroneCiService < CiService
end
def merge_request_page(iid, sha, ref)
url = [drone_url,
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
URI.join(*url).to_s
end
def commit_page(sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}"]
URI.join(*url).to_s
......@@ -163,10 +160,10 @@ class DroneCiService < CiService
end
def push_valid?(data)
opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
source_branch: Gitlab::Git.ref_name(data[:ref]))
opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
!Gitlab::Git.blank_ref?(data[:after])
end
......
......@@ -22,10 +22,8 @@ class ExternalWikiService < Service
include HTTParty
prop_accessor :external_wiki_url
validates :external_wiki_url,
presence: true,
format: { with: /\A#{URI.regexp}\z/ },
if: :activated?
validates :external_wiki_url, presence: true, url: true, if: :activated?
def title
'External Wiki'
......
......@@ -23,16 +23,16 @@ class TeamcityService < CiService
prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url,
presence: true,
format: { with: /\A#{URI.regexp}\z/ }, if: :activated?
validates :teamcity_url, presence: true, url: true, if: :activated?
validates :build_type, presence: true, if: :activated?
validates :username,
presence: true,
if: ->(service) { service.password? }, if: :activated?
if: ->(service) { service.password? },
if: :activated?
validates :password,
presence: true,
if: ->(service) { service.username? }, if: :activated?
if: ->(service) { service.username? },
if: :activated?
attr_accessor :response
......
require 'securerandom'
class Repository
class PreReceiveError < StandardError; end
class CommitError < StandardError; end
include Gitlab::ShellAdapter
......@@ -101,17 +100,26 @@ class Repository
end
def find_branch(name)
branches.find { |branch| branch.name == name }
raw_repository.branches.find { |branch| branch.name == name }
end
def find_tag(name)
tags.find { |tag| tag.name == name }
raw_repository.tags.find { |tag| tag.name == name }
end
def add_branch(branch_name, ref)
expire_branches_cache
def add_branch(user, branch_name, target)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
target = commit(target).try(:id)
return false unless target
GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
rugged.branches.create(branch_name, target)
end
gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
expire_branches_cache
find_branch(branch_name)
end
def add_tag(tag_name, ref, message = nil)
......@@ -120,10 +128,20 @@ class Repository
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
def rm_branch(branch_name)
def rm_branch(user, branch_name)
expire_branches_cache
gitlab_shell.rm_branch(path_with_namespace, branch_name)
branch = find_branch(branch_name)
oldrev = branch.try(:target)
newrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
rugged.branches.delete(branch_name)
end
expire_branches_cache
true
end
def rm_tag(tag_name)
......@@ -550,7 +568,6 @@ class Repository
def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
gl_id = Gitlab::ShellEnv.gl_id(current_user)
was_empty = empty?
# Create temporary ref
......@@ -569,15 +586,7 @@ class Repository
raise CommitError.new('Failed to create commit')
end
# Run GitLab pre-receive hook
pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
# Run GitLab update hook
update_hook = Gitlab::Git::Hook.new('update', path_to_repo)
update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref)
if pre_receive_hook_status && update_hook_status
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty
# Create branch
rugged.references.create(ref, newrev)
......@@ -592,16 +601,11 @@ class Repository
raise CommitError.new('Commit was rejected because branch received new push')
end
end
# Run GitLab post receive hook
post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
else
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
raise PreReceiveError.new('Commit was rejected by git hook')
end
rescue GitHooksService::PreReceiveError
# Remove tmp ref and return error to user
rugged.references.delete(tmp_ref)
raise
end
private
......
......@@ -21,7 +21,7 @@ class SentNotification < ActiveRecord::Base
validates :reply_key, uniqueness: true
validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit?
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
validates :line_code, line_code: true, allow_blank: true
class << self
def reply_key
......
......@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
}x
end
def self.link_reference_pattern
super("snippets", /(?<snippet>\d+)/)
end
def to_reference(from_project = nil)
reference = "#{self.class.reference_prefix}#{id}"
......
......@@ -148,11 +148,9 @@ class User < ActiveRecord::Base
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username,
namespace: true,
presence: true,
uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.namespace_regex,
message: Gitlab::Regex.namespace_regex_message }
uniqueness: { case_sensitive: false }
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
validate :namespace_uniq, if: ->(user) { user.username_changed? }
......
......@@ -13,8 +13,7 @@ class CreateBranchService < BaseService
return error('Branch already exists')
end
repository.add_branch(branch_name, ref)
new_branch = repository.find_branch(branch_name)
new_branch = repository.add_branch(current_user, branch_name, ref)
if new_branch
push_data = build_push_data(project, current_user, new_branch)
......@@ -27,6 +26,8 @@ class CreateBranchService < BaseService
else
error('Invalid reference name')
end
rescue GitHooksService::PreReceiveError
error('Branch creation was rejected by Git hook')
end
def success(branch)
......
......@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
return error('You dont have push access to repo', 405)
end
if repository.rm_branch(branch_name)
if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch)
EventCreateService.new.push(project, current_user, push_data)
......@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
else
error('Failed to remove branch')
end
rescue GitHooksService::PreReceiveError
error('Branch deletion was rejected by Git hook')
end
def error(message, return_code = 400)
......
......@@ -26,7 +26,7 @@ module Files
else
error("Something went wrong. Your changes were not committed")
end
rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message)
end
......
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
note.author = current_user
note.system = false
if contains_emoji_only?(params[:note])
note.is_award = true
note.note = emoji_name(params[:note])
end
if note.save
notification_service.new_note(note)
......@@ -33,13 +28,5 @@ module Notes
note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks)
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
......@@ -145,6 +145,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, note.noteable)
recipients.delete(note.author)
recipients = recipients.uniq
# build notify method like 'note_commit_email'
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
......
......@@ -125,7 +125,7 @@ class SystemNoteService
# Returns the created Note object
def self.change_status(noteable, project, author, status, source)
body = "Status changed to #{status}"
body += " by #{source.gfm_reference}" if source
body += " by #{source.gfm_reference(project)}" if source
create_note(noteable: noteable, project: project, author: author, note: body)
end
......
# ColorValidator
#
# Custom validator for web color codes. It requires the leading hash symbol and
# will accept RGB triplet or hexadecimal formats.
#
# Example:
#
# class User < ActiveRecord::Base
# validates :background_color, allow_blank: true, color: true
# end
#
class ColorValidator < ActiveModel::EachValidator
PATTERN = /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/.freeze
def validate_each(record, attribute, value)
unless value =~ PATTERN
record.errors.add(attribute, "must be a valid color code")
end
end
end
# EmailValidator
#
# Based on https://github.com/balexand/email_validator
#
# Extended to use only strict mode with following allowed characters:
......@@ -6,15 +8,10 @@
# See http://www.remote.org/jochen/mail/info/chars.html
#
class EmailValidator < ActiveModel::EachValidator
@@default_options = {}
def self.default_options
@@default_options
end
PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
def validate_each(record, attribute, value)
options = @@default_options.merge(self.options)
unless value =~ /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i
unless value =~ PATTERN
record.errors.add(attribute, options[:message] || :invalid)
end
end
......
# LineCodeValidator
#
# Custom validator for GitLab line codes.
class LineCodeValidator < ActiveModel::EachValidator
PATTERN = /\A[a-z0-9]+_\d+_\d+\z/.freeze
def validate_each(record, attribute, value)
unless value =~ PATTERN
record.errors.add(attribute, "must be a valid line code")
end
end
end
# NamespaceNameValidator
#
# Custom validator for GitLab namespace name strings.
class NamespaceNameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ Gitlab::Regex.namespace_name_regex
record.errors.add(attribute, Gitlab::Regex.namespace_name_regex_message)
end
end
end
# NamespaceValidator
#
# Custom validator for GitLab namespace values.
#
# Values are checked for formatting and exclusion from a list of reserved path
# names.
class NamespaceValidator < ActiveModel::EachValidator
RESERVED = %w(
admin
all
assets
ci
dashboard
files
groups
help
hooks
issues
merge_requests
notes
profile
projects
public
repository
s
search
services
snippets
teams
u
unsubscribes
users
).freeze
def validate_each(record, attribute, value)
unless value =~ Gitlab::Regex.namespace_regex
record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
end
if reserved?(value)
record.errors.add(attribute, "#{value} is a reserved name")
end
end
private
def reserved?(value)
RESERVED.include?(value)
end
end
# UrlValidator
#
# Custom validator for URLs.
#
# By default, only URLs for the HTTP(S) protocols will be considered valid.
# Provide a `:protocols` option to configure accepted protocols.
#
# Example:
#
# class User < ActiveRecord::Base
# validates :personal_url, url: true
#
# validates :ftp_url, url: { protocols: %w(ftp) }
#
# validates :git_url, url: { protocols: %w(http https ssh git) }
# end
#
class UrlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless valid_url?(value)
record.errors.add(attribute, "must be a valid URL")
end
end
private
def default_options
@default_options ||= { protocols: %w(http https) }
end
def valid_url?(value)
options = default_options.merge(self.options)
value =~ /\A#{URI.regexp(options[:protocols])}\z/
end
end
= content_for :flash_message do
= render 'shared/project_limit'
%ul.center-top-menu
= nav_link(path: ['projects#index', 'root#index']) do
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
......
......@@ -16,7 +16,10 @@
= milestone_progress_bar(milestone)
.row
.col-sm-6
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= milestone.project.name_with_namespace
.expiration
= render 'shared/milestone_expired', milestone: milestone
.projects
- milestone.milestones.each do |milestone|
= link_to milestone_path(milestone) do
%span.label.label-gray
= milestone.project.name_with_namespace
......@@ -8,17 +8,18 @@
%a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
Preview
- if defined?(referenced_users) && referenced_users
%span.referenced-users.pull-left.hide
%div
.md-write-holder
= yield
.md.md-preview-holder.hide
.js-md-preview{class: (preview_class if defined?(preview_class))}
- if defined?(referenced_users) && referenced_users
%div.referenced-users.hide
%span
= icon('exclamation-triangle')
You are about to add
%strong
%span.js-referenced-users-count 0
people
to the discussion. Proceed with caution.
%div
.md-write-holder
= yield
.md.md-preview-holder.hide
.js-md-preview{class: (preview_class if defined?(preview_class))}
......@@ -3,9 +3,8 @@
- if diff_file.diff.submodule?
%span
= icon('archive fw')
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
%strong
= submodule_link(submodule_item, @commit.id, project.repository)
= submodule_link(blob, @commit.id, project.repository)
- else
%span
= blob_icon blob.mode, blob.name
......
.issue-closed-by-widget
= icon('check')
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests))} is accepted.
......@@ -18,11 +18,7 @@
.row
.col-sm-6
- if milestone.expired? and not milestone.closed?
%span.cred (Expired)
- if milestone.expires_at
%span
= milestone.expires_at
= render 'shared/milestone_expired', milestone: milestone
.col-sm-6
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
......
- 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'
class StuckCiBuildsWorker
include Sidekiq::Worker
include Sidetiq::Schedulable
BUILD_STUCK_TIMEOUT = 1.day
recurrence { daily }
def perform
Rails.logger.info 'Cleaning stuck builds'
......
......@@ -164,7 +164,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
......
......@@ -17,6 +17,12 @@ Sidekiq.configure_server do |config|
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
end
# Sidekiq-cron: load recurring jobs from schedule.yml
schedule_file = 'config/schedule.yml'
if File.exists?(schedule_file)
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
end
end
Sidekiq.configure_client do |config|
......
require 'sidekiq/web'
require 'sidekiq/cron/web'
require 'api/api'
Rails.application.routes.draw do
......@@ -368,7 +369,7 @@ Rails.application.routes.draw do
end
resource :avatar, only: [:destroy]
resources :milestones, only: [:index, :show, :update, :new, :create]
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
end
end
......
# Here is a list of jobs that are scheduled to run periodically.
# We use a UNIX cron notation to specify execution schedule.
#
# Please read here for more information:
# https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
stuck_ci_builds_worker:
cron: "0 0 * * *"
class: "StuckCiBuildsWorker"
queue: "default"
class AddHideProjectLimitToUsers < ActiveRecord::Migration
def change
add_column :users, :hide_project_limit, :boolean, default: false
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151118162244) do
ActiveRecord::Schema.define(version: 20151203162133) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -814,6 +814,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
t.integer "project_view", default: 0
t.integer "consumed_timestep"
t.integer "layout", default: 0
t.boolean "hide_project_limit", default: false
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
......@@ -190,7 +190,7 @@ This will create two service containers (MySQL and PostgreSQL).
1. Create a build container and execute script in its context:
```
$ cat build_script | docker run -n build -i -l mysql:service-mysql -l postgres:service-postgres ruby:2.1 /bin/bash
$ docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
```
This will create build container that has two service containers linked.
The build_script is piped using STDIN to bash interpreter which executes the build script in container.
......
GitLab has the following updates:
## Release cycle
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). Features that will likely be in the next releases can be found on the [direction page](https://about.gitlab.com/direction/).
## Release process documentation
- [Monthly release](monthly.md), every month on the 22nd.
- [Patch release](patch.md), if there are serious regressions.
......
......@@ -57,6 +57,9 @@ X-Gitlab-Event: Push Hook
"name": "Jordi Mallach",
"email": "jordi@softcatala.org"
}
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
},
{
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
......@@ -66,13 +69,14 @@ X-Gitlab-Event: Push Hook
"author": {
"name": "GitLab dev user",
"email": "gitlabdev@dv6700.(none)"
}
},
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
}
],
"total_commits_count": 4,
"added": ["CHANGELOG"],
"modified": ["app/controller/application.rb"],
"removed": []
"total_commits_count": 4
}
```
......
......@@ -13,6 +13,12 @@ Feature: Project Commits Diff Comments
Given I leave a diff comment like "Typo, please fix"
Then I should see a diff comment saying "Typo, please fix"
@javascript
Scenario: I can add a diff comment with a single emoji
Given I open a diff comment form
And I write a diff comment like ":smile:"
Then I should see a diff comment with an emoji image
@javascript
Scenario: I get a temporary form for the first comment on a diff line
Given I open a diff comment form
......
......@@ -11,4 +11,8 @@ Feature: Award Emoji
And I click to emoji in the picker
Then I have award added
And I can remove it by clicking to icon
\ No newline at end of file
@javascript
Scenario: I add award emoji using regular comment
Given I leave comment with a single emoji
Then I have award added
......@@ -8,10 +8,12 @@ Feature: Project Merge Requests Acceptance
Given I am on the Merge Request detail page
When I click on "Remove source branch" option
And I click on Accept Merge Request
Then I should not see the Remove Source Branch button
Then I should see merge request merged
And I should not see the Remove Source Branch button
@javascript
Scenario: Accepting the Merge Request without removing the source branch
Given I am on the Merge Request detail page
When I click on Accept Merge Request
Then I should see the Remove Source Branch button
Then I should see merge request merged
And I should see the Remove Source Branch button
......@@ -71,7 +71,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
step 'I should see label color error message' do
page.within '.label-form' do
expect(page).to have_content 'Color is invalid'
expect(page).to have_content 'Color must be a valid color code'
end
end
......
......@@ -70,8 +70,6 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
step 'I should see hook service down error message' do
expect(page).to have_selector '.flash-alert',
text: 'Hook execution failed. '\
'Ensure hook URL is correct and '\
'service is up.'
text: 'Hook execution failed: Exception from'
end
end
......@@ -9,33 +9,40 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'I click to emoji-picker' do
page.within ".awards-controls" do
page.find(".add-award").click
page.within '.awards-controls' do
page.find('.add-award').click
end
end
step 'I click to emoji in the picker' do
page.within ".awards-menu" do
page.first("img").click
page.within '.awards-menu' do
page.first('img').click
end
end
step 'I can remove it by clicking to icon' do
page.within ".awards" do
page.first(".award").click
expect(page).to_not have_selector ".award"
page.within '.awards' do
page.first('.award').click
expect(page).to_not have_selector '.award'
end
end
step 'I have award added' do
page.within ".awards" do
expect(page).to have_selector ".award"
expect(page.find(".award .counter")).to have_content "1"
page.within '.awards' do
expect(page).to have_selector '.award'
expect(page.find('.award .counter')).to have_content '1'
end
end
step 'project "Shop" has issue "Bugfix"' do
@project = Project.find_by(name: "Shop")
@issue = create(:issue, title: "Bugfix", project: project)
@project = Project.find_by(name: 'Shop')
@issue = create(:issue, title: 'Bugfix', project: project)
end
step 'I leave comment with a single emoji' do
page.within('.js-main-target-form') do
fill_in 'note[note]', with: ':smile:'
click_button 'Add Comment'
end
end
end
......@@ -55,7 +55,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
step 'I should see label color error message' do
page.within '.label-form' do
expect(page).to have_content 'Color is invalid'
expect(page).to have_content 'Color must be a valid color code'
end
end
......
......@@ -32,4 +32,8 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
step 'I am signed in as a developer of the project' do
login_as(@user)
end
step 'I should see merge request merged' do
expect(page).to have_content('The changes were merged into')
end
end
......@@ -87,6 +87,17 @@ module SharedDiffNote
end
end
step 'I write a diff comment like ":smile:"' do
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
page.within("form[rel$='#{sample_commit.line_code}']") do
fill_in 'note[note]', with: ':smile:'
click_button('Add Comment')
end
end
end
step 'I submit the diff comment' do
page.within(diff_file_selector) do
click_button("Add Comment")
......@@ -197,6 +208,12 @@ module SharedDiffNote
end
end
step 'I should see a diff comment with an emoji image' do
page.within("#{diff_file_selector} .note") do
expect(page).to have_xpath("//img[@alt=':smile:']")
end
end
step 'I click side-by-side diff button' do
find('#parallel-diff-btn').trigger('click')
end
......
......@@ -7,8 +7,12 @@ module API
helpers do
def map_public_to_visibility_level(attrs)
publik = attrs.delete(:public)
publik = parse_boolean(publik)
attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
if publik.present? && !attrs[:visibility_level].present?
publik = parse_boolean(publik)
# Since setting the public attribute to private could mean either
# private or internal, use the more conservative option, private.
attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
end
attrs
end
end
......
module Gitlab
module Blacklist
extend self
def path
%w(
admin
dashboard
files
groups
help
profile
projects
search
public
assets
u
s
teams
merge_requests
issues
users
snippets
services
repository
hooks
notes
unsubscribes
all
ci
)
end
end
end
module Gitlab
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)
@extractor = Gitlab::ReferenceExtractor.new(project, current_user)
......@@ -9,10 +15,12 @@ module Gitlab
def closed_by_message(message)
return [] if message.nil?
closing_statements = message.scan(ISSUE_CLOSING_REGEX).
map { |ref| ref[0] }.join(" ")
closing_statements = []
message.scan(ISSUE_CLOSING_REGEX) do
closing_statements << Regexp.last_match[0]
end
@extractor.analyze(closing_statements)
@extractor.analyze(closing_statements.join(" "))
@extractor.issues
end
......
......@@ -178,7 +178,6 @@ module Gitlab
Gitlab::Markdown::SanitizationFilter,
Gitlab::Markdown::UploadLinkFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
Gitlab::Markdown::AutolinkFilter,
......@@ -193,6 +192,8 @@ module Gitlab
Gitlab::Markdown::CommitReferenceFilter,
Gitlab::Markdown::LabelReferenceFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::TaskListFilter
]
end
......
......@@ -2,8 +2,8 @@ require 'gitlab/markdown'
module Gitlab
module Markdown
# Issues, Snippets and Merge Requests shares similar functionality in refernce filtering.
# All this functionality moved to this class
# Issues, Merge Requests, Snippets, Commits and Commit Ranges share
# similar functionality in reference filtering.
class AbstractReferenceFilter < ReferenceFilter
include CrossProjectReference
......@@ -26,21 +26,20 @@ module Gitlab
# Public: Find references in text (like `!123` for merge requests)
#
# AnyReferenceFilter.references_in(text) do |match, object|
# "<a href=...>PREFIX#{object}</a>"
# AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
# object = find_object(project_ref, id)
# "<a href=...>#{object.to_reference}</a>"
# end
#
# PREFIX - symbol that detects reference (like ! for merge requests)
# object - reference object (snippet, merget request etc)
# text - String text to search.
#
# Yields the String match, the Integer referenced object ID, and an optional String
# of the external project reference.
# Yields the String match, the Integer referenced object ID, an optional String
# of the external project reference, and all of the matchdata.
#
# Returns a String replaced with the return of the block.
def self.references_in(text)
text.gsub(object_class.reference_pattern) do |match|
yield match, $~[object_sym].to_i, $~[:project]
def self.references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[object_sym].to_i, $~[:project], $~
end
end
......@@ -61,8 +60,27 @@ module Gitlab
end
def call
# `#123`
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
......@@ -70,30 +88,57 @@ module Gitlab
# to the referenced object's details page.
#
# text - String text to replace references in.
# pattern - Reference pattern to match against.
# link_text - Original content of the link being replaced.
#
# Returns a String with references replaced with links. All links
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text)
references_in(text) do |match, id, project_ref|
def object_link_filter(text, pattern, link_text: nil)
references_in(text, pattern) do |match, id, project_ref, matches|
project = project_from_ref(project_ref)
if project && object = find_object(project, id)
title = escape_once("#{object_title}: #{object.title}")
title = escape_once(object_link_title(object))
klass = reference_class(object_sym)
data = data_attribute(project: project.id, object_sym => object.id)
url = url_for_object(object, project)
data = data_attribute(
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}
title="#{title}"
class="#{klass}">#{match}</a>)
class="#{klass}">#{text}</a>)
else
match
end
end
end
def object_title
object_class.name.titleize
def object_link_text_extras(object, matches)
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
......
......@@ -5,24 +5,14 @@ module Gitlab
# HTML filter that replaces commit range references with links.
#
# This filter supports cross-project references.
class CommitRangeReferenceFilter < ReferenceFilter
include CrossProjectReference
class CommitRangeReferenceFilter < AbstractReferenceFilter
def self.object_class
CommitRange
end
# Public: Find commit range references in text
#
# CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref|
# "<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]
def self.references_in(text, pattern = CommitRange.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:commit_range], $~[:project], $~
end
end
......@@ -31,9 +21,9 @@ module Gitlab
return unless project
id = node.attr("data-commit-range")
range = CommitRange.new(id, project)
range = find_object(project, id)
return unless range.valid_commits?
return unless range
{ commit_range: range }
end
......@@ -44,49 +34,25 @@ module Gitlab
@commit_map = {}
end
def call
replace_text_nodes_matching(CommitRange.reference_pattern) do |content|
commit_range_link_filter(content)
end
end
# Replace commit range references in text with links to compare the commit
# ranges.
#
# text - String text to replace references in.
#
# Returns a String with commit range references replaced with links. All
# links have `gfm` and `gfm-commit_range` class names attached for
# styling.
def commit_range_link_filter(text)
self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
range = CommitRange.new(id, project)
if range.valid_commits?
url = url_for_commit_range(project, range)
title = range.reference_title
klass = reference_class(:commit_range)
data = data_attribute(project: project.id, commit_range: id)
def self.find_object(project, id)
range = CommitRange.new(id, project)
project_ref += '@' if project_ref
range.valid_commits? ? range : nil
end
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{project_ref}#{range}</a>)
else
match
end
end
def find_object(*args)
self.class.find_object(*args)
end
def url_for_commit_range(project, range)
def url_for_object(range, project)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_compare_url(project.namespace, project,
range.to_param.merge(only_path: context[:only_path]))
end
def object_link_title(range)
range.reference_title
end
end
end
end
......@@ -5,24 +5,14 @@ module Gitlab
# HTML filter that replaces commit references with links.
#
# This filter supports cross-project references.
class CommitReferenceFilter < ReferenceFilter
include CrossProjectReference
class CommitReferenceFilter < AbstractReferenceFilter
def self.object_class
Commit
end
# Public: Find commit references in text
#
# CommitReferenceFilter.references_in(text) do |match, commit, project_ref|
# "<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]
def self.references_in(text, pattern = Commit.reference_pattern)
text.gsub(pattern) do |match|
yield match, $~[:commit], $~[:project], $~
end
end
......@@ -31,58 +21,32 @@ module Gitlab
return unless project
id = node.attr("data-commit")
commit = commit_from_ref(project, id)
commit = find_object(project, id)
return unless commit
{ commit: commit }
end
def call
replace_text_nodes_matching(Commit.reference_pattern) do |content|
commit_link_filter(content)
end
end
# Replace commit references in text with links to the commit specified.
#
# text - String text to replace references in.
#
# Returns a String with commit references replaced with links. All links
# have `gfm` and `gfm-commit` class names attached for styling.
def commit_link_filter(text)
self.class.references_in(text) do |match, id, project_ref|
project = self.project_from_ref(project_ref)
if commit = self.class.commit_from_ref(project, id)
url = url_for_commit(project, commit)
title = escape_once(commit.link_title)
klass = reference_class(:commit)
data = data_attribute(project: project.id, commit: id)
project_ref += '@' if project_ref
%(<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)
def self.find_object(project, id)
if project && project.valid_repo?
project.commit(id)
end
end
def url_for_commit(project, commit)
def find_object(*args)
self.class.find_object(*args)
end
def url_for_object(commit, project)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_commit_url(project.namespace, project, commit,
only_path: context[:only_path])
end
def object_link_title(commit)
commit.link_title
end
end
end
end
......@@ -30,6 +30,10 @@ module Gitlab
replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
issue_link_filter(content)
end
replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
issue_link_filter(link, link_text: text)
end
end
# Replace `JIRA-123` issue references in text with links to the referenced
......@@ -39,7 +43,7 @@ module Gitlab
#
# Returns a String with `JIRA-123` references replaced with links. All
# 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]
self.class.references_in(text) do |match, issue|
......@@ -49,9 +53,11 @@ module Gitlab
klass = reference_class(:issue)
data = data_attribute(project: project.id)
text = link_text || match
%(<a href="#{url}" #{data}
title="#{title}"
class="#{klass}">#{match}</a>)
class="#{klass}">#{text}</a>)
end
end
......
......@@ -8,9 +8,9 @@ module Gitlab
class ExternalLinkFilter < HTML::Pipeline::Filter
def call
doc.search('a').each do |node|
next unless node.has_attribute?('href')
link = node.attr('href')
link = node.attribute('href').value
next unless link
# Skip non-HTTP(S) links
next unless link.start_with?('http')
......
......@@ -30,6 +30,10 @@ module Gitlab
replace_text_nodes_matching(Label.reference_pattern) do |content|
label_link_filter(content)
end
replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
label_link_filter(link, link_text: text)
end
end
# Replace label references in text with links to the label specified.
......@@ -38,7 +42,7 @@ module Gitlab
#
# Returns a String with label references replaced with links. All links
# have `gfm` and `gfm-label` class names attached for styling.
def label_link_filter(text)
def label_link_filter(text, link_text: nil)
project = context[:project]
self.class.references_in(text) do |match, id, name|
......@@ -47,10 +51,16 @@ module Gitlab
if label = project.labels.find_by(params)
url = url_for_label(project, label)
klass = reference_class(:label)
data = data_attribute(project: project.id, label: label.id)
data = data_attribute(
original: link_text || match,
project: project.id,
label: label.id
)
text = link_text || render_colored_label(label)
%(<a href="#{url}" #{data}
class="#{klass}">#{render_colored_label(label)}</a>)
class="#{klass}">#{text}</a>)
else
match
end
......@@ -59,8 +69,8 @@ module Gitlab
def url_for_label(project, label)
h = Gitlab::Application.routes.url_helpers
h.namespace_project_issues_path(project.namespace, project,
label_name: label.name)
h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
only_path: context[:only_path])
end
def render_colored_label(label)
......
......@@ -20,6 +20,16 @@ module Gitlab
h.namespace_project_merge_request_url(project.namespace, project, mr,
only_path: context[:only_path])
end
def object_link_text_extras(object, matches)
extras = super
if matches.names.include?("path") && matches[:path] && matches[:path] == '/diffs'
extras.unshift "diffs"
end
extras
end
end
end
end
......@@ -12,7 +12,10 @@ module Gitlab
def call
doc.css('a.gfm').each do |node|
unless user_can_reference?(node)
node.replace(node.text)
# The reference should be replaced by the original text,
# which is not always the same as the rendered text.
text = node.attr('data-original') || node.text
node.replace(text)
end
end
......
......@@ -122,6 +122,80 @@ module Gitlab
doc
end
# Iterate through the document's link nodes, yielding the current node's
# content if:
#
# * The `project` context value is present AND
# * The node's content matches `pattern`
#
# pattern - Regex pattern against which to match the node's content
#
# Yields the current node's String contents. The result of the block will
# replace the node and update the current document.
#
# Returns the updated Nokogiri::HTML::DocumentFragment object.
def replace_link_nodes_with_text(pattern)
return doc if project.nil?
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
#
# Note that while the key might exist, its value could be nil!
......
......@@ -17,6 +17,9 @@ module Gitlab
return doc unless linkable_files?
doc.search('a').each do |el|
klass = el.attr('class')
next if klass && klass.include?('gfm')
process_link_attr el.attribute('href')
end
......
......@@ -52,6 +52,10 @@ module Gitlab
replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content)
end
replace_link_nodes_with_href(User.reference_pattern) do |link, text|
user_link_filter(link, link_text: text)
end
end
# Replace `@user` user references in text with links to the referenced
......@@ -61,12 +65,12 @@ module Gitlab
#
# Returns a String with `@user` references replaced with links. All links
# have `gfm` and `gfm-project_member` class names attached for styling.
def user_link_filter(text)
def user_link_filter(text, link_text: nil)
self.class.references_in(text) do |match, username|
if username == 'all'
link_to_all
link_to_all(link_text: link_text)
elsif namespace = Namespace.find_by(path: username)
link_to_namespace(namespace) || match
link_to_namespace(namespace, link_text: link_text) || match
else
match
end
......@@ -83,36 +87,36 @@ module Gitlab
reference_class(:project_member)
end
def link_to_all
def link_to_all(link_text: nil)
project = context[:project]
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
data = data_attribute(project: project.id)
text = User.reference_prefix + 'all'
text = link_text || User.reference_prefix + 'all'
link_tag(url, data, text)
end
def link_to_namespace(namespace)
def link_to_namespace(namespace, link_text: nil)
if namespace.is_a?(Group)
link_to_group(namespace.path, namespace)
link_to_group(namespace.path, namespace, link_text: link_text)
else
link_to_user(namespace.path, namespace)
link_to_user(namespace.path, namespace, link_text: link_text)
end
end
def link_to_group(group, namespace)
def link_to_group(group, namespace, link_text: nil)
url = urls.group_url(group, only_path: context[:only_path])
data = data_attribute(group: namespace.id)
text = Group.reference_prefix + group
text = link_text || Group.reference_prefix + group
link_tag(url, data, text)
end
def link_to_user(user, namespace)
def link_to_user(user, namespace, link_text: nil)
url = urls.user_url(user, only_path: context[:only_path])
data = data_attribute(user: namespace.owner_id)
text = User.reference_prefix + user
text = link_text || User.reference_prefix + user
link_tag(url, data, text)
end
......
......@@ -18,10 +18,7 @@ module Gitlab
# homepage: String,
# },
# commits: Array,
# total_commits_count: Fixnum,
# added: ["CHANGELOG"],
# modified: [],
# removed: ["tmp/file.txt"]
# total_commits_count: Fixnum
# }
#
def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
......@@ -33,11 +30,12 @@ module Gitlab
# For performance purposes maximum 20 latest commits
# will be passed as post receive hook data.
commit_attrs = commits_limited.map(&:hook_attrs)
commit_attrs = commits_limited.map do |commit|
commit.hook_attrs(with_changed_files: true)
end
type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
repo_changes = repo_changes(project, newrev, oldrev)
# Hash to be passed as post_receive_data
data = {
object_kind: type,
......@@ -60,10 +58,7 @@ module Gitlab
visibility_level: project.visibility_level
},
commits: commit_attrs,
total_commits_count: commits_count,
added: repo_changes[:added],
modified: repo_changes[:modified],
removed: repo_changes[:removed]
total_commits_count: commits_count
}
data
......@@ -94,27 +89,6 @@ module Gitlab
newrev
end
end
def repo_changes(project, newrev, oldrev)
changes = { added: [], modified: [], removed: [] }
compare_result = CompareService.new.
execute(project, newrev, project, oldrev)
if compare_result
compare_result.diffs.each do |diff|
case true
when diff.deleted_file
changes[:removed] << diff.old_path
when diff.renamed_file, diff.new_file
changes[:added] << diff.new_path
else
changes[:modified] << diff.new_path
end
end
end
changes
end
end
end
end
......@@ -41,14 +41,14 @@ module Gitlab
# Returns the results Array for the requested filter type
def pipeline_result(filter_type)
return [] if @text.blank?
klass = "#{filter_type.to_s.camelize}ReferenceFilter"
filter = Gitlab::Markdown.const_get(klass)
context = {
project: project,
current_user: current_user,
# We don't actually care about the links generated
only_path: true,
ignore_blockquotes: true,
......@@ -58,7 +58,15 @@ module Gitlab
reference_filter: filter
}
pipeline = HTML::Pipeline.new([filter, Gitlab::Markdown::ReferenceGathererFilter], context)
# We need to autolink first to finds links to referables, and to prevent
# numeric anchors to be parsed as issue references.
filters = [
Gitlab::Markdown::AutolinkFilter,
filter,
Gitlab::Markdown::ReferenceGathererFilter
]
pipeline = HTML::Pipeline.new(filters, context)
result = pipeline.call(@text)
values = result[:references][filter_type].uniq
......
......@@ -327,7 +327,7 @@ print_status() {
printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n"
fi
fi
if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
fi
}
......
......@@ -56,7 +56,7 @@ server {
listen [::]:80 ipv6only=on default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
return 301 https://$server_name$request_uri;
return 301 https://$http_host$request_uri;
access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log;
}
......
......@@ -110,6 +110,26 @@ describe Projects::CommitController do
expect(response.body).to match(/^diff --git/)
end
end
context 'commit that removes a submodule' do
render_views
let(:fork_project) { create(:forked_project_with_submodules) }
let(:commit) { fork_project.commit('remove-submodule') }
before do
fork_project.team << [user, :master]
end
it 'renders it' do
get(:show,
namespace_id: fork_project.namespace.to_param,
project_id: fork_project.to_param,
id: commit.id)
expect(response).to be_success
end
end
end
describe "#branches" do
......
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
......@@ -10,6 +10,30 @@ describe Projects::MergeRequestsController do
project.team << [user, :master]
end
describe '#new' do
context 'merge request that removes a submodule' do
render_views
let(:fork_project) { create(:forked_project_with_submodules) }
before do
fork_project.team << [user, :master]
end
it 'renders it' do
get :new,
namespace_id: fork_project.namespace.to_param,
project_id: fork_project.to_param,
merge_request: {
source_branch: 'remove-submodule',
target_branch: 'master'
}
expect(response).to be_success
end
end
end
describe "#show" do
shared_examples "export merge as" do |format|
it "should generally work" do
......
......@@ -5,7 +5,7 @@ describe Projects::MilestonesController do
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
let(:issue) { create(:issue, project: project, milestone: milestone) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
before do
sign_in(user)
......@@ -15,10 +15,9 @@ describe Projects::MilestonesController do
describe "#destroy" do
it "should remove milestone" do
merge_request.reload
expect(issue.milestone_id).to eq(milestone.id)
delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js
delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js
expect(response).to be_success
expect(Event.first.action).to eq(Event::DESTROYED)
......
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe 'Comments', feature: true do
include RepoHelpers
include WaitForAjax
describe 'On a merge request', js: true, feature: true do
let!(:merge_request) { create(:merge_request) }
......@@ -123,8 +124,8 @@ describe 'Comments', feature: true do
it 'removes the attachment div and resets the edit form' do
find('.js-note-attachment-delete').click
is_expected.not_to have_css('.note-attachment')
expect(find('.current-note-edit-form', visible: false)).
not_to be_visible
is_expected.not_to have_css('.current-note-edit-form')
wait_for_ajax
end
end
end
......
......@@ -153,6 +153,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Ignores invalid: <%= User.reference_prefix %>fake_user
- Ignored in code: `<%= user.to_reference %>`
- Ignored in links: [Link to <%= user.to_reference %>](#user-link)
- Link to user by reference: [User](<%= user.to_reference %>)
#### IssueReferenceFilter
......@@ -160,6 +161,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Issue in another project: <%= xissue.to_reference(project) %>
- Ignored in code: `<%= issue.to_reference %>`
- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link)
- Issue by URL: <%= 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
......@@ -167,6 +171,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Merge request in another project: <%= xmerge_request.to_reference(project) %>
- Ignored in code: `<%= merge_request.to_reference %>`
- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link)
- Merge request by URL: <%= 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
......@@ -174,6 +181,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Snippet in another project: <%= xsnippet.to_reference(project) %>
- Ignored in code: `<%= snippet.to_reference %>`
- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link)
- Snippet by URL: <%= 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
......@@ -181,6 +191,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Range in another project: <%= xcommit_range.to_reference(project) %>
- Ignored in code: `<%= commit_range.to_reference %>`
- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link)
- Range by URL: <%= 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
......@@ -188,6 +201,9 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Commit in another project: <%= xcommit.to_reference(project) %>
- Ignored in code: `<%= commit.to_reference %>`
- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link)
- Commit by URL: <%= 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
......@@ -196,6 +212,7 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Label by name in quotes: <%= label.to_reference(:name) %>
- Ignored in code: `<%= simple_label.to_reference %>`
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
- Link to label by reference: [Label](<%= label.to_reference %>)
### Task Lists
......
......@@ -278,7 +278,7 @@ describe ApplicationHelper do
el = element.next_element
expect(el.name).to eq 'script'
expect(el.text).to include "$('.js-timeago').timeago()"
expect(el.text).to include "$('.js-timeago').last().timeago()"
end
it 'allows the script tag to be excluded' do
......
......@@ -2,11 +2,18 @@ require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do
let(:project) { create(:project) }
let(:project2) { create(:project) }
let(:issue) { create(:issue, project: project) }
let(:issue2) { create(:issue, project: project2) }
let(:reference) { issue.to_reference }
let(:cross_reference) { issue2.to_reference(project) }
subject { described_class.new(project, project.creator) }
before do
project2.team << [project.creator, :master]
end
describe "#closed_by_message" do
context 'with a single reference' do
it do
......@@ -130,6 +137,27 @@ describe Gitlab::ClosingIssueExtractor do
end
end
context "with a cross-project reference" do
it do
message = "Closes #{cross_reference}"
expect(subject.closed_by_message(message)).to eq([issue2])
end
end
context "with a cross-project URL" do
it do
message = "Closes #{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
let(:other_issue) { create(:issue, project: project) }
let(:third_issue) { create(:issue, project: project) }
......@@ -171,6 +199,31 @@ describe Gitlab::ClosingIssueExtractor do
expect(subject.closed_by_message(message)).
to match_array([issue, other_issue, third_issue])
end
it "fetches cross-project references" do
message = "Closes #{reference} and #{cross_reference}"
expect(subject.closed_by_message(message)).
to match_array([issue, issue2])
end
it "fetches cross-project URL references" do
message = "Closes #{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
def urls
Gitlab::Application.routes.url_helpers
end
end
......@@ -5,11 +5,11 @@ module Gitlab::Markdown
include FilterSpecHelper
let(:project) { create(:project, :public) }
let(:commit1) { project.commit }
let(:commit2) { project.commit("HEAD~2") }
let(:commit1) { project.commit("HEAD~2") }
let(:commit2) { project.commit }
let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") }
let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") }
let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}", project) }
let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}", project) }
it 'requires project context' do
expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
......@@ -18,7 +18,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -27,14 +27,14 @@ module Gitlab::Markdown
let(:reference2) { range2.to_reference }
it 'links to a valid two-dot reference' do
doc = filter("See #{reference2}")
doc = reference_filter("See #{reference2}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param)
end
it 'links to a valid three-dot reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param)
......@@ -46,14 +46,14 @@ module Gitlab::Markdown
exp = commit1.short_id + '...' + commit2.short_id
expect(filter("See #{reference}").css('a').first.text).to eq exp
expect(filter("See #{reference2}").css('a').first.text).to eq exp
expect(reference_filter("See #{reference}").css('a').first.text).to eq exp
expect(reference_filter("See #{reference2}").css('a').first.text).to eq exp
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
doc = reference_filter("See (#{reference}.)")
exp = Regexp.escape(range.to_s)
exp = Regexp.escape(range.reference_link_text)
expect(doc.to_html).to match(/\(<a.+>#{exp}<\/a>\.\)/)
end
......@@ -62,21 +62,22 @@ module Gitlab::Markdown
expect(project).to receive(:valid_repo?).and_return(true)
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
it 'includes a title attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq range.reference_title
end
it 'includes default classes' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range'
end
it 'includes a data-project attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -84,15 +85,15 @@ module Gitlab::Markdown
end
it 'includes a data-commit-range attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit-range')
expect(link.attr('data-commit-range')).to eq range.to_reference
expect(link.attr('data-commit-range')).to eq range.to_s
end
it 'supports an :only_path option' do
doc = filter("See #{reference}", only_path: true)
doc = reference_filter("See #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......@@ -115,25 +116,63 @@ module Gitlab::Markdown
end
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}")
exp = Regexp.escape("#{project2.to_reference}@#{range.reference_link_text}")
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(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
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
it 'adds to the results hash' do
......
......@@ -14,7 +14,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Commit #{commit.id}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -24,7 +24,7 @@ module Gitlab::Markdown
# Let's test a variety of commit SHA sizes just to be paranoid
[6, 8, 12, 18, 20, 32, 40].each do |size|
it "links to a valid reference of #{size} characters" do
doc = filter("See #{reference[0...size]}")
doc = reference_filter("See #{reference[0...size]}")
expect(doc.css('a').first.text).to eq commit.short_id
expect(doc.css('a').first.attr('href')).
......@@ -33,15 +33,15 @@ module Gitlab::Markdown
end
it 'always uses the short ID as the link text' do
doc = filter("See #{commit.id}")
doc = reference_filter("See #{commit.id}")
expect(doc.text).to eq "See #{commit.short_id}"
doc = filter("See #{commit.id[0...6]}")
doc = reference_filter("See #{commit.id[0...6]}")
expect(doc.text).to eq "See #{commit.short_id}"
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{commit.short_id}<\/a>\.\)/)
end
......@@ -51,28 +51,28 @@ module Gitlab::Markdown
expect(project).to receive(:valid_repo?).and_return(true)
expect(project.repository).to receive(:commit).with(invalid)
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq commit.link_title
end
it 'escapes the title attribute' do
allow_any_instance_of(Commit).to receive(:title).and_return(%{"></a>whatever<a title="})
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.text).to eq "See #{commit.short_id}"
end
it 'includes default classes' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit'
end
it 'includes a data-project attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -80,7 +80,7 @@ module Gitlab::Markdown
end
it 'includes a data-commit attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-commit')
......@@ -88,7 +88,7 @@ module Gitlab::Markdown
end
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')
expect(link).not_to match %r(https?://)
......@@ -108,14 +108,14 @@ module Gitlab::Markdown
let(:reference) { commit.to_reference(project) }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_commit_url(project2.namespace, project2, commit.id)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
doc = reference_filter("Fixed (#{reference}.)")
exp = Regexp.escape(project2.to_reference)
expect(doc.to_html).to match(/\(<a.+>#{exp}@#{commit.short_id}<\/a>\.\)/)
......@@ -123,7 +123,37 @@ module Gitlab::Markdown
it 'ignores invalid commit IDs on the referenced project' do
exp = act = "Committed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
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
it 'adds to the results hash' do
......
......@@ -18,7 +18,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -29,18 +29,18 @@ module Gitlab::Markdown
expect(project).to receive(:get_issue).with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = filter("Fixed #{reference}")
doc = reference_filter("Fixed #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project)
end
it 'links with adjacent text' do
doc = filter("Fixed (#{reference}.)")
doc = reference_filter("Fixed (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
......@@ -48,28 +48,28 @@ module Gitlab::Markdown
invalid = invalidate_reference(reference)
exp = act = "Fixed #{invalid}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Issue #{reference}")
doc = reference_filter("Issue #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}"
end
it 'escapes the title attribute' do
issue.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Issue #{reference}")
doc = reference_filter("Issue #{reference}")
expect(doc.text).to eq "Issue #{reference}"
end
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'
end
it 'includes a data-project attribute' do
doc = filter("Issue #{reference}")
doc = reference_filter("Issue #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -77,7 +77,7 @@ module Gitlab::Markdown
end
it 'includes a data-issue attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-issue')
......@@ -85,7 +85,7 @@ module Gitlab::Markdown
end
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')
expect(link).not_to match %r(https?://)
......@@ -109,25 +109,97 @@ module Gitlab::Markdown
with(issue.iid).and_return(nil)
exp = act = "Issue #{reference}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq helper.url_for_issue(issue.iid, project2)
end
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>\.\)/)
end
it 'ignores invalid issue IDs on the referenced project' do
exp = act = "Fixed #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
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
it 'adds to the results hash' do
......
......@@ -16,17 +16,17 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Label #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
it 'includes default classes' do
doc = filter("Label #{reference}")
doc = reference_filter("Label #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label'
end
it 'includes a data-project attribute' do
doc = filter("Label #{reference}")
doc = reference_filter("Label #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -34,7 +34,7 @@ module Gitlab::Markdown
end
it 'includes a data-label attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-label')
......@@ -42,7 +42,7 @@ module Gitlab::Markdown
end
it 'supports an :only_path context' do
doc = filter("Label #{reference}", only_path: true)
doc = reference_filter("Label #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
......@@ -56,33 +56,33 @@ module Gitlab::Markdown
describe 'label span element' do
it 'includes default classes' do
doc = filter("Label #{reference}")
doc = reference_filter("Label #{reference}")
expect(doc.css('a span').first.attr('class')).to eq 'label color-label'
end
it 'includes a style attribute' do
doc = filter("Label #{reference}")
doc = reference_filter("Label #{reference}")
expect(doc.css('a span').first.attr('style')).to match(/\Abackground-color: #\h{6}; color: #\h{6}\z/)
end
end
context 'Integer-based references' do
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_path(project.namespace, project, label_name: label.name)
namespace_project_issues_url(project.namespace, project, label_name: label.name)
end
it 'links with adjacent text' do
doc = filter("Label (#{reference}.)")
doc = reference_filter("Label (#{reference}.)")
expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
end
it 'ignores invalid label IDs' do
exp = act = "Label #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -91,22 +91,22 @@ module Gitlab::Markdown
let(:reference) { "#{Label.reference_prefix}#{label.name}" }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_path(project.namespace, project, label_name: label.name)
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm'
end
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>\.\)))
end
it 'ignores invalid label names' do
exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -115,29 +115,66 @@ module Gitlab::Markdown
let(:reference) { label.to_reference(:name) }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_issues_path(project.namespace, project, label_name: label.name)
namespace_project_issues_url(project.namespace, project, label_name: label.name)
expect(doc.text).to eq 'See gfm references'
end
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>\.\)))
end
it 'ignores invalid label names' do
exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
describe 'edge cases' do
it 'gracefully handles non-references matching the pattern' do
exp = act = '(format nil "~0f" 3.0) ; 3.0'
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
describe 'referencing a label in a link href' do
let(:reference) { %Q{<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
......
......@@ -14,7 +14,7 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -22,42 +22,42 @@ module Gitlab::Markdown
let(:reference) { merge.to_reference }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_merge_request_url(project.namespace, project, merge)
end
it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)")
doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs' do
exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Merge #{reference}")
doc = reference_filter("Merge #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}"
end
it 'escapes the title attribute' do
merge.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Merge #{reference}")
doc = reference_filter("Merge #{reference}")
expect(doc.text).to eq "Merge #{reference}"
end
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'
end
it 'includes a data-project attribute' do
doc = filter("Merge #{reference}")
doc = reference_filter("Merge #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -65,7 +65,7 @@ module Gitlab::Markdown
end
it 'includes a data-merge-request attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-merge-request')
......@@ -73,7 +73,7 @@ module Gitlab::Markdown
end
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')
expect(link).not_to match %r(https?://)
......@@ -89,26 +89,50 @@ module Gitlab::Markdown
context 'cross-project reference' do
let(:namespace) { create(:namespace, name: 'cross-reference') }
let(:project2) { create(:project, :public, namespace: namespace) }
let(:merge) { create(:merge_request, source_project: project2) }
let(:merge) { create(:merge_request, source_project: project2, target_project: project2) }
let(:reference) { merge.to_reference(project) }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_merge_request_url(project2.namespace,
project, merge)
project2, merge)
end
it 'links with adjacent text' do
doc = filter("Merge (#{reference}.)")
doc = reference_filter("Merge (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid merge IDs on the referenced project' do
exp = act = "Merge #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
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
it 'adds to the results hash' do
......
......@@ -15,48 +15,48 @@ module Gitlab::Markdown
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Snippet #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
context 'internal reference' do
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.
namespace_project_snippet_url(project.namespace, project, snippet)
end
it 'links with adjacent text' do
doc = filter("Snippet (#{reference}.)")
doc = reference_filter("Snippet (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs' do
exp = act = "Snippet #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'includes a title attribute' do
doc = filter("Snippet #{reference}")
doc = reference_filter("Snippet #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}"
end
it 'escapes the title attribute' do
snippet.update_attribute(:title, %{"></a>whatever<a title="})
doc = filter("Snippet #{reference}")
doc = reference_filter("Snippet #{reference}")
expect(doc.text).to eq "Snippet #{reference}"
end
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'
end
it 'includes a data-project attribute' do
doc = filter("Snippet #{reference}")
doc = reference_filter("Snippet #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-project')
......@@ -64,7 +64,7 @@ module Gitlab::Markdown
end
it 'includes a data-snippet attribute' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-snippet')
......@@ -72,7 +72,7 @@ module Gitlab::Markdown
end
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')
expect(link).not_to match %r(https?://)
......@@ -92,21 +92,51 @@ module Gitlab::Markdown
let(:reference) { snippet.to_reference(project) }
it 'links to a valid reference' do
doc = filter("See #{reference}")
doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('href')).
to eq urls.namespace_project_snippet_url(project2.namespace, project2, snippet)
end
it 'links with adjacent text' do
doc = filter("See (#{reference}.)")
doc = reference_filter("See (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(reference)}<\/a>\.\)/)
end
it 'ignores invalid snippet IDs on the referenced project' do
exp = act = "See #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
it 'adds to the results hash' do
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
it 'adds to the results hash' do
......
......@@ -14,13 +14,13 @@ module Gitlab::Markdown
it 'ignores invalid users' do
exp = act = "Hey #{invalidate_reference(reference)}"
expect(filter(act).to_html).to eq(exp)
expect(reference_filter(act).to_html).to eq(exp)
end
%w(pre code a style).each do |elem|
it "ignores valid references contained inside '#{elem}' element" do
exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
expect(filter(act).to_html).to eq exp
expect(reference_filter(act).to_html).to eq exp
end
end
......@@ -32,7 +32,7 @@ module Gitlab::Markdown
end
it 'supports a special @all mention' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').length).to eq 1
expect(doc.css('a').first.attr('href'))
.to eq urls.namespace_project_url(project.namespace, project)
......@@ -46,26 +46,26 @@ module Gitlab::Markdown
context 'mentioning a user' do
it 'links to a User' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.user_url(user)
end
it 'links to a User with a period' do
user = create(:user, name: 'alphA.Beta')
doc = filter("Hey #{user.to_reference}")
doc = reference_filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
it 'links to a User with an underscore' do
user = create(:user, name: 'ping_pong_king')
doc = filter("Hey #{user.to_reference}")
doc = reference_filter("Hey #{user.to_reference}")
expect(doc.css('a').length).to eq 1
end
it 'includes a data-user attribute' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-user')
......@@ -83,12 +83,12 @@ module Gitlab::Markdown
let(:reference) { group.to_reference }
it 'links to the Group' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('href')).to eq urls.group_url(group)
end
it 'includes a data-group attribute' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-group')
......@@ -102,21 +102,48 @@ module Gitlab::Markdown
end
it 'links with adjacent text' do
doc = filter("Mention me (#{reference}.)")
doc = reference_filter("Mention me (#{reference}.)")
expect(doc.to_html).to match(/\(<a.+>#{reference}<\/a>\.\)/)
end
it 'includes default classes' do
doc = filter("Hey #{reference}")
doc = reference_filter("Hey #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
end
it 'supports an :only_path context' do
doc = filter("Hey #{reference}", only_path: true)
doc = reference_filter("Hey #{reference}", only_path: true)
link = doc.css('a').first.attr('href')
expect(link).not_to match %r(https?://)
expect(link).to eq urls.user_path(user)
end
context 'referencing a user in a link href' do
let(:reference) { %Q{<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
......@@ -17,9 +17,9 @@ describe 'Gitlab::PushDataBuilder' do
it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) }
it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) }
it { expect(data[:total_commits_count]).to eq(3) }
it { expect(data[:added]).to eq(["gitlab-grack"]) }
it { expect(data[:modified]).to eq([".gitmodules", "files/ruby/popen.rb", "files/ruby/regex.rb"]) }
it { expect(data[:removed]).to eq([]) }
it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
it { expect(data[:commits].first[:removed]).to eq([]) }
end
describe :build do
......@@ -38,8 +38,5 @@ describe 'Gitlab::PushDataBuilder' do
it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
it { expect(data[:commits]).to be_empty }
it { expect(data[:total_commits_count]).to be_zero }
it { expect(data[:added]).to eq([]) }
it { expect(data[:modified]).to eq([]) }
it { expect(data[:removed]).to eq([]) }
end
end
......@@ -36,6 +36,22 @@ describe ApplicationSetting, models: true do
it { expect(setting).to be_valid }
describe 'validations' do
let(:http) { 'http://example.com' }
let(:https) { 'https://example.com' }
let(:ftp) { 'ftp://example.com' }
it { is_expected.to allow_value(nil).for(:home_page_url) }
it { is_expected.to allow_value(http).for(:home_page_url) }
it { is_expected.to allow_value(https).for(:home_page_url) }
it { is_expected.not_to allow_value(ftp).for(:home_page_url) }
it { is_expected.to allow_value(nil).for(:after_sign_out_path) }
it { is_expected.to allow_value(http).for(:after_sign_out_path) }
it { is_expected.to allow_value(https).for(:after_sign_out_path) }
it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
end
context 'restricted signup domains' do
it 'set single domain' do
setting.restricted_signup_domains_raw = 'example.com'
......
......@@ -20,6 +20,21 @@ describe BroadcastMessage do
it { is_expected.to be_valid }
describe 'validations' do
let(:triplet) { '#000' }
let(:hex) { '#AABBCC' }
it { is_expected.to allow_value(nil).for(:color) }
it { is_expected.to allow_value(triplet).for(:color) }
it { is_expected.to allow_value(hex).for(:color) }
it { is_expected.not_to allow_value('000').for(:color) }
it { is_expected.to allow_value(nil).for(:font) }
it { is_expected.to allow_value(triplet).for(:font) }
it { is_expected.to allow_value(hex).for(:font) }
it { is_expected.not_to allow_value('000').for(:font) }
end
describe :current do
it "should return last message if time match" do
broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow)
......
......@@ -7,50 +7,72 @@ describe CommitRange do
it { is_expected.to include_module(Referable) }
end
let(:sha_from) { 'f3f85602' }
let(:sha_to) { 'e86e1013' }
let!(:project) { create(:project, :public) }
let!(:commit1) { project.commit("HEAD~2") }
let!(:commit2) { project.commit }
let(:range) { described_class.new("#{sha_from}...#{sha_to}") }
let(:range2) { described_class.new("#{sha_from}..#{sha_to}") }
let(:sha_from) { commit1.short_id }
let(:sha_to) { commit2.short_id }
let(:full_sha_from) { commit1.id }
let(:full_sha_to) { commit2.id }
let(:range) { described_class.new("#{sha_from}...#{sha_to}", project) }
let(:range2) { described_class.new("#{sha_from}..#{sha_to}", project) }
it 'raises ArgumentError when given an invalid range string' do
expect { described_class.new("Foo") }.to raise_error(ArgumentError)
expect { described_class.new("Foo", project) }.to raise_error(ArgumentError)
end
describe '#to_s' do
it 'is correct for three-dot syntax' do
expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}"
expect(range.to_s).to eq "#{full_sha_from}...#{full_sha_to}"
end
it 'is correct for two-dot syntax' do
expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}"
expect(range2.to_s).to eq "#{full_sha_from}..#{full_sha_to}"
end
end
describe '#to_reference' do
let(:project) { double('project', to_reference: 'namespace1/project') }
let(:cross) { create(:project) }
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
range.project = project
describe '#reference_link_text' do
let(:cross) { create(:project) }
it 'returns a String reference to the object' do
expect(range.reference_link_text).to eq "#{sha_from}...#{sha_to}"
end
it 'returns a String reference to the object' do
expect(range.to_reference).to eq range.to_s
expect(range2.reference_link_text).to eq "#{sha_from}..#{sha_to}"
end
it 'supports a cross-project reference' do
cross = double('project')
expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}"
expect(range.reference_link_text(cross)).to eq "#{project.to_reference}@#{sha_from}...#{sha_to}"
end
end
describe '#reference_title' do
it 'returns the correct String for three-dot ranges' do
expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}"
expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}"
end
it 'returns the correct String for two-dot ranges' do
expect(range2.reference_title).to eq "Commits #{sha_from}^ through #{sha_to}"
expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}"
end
end
......@@ -60,11 +82,11 @@ describe CommitRange do
end
it 'includes the correct values for a three-dot range' do
expect(range.to_param).to eq({ from: sha_from, to: sha_to })
expect(range.to_param).to eq({ from: full_sha_from, to: full_sha_to })
end
it 'includes the correct values for a two-dot range' do
expect(range2.to_param).to eq({ from: sha_from + '^', to: sha_to })
expect(range2.to_param).to eq({ from: full_sha_from + '^', to: full_sha_to })
end
end
......@@ -79,64 +101,37 @@ describe CommitRange do
end
describe '#valid_commits?' do
context 'without a project' do
it 'returns nil' do
expect(range.valid_commits?).to be_nil
context 'with a valid repo' do
before do
expect(project).to receive(:valid_repo?).and_return(true)
end
end
it 'accepts an optional project argument' do
project1 = double('project1').as_null_object
project2 = double('project2').as_null_object
# 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)
end
context 'with a project' do
let(:project) { double('project', repository: double('repository')) }
it 'is false when `sha_from` is invalid' do
expect(project).to receive(:commit).with(sha_from).and_return(nil)
expect(project).to receive(:commit).with(sha_to).and_call_original
context 'with a valid repo' do
before do
expect(project).to receive(:valid_repo?).and_return(true)
range.project = project
end
expect(range).not_to be_valid_commits
end
it 'is false when `sha_from` is invalid' do
expect(project.repository).to receive(:commit).with(sha_from).and_return(false)
expect(project.repository).not_to receive(:commit).with(sha_to)
expect(range).not_to be_valid_commits
end
it 'is false when `sha_to` is invalid' do
expect(project).to receive(:commit).with(sha_from).and_call_original
expect(project).to receive(:commit).with(sha_to).and_return(nil)
it 'is false when `sha_to` is invalid' do
expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
expect(project.repository).to receive(:commit).with(sha_to).and_return(false)
expect(range).not_to be_valid_commits
end
expect(range).not_to be_valid_commits
end
it 'is true when both `sha_from` and `sha_to` are valid' do
expect(project.repository).to receive(:commit).with(sha_from).and_return(true)
expect(project.repository).to receive(:commit).with(sha_to).and_return(true)
expect(range).to be_valid_commits
end
it 'is true when both `sha_from` and `sha_to` are valid' do
expect(range).to be_valid_commits
end
end
context 'without a valid repo' do
before do
expect(project).to receive(:valid_repo?).and_return(false)
range.project = project
end
context 'without a valid repo' do
before do
expect(project).to receive(:valid_repo?).and_return(false)
end
it 'returns false' do
expect(range).not_to be_valid_commits
end
it 'returns false' do
expect(range).not_to be_valid_commits
end
end
end
......
......@@ -24,6 +24,17 @@ describe Commit do
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
it "returns no_commit_message when safe_message is blank" do
allow(commit).to receive(:safe_message).and_return('')
......@@ -77,14 +88,10 @@ eos
let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do
allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid}")
expect(commit.closes_issues).to eq([issue])
end
it 'does not detect issues from other projects' do
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}")
expect(commit.closes_issues).to be_empty
allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
expect(commit.closes_issues).to include(issue)
expect(commit.closes_issues).to include(other_issue)
end
end
......@@ -100,4 +107,15 @@ eos
# Include the subject in the repository stub.
let(:extra_commits) { [subject] }
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
......@@ -71,5 +71,11 @@ describe ProjectHook do
expect { @project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
end
it "handles SSL exceptions" do
expect(WebHook).to receive(:post).and_raise(OpenSSL::SSL::SSLError.new('SSL error'))
expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
end
end
end
......@@ -4,6 +4,7 @@ describe Repository do
include RepoHelpers
let(:repository) { create(:project).repository }
let(:user) { create(:user) }
describe :branch_names_contains do
subject { repository.branch_names_contains(sample_commit.id) }
......@@ -99,5 +100,104 @@ describe Repository do
it { expect(subject.startline).to eq(186) }
it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
end
end
describe :add_branch do
context 'when pre hooks were successful' do
it 'should run without errors' do
hook = double(trigger: true)
expect(Gitlab::Git::Hook).to receive(:new).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
......@@ -91,7 +91,23 @@ describe User do
end
describe 'validations' do
it { is_expected.to validate_presence_of(:username) }
describe 'username' do
it 'validates presence' do
expect(subject).to validate_presence_of(:username)
end
it 'rejects blacklisted names' do
user = build(:user, username: 'dashboard')
expect(user).not_to be_valid
expect(user.errors.values).to eq [['dashboard is a reserved name']]
end
it 'validates uniqueness' do
expect(subject).to validate_uniqueness_of(:username)
end
end
it { is_expected.to validate_presence_of(:projects_limit) }
it { is_expected.to validate_numericality_of(:projects_limit) }
it { is_expected.to allow_value(0).for(:projects_limit) }
......
......@@ -47,7 +47,7 @@ describe API::API, api: true do
name: 'Foo',
color: '#FFAA'
expect(response.status).to eq(400)
expect(json_response['message']['color']).to eq(['is invalid'])
expect(json_response['message']['color']).to eq(['must be a valid color code'])
end
it 'should return 400 for too long color code' do
......@@ -55,7 +55,7 @@ describe API::API, api: true do
name: 'Foo',
color: '#FFAAFFFF'
expect(response.status).to eq(400)
expect(json_response['message']['color']).to eq(['is invalid'])
expect(json_response['message']['color']).to eq(['must be a valid color code'])
end
it 'should return 400 for invalid name' do
......@@ -151,12 +151,12 @@ describe API::API, api: true do
expect(json_response['message']['title']).to eq(['is invalid'])
end
it 'should return 400 for invalid name' do
it 'should return 400 when color code is too short' do
put api("/projects/#{project.id}/labels", user),
name: 'label1',
color: '#FF'
expect(response.status).to eq(400)
expect(json_response['message']['color']).to eq(['is invalid'])
expect(json_response['message']['color']).to eq(['must be a valid color code'])
end
it 'should return 400 for too long color code' do
......@@ -164,7 +164,7 @@ describe API::API, api: true do
name: 'Foo',
color: '#FFAAFFFF'
expect(response.status).to eq(400)
expect(json_response['message']['color']).to eq(['is invalid'])
expect(json_response['message']['color']).to eq(['must be a valid color code'])
end
end
end
......@@ -742,6 +742,18 @@ describe API::API, api: true do
end
end
it 'should update visibility_level from public to private' do
project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
project_param = { public: false }
put api("/projects/#{project3.id}", user), project_param
expect(response.status).to eq(200)
project_param.each_pair do |k, v|
expect(json_response[k.to_s]).to eq(v)
end
expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
it 'should not update name to existing name' do
project_param = { name: project3.name }
put api("/projects/#{project.id}", user), project_param
......
......@@ -153,7 +153,7 @@ describe API::API, api: true do
expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']).
to eq([Gitlab::Regex.send(:namespace_regex_message)])
to eq([Gitlab::Regex.namespace_regex_message])
end
it "shouldn't available for non admin users" do
......@@ -296,7 +296,7 @@ describe API::API, api: true do
expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']).
to eq([Gitlab::Regex.send(:namespace_regex_message)])
to eq([Gitlab::Regex.namespace_regex_message])
end
context "with existing user" do
......
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
project.team << [issue.author, :master]
project.team << [issue.assignee, :master]
project.team << [note.author, :master]
create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@subscribed_participant cc this guy')
end
describe :new_note do
......@@ -60,6 +61,7 @@ describe NotificationService do
should_email(note.noteable.assignee)
should_email(@u_mentioned)
should_email(@subscriber)
should_email(@subscribed_participant)
should_not_email(note.author)
should_not_email(@u_participating)
should_not_email(@u_disabled)
......@@ -381,18 +383,19 @@ describe NotificationService do
def add_users_with_subscription(project, issuable)
@subscriber = create :user
@unsubscriber = create :user
@subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING)
project.team << [@subscribed_participant, :master]
project.team << [@subscriber, :master]
project.team << [@unsubscriber, :master]
issuable.subscriptions.create(user: @subscriber, subscribed: true)
issuable.subscriptions.create(user: @subscribed_participant, subscribed: true)
issuable.subscriptions.create(user: @unsubscriber, subscribed: false)
end
def sent_to_user?(user)
ActionMailer::Base.deliveries.any? do |message|
message.to.include?(user.email)
end
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
end
def should_email(user)
......
......@@ -35,11 +35,24 @@ module FilterSpecHelper
pipeline.call(body)
end
def reference_pipeline_result(body, contexts = {})
def reference_pipeline(contexts = {})
contexts.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class, Gitlab::Markdown::ReferenceGathererFilter], contexts)
pipeline.call(body)
filters = [
Gitlab::Markdown::AutolinkFilter,
described_class,
Gitlab::Markdown::ReferenceGathererFilter
]
HTML::Pipeline.new(filters, contexts)
end
def reference_pipeline_result(body, contexts = {})
reference_pipeline(contexts).call(body)
end
def reference_filter(html, contexts = {})
reference_pipeline(contexts).to_document(html)
end
# Modify a String reference to make it invalid
......
......@@ -93,6 +93,10 @@ class MarkdownFeature
end
end
def urls
Gitlab::Application.routes.url_helpers
end
def raw_markdown
markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
ERB.new(markdown).result(binding)
......
......@@ -71,7 +71,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
expect(actual).to have_selector('a.gfm.gfm-project_member', count: 4)
end
end
......@@ -80,7 +80,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
expect(actual).to have_selector('a.gfm.gfm-issue', count: 6)
end
end
......@@ -89,7 +89,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 6)
expect(actual).to have_selector('em a.gfm-merge_request')
end
end
......@@ -99,7 +99,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
expect(actual).to have_selector('a.gfm.gfm-snippet', count: 5)
end
end
......@@ -108,7 +108,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 5)
end
end
......@@ -117,7 +117,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
expect(actual).to have_selector('a.gfm.gfm-commit', count: 5)
end
end
......@@ -126,7 +126,7 @@ module MarkdownMatchers
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
expect(actual).to have_selector('a.gfm.gfm-label', count: 4)
end
end
......
......@@ -10,12 +10,12 @@ def common_mentionable_setup
let(:mentioned_issue) { create(:issue, project: project) }
let!(:mentioned_mr) { create(:merge_request, :simple, source_project: project) }
let(:mentioned_commit) { project.commit }
let(:mentioned_commit) { project.commit("HEAD~1") }
let(:ext_proj) { create(:project, :public) }
let(:ext_issue) { create(:issue, project: ext_proj) }
let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) }
let(:ext_commit) { ext_proj.commit }
let(:ext_commit) { ext_proj.commit("HEAD~2") }
# Override to add known commits to the repository stub.
let(:extra_commits) { [] }
......@@ -45,14 +45,11 @@ def common_mentionable_setup
before do
# Wire the project's repository to return the mentioned commit, and +nil+
# for any unrecognized commits.
commitmap = {
mentioned_commit.id => mentioned_commit
}
extra_commits.each { |c| commitmap[c.short_id] = c }
allow(Project).to receive(:find).and_call_original
allow(Project).to receive(:find).with(project.id.to_s).and_return(project)
allow(project.repository).to receive(:commit) { |sha| commitmap[sha] }
allow_any_instance_of(::Repository).to receive(:commit).and_call_original
allow_any_instance_of(::Repository).to receive(:commit).with(mentioned_commit.short_id).and_return(mentioned_commit)
extra_commits.each do |commit|
allow_any_instance_of(::Repository).to receive(:commit).with(commit.short_id).and_return(commit)
end
set_mentionable_text.call(ref_string)
end
......
......@@ -21,7 +21,8 @@ module TestEnv
# We currently only need a subset of the branches
FORKED_BRANCH_SHA = {
'add-submodule-version-bump' => '3f547c08',
'master' => '5937ac0'
'master' => '5937ac0',
'remove-submodule' => '2a33e0c0'
}
# Test environment
......
module WaitForAjax
def wait_for_ajax
Timeout.timeout(Capybara.default_wait_time) do
loop until finished_all_ajax_requests?
end
end
def finished_all_ajax_requests?
page.evaluate_script('jQuery.active').zero?
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