Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRegis <boudinot.regis@yahoo.com>2017-01-17 21:27:06 +0300
committerRegis <boudinot.regis@yahoo.com>2017-01-17 21:27:06 +0300
commit28e77da976f1c1cd533099da1c4b447413b7e3af (patch)
tree35996788575ee8d1abdb0c2d447a1a06911e0207
parent4fb4f61541a624608d6978208720d1db3970cfcd (diff)
parente2f0b83061df3b19b683b67d142acea65d5df0fd (diff)
Merge branch 'master' into ui_pipelines_mini_graph
-rw-r--r--.rubocop_todo.yml4
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock32
-rw-r--r--app/assets/javascripts/gl_dropdown.js2
-rw-r--r--app/assets/javascripts/notes.js12
-rw-r--r--app/assets/javascripts/search.js6
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss6
-rw-r--r--app/controllers/projects/merge_requests_controller.rb10
-rw-r--r--app/controllers/projects/notes_controller.rb3
-rw-r--r--app/helpers/diff_helper.rb6
-rw-r--r--app/helpers/merge_requests_helper.rb8
-rw-r--r--app/models/ci/build.rb4
-rw-r--r--app/models/commit.rb8
-rw-r--r--app/models/concerns/milestoneish.rb7
-rw-r--r--app/models/merge_request.rb14
-rw-r--r--app/models/merge_request_diff.rb20
-rw-r--r--app/services/merge_requests/update_service.rb15
-rw-r--r--app/services/notes/create_service.rb5
-rw-r--r--app/services/notes/slash_commands_service.rb4
-rw-r--r--app/services/slash_commands/interpret_service.rb16
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb41
-rw-r--r--app/views/projects/builds/_sidebar.html.haml4
-rw-r--r--app/views/projects/commit/_change.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml3
-rw-r--r--app/views/projects/merge_requests/show/_diffs.html.haml10
-rw-r--r--app/views/projects/notes/_form.html.haml1
-rw-r--r--app/views/shared/milestones/_milestone.html.haml2
-rw-r--r--changelogs/unreleased/24915_merge_slash_command.yml4
-rw-r--r--changelogs/unreleased/26616-fix-search-group-project-filters.yml4
-rw-r--r--changelogs/unreleased/fix-keep-artifacts-button-visibility.yml4
-rw-r--r--changelogs/unreleased/issue_25017.yml4
-rw-r--r--changelogs/unreleased/mr-tabs-alignment-sidebar-open.yml4
-rw-r--r--changelogs/unreleased/reduce-queries-milestone-index.yml4
-rw-r--r--changelogs/unreleased/refresh-authorizations-tighter-lease.yml4
-rw-r--r--changelogs/unreleased/switch-to-sassc.yml4
-rw-r--r--config/routes/project.rb1
-rw-r--r--doc/user/project/slash_commands.md1
-rw-r--r--lib/gitlab/git/blame.rb2
-rw-r--r--lib/gitlab/git/blob.rb3
-rw-r--r--lib/gitlab/git/repository.rb2
-rw-r--r--lib/tasks/dev.rake5
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb68
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb48
-rw-r--r--spec/features/boards/sidebar_spec.rb4
-rw-r--r--spec/features/merge_requests/diffs_spec.rb14
-rw-r--r--spec/features/merge_requests/user_uses_slash_commands_spec.rb45
-rw-r--r--spec/features/projects/builds_spec.rb28
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb15
-rw-r--r--spec/javascripts/gl_dropdown_spec.js.es619
-rw-r--r--spec/models/ci/build_spec.rb18
-rw-r--r--spec/models/merge_request_diff_spec.rb26
-rw-r--r--spec/models/merge_request_spec.rb102
-rw-r--r--spec/services/merge_requests/update_service_spec.rb93
-rw-r--r--spec/services/notes/create_service_spec.rb11
-rw-r--r--spec/services/slash_commands/interpret_service_spec.rb69
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb40
57 files changed, 779 insertions, 121 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 6d4d7170fe8..d581610162f 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -62,7 +62,7 @@ Lint/UnusedMethodArgument:
# Offense count: 93
# Configuration parameters: CountComments.
Metrics/BlockLength:
- Max: 288
+ Enabled: false
# Offense count: 3
# Cop supports --auto-correct.
@@ -125,7 +125,7 @@ RSpec/MessageSpies:
# Offense count: 3036
RSpec/MultipleExpectations:
- Max: 37
+ Enabled: false
# Offense count: 2133
RSpec/NamedSubject:
diff --git a/Gemfile b/Gemfile
index 07ff500dfea..9bf37891f74 100644
--- a/Gemfile
+++ b/Gemfile
@@ -219,7 +219,7 @@ gem 'oj', '~> 2.17.4'
gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6'
-gem 'sass-rails', '~> 5.0.6'
+gem 'sassc-rails', '~> 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2'
gem 'gitlab-turbolinks-classic', '~> 2.5', '>= 2.5.6'
@@ -255,7 +255,6 @@ group :development do
gem 'brakeman', '~> 3.3.0', require: false
gem 'letter_opener_web', '~> 1.3.0'
- gem 'rerun', '~> 0.11.0'
gem 'bullet', '~> 5.2.0', require: false
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
gem 'web-console', '~> 2.0'
@@ -286,7 +285,7 @@ group :development, :test do
gem 'minitest', '~> 5.7.0'
# Generate Fake data
- gem 'ffaker', '~> 2.0.0'
+ gem 'ffaker', '~> 2.4'
gem 'capybara', '~> 2.6.2'
gem 'capybara-screenshot', '~> 1.0.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index e2d7f94e571..4d025683b20 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -198,7 +198,7 @@ GEM
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
- ffaker (2.0.0)
+ ffaker (2.4.0)
ffi (1.9.10)
flay (2.6.1)
ruby_parser (~> 3.0)
@@ -407,9 +407,6 @@ GEM
xml-simple
licensee (8.0.0)
rugged (>= 0.24b)
- listen (3.0.5)
- rb-fsevent (>= 0.9.3)
- rb-inotify (>= 0.9)
little-plugger (1.1.4)
logging (2.1.0)
little-plugger (~> 1.1)
@@ -580,9 +577,6 @@ GEM
rainbow (2.1.0)
raindrops (0.17.0)
rake (10.5.0)
- rb-fsevent (0.9.6)
- rb-inotify (0.9.5)
- ffi (>= 0.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rdoc (4.2.2)
@@ -611,8 +605,6 @@ GEM
redis-store (1.2.0)
redis (>= 2.2)
request_store (1.3.1)
- rerun (0.11.0)
- listen (~> 3.0)
responders (2.3.0)
railties (>= 4.2.0, < 5.1)
rest-client (2.0.0)
@@ -675,12 +667,17 @@ GEM
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.4.22)
- sass-rails (5.0.6)
- railties (>= 4.0.0, < 6)
- sass (~> 3.1)
- sprockets (>= 2.8, < 4.0)
- sprockets-rails (>= 2.0, < 4.0)
- tilt (>= 1.1, < 3)
+ sassc (1.11.1)
+ bundler
+ ffi (~> 1.9.6)
+ sass (>= 3.3.0)
+ sassc-rails (1.3.0)
+ railties (>= 4.0.0)
+ sass
+ sassc (~> 1.9)
+ sprockets (> 2.11)
+ sprockets-rails
+ tilt
sawyer (0.8.1)
addressable (>= 2.3.5, < 2.6)
faraday (~> 0.8, < 1.0)
@@ -877,7 +874,7 @@ DEPENDENCIES
email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.7.0)
- ffaker (~> 2.0.0)
+ ffaker (~> 2.4)
flay (~> 2.6.1)
fog-aws (~> 0.9)
fog-core (~> 1.40)
@@ -968,7 +965,6 @@ DEPENDENCIES
redis-namespace (~> 1.5.2)
redis-rails (~> 5.0.1)
request_store (~> 1.3)
- rerun (~> 0.11.0)
responders (~> 2.0)
rouge (~> 2.0)
rqrcode-rails3 (~> 0.1.7)
@@ -980,7 +976,7 @@ DEPENDENCIES
ruby-prof (~> 0.16.2)
rugged (~> 0.24.0)
sanitize (~> 2.0)
- sass-rails (~> 5.0.6)
+ sassc-rails (~> 1.3.0)
scss_lint (~> 0.47.0)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index bb516b3d2df..00859728c30 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -476,7 +476,7 @@
this.removeArrayKeyEvent();
$input = this.dropdown.find(".dropdown-input-field");
if (this.options.filterable) {
- $input.blur().val("");
+ $input.blur();
}
if (this.dropdown.find(".dropdown-toggle-page").length) {
$('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 603db88567d..fac21f8cd32 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -3,6 +3,7 @@
/* global GLForm */
/* global Autosave */
/* global ResolveService */
+/* global mrRefreshWidgetUrl */
/*= require autosave */
/*= require autosize */
@@ -244,6 +245,16 @@
};
+ Notes.prototype.handleCreateChanges = function(note) {
+ if (typeof note === 'undefined') {
+ return;
+ }
+
+ if (note.commands_changes && note.commands_changes.indexOf('merge') !== -1) {
+ $.get(mrRefreshWidgetUrl);
+ }
+ };
+
/*
Render note in main comments area.
@@ -429,6 +440,7 @@
*/
Notes.prototype.addNote = function(xhr, note, status) {
+ this.handleCreateChanges(note);
return this.renderNote(note);
};
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index 4b6ebadeac7..5945cab4cf0 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -12,6 +12,9 @@
selectable: true,
filterable: true,
fieldName: 'group_id',
+ search: {
+ fields: ['name']
+ },
data: function(term, callback) {
return Api.groups(term, {}, function(data) {
data.unshift({
@@ -40,6 +43,9 @@
selectable: true,
filterable: true,
fieldName: 'project_id',
+ search: {
+ fields: ['name']
+ },
data: function(term, callback) {
return Api.projects(term, 'id', function(data) {
data.unshift({
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 838f5442fff..f0b03710c79 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -236,9 +236,13 @@ header.header-sidebar-pinned {
@media (min-width: $screen-md-min) {
padding-right: $gutter_width;
- .merge-request-tabs-holder.affix {
+ &:not(.with-overlay) .merge-request-tabs-holder.affix {
right: $gutter_width;
}
+
+ &.with-overlay .merge-request-tabs-holder.affix {
+ right: $sidebar_collapsed_width;
+ }
}
&.with-overlay {
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index aaebd4efa00..9ac5bf4b9f8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -347,6 +347,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def merge_widget_refresh
+ if merge_request.in_progress_merge_commit_sha || merge_request.state == 'merged'
+ @status = :success
+ elsif merge_request.merge_when_build_succeeds
+ @status = :merge_when_build_succeeds
+ end
+
+ render 'merge'
+ end
+
def branch_from
# This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index b71509f2c9b..c5d93ce25bc 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -23,7 +23,8 @@ class Projects::NotesController < Projects::ApplicationController
end
def create
- @note = Notes::CreateService.new(project, current_user, note_params).execute
+ create_params = note_params.merge(merge_request_diff_head_sha: params[:merge_request_diff_head_sha])
+ @note = Notes::CreateService.new(project, current_user, create_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index c35d6611ab0..aed1d7c839f 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -165,4 +165,10 @@ module DiffHelper
link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class]
end
+
+ def render_overflow_warning?(diff_files)
+ diffs = @merge_request_diff.presence || diff_files
+
+ diffs.overflow?
+ end
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 20218775659..8c2c4e8833b 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -19,6 +19,14 @@ module MergeRequestsHelper
}
end
+ def mr_widget_refresh_url(mr)
+ if mr && mr.source_project
+ merge_widget_refresh_namespace_project_merge_request_url(mr.source_project.namespace, mr.source_project, mr)
+ else
+ ''
+ end
+ end
+
def mr_css_classes(mr)
classes = "merge-request"
classes << " closed" if mr.closed?
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 27042798741..48ffe40abc6 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -507,6 +507,10 @@ module Ci
end
end
+ def has_expiring_artifacts?
+ artifacts_expire_at.present?
+ end
+
def keep_artifacts!
self.update(artifacts_expire_at: nil)
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 0b924b063a4..3365f4ffdbf 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -318,6 +318,14 @@ class Commit
Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options)
end
+ def persisted?
+ true
+ end
+
+ def touch
+ # no-op but needs to be defined since #persisted? is defined
+ end
+
private
def commit_reference(from_project, referable_commit_id, full: false)
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index fcc8feddb39..e9450dd0c26 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -7,11 +7,14 @@ module Milestoneish
def total_items_count(user)
memoize_per_user(user, :total_items_count) do
- issues_count = count_issues_by_state(user).values.sum
- issues_count + merge_requests.size
+ total_issues_count(user) + merge_requests.size
end
end
+ def total_issues_count(user)
+ count_issues_by_state(user).values.sum
+ end
+
def complete?(user)
total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 70005a87f4b..10251302db8 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -898,10 +898,22 @@ class MergeRequest < ActiveRecord::Base
end
def has_commits?
- commits_count > 0
+ merge_request_diff && commits_count > 0
end
def has_no_commits?
!has_commits?
end
+
+ def mergeable_with_slash_command?(current_user, autocomplete_precheck: false, last_diff_sha: nil)
+ return false unless can_be_merged_by?(current_user)
+
+ return true if autocomplete_precheck
+
+ return false unless mergeable?(skip_ci_check: true)
+ return false if head_pipeline && !(head_pipeline.success? || head_pipeline.active?)
+ return false if last_diff_sha != diff_head_sha
+
+ true
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 64dd586c9e0..dadb81f9b6e 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -234,28 +234,28 @@ class MergeRequestDiff < ActiveRecord::Base
# and save it as array of hashes in st_diffs db field
def save_diffs
new_attributes = {}
- new_diffs = []
if commits.size.zero?
new_attributes[:state] = :empty
else
diff_collection = compare.diffs(Commit.max_diff_options)
-
- if diff_collection.overflow?
- # Set our state to 'overflow' to make the #empty? and #collected?
- # methods (generated by StateMachine) return false.
- new_attributes[:state] = :overflow
- end
-
- new_attributes[:real_size] = diff_collection.real_size
+ new_attributes[:real_size] = compare.diffs.real_size
if diff_collection.any?
new_diffs = dump_diffs(diff_collection)
new_attributes[:state] = :collected
end
+
+ new_attributes[:st_diffs] = new_diffs || []
+
+ # Set our state to 'overflow' to make the #empty? and #collected?
+ # methods (generated by StateMachine) return false.
+ #
+ # This attribution has to come at the end of the method so 'overflow'
+ # state does not get overridden by 'collected'.
+ new_attributes[:state] = :overflow if diff_collection.overflow?
end
- new_attributes[:st_diffs] = new_diffs
update_columns_serialized(new_attributes)
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index ad16ef8c70f..3cb9aae83f6 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -7,6 +7,8 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
+ merge_from_slash_command(merge_request) if params[:merge]
+
if merge_request.closed_without_fork?
params.except!(:target_branch, :force_remove_source_branch)
end
@@ -69,6 +71,19 @@ module MergeRequests
end
end
+ def merge_from_slash_command(merge_request)
+ last_diff_sha = params.delete(:merge)
+ return unless merge_request.mergeable_with_slash_command?(current_user, last_diff_sha: last_diff_sha)
+
+ merge_request.update(merge_error: nil)
+
+ if merge_request.head_pipeline && merge_request.head_pipeline.active?
+ MergeRequests::MergeWhenPipelineSucceedsService.new(project, current_user).execute(merge_request)
+ else
+ MergeWorker.perform_async(merge_request.id, current_user.id, {})
+ end
+ end
+
def reopen_service
MergeRequests::ReopenService
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 1beca9f4109..cdd765c85eb 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -1,6 +1,8 @@
module Notes
class CreateService < BaseService
def execute
+ merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
+
note = project.notes.new(params)
note.author = current_user
note.system = false
@@ -19,7 +21,8 @@ module Notes
slash_commands_service = SlashCommandsService.new(project, current_user)
if slash_commands_service.supported?(note)
- content, command_params = slash_commands_service.extract_commands(note)
+ options = { merge_request_diff_head_sha: merge_request_diff_head_sha }
+ content, command_params = slash_commands_service.extract_commands(note, options)
only_commands = content.empty?
diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb
index 2edbd39a9e7..aaea9717fc4 100644
--- a/app/services/notes/slash_commands_service.rb
+++ b/app/services/notes/slash_commands_service.rb
@@ -19,10 +19,10 @@ module Notes
self.class.supported?(note, current_user)
end
- def extract_commands(note)
+ def extract_commands(note, options = {})
return [note.note, {}] unless supported?(note)
- SlashCommands::InterpretService.new(project, current_user).
+ SlashCommands::InterpretService.new(project, current_user, options).
execute(note.note, note.noteable)
end
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index d75c5b1800e..d18844ac0e3 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -2,7 +2,7 @@ module SlashCommands
class InterpretService < BaseService
include Gitlab::SlashCommands::Dsl
- attr_reader :issuable
+ attr_reader :issuable, :options
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
@@ -13,7 +13,8 @@ module SlashCommands
opts = {
issuable: issuable,
current_user: current_user,
- project: project
+ project: project,
+ params: params
}
content, commands = extractor.extract_commands(content, opts)
@@ -58,6 +59,17 @@ module SlashCommands
@updates[:state_event] = 'reopen'
end
+ desc 'Merge (when build succeeds)'
+ condition do
+ last_diff_sha = params && params[:merge_request_diff_head_sha]
+ issuable.is_a?(MergeRequest) &&
+ issuable.persisted? &&
+ issuable.mergeable_with_slash_command?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
+ end
+ command :merge do
+ @updates[:merge] = params[:merge_request_diff_head_sha]
+ end
+
desc 'Change title'
params '<New title>'
condition do
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 21ec1bd9e65..2d211d5ebbe 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -26,8 +26,26 @@ module Users
user.reload
end
- # This method returns the updated User object.
def execute
+ lease_key = "refresh_authorized_projects:#{user.id}"
+ lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
+
+ until uuid = lease.try_obtain
+ # Keep trying until we obtain the lease. If we don't do so we may end up
+ # not updating the list of authorized projects properly. To prevent
+ # hammering Redis too much we'll wait for a bit between retries.
+ sleep(1)
+ end
+
+ begin
+ execute_without_lease
+ ensure
+ Gitlab::ExclusiveLease.cancel(lease_key, uuid)
+ end
+ end
+
+ # This method returns the updated User object.
+ def execute_without_lease
current = current_authorizations_per_project
fresh = fresh_access_levels_per_project
@@ -47,26 +65,7 @@ module Users
end
end
- update_with_lease(remove, add)
- end
-
- # Updates the list of authorizations using an exclusive lease.
- def update_with_lease(remove = [], add = [])
- lease_key = "refresh_authorized_projects:#{user.id}"
- lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
-
- until uuid = lease.try_obtain
- # Keep trying until we obtain the lease. If we don't do so we may end up
- # not updating the list of authorized projects properly. To prevent
- # hammering Redis too much we'll wait for a bit between retries.
- sleep(1)
- end
-
- begin
- update_authorizations(remove, add)
- ensure
- Gitlab::ExclusiveLease.cancel(lease_key, uuid)
- end
+ update_authorizations(remove, add)
end
# Updates the list of authorizations for the current user.
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 0b3adcbe121..37bf085130a 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -22,14 +22,14 @@
%p.build-detail-row
The artifacts were removed
#{time_ago_with_tooltip(@build.artifacts_expire_at)}
- - elsif @build.artifacts_expire_at
+ - elsif @build.has_expiring_artifacts?
%p.build-detail-row
The artifacts will be removed in
%span.js-artifacts-remove= @build.artifacts_expire_at
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
- - if @build.artifacts_expire_at
+ - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
= link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
Keep
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index 12e4280d344..990908211de 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -13,7 +13,7 @@
%a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title== #{label} this #{commit.change_type_title(current_user)}
.modal-body
- = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do
+ = form_tag [type.underscore, @project.namespace, @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do
.form-group.branch
= label_tag 'target_branch', target_label, class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index ab4a2dc36e5..58c20e225c6 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -18,8 +18,8 @@
= parallel_diff_btn
= render 'projects/diffs/stats', diff_files: diff_files
-- if diff_files.overflow?
- = render 'projects/diffs/warning', diff_files: diff_files
+- if render_overflow_warning?(diff_files)
+ = render 'projects/diffs/warning', diff_files: diffs
.files{ data: { can_create_note: can_create_note } }
- diff_files.each_with_index do |diff_file|
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 110dd11d1ce..0e14b9d4bf4 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -112,3 +112,6 @@
merge_request = new MergeRequest({
action: "#{controller.action_name}"
});
+
+ var mrRefreshWidgetUrl = "#{mr_widget_refresh_url(@merge_request)}";
+
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index 99c71e1454a..5f048d04b27 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,13 +1,5 @@
-- if @merge_request_diff.collected?
+- if @merge_request_diff.collected? || @merge_request_diff.overflow?
= render 'projects/merge_requests/show/versions'
= render "projects/diffs/diffs", diffs: @diffs
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
-- else
- .alert.alert-warning
- %h4
- Changes view for this comparison is extremely large.
- %p
- You can
- = link_to "download it", merge_request_path(@merge_request, format: :diff), class: "vlink"
- instead.
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 39731668a61..b561052e721 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -3,6 +3,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
+ = hidden_field_tag :merge_request_diff_head_sha, @note.noteable.try(:diff_head_sha)
= note_target_fields(@note)
= f.hidden_field :commit_id
= f.hidden_field :line_code
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 3200aacf542..9e6a76e1ddb 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -9,7 +9,7 @@
.pull-right.light #{milestone.percent_complete(current_user)}% complete
.row
.col-sm-6
- = link_to pluralize(milestone.issues_visible_to_user(current_user).size, 'Issue'), issues_path
+ = link_to pluralize(milestone.total_issues_count(current_user), 'Issue'), issues_path
&middot;
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
.col-sm-6= milestone_progress_bar(milestone)
diff --git a/changelogs/unreleased/24915_merge_slash_command.yml b/changelogs/unreleased/24915_merge_slash_command.yml
new file mode 100644
index 00000000000..eb8ced8ab01
--- /dev/null
+++ b/changelogs/unreleased/24915_merge_slash_command.yml
@@ -0,0 +1,4 @@
+---
+title: Support slash comand `/merge` for merging merge requests.
+merge_request: 7746
+author: Jarka Kadlecova
diff --git a/changelogs/unreleased/26616-fix-search-group-project-filters.yml b/changelogs/unreleased/26616-fix-search-group-project-filters.yml
new file mode 100644
index 00000000000..0fd0dbbfc24
--- /dev/null
+++ b/changelogs/unreleased/26616-fix-search-group-project-filters.yml
@@ -0,0 +1,4 @@
+---
+title: Fix search group/project filtering to show results
+merge_request:
+author:
diff --git a/changelogs/unreleased/fix-keep-artifacts-button-visibility.yml b/changelogs/unreleased/fix-keep-artifacts-button-visibility.yml
new file mode 100644
index 00000000000..3d8cf1c74a2
--- /dev/null
+++ b/changelogs/unreleased/fix-keep-artifacts-button-visibility.yml
@@ -0,0 +1,4 @@
+---
+title: Hide build artifacts keep button if operation is not allowed
+merge_request: 8501
+author:
diff --git a/changelogs/unreleased/issue_25017.yml b/changelogs/unreleased/issue_25017.yml
new file mode 100644
index 00000000000..09126ae81bc
--- /dev/null
+++ b/changelogs/unreleased/issue_25017.yml
@@ -0,0 +1,4 @@
+---
+title: Show 'too many changes' message for created merge requests when they are too large
+merge_request:
+author:
diff --git a/changelogs/unreleased/mr-tabs-alignment-sidebar-open.yml b/changelogs/unreleased/mr-tabs-alignment-sidebar-open.yml
new file mode 100644
index 00000000000..b8c7b78cf0d
--- /dev/null
+++ b/changelogs/unreleased/mr-tabs-alignment-sidebar-open.yml
@@ -0,0 +1,4 @@
+---
+title: Fixed merge request tabs dont move when opening collapsed sidebar
+merge_request:
+author:
diff --git a/changelogs/unreleased/reduce-queries-milestone-index.yml b/changelogs/unreleased/reduce-queries-milestone-index.yml
new file mode 100644
index 00000000000..a779b58c973
--- /dev/null
+++ b/changelogs/unreleased/reduce-queries-milestone-index.yml
@@ -0,0 +1,4 @@
+---
+title: Use cached values to compute total issues count in milestone index pages
+merge_request: 8518
+author:
diff --git a/changelogs/unreleased/refresh-authorizations-tighter-lease.yml b/changelogs/unreleased/refresh-authorizations-tighter-lease.yml
new file mode 100644
index 00000000000..ab42b2eb72d
--- /dev/null
+++ b/changelogs/unreleased/refresh-authorizations-tighter-lease.yml
@@ -0,0 +1,4 @@
+---
+title: Synchronize all project authorization refreshing work to prevent race conditions
+merge_request:
+author:
diff --git a/changelogs/unreleased/switch-to-sassc.yml b/changelogs/unreleased/switch-to-sassc.yml
new file mode 100644
index 00000000000..3e6c4baf6d9
--- /dev/null
+++ b/changelogs/unreleased/switch-to-sassc.yml
@@ -0,0 +1,4 @@
+---
+title: Switch to sassc-rails for faster stylesheet compilation
+merge_request: 8556
+author: Richard Macklin
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 26e2dc9e6e7..1fc6ed28c74 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -94,6 +94,7 @@ constraints(ProjectUrlConstrainer.new) do
get :pipelines
get :merge_check
post :merge
+ get :merge_widget_refresh
post :cancel_merge_when_build_succeeds
get :ci_status
get :ci_environments_status
diff --git a/doc/user/project/slash_commands.md b/doc/user/project/slash_commands.md
index 5f6a6c6503e..0a66e9a3e15 100644
--- a/doc/user/project/slash_commands.md
+++ b/doc/user/project/slash_commands.md
@@ -14,6 +14,7 @@ do.
|:---------------------------|:-------------|
| `/close` | Close the issue or merge request |
| `/reopen` | Reopen the issue or merge request |
+| `/merge` | Merge (when build succeeds) |
| `/title <New title>` | Change title |
| `/assign @username` | Assign |
| `/unassign` | Remove assignee |
diff --git a/lib/gitlab/git/blame.rb b/lib/gitlab/git/blame.rb
index 2913230e979..58193391926 100644
--- a/lib/gitlab/git/blame.rb
+++ b/lib/gitlab/git/blame.rb
@@ -1,5 +1,3 @@
-require_relative 'encoding_helper'
-
module Gitlab
module Git
class Blame
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 4a623311c14..b742d9e1e4b 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -1,6 +1,3 @@
-require_relative 'encoding_helper'
-require_relative 'path_helper'
-
module Gitlab
module Git
class Blob
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 79b23d59b3a..7068e68a855 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1,6 +1,4 @@
# Gitlab::Git::Repository is a wrapper around native Rugged::Repository object
-require_relative 'encoding_helper'
-require_relative 'path_helper'
require 'forwardable'
require 'tempfile'
require 'forwardable'
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index 6f27972c4e4..5e94fba97bf 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -7,9 +7,4 @@ namespace :dev do
Rake::Task["gitlab:setup"].invoke
Rake::Task["gitlab:shell:setup"].invoke
end
-
- desc 'GitLab | Start/restart foreman and watch for changes'
- task :foreman => :environment do
- sh 'rerun --dir app,config,lib -- foreman start'
- end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 2a411d78395..7ea3ea4f376 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -1048,4 +1048,72 @@ describe Projects::MergeRequestsController do
end
end
end
+
+ describe 'GET merge_widget_refresh' do
+ let(:params) do
+ {
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: merge_request.iid,
+ format: :raw
+ }
+ end
+
+ before do
+ project.team << [user, :developer]
+ xhr :get, :merge_widget_refresh, params
+ end
+
+ context 'when merge in progress' do
+ let(:merge_request) { create(:merge_request, source_project: project, in_progress_merge_commit_sha: 'sha') }
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sets status to :success' do
+ expect(assigns(:status)).to eq(:success)
+ expect(response).to render_template('merge')
+ end
+ end
+
+ context 'when merge request was merged already' do
+ let(:merge_request) { create(:merge_request, source_project: project, state: :merged) }
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sets status to :success' do
+ expect(assigns(:status)).to eq(:success)
+ expect(response).to render_template('merge')
+ end
+ end
+
+ context 'when waiting for build' do
+ let(:merge_request) { create(:merge_request, source_project: project, merge_when_build_succeeds: true, merge_user: user) }
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sets status to :merge_when_build_succeeds' do
+ expect(assigns(:status)).to eq(:merge_when_build_succeeds)
+ expect(response).to render_template('merge')
+ end
+ end
+
+ context 'when no special status for MR' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+
+ it 'returns an OK response' do
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'sets status to nil' do
+ expect(assigns(:status)).to be_nil
+ expect(response).to render_template('merge')
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 92e38b02615..9f6d4ec6537 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -14,6 +14,54 @@ describe Projects::NotesController do
}
end
+ describe 'POST create' do
+ let(:merge_request) { create(:merge_request) }
+ let(:request_params) do
+ {
+ note: { note: 'some note', noteable_id: merge_request.id, noteable_type: 'MergeRequest' },
+ namespace_id: project.namespace,
+ project_id: project,
+ merge_request_diff_head_sha: 'sha'
+ }
+ end
+
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ it "returns status 302 for html" do
+ post :create, request_params
+
+ expect(response).to have_http_status(302)
+ end
+
+ it "returns status 200 for json" do
+ post :create, request_params.merge(format: :json)
+
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when merge_request_diff_head_sha present' do
+ before do
+ service_params = {
+ note: 'some note',
+ noteable_id: merge_request.id.to_s,
+ noteable_type: 'MergeRequest',
+ merge_request_diff_head_sha: 'sha'
+ }
+
+ expect(Notes::CreateService).to receive(:new).with(project, user, service_params).and_return(double(execute: true))
+ end
+
+ it "returns status 302 for html" do
+ post :create, request_params
+
+ expect(response).to have_http_status(302)
+ end
+ end
+ end
+
describe 'POST toggle_award_emoji' do
before do
sign_in(user)
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index c16aafa1470..188d33e8ef4 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -125,7 +125,9 @@ describe 'Issue Boards', feature: true, js: true do
first('.card').click
end
- page.within('.assignee') do
+ page.within(find('.assignee')) do
+ expect(page).to have_content('No assignee')
+
click_link 'assign yourself'
wait_for_vue_resource
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
index c9a0059645d..4a6c76a5caf 100644
--- a/spec/features/merge_requests/diffs_spec.rb
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -22,4 +22,18 @@ feature 'Diffs URL', js: true, feature: true do
expect(page).to have_css('.diffs.tab-pane.active')
end
end
+
+ context 'when merge request has overflow' do
+ it 'displays warning' do
+ allow_any_instance_of(MergeRequestDiff).to receive(:overflow?).and_return(true)
+ allow(Commit).to receive(:max_diff_options).and_return(max_files: 20, max_lines: 20)
+
+ visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+
+ page.within('.alert') do
+ expect(page).to have_text("Too many changes to show. Plain diff Email patch To preserve
+ performance only 3 of 3+ files are displayed.")
+ end
+ end
+ end
end
diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
index b1b3a47a1ce..b13674b4db9 100644
--- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb
+++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb
@@ -68,6 +68,51 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do
end
end
+ describe 'merging the MR from the note' do
+ context 'when the current user can merge the MR' do
+ it 'merges the MR' do
+ write_note("/merge")
+
+ expect(page).to have_content 'Commands applied'
+
+ expect(merge_request.reload).to be_merged
+ end
+ end
+
+ context 'when the head diff changes in the meanwhile' do
+ before do
+ merge_request.source_branch = 'another_branch'
+ merge_request.save
+ end
+
+ it 'does not merge the MR' do
+ write_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
+
+ context 'when the current user cannot merge the MR' do
+ let(:guest) { create(:user) }
+ before do
+ project.team << [guest, :guest]
+ logout
+ login_with(guest)
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'does not merge the MR' do
+ write_note("/merge")
+
+ expect(page).not_to have_content 'Your commands have been executed!'
+
+ expect(merge_request.reload).not_to be_merged
+ end
+ end
+ end
+
describe 'adding a due date from note' do
it 'does not recognize the command nor create a note' do
write_note('/due 2016-08-28')
diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb
index 8c4d4320dc5..11d27feab0b 100644
--- a/spec/features/projects/builds_spec.rb
+++ b/spec/features/projects/builds_spec.rb
@@ -3,6 +3,7 @@ require 'tempfile'
feature 'Builds', :feature do
let(:user) { create(:user) }
+ let(:user_access_level) { :developer }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -14,7 +15,7 @@ feature 'Builds', :feature do
end
before do
- project.team << [user, :developer]
+ project.team << [user, user_access_level]
login_as(user)
end
@@ -131,7 +132,9 @@ feature 'Builds', :feature do
context 'Artifacts expire date' do
before do
- build.update_attributes(artifacts_file: artifacts_file, artifacts_expire_at: expire_at)
+ build.update_attributes(artifacts_file: artifacts_file,
+ artifacts_expire_at: expire_at)
+
visit namespace_project_build_path(project.namespace, project, build)
end
@@ -146,12 +149,23 @@ feature 'Builds', :feature do
context 'when expire date is defined' do
let(:expire_at) { Time.now + 7.days }
- it 'keeps artifacts when Keep button is clicked' do
- expect(page).to have_content 'The artifacts will be removed'
- click_link 'Keep'
+ context 'when user has ability to update build' do
+ it 'keeps artifacts when keep button is clicked' do
+ expect(page).to have_content 'The artifacts will be removed'
- expect(page).not_to have_link 'Keep'
- expect(page).not_to have_content 'The artifacts will be removed'
+ click_link 'Keep'
+
+ expect(page).to have_no_link 'Keep'
+ expect(page).to have_no_content 'The artifacts will be removed'
+ end
+ end
+
+ context 'when user does not have ability to update build' do
+ let(:user_access_level) { :guest }
+
+ it 'does not have keep button' do
+ expect(page).to have_no_link 'Keep'
+ end
end
end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 903224589dd..1f221487393 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -62,4 +62,19 @@ describe MergeRequestsHelper do
it { is_expected.to eq([source_title, target_title]) }
end
end
+
+ describe 'mr_widget_refresh_url' do
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:project) { create(:project) }
+
+ it 'returns correct url for MR' do
+ expected_url = "#{project.path_with_namespace}/merge_requests/#{merge_request.iid}/merge_widget_refresh"
+
+ expect(mr_widget_refresh_url(merge_request)).to end_with(expected_url)
+ end
+
+ it 'returns empty string for nil' do
+ expect(mr_widget_refresh_url(nil)).to end_with('')
+ end
+ end
end
diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6
index ce96571bd52..d11b1182d9a 100644
--- a/spec/javascripts/gl_dropdown_spec.js.es6
+++ b/spec/javascripts/gl_dropdown_spec.js.es6
@@ -50,6 +50,9 @@
selectable: true,
filterable: isFilterable,
data: hasRemote ? remoteMock.bind({}, this.projectsData) : this.projectsData,
+ search: {
+ fields: ['name']
+ },
text: (project) => {
(project.name_with_namespace || project.name);
},
@@ -167,5 +170,21 @@
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
});
});
+
+
+ it('should still have input value on close and restore', () => {
+ let $searchInput = $(SEARCH_INPUT_SELECTOR);
+ initDropDown.call(this, false, true);
+ $searchInput
+ .trigger('focus')
+ .val('g')
+ .trigger('input');
+ expect($searchInput.val()).toEqual('g');
+ this.dropdownButtonElement.trigger('hidden.bs.dropdown');
+ $searchInput
+ .trigger('blur')
+ .trigger('focus');
+ expect($searchInput.val()).toEqual('g');
+ });
});
})();
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index af0f6a31eda..3309a7fff9f 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1013,6 +1013,24 @@ describe Ci::Build, :models do
end
end
+ describe '#has_expiring_artifacts?' do
+ context 'when artifacts have expiration date set' do
+ before { build.update(artifacts_expire_at: 1.day.from_now) }
+
+ it 'has expiring artifacts' do
+ expect(build).to have_expiring_artifacts
+ end
+ end
+
+ context 'when artifacts do not have expiration date set' do
+ before { build.update(artifacts_expire_at: nil) }
+
+ it 'does not have expiring artifacts' do
+ expect(build).not_to have_expiring_artifacts
+ end
+ end
+ end
+
describe '#has_trace_file?' do
context 'when there is no trace' do
it { expect(build.has_trace_file?).to be_falsey }
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index eb876d105da..6d599e148a2 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -76,6 +76,32 @@ describe MergeRequestDiff, models: true do
end
end
+ describe '#save_diffs' do
+ it 'saves collected state' do
+ mr_diff = create(:merge_request).merge_request_diff
+
+ expect(mr_diff.collected?).to be_truthy
+ end
+
+ it 'saves overflow state' do
+ allow(Commit).to receive(:max_diff_options)
+ .and_return(max_lines: 0, max_files: 0)
+
+ mr_diff = create(:merge_request).merge_request_diff
+
+ expect(mr_diff.overflow?).to be_truthy
+ end
+
+ it 'saves empty state' do
+ allow_any_instance_of(MergeRequestDiff).to receive(:commits)
+ .and_return([])
+
+ mr_diff = create(:merge_request).merge_request_diff
+
+ expect(mr_diff.empty?).to be_truthy
+ end
+ end
+
describe '#commits_sha' do
it 'returns all commits SHA using serialized commits' do
subject.st_commits = [
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 8d1385016fd..861426acbc3 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1514,6 +1514,108 @@ describe MergeRequest, models: true do
end
end
+ describe '#mergeable_with_slash_command?' do
+ def create_pipeline(status)
+ create(:ci_pipeline_with_one_job,
+ project: project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ status: status)
+ end
+
+ let(:project) { create(:project, :public, only_allow_merge_if_build_succeeds: true) }
+ let(:developer) { create(:user) }
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:mr_sha) { merge_request.diff_head_sha }
+
+ before do
+ project.team << [developer, :developer]
+ end
+
+ context 'when autocomplete_precheck is set to true' do
+ it 'is mergeable by developer' do
+ expect(merge_request.mergeable_with_slash_command?(developer, autocomplete_precheck: true)).to be_truthy
+ end
+
+ it 'is not mergeable by normal user' do
+ expect(merge_request.mergeable_with_slash_command?(user, autocomplete_precheck: true)).to be_falsey
+ end
+ end
+
+ context 'when autocomplete_precheck is set to false' do
+ it 'is mergeable by developer' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+ end
+
+ it 'is not mergeable by normal user' do
+ expect(merge_request.mergeable_with_slash_command?(user, last_diff_sha: mr_sha)).to be_falsey
+ end
+
+ context 'closed MR' do
+ before do
+ merge_request.update_attribute(:state, :closed)
+ end
+
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+ end
+ end
+
+ context 'MR with WIP' do
+ before do
+ merge_request.update_attribute(:title, 'WIP: some MR')
+ end
+
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+ end
+ end
+
+ context 'sha differs from the MR diff_head_sha' do
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: 'some other sha')).to be_falsey
+ end
+ end
+
+ context 'sha is not provided' do
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer)).to be_falsey
+ end
+ end
+
+ context 'with pipeline ok' do
+ before do
+ create_pipeline(:success)
+ end
+
+ it 'is mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+ end
+ end
+
+ context 'with failing pipeline' do
+ before do
+ create_pipeline(:failed)
+ end
+
+ it 'is not mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_falsey
+ end
+ end
+
+ context 'with running pipeline' do
+ before do
+ create_pipeline(:running)
+ end
+
+ it 'is mergeable' do
+ expect(merge_request.mergeable_with_slash_command?(developer, last_diff_sha: mr_sha)).to be_truthy
+ end
+ end
+ end
+ end
+
describe '#has_commits?' do
before do
allow(subject.merge_request_diff).to receive(:commits_count).
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 88c786947d3..7d73c0ea5d0 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -121,6 +121,99 @@ describe MergeRequests::UpdateService, services: true do
end
end
+ context 'merge' do
+ let(:opts) do
+ {
+ merge: merge_request.diff_head_sha
+ }
+ end
+
+ let(:service) { MergeRequests::UpdateService.new(project, user, opts) }
+
+ context 'without pipeline' do
+ before do
+ merge_request.merge_error = 'Error'
+
+ perform_enqueued_jobs do
+ service.execute(merge_request)
+ @merge_request = MergeRequest.find(merge_request.id)
+ end
+ end
+
+ it { expect(@merge_request).to be_valid }
+ it { expect(@merge_request.state).to eq('merged') }
+ it { expect(@merge_request.merge_error).to be_nil }
+ end
+
+ context 'with finished pipeline' do
+ before do
+ create(:ci_pipeline_with_one_job,
+ project: project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha,
+ status: :success)
+
+ perform_enqueued_jobs do
+ @merge_request = service.execute(merge_request)
+ @merge_request = MergeRequest.find(merge_request.id)
+ end
+ end
+
+ it { expect(@merge_request).to be_valid }
+ it { expect(@merge_request.state).to eq('merged') }
+ end
+
+ context 'with active pipeline' do
+ before do
+ service_mock = double
+ create(:ci_pipeline_with_one_job,
+ project: project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha)
+
+ expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user).
+ and_return(service_mock)
+ expect(service_mock).to receive(:execute).with(merge_request)
+ end
+
+ it { service.execute(merge_request) }
+ end
+
+ context 'with a non-authorised user' do
+ let(:visitor) { create(:user) }
+ let(:service) { MergeRequests::UpdateService.new(project, visitor, opts) }
+
+ before do
+ merge_request.update_attribute(:merge_error, 'Error')
+
+ perform_enqueued_jobs do
+ @merge_request = service.execute(merge_request)
+ @merge_request = MergeRequest.find(merge_request.id)
+ end
+ end
+
+ it { expect(@merge_request.state).to eq('opened') }
+ it { expect(@merge_request.merge_error).not_to be_nil }
+ end
+
+ context 'MR can not be merged when note sha != MR sha' do
+ let(:opts) do
+ {
+ merge: 'other_commit'
+ }
+ end
+
+ before do
+ perform_enqueued_jobs do
+ @merge_request = service.execute(merge_request)
+ @merge_request = MergeRequest.find(merge_request.id)
+ end
+ end
+
+ it { expect(@merge_request.state).to eq('opened') }
+ end
+ end
+
context 'todos' do
let!(:pending_todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) }
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 25804696d2e..b0cc3ce5f5a 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -63,6 +63,17 @@ describe Notes::CreateService, services: true do
expect(note.note).to eq "HELLO\nWORLD"
end
end
+
+ describe '/merge with sha option' do
+ let(:note_text) { %(HELLO\n/merge\nWORLD) }
+ let(:params) { opts.merge(note: note_text, merge_request_diff_head_sha: 'sha') }
+
+ it 'saves the note and exectues merge command' do
+ note = described_class.new(project, user, params).execute
+
+ expect(note.note).to eq "HELLO\nWORLD"
+ end
+ end
end
end
diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb
index becf627a4f5..ffcf02d2c56 100644
--- a/spec/services/slash_commands/interpret_service_spec.rb
+++ b/spec/services/slash_commands/interpret_service_spec.rb
@@ -1,12 +1,13 @@
require 'spec_helper'
describe SlashCommands::InterpretService, services: true do
- let(:project) { create(:empty_project, :public) }
+ let(:project) { create(:project, :public) }
let(:developer) { create(:user) }
let(:issue) { create(:issue, project: project) }
let(:milestone) { create(:milestone, project: project, title: '9.10') }
let(:inprogress) { create(:label, project: project, title: 'In Progress') }
let(:bug) { create(:label, project: project, title: 'Bug') }
+ let(:note) { build(:note, commit_id: merge_request.diff_head_sha) }
before do
project.team << [developer, :developer]
@@ -218,6 +219,14 @@ describe SlashCommands::InterpretService, services: true do
end
end
+ shared_examples 'merge command' do
+ it 'runs merge command if content contains /merge' do
+ _, updates = service.execute(content, issuable)
+
+ expect(updates).to eq(merge: merge_request.diff_head_sha)
+ end
+ end
+
it_behaves_like 'reopen command' do
let(:content) { '/reopen' }
let(:issuable) { issue }
@@ -238,6 +247,64 @@ describe SlashCommands::InterpretService, services: true do
let(:issuable) { merge_request }
end
+ context 'merge command' do
+ let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: merge_request.diff_head_sha }) }
+
+ it_behaves_like 'merge command' do
+ let(:content) { '/merge' }
+ let(:issuable) { merge_request }
+ end
+
+ context 'can not be merged when logged user does not have permissions' do
+ let(:service) { described_class.new(project, create(:user)) }
+
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { merge_request }
+ end
+ end
+
+ context 'can not be merged when sha does not match' do
+ let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: 'othersha' }) }
+
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { merge_request }
+ end
+ end
+
+ context 'when sha is missing' do
+ let(:service) { described_class.new(project, developer, {}) }
+
+ it 'precheck passes and returns merge command' do
+ _, updates = service.execute('/merge', merge_request)
+
+ expect(updates).to eq(merge: nil)
+ end
+ end
+
+ context 'issue can not be merged' do
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { issue }
+ end
+ end
+
+ context 'non persisted merge request cant be merged' do
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { build(:merge_request) }
+ end
+ end
+
+ context 'not persisted merge request can not be merged' do
+ it_behaves_like 'empty command' do
+ let(:content) { "/merge" }
+ let(:issuable) { build(:merge_request, source_project: project) }
+ end
+ end
+ end
+
it_behaves_like 'title command' do
let(:content) { '/title A brand new title' }
let(:issuable) { issue }
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 9fbb61565e3..690fe979492 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -10,7 +10,21 @@ describe Users::RefreshAuthorizedProjectsService do
create!(project: project, user: user, access_level: access_level)
end
- describe '#execute' do
+ describe '#execute', :redis do
+ it 'refreshes the authorizations using a lease' do
+ expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
+ and_return('foo')
+
+ expect(Gitlab::ExclusiveLease).to receive(:cancel).
+ with(an_instance_of(String), 'foo')
+
+ expect(service).to receive(:execute_without_lease)
+
+ service.execute
+ end
+ end
+
+ describe '#execute_without_lease' do
before do
user.project_authorizations.delete_all
end
@@ -19,37 +33,23 @@ describe Users::RefreshAuthorizedProjectsService do
project2 = create(:empty_project)
to_remove = create_authorization(project2, user)
- expect(service).to receive(:update_with_lease).
+ expect(service).to receive(:update_authorizations).
with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
- service.execute
+ service.execute_without_lease
end
it 'sets the access level of a project to the highest available level' do
to_remove = create_authorization(project, user, Gitlab::Access::DEVELOPER)
- expect(service).to receive(:update_with_lease).
+ expect(service).to receive(:update_authorizations).
with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
- service.execute
+ service.execute_without_lease
end
it 'returns a User' do
- expect(service.execute).to be_an_instance_of(User)
- end
- end
-
- describe '#update_with_lease', :redis do
- it 'refreshes the authorizations using a lease' do
- expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
- and_return('foo')
-
- expect(Gitlab::ExclusiveLease).to receive(:cancel).
- with(an_instance_of(String), 'foo')
-
- expect(service).to receive(:update_authorizations).with([1], [])
-
- service.update_with_lease([1])
+ expect(service.execute_without_lease).to be_an_instance_of(User)
end
end