summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG4
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/awards_handler.coffee7
-rw-r--r--app/assets/javascripts/flash.js.coffee16
-rw-r--r--app/assets/javascripts/notes.js.coffee7
-rw-r--r--app/assets/stylesheets/framework/flash.scss10
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss7
-rw-r--r--app/controllers/concerns/global_milestones.rb2
-rw-r--r--app/controllers/groups/milestones_controller.rb2
-rw-r--r--app/controllers/projects/notes_controller.rb29
-rw-r--r--app/finders/milestones_finder.rb2
-rw-r--r--app/helpers/milestones_helper.rb2
-rw-r--r--app/models/global_milestone.rb31
-rw-r--r--app/models/note.rb30
-rw-r--r--app/models/repository.rb54
-rw-r--r--app/services/create_branch_service.rb5
-rw-r--r--app/services/delete_branch_service.rb4
-rw-r--r--app/services/files/base_service.rb2
-rw-r--r--app/services/git_hooks_service.rb28
-rw-r--r--app/services/notes/create_service.rb13
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml11
-rw-r--r--app/views/projects/_md_preview.html.haml17
-rw-r--r--app/views/projects/milestones/_milestone.html.haml6
-rw-r--r--app/views/shared/_milestone_expired.html.haml5
-rw-r--r--config/routes.rb2
-rw-r--r--features/project/commits/diff_comments.feature6
-rw-r--r--features/project/issues/award_emoji.feature6
-rw-r--r--features/steps/project/issues/award_emoji.rb31
-rw-r--r--features/steps/shared/diff_note.rb17
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb27
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb5
-rw-r--r--spec/models/repository_spec.rb100
-rw-r--r--spec/services/git_hooks_service_spec.rb53
34 files changed, 443 insertions, 101 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 99c5fdd4d07..bd795138e86 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -7,13 +7,17 @@ v 8.3.0 (unreleased)
- 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
+ - Run custom Git hooks when branch is created or deleted.
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
+ - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
v 8.2.2
- Fix 404 in redirection after removing a project (Stan Hu)
diff --git a/Gemfile b/Gemfile
index 67640bb9ae0..860e6bee47d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -171,6 +171,7 @@ gem "underscore-rails", "~> 1.4.4"
# Sanitize user input
gem "sanitize", '~> 2.0'
+gem 'babosa', '~> 1.0.2'
# Protect against bruteforcing
gem "rack-attack", '~> 4.3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index cd1855758b2..be3e39d8e84 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -73,6 +73,7 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
+ babosa (1.0.2)
bcrypt (3.1.10)
benchmark-ips (2.3.0)
better_errors (1.0.1)
@@ -823,6 +824,7 @@ DEPENDENCIES
asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4)
awesome_print (~> 1.2.0)
+ babosa (~> 1.0.2)
benchmark-ips
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 09b48fe5572..96fd8f8773e 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -88,4 +88,9 @@ class @AwardsHandler
callback.call()
findEmojiIcon: (emoji) ->
- $(".icon[data-emoji='" + emoji + "']") \ No newline at end of file
+ $(".icon[data-emoji='" + emoji + "']")
+
+ scrollToAwards: ->
+ $('body, html').animate({
+ scrollTop: $('.awards').offset().top - 80
+ }, 200)
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index b39ab0c4475..9b59d4e57f7 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,12 +1,16 @@
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')
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 7de7632201d..dd6cbcfc70b 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -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
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 82eb50ad4be..1b723021d76 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -15,3 +15,13 @@
@extend .alert-danger;
}
}
+
+.flash-pinned {
+ position: fixed;
+ top: 80px;
+ width: 80%;
+}
+
+.flash-raised {
+ z-index: 1000;
+}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index cc660529cb4..2b044786738 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -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 {
diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb
index b428249acd3..3e4c0e63601 100644
--- a/app/controllers/concerns/global_milestones.rb
+++ b/app/controllers/concerns/global_milestones.rb
@@ -2,8 +2,10 @@ module GlobalMilestones
extend ActiveSupport::Concern
def milestones
+ epoch = DateTime.parse('1970-01-01')
@milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones)
+ @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
end
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 10233222ee1..0c2a350bc39 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def milestone_path(title)
- group_milestone_path(@group, title.parameterize, title: title)
+ group_milestone_path(@group, title.to_slug.to_s, title: title)
end
def projects
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 5ac18446aa7..88b949a27ab 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -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!
diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb
index b704e878903..630c73c2a94 100644
--- a/app/finders/milestones_finder.rb
+++ b/app/finders/milestones_finder.rb
@@ -1,7 +1,7 @@
class MilestonesFinder
def execute(projects, params)
milestones = Milestone.of_projects(projects)
- milestones = milestones.order("due_date ASC")
+ milestones = milestones.reorder("due_date ASC")
case params[:state]
when 'closed' then milestones.closed
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index ad43892b639..a42cbcff182 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -28,7 +28,9 @@ module MilestonesHelper
Milestone.where(project_id: @projects)
end.active
+ epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones)
+ grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
grouped_milestones.unshift(Milestone::None)
grouped_milestones.unshift(Milestone::Any)
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 1321ccd963f..8bfc79d88f8 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -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
diff --git a/app/models/note.rb b/app/models/note.rb
index 1c6345e735c..239a0f77f8e 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -39,8 +39,11 @@ class Note < ActiveRecord::Base
delegate :name, to: :project, prefix: true
delegate :name, :email, to: :author, prefix: true
+ before_validation :set_award!
+
validates :note, :project, presence: true
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
+ validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -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
diff --git a/app/models/repository.rb b/app/models/repository.rb
index d247b0f5012..c304955b0b3 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1,7 +1,6 @@
require 'securerandom'
class Repository
- class PreReceiveError < StandardError; end
class CommitError < StandardError; end
include Gitlab::ShellAdapter
@@ -108,10 +107,19 @@ class Repository
tags.find { |tag| tag.name == name }
end
- def add_branch(branch_name, ref)
- expire_branches_cache
+ def add_branch(user, branch_name, target)
+ oldrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+ target = commit(target).try(:id)
+
+ return false unless target
+
+ GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
+ rugged.branches.create(branch_name, target)
+ end
- gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
+ expire_branches_cache
+ find_branch(branch_name)
end
def add_tag(tag_name, ref, message = nil)
@@ -120,10 +128,20 @@ class Repository
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
- def rm_branch(branch_name)
+ def rm_branch(user, branch_name)
expire_branches_cache
- gitlab_shell.rm_branch(path_with_namespace, branch_name)
+ branch = find_branch(branch_name)
+ oldrev = branch.try(:target)
+ newrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+
+ GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
+ rugged.branches.delete(branch_name)
+ end
+
+ expire_branches_cache
+ true
end
def rm_tag(tag_name)
@@ -550,7 +568,6 @@ class Repository
def commit_with_hooks(current_user, branch)
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
- gl_id = Gitlab::ShellEnv.gl_id(current_user)
was_empty = empty?
# Create temporary ref
@@ -569,15 +586,7 @@ class Repository
raise CommitError.new('Failed to create commit')
end
- # Run GitLab pre-receive hook
- pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
- pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
-
- # Run GitLab update hook
- update_hook = Gitlab::Git::Hook.new('update', path_to_repo)
- update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref)
-
- if pre_receive_hook_status && update_hook_status
+ GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
if was_empty
# Create branch
rugged.references.create(ref, newrev)
@@ -592,16 +601,11 @@ class Repository
raise CommitError.new('Commit was rejected because branch received new push')
end
end
-
- # Run GitLab post receive hook
- post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
- post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
- else
- # Remove tmp ref and return error to user
- rugged.references.delete(tmp_ref)
-
- raise PreReceiveError.new('Commit was rejected by git hook')
end
+ rescue GitHooksService::PreReceiveError
+ # Remove tmp ref and return error to user
+ rugged.references.delete(tmp_ref)
+ raise
end
private
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index cf7ae4345f3..de18f3bc556 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -13,8 +13,7 @@ class CreateBranchService < BaseService
return error('Branch already exists')
end
- repository.add_branch(branch_name, ref)
- new_branch = repository.find_branch(branch_name)
+ new_branch = repository.add_branch(current_user, branch_name, ref)
if new_branch
push_data = build_push_data(project, current_user, new_branch)
@@ -27,6 +26,8 @@ class CreateBranchService < BaseService
else
error('Invalid reference name')
end
+ rescue GitHooksService::PreReceiveError
+ error('Branch creation was rejected by Git hook')
end
def success(branch)
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index b19b112a0c4..22bf9dd935e 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
return error('You dont have push access to repo', 405)
end
- if repository.rm_branch(branch_name)
+ if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch)
EventCreateService.new.push(project, current_user, push_data)
@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
else
error('Failed to remove branch')
end
+ rescue GitHooksService::PreReceiveError
+ error('Branch deletion was rejected by Git hook')
end
def error(message, return_code = 400)
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 008833eed80..f50aaf2eb52 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -26,7 +26,7 @@ module Files
else
error("Something went wrong. Your changes were not committed")
end
- rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
+ rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message)
end
diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb
new file mode 100644
index 00000000000..8f5c3393dfc
--- /dev/null
+++ b/app/services/git_hooks_service.rb
@@ -0,0 +1,28 @@
+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
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index dbff58dfb9c..a8486e6a5a1 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,11 +5,6 @@ module Notes
note.author = current_user
note.system = false
- if 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
diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 55080d6b3fe..7c882a32702 100644
--- a/app/views/dashboard/milestones/_milestone.html.haml
+++ b/app/views/dashboard/milestones/_milestone.html.haml
@@ -16,7 +16,10 @@
= milestone_progress_bar(milestone)
.row
.col-sm-6
- - milestone.milestones.each do |milestone|
- = link_to milestone_path(milestone) do
- %span.label.label-gray
- = milestone.project.name_with_namespace
+ .expiration
+ = render 'shared/milestone_expired', milestone: milestone
+ .projects
+ - milestone.milestones.each do |milestone|
+ = link_to milestone_path(milestone) do
+ %span.label.label-gray
+ = milestone.project.name_with_namespace
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 8218cf11201..54c818baaf4 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -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))}
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 334172b976f..d6a44c9f0a1 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -18,11 +18,7 @@
.row
.col-sm-6
- - if milestone.expired? and not milestone.closed?
- %span.cred (Expired)
- - if milestone.expires_at
- %span
- = milestone.expires_at
+ = render 'shared/milestone_expired', milestone: milestone
.col-sm-6
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
diff --git a/app/views/shared/_milestone_expired.html.haml b/app/views/shared/_milestone_expired.html.haml
new file mode 100644
index 00000000000..b8eef15fbec
--- /dev/null
+++ b/app/views/shared/_milestone_expired.html.haml
@@ -0,0 +1,5 @@
+- if milestone.expired? and not milestone.closed?
+ %span.cred (Expired)
+- if milestone.expires_at
+ %span
+ = milestone.expires_at
diff --git a/config/routes.rb b/config/routes.rb
index 5c114452a3f..fdd387fd184 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -368,7 +368,7 @@ Rails.application.routes.draw do
end
resource :avatar, only: [:destroy]
- resources :milestones, only: [:index, :show, :update, :new, :create]
+ resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
end
end
diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index 4a2b870e082..d6e0c84537e 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -14,6 +14,12 @@ Feature: Project Commits Diff Comments
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
Then I should see a temporary diff comment form
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index a9bc8ffb9bb..2609f129d07 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -11,4 +11,8 @@ Feature: Award Emoji
And I click to emoji in the picker
Then I have award added
And I can remove it by clicking to icon
- \ No newline at end of file
+
+ @javascript
+ Scenario: I add award emoji using regular comment
+ Given I leave comment with a single emoji
+ Then I have award added
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 8f7a45dec0e..325eaf2ea6a 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -9,33 +9,40 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'I click to emoji-picker' do
- page.within ".awards-controls" do
- page.find(".add-award").click
+ page.within '.awards-controls' do
+ page.find('.add-award').click
end
end
step 'I click to emoji in the picker' do
- page.within ".awards-menu" do
- page.first("img").click
+ page.within '.awards-menu' do
+ page.first('img').click
end
end
step 'I can remove it by clicking to icon' do
- page.within ".awards" do
- page.first(".award").click
- expect(page).to_not have_selector ".award"
+ page.within '.awards' do
+ page.first('.award').click
+ expect(page).to_not have_selector '.award'
end
end
step 'I have award added' do
- page.within ".awards" do
- expect(page).to have_selector ".award"
- expect(page.find(".award .counter")).to have_content "1"
+ page.within '.awards' do
+ expect(page).to have_selector '.award'
+ expect(page.find('.award .counter')).to have_content '1'
end
end
step 'project "Shop" has issue "Bugfix"' do
- @project = Project.find_by(name: "Shop")
- @issue = create(:issue, title: "Bugfix", project: project)
+ @project = Project.find_by(name: 'Shop')
+ @issue = create(:issue, title: 'Bugfix', project: project)
+ end
+
+ step 'I leave comment with a single emoji' do
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: ':smile:'
+ click_button 'Add Comment'
+ end
end
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index 72621911a37..dd466cde28d 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -87,6 +87,17 @@ module SharedDiffNote
end
end
+ step 'I write a diff comment like ":smile:"' do
+ page.within(diff_file_selector) do
+ click_diff_line(sample_commit.line_code)
+
+ page.within("form[rel$='#{sample_commit.line_code}']") do
+ fill_in 'note[note]', with: ':smile:'
+ click_button('Add Comment')
+ end
+ end
+ end
+
step 'I submit the diff comment' do
page.within(diff_file_selector) do
click_button("Add Comment")
@@ -197,6 +208,12 @@ module SharedDiffNote
end
end
+ step 'I should see a diff comment with an emoji image' do
+ page.within("#{diff_file_selector} .note") do
+ expect(page).to have_xpath("//img[@alt=':smile:']")
+ end
+ end
+
step 'I click side-by-side diff button' do
find('#parallel-diff-btn').trigger('click')
end
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
new file mode 100644
index 00000000000..eb0c6ac6d80
--- /dev/null
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Groups::MilestonesController do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:project2) { create(:empty_project, group: group) }
+ let(:user) { create(:user) }
+ let(:title) { '肯定不是中文的问题' }
+
+ before do
+ sign_in(user)
+ group.add_owner(user)
+ project.team << [user, :master]
+ controller.instance_variable_set(:@group, group)
+ end
+
+ describe "#create" do
+ it "should create group milestone with Chinese title" do
+ post :create,
+ group_id: group.id,
+ milestone: { project_ids: [project.id, project2.id], title: title }
+
+ expect(response).to redirect_to(group_milestone_path(group, title.to_slug.to_s, title: title))
+ expect(Milestone.where(title: title).count).to eq(2)
+ end
+ end
+end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 8127efabe6e..d173bb350f1 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -5,7 +5,7 @@ describe Projects::MilestonesController do
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
let(:issue) { create(:issue, project: project, milestone: milestone) }
- let(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
before do
sign_in(user)
@@ -15,10 +15,9 @@ describe Projects::MilestonesController do
describe "#destroy" do
it "should remove milestone" do
- merge_request.reload
expect(issue.milestone_id).to eq(milestone.id)
- delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js
+ delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid, format: :js
expect(response).to be_success
expect(Event.first.action).to eq(Event::DESTROYED)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 319fa0a7c8d..fa261e64c35 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -4,6 +4,7 @@ describe Repository do
include RepoHelpers
let(:repository) { create(:project).repository }
+ let(:user) { create(:user) }
describe :branch_names_contains do
subject { repository.branch_names_contains(sample_commit.id) }
@@ -99,5 +100,104 @@ describe Repository do
it { expect(subject.startline).to eq(186) }
it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
end
+
end
+
+ describe :add_branch do
+ context 'when pre hooks were successful' do
+ it 'should run without errors' do
+ hook = double(trigger: true)
+ expect(Gitlab::Git::Hook).to receive(:new).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
diff --git a/spec/services/git_hooks_service_spec.rb b/spec/services/git_hooks_service_spec.rb
new file mode 100644
index 00000000000..7e018d3c9fe
--- /dev/null
+++ b/spec/services/git_hooks_service_spec.rb
@@ -0,0 +1,53 @@
+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