diff options
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) @@ -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 |