summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml13
-rw-r--r--app/assets/javascripts/notes.js2
-rw-r--r--app/assets/javascripts/sidebar/event_hub.js7
-rw-r--r--app/assets/javascripts/users_select.js14
-rw-r--r--app/controllers/concerns/issuable_collections.rb2
-rw-r--r--app/models/ci/pipeline.rb12
-rw-r--r--app/models/concerns/mentionable.rb2
-rw-r--r--app/models/merge_request.rb8
-rw-r--r--app/models/user.rb11
-rw-r--r--app/services/ci/create_pipeline_service.rb7
-rw-r--r--app/services/ci/process_pipeline_service.rb2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml50
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml49
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml21
-rw-r--r--app/views/shared/issuable/form/_metadata_issue_assignee.html.haml11
-rw-r--r--app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml8
-rw-r--r--changelogs/unreleased/fix-gb-fix-skipped-manual-actions.yml4
-rw-r--r--changelogs/unreleased/issue_27168_2.yml4
-rw-r--r--changelogs/unreleased/tc-cache-trackable-attributes.yml4
-rw-r--r--changelogs/unreleased/up-arrow-focus-discussion-comment.yml4
-rw-r--r--db/migrate/20170507205316_add_head_pipeline_id_to_merge_requests.rb7
-rw-r--r--db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb25
-rw-r--r--db/schema.rb3
-rw-r--r--features/steps/project/merge_requests.rb1
-rw-r--r--lib/gitlab/git/repository.rb50
-rwxr-xr-xscripts/trigger-build21
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb8
-rw-r--r--spec/features/cycle_analytics_spec.rb10
-rw-r--r--spec/features/groups/members/sorting_spec.rb4
-rw-r--r--spec/features/issuables/issuable_list_spec.rb3
-rw-r--r--spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb10
-rw-r--r--spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb5
-rw-r--r--spec/features/merge_requests/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb2
-rw-r--r--spec/features/merge_requests/widget_spec.rb17
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb16
-rw-r--r--spec/javascripts/merge_request_notes_spec.js61
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb33
-rw-r--r--spec/models/ci/pipeline_spec.rb2
-rw-r--r--spec/models/cycle_analytics/test_spec.rb1
-rw-r--r--spec/models/merge_request_spec.rb15
-rw-r--r--spec/models/user_spec.rb29
-rw-r--r--spec/policies/environment_policy_spec.rb2
-rw-r--r--spec/requests/projects/cycle_analytics_events_spec.rb1
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb36
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb22
-rw-r--r--spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb6
-rw-r--r--spec/services/merge_requests/update_service_spec.rb4
-rw-r--r--spec/workers/pipeline_metrics_worker_spec.rb2
52 files changed, 455 insertions, 185 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 588f255eff8..88d536fa9b3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -141,6 +141,7 @@ stages:
# Trigger a package build on omnibus-gitlab repository
build-package:
+ before_script: []
services: []
variables:
SETUP_DB: "false"
@@ -148,17 +149,7 @@ build-package:
stage: build
when: manual
script:
- # If no branch in omnibus is specified, trigger pipeline against master
- - if [ -z "$OMNIBUS_BRANCH" ] ; then export OMNIBUS_BRANCH=master ;fi
- - echo "token=${BUILD_TRIGGER_TOKEN}" > version_details
- - echo "ref=${OMNIBUS_BRANCH}" >> version_details
- - echo "variables[ALTERNATIVE_SOURCES]=true" >> version_details
- - echo "variables[GITLAB_VERSION]=${CI_COMMIT_SHA}" >> version_details
- # Collect version details of all components
- - for f in *_VERSION; do echo "variables[$f]=$(cat $f)" >> version_details; done
- # Trigger the API and pass values collected above as parameters to it
- - cat version_details | tr '\n' '&' | curl -X POST https://gitlab.com/api/v4/projects/20699/trigger/pipeline --data-binary @-
- - rm version_details
+ - scripts/trigger-build
# Prepare and merge knapsack tests
knapsack:
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index d2e602a0763..194c29f4710 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -175,7 +175,7 @@ const normalizeNewlines = function(str) {
if ($textarea.val() !== '') {
return;
}
- myLastNote = $("li.note[data-author-id='" + gon.current_user_id + "'][data-editable]:last");
+ myLastNote = $(`li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`, $textarea.closest('.note, #notes'));
if (myLastNote.length) {
myLastNoteEditBtn = myLastNote.find('.js-note-edit');
return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
diff --git a/app/assets/javascripts/sidebar/event_hub.js b/app/assets/javascripts/sidebar/event_hub.js
index 0948c2e5352..f35506fd5de 100644
--- a/app/assets/javascripts/sidebar/event_hub.js
+++ b/app/assets/javascripts/sidebar/event_hub.js
@@ -1,3 +1,8 @@
import Vue from 'vue';
-export default new Vue();
+const eventHub = new Vue();
+
+// TODO: remove eventHub hack after code splitting refactor
+window.emitSidebarEvent = (...args) => eventHub.$emit(...args);
+
+export default eventHub;
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index be29b08c343..38462782007 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -1,7 +1,9 @@
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */
/* global Issuable */
+/* global emitSidebarEvent */
-import eventHub from './sidebar/event_hub';
+// TODO: remove eventHub hack after code splitting refactor
+window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
@@ -110,7 +112,7 @@ import eventHub from './sidebar/event_hub';
.find(`input[name='${$dropdown.data('field-name')}'][value=${firstSelectedId}]`);
firstSelected.remove();
- eventHub.$emit('sidebar.removeAssignee', {
+ emitSidebarEvent('sidebar.removeAssignee', {
id: firstSelectedId,
});
}
@@ -330,7 +332,7 @@ import eventHub from './sidebar/event_hub';
defaultLabel: defaultLabel,
hidden: function(e) {
if ($dropdown.hasClass('js-multiselect')) {
- eventHub.$emit('sidebar.saveAssignees');
+ emitSidebarEvent('sidebar.saveAssignees');
}
if (!$dropdown.data('always-show-selectbox')) {
@@ -364,10 +366,10 @@ import eventHub from './sidebar/event_hub';
const id = parseInt(element.value, 10);
element.remove();
});
- eventHub.$emit('sidebar.removeAllAssignees');
+ emitSidebarEvent('sidebar.removeAllAssignees');
} else if (isActive) {
// user selected
- eventHub.$emit('sidebar.addAssignee', user);
+ emitSidebarEvent('sidebar.addAssignee', user);
// Remove unassigned selection (if it was previously selected)
const unassignedSelected = $dropdown.closest('.selectbox')
@@ -383,7 +385,7 @@ import eventHub from './sidebar/event_hub';
}
// User unselected
- eventHub.$emit('sidebar.removeAssignee', user);
+ emitSidebarEvent('sidebar.removeAssignee', user);
}
if (getSelected().find(u => u === gon.current_user_id)) {
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 6df2c068745..650ec1e326a 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -47,7 +47,7 @@ module IssuableCollections
end
def merge_requests_collection
- merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, target_project: :namespace)
+ merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, :head_pipeline, target_project: :namespace)
end
def issues_finder
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index db994b861e5..81c30b0e077 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -18,6 +18,10 @@ module Ci
has_many :builds, foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
+ # Merge requests for which the current pipeline is running against
+ # the merge request's latest commit.
+ has_many :merge_requests, foreign_key: "head_pipeline_id"
+
has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :retryable_builds, -> { latest.failed_or_canceled }, foreign_key: :commit_id, class_name: 'Ci::Build'
has_many :cancelable_statuses, -> { cancelable }, foreign_key: :commit_id, class_name: 'CommitStatus'
@@ -381,14 +385,6 @@ module Ci
project.execute_services(data, :pipeline_hooks)
end
- # Merge requests for which the current pipeline is running against
- # the merge request's latest commit.
- def merge_requests
- @merge_requests ||= project.merge_requests
- .where(source_branch: self.ref)
- .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id }
- end
-
# All the merge requests for which the current pipeline runs/ran against
def all_merge_requests
@all_merge_requests ||= project.merge_requests.where(source_branch: ref)
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 5ac56ac6fa0..6eddeab515e 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -92,7 +92,7 @@ module Mentionable
# Uses regex to quickly determine if mentionables might be referenced
# Allows heavy processing to be skipped
def matches_cross_reference_regex?
- reference_pattern = if project.default_issues_tracker?
+ reference_pattern = if !project || project.default_issues_tracker?
ReferenceRegexes::DEFAULT_PATTERN
else
ReferenceRegexes::EXTERNAL_PATTERN
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1b6904aa077..59736f70f24 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -13,6 +13,8 @@ class MergeRequest < ActiveRecord::Base
has_one :merge_request_diff,
-> { order('merge_request_diffs.id DESC') }
+ belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline"
+
has_many :events, as: :target, dependent: :destroy
has_many :merge_requests_closing_issues, class_name: 'MergeRequestsClosingIssues', dependent: :delete_all
@@ -829,12 +831,6 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0
end
- def head_pipeline
- return unless diff_head_sha && source_project
-
- @head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha)
- end
-
def all_pipelines
return Ci::Pipeline.none unless source_project
diff --git a/app/models/user.rb b/app/models/user.rb
index 4e5f94683b8..77b2b12ee0b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -40,6 +40,17 @@ class User < ActiveRecord::Base
devise :lockable, :recoverable, :rememberable, :trackable,
:validatable, :omniauthable, :confirmable, :registerable
+ # Override Devise::Models::Trackable#update_tracked_fields!
+ # to limit database writes to at most once every hour
+ def update_tracked_fields!(request)
+ update_tracked_fields(request)
+
+ lease = Gitlab::ExclusiveLease.new("user_update_tracked_fields:#{id}", timeout: 1.hour.to_i)
+ return unless lease.try_obtain
+
+ save(validate: false)
+ end
+
attr_accessor :force_random_password
# Virtual attribute for authenticating by either username or email
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index ccdda08d885..1f6c1f4a7f6 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -47,7 +47,7 @@ module Ci
end
Ci::Pipeline.transaction do
- pipeline.save
+ update_merge_requests_head_pipeline if pipeline.save
Ci::CreatePipelineBuildsService
.new(project, current_user)
@@ -118,6 +118,11 @@ module Ci
origin_sha && origin_sha != Gitlab::Git::BLANK_SHA
end
+ def update_merge_requests_head_pipeline
+ MergeRequest.where(source_branch: @pipeline.ref, source_project: @pipeline.project).
+ update_all(head_pipeline_id: @pipeline.id)
+ end
+
def error(message, save: false)
pipeline.errors.add(:base, message)
pipeline.drop if save
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 33edcd60944..25ba54ffa0d 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -50,7 +50,7 @@ module Ci
when 'always'
%w[success failed skipped]
when 'manual'
- %w[success]
+ %w[success skipped]
else
[]
end
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 44e624c15a7..3a66880e177 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -20,55 +20,7 @@
.block.todo.hide-expanded
= render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable, is_collapsed: true
.block.assignee
- - if issuable.instance_of?(Issue)
- #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]" } }
- - else
- .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) }
- - if issuable.assignee
- = link_to_member(@project, issuable.assignee, size: 24)
- - else
- = icon('user', 'aria-hidden': 'true')
- .title.hide-collapsed
- Assignee
- = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- - if can_edit_issuable
- = link_to 'Edit', '#', class: 'edit-link pull-right'
- .value.hide-collapsed
- - if issuable.assignee
- = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
- - if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
- %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' }
- = icon('exclamation-triangle', 'aria-hidden': 'true')
- %span.username
- = issuable.assignee.to_reference
- - else
- %span.assign-yourself.no-value
- No assignee
- - if can_edit_issuable
- \-
- %a.js-assign-yourself{ href: '#' }
- assign yourself
-
- .selectbox.hide-collapsed
- - issuable.assignees.each do |assignee|
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil
-
- - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
-
- - if issuable.instance_of?(Issue)
- - if issuable.assignees.length == 0
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
- - title = 'Select assignee'
- - options[:toggle_class] += ' js-multiselect js-save-user-data'
- - options[:data][:field_name] = "#{issuable.to_ability_name}[assignee_ids][]"
- - options[:data][:multi_select] = true
- - options[:data]['dropdown-title'] = title
- - options[:data]['dropdown-header'] = 'Assignee'
- - options[:data]['max-select'] = 1
- - else
- - title = 'Select assignee'
-
- = dropdown_tag(title, options: options)
+ = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable
.block.milestone
.sidebar-collapsed-icon
= icon('clock-o', 'aria-hidden': 'true')
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
new file mode 100644
index 00000000000..c36a45098a8
--- /dev/null
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -0,0 +1,49 @@
+- if issuable.instance_of?(Issue)
+ #js-vue-sidebar-assignees{ data: { field: "#{issuable.to_ability_name}[assignee_ids]" } }
+- else
+ .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) }
+ - if issuable.assignee
+ = link_to_member(@project, issuable.assignee, size: 24)
+ - else
+ = icon('user', 'aria-hidden': 'true')
+ .title.hide-collapsed
+ Assignee
+ = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
+ - if can_edit_issuable
+ = link_to 'Edit', '#', class: 'edit-link pull-right'
+ .value.hide-collapsed
+ - if issuable.assignee
+ = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do
+ - if !issuable.can_be_merged_by?(issuable.assignee)
+ %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' }
+ = icon('exclamation-triangle', 'aria-hidden': 'true')
+ %span.username
+ = issuable.assignee.to_reference
+ - else
+ %span.assign-yourself.no-value
+ No assignee
+ - if can_edit_issuable
+ \-
+ %a.js-assign-yourself{ href: '#' }
+ assign yourself
+
+.selectbox.hide-collapsed
+ - issuable.assignees.each do |assignee|
+ = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil
+
+ - options = { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_ids][]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } }
+
+ - if issuable.instance_of?(Issue)
+ - if issuable.assignees.length == 0
+ = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil
+ - title = 'Select assignee'
+ - options[:toggle_class] += ' js-multiselect js-save-user-data'
+ - options[:data][:field_name] = "#{issuable.to_ability_name}[assignee_ids][]"
+ - options[:data][:multi_select] = true
+ - options[:data]['dropdown-title'] = title
+ - options[:data]['dropdown-header'] = 'Assignee'
+ - options[:data]['max-select'] = 1
+ - else
+ - title = 'Select assignee'
+
+ = dropdown_tag(title, options: options)
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index 9281a515744..1608bd59cf1 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -11,26 +11,9 @@
%div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") }
.form-group.issue-assignee
- if issuable.is_a?(Issue)
- = form.label :assignee_ids, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
- .col-sm-10{ class: ("col-lg-8" if has_due_date) }
- .issuable-form-select-holder.selectbox
- - issuable.assignees.each do |assignee|
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { meta: assignee.name }
-
- - if issuable.assignees.length === 0
- = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
-
- = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false))
- = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
+ = render "shared/issuable/form/metadata_issue_assignee", issuable: issuable, form: form, has_due_date: has_due_date
- else
- = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
- .col-sm-10{ class: ("col-lg-8" if has_due_date) }
- .issuable-form-select-holder
- = form.hidden_field :assignee_id
-
- = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
- placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
- = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignee_id == current_user.id}"
+ = render "shared/issuable/form/metadata_merge_request_assignee", issuable: issuable, form: form, has_due_date: has_due_date
.form-group.issue-milestone
= form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
diff --git a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
new file mode 100644
index 00000000000..8119f19291b
--- /dev/null
+++ b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml
@@ -0,0 +1,11 @@
+= form.label :assignee_ids, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
+.col-sm-10{ class: ("col-lg-8" if has_due_date) }
+ .issuable-form-select-holder.selectbox
+ - issuable.assignees.each do |assignee|
+ = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { meta: assignee.name }
+
+ - if issuable.assignees.length === 0
+ = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
+
+ = dropdown_tag(users_dropdown_label(issuable.assignees), options: issue_dropdown_options(issuable,false))
+ = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
diff --git a/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml b/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml
new file mode 100644
index 00000000000..d0ea4e149df
--- /dev/null
+++ b/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml
@@ -0,0 +1,8 @@
+= form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
+.col-sm-10{ class: ("col-lg-8" if has_due_date) }
+ .issuable-form-select-holder
+ = form.hidden_field :assignee_id
+
+ = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
+ placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} })
+ = link_to 'Assign to me', '#', class: "assign-to-me-link #{'hide' if issuable.assignee_id == current_user.id}"
diff --git a/changelogs/unreleased/fix-gb-fix-skipped-manual-actions.yml b/changelogs/unreleased/fix-gb-fix-skipped-manual-actions.yml
new file mode 100644
index 00000000000..d8d4c668a44
--- /dev/null
+++ b/changelogs/unreleased/fix-gb-fix-skipped-manual-actions.yml
@@ -0,0 +1,4 @@
+---
+title: Fix skipped manual actions problem when processing the pipeline
+merge_request: 11164
+author:
diff --git a/changelogs/unreleased/issue_27168_2.yml b/changelogs/unreleased/issue_27168_2.yml
new file mode 100644
index 00000000000..c67692493e0
--- /dev/null
+++ b/changelogs/unreleased/issue_27168_2.yml
@@ -0,0 +1,4 @@
+---
+title: Preloads head pipeline for merge request collection
+merge_request:
+author:
diff --git a/changelogs/unreleased/tc-cache-trackable-attributes.yml b/changelogs/unreleased/tc-cache-trackable-attributes.yml
new file mode 100644
index 00000000000..4a2cf50893a
--- /dev/null
+++ b/changelogs/unreleased/tc-cache-trackable-attributes.yml
@@ -0,0 +1,4 @@
+---
+title: "Limit User's trackable attributes, like `current_sign_in_at`, to update at most once/hour"
+merge_request: 11053
+author:
diff --git a/changelogs/unreleased/up-arrow-focus-discussion-comment.yml b/changelogs/unreleased/up-arrow-focus-discussion-comment.yml
new file mode 100644
index 00000000000..5457dab6d3d
--- /dev/null
+++ b/changelogs/unreleased/up-arrow-focus-discussion-comment.yml
@@ -0,0 +1,4 @@
+---
+title: Fix up arrow not editing last discussion comment
+merge_request:
+author:
diff --git a/db/migrate/20170507205316_add_head_pipeline_id_to_merge_requests.rb b/db/migrate/20170507205316_add_head_pipeline_id_to_merge_requests.rb
new file mode 100644
index 00000000000..8fc6e380a77
--- /dev/null
+++ b/db/migrate/20170507205316_add_head_pipeline_id_to_merge_requests.rb
@@ -0,0 +1,7 @@
+class AddHeadPipelineIdToMergeRequests < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :merge_requests, :head_pipeline_id, :integer
+ end
+end
diff --git a/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb b/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb
new file mode 100644
index 00000000000..bc3850c0c23
--- /dev/null
+++ b/db/post_migrate/20170508170547_add_head_pipeline_for_each_merge_request.rb
@@ -0,0 +1,25 @@
+class AddHeadPipelineForEachMergeRequest < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ disable_statement_timeout
+
+ pipelines = Arel::Table.new(:ci_pipelines)
+ merge_requests = Arel::Table.new(:merge_requests)
+
+ head_id = pipelines.
+ project(Arel::Nodes::NamedFunction.new('max', [pipelines[:id]])).
+ from(pipelines).
+ where(pipelines[:ref].eq(merge_requests[:source_branch])).
+ where(pipelines[:project_id].eq(merge_requests[:source_project_id]))
+
+ sub_query = Arel::Nodes::SqlLiteral.new(Arel::Nodes::Grouping.new(head_id).to_sql)
+
+ update_column_in_batches(:merge_requests, :head_pipeline_id, sub_query)
+ end
+
+ def down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 722e776c27d..b91b3e6e977 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170506185517) do
+ActiveRecord::Schema.define(version: 20170508170547) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -690,6 +690,7 @@ ActiveRecord::Schema.define(version: 20170506185517) do
t.integer "cached_markdown_version"
t.datetime "last_edited_at"
t.integer "last_edited_by_id"
+ t.integer "head_pipeline_id"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 573be44c695..d15417fa173 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -557,6 +557,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
project = merge_request.source_project
project.enable_ci
pipeline = create :ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch
+ merge_request.update(head_pipeline: pipeline)
create :ci_build, pipeline: pipeline
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 6a0f12b7e50..9ed12ead023 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1112,56 +1112,6 @@ module Gitlab
end
end
- def archive_to_file(treeish = 'master', filename = 'archive.tar.gz', format = nil, compress_cmd = %w(gzip -n))
- git_archive_cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} archive)
-
- # Put files into a directory before archiving
- prefix = "#{archive_name(treeish)}/"
- git_archive_cmd << "--prefix=#{prefix}"
-
- # Format defaults to tar
- git_archive_cmd << "--format=#{format}" if format
-
- git_archive_cmd += %W(-- #{treeish})
-
- open(filename, 'w') do |file|
- # Create a pipe to act as the '|' in 'git archive ... | gzip'
- pipe_rd, pipe_wr = IO.pipe
-
- # Get the compression process ready to accept data from the read end
- # of the pipe
- compress_pid = spawn(*nice(compress_cmd), in: pipe_rd, out: file)
- # The read end belongs to the compression process now; we should
- # close our file descriptor for it.
- pipe_rd.close
-
- # Start 'git archive' and tell it to write into the write end of the
- # pipe.
- git_archive_pid = spawn(*nice(git_archive_cmd), out: pipe_wr)
- # The write end belongs to 'git archive' now; close it.
- pipe_wr.close
-
- # When 'git archive' and the compression process are finished, we are
- # done.
- Process.waitpid(git_archive_pid)
- raise "#{git_archive_cmd.join(' ')} failed" unless $?.success?
- Process.waitpid(compress_pid)
- raise "#{compress_cmd.join(' ')} failed" unless $?.success?
- end
- end
-
- def nice(cmd)
- nice_cmd = %w(nice -n 20)
- unless unsupported_platform?
- nice_cmd += %w(ionice -c 2 -n 7)
- end
- nice_cmd + cmd
- end
-
- def unsupported_platform?
- %w[darwin freebsd solaris].map { |platform| RUBY_PLATFORM.include?(platform) }.any?
- end
-
# Returns true if the index entry has the special file mode that denotes
# a submodule.
def submodule?(index_entry)
diff --git a/scripts/trigger-build b/scripts/trigger-build
new file mode 100755
index 00000000000..741e6361f01
--- /dev/null
+++ b/scripts/trigger-build
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+
+require 'net/http'
+require 'json'
+
+uri = URI('https://gitlab.com/api/v4/projects/20699/trigger/pipeline')
+params = {
+ "ref" => ENV["OMNIBUS_BRANCH"] || "master",
+ "token" => ENV["BUILD_TRIGGER_TOKEN"],
+ "variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"],
+ "variables[ALTERNATIVE_SOURCES]" => true,
+}
+
+Dir.glob("*_VERSION").each do |version_file|
+ params["variables[#{version_file}]"] = File.read(version_file).strip
+end
+
+res = Net::HTTP.post_form(uri, params)
+pipeline_id = JSON.parse(res.body)['id']
+
+puts "Triggered pipeline can be found at https://gitlab.com/gitlab-org/omnibus-gitlab/pipelines/#{pipeline_id}"
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 964246b580c..37a253fde9b 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -345,7 +345,8 @@ describe Projects::MergeRequestsController do
end
before do
- create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch)
+ pipeline = create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch)
+ merge_request.update(head_pipeline: pipeline)
end
it 'returns :merge_when_pipeline_succeeds' do
@@ -1159,7 +1160,10 @@ describe Projects::MergeRequestsController do
let(:status) { pipeline.detailed_status(double('user')) }
- before { get_pipeline_status }
+ before do
+ merge_request.update(head_pipeline: pipeline)
+ get_pipeline_status
+ end
it 'return a detailed head_pipeline status in json' do
expect(response).to have_http_status(:ok)
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index df2714f91ff..cbeb73d9cae 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -12,8 +12,10 @@ feature 'Cycle Analytics', feature: true, js: true do
context 'as an allowed user' do
context 'when project is new' do
before do
- project.team << [user, :master]
+ project.add_master(user)
+
login_as(user)
+
visit namespace_project_cycle_analytics_path(project.namespace, project)
wait_for_ajax
end
@@ -30,7 +32,9 @@ feature 'Cycle Analytics', feature: true, js: true do
context "when there's cycle analytics data" do
before do
- project.team << [user, :master]
+ allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
+ mr.update(head_pipeline: pipeline)
+ project.add_master(user)
create_cycle
deploy_master
@@ -84,7 +88,7 @@ feature 'Cycle Analytics', feature: true, js: true do
context "as a guest" do
before do
- project.team << [guest, :guest]
+ project.add_guest(guest)
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
create_cycle
diff --git a/spec/features/groups/members/sorting_spec.rb b/spec/features/groups/members/sorting_spec.rb
index 608aedd3471..902d3f789ff 100644
--- a/spec/features/groups/members/sorting_spec.rb
+++ b/spec/features/groups/members/sorting_spec.rb
@@ -68,7 +68,7 @@ feature 'Groups > Members > Sorting', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
- scenario 'sorts by recent sign in' do
+ scenario 'sorts by recent sign in', :redis do
visit_members_list(sort: :recent_sign_in)
expect(first_member).to include(owner.name)
@@ -76,7 +76,7 @@ feature 'Groups > Members > Sorting', feature: true do
expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
- scenario 'sorts by oldest sign in' do
+ scenario 'sorts by oldest sign in', :redis do
visit_members_list(sort: :oldest_sign_in)
expect(first_member).to include(developer.name)
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index f3ec80bb149..414838fa22e 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -52,6 +52,9 @@ describe 'issuable list', feature: true do
create(:issue, project: project, author: user)
else
create(:merge_request, source_project: project, source_branch: generate(:branch))
+ source_branch = FFaker::Name.name
+ pipeline = create(:ci_empty_pipeline, project: project, ref: source_branch, status: %w(running failed success).sample, sha: 'any')
+ create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: source_branch, head_pipeline: pipeline)
end
2.times do
diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
index b79667a1a4c..5820784f8e7 100644
--- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
+++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb
@@ -4,16 +4,18 @@ feature 'Merge immediately', :feature, :js do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
- let(:merge_request) do
+ let!(:merge_request) do
create(:merge_request_with_diffs, source_project: project,
author: user,
- title: 'Bug NS-04')
+ title: 'Bug NS-04',
+ head_pipeline: pipeline,
+ source_branch: pipeline.ref)
end
let(:pipeline) do
create(:ci_pipeline, project: project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch)
+ ref: 'master',
+ sha: project.repository.commit('master').id)
end
before { project.team << [user, :master] }
diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
index b33d7f90a31..11b6f0c0a64 100644
--- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb
@@ -16,7 +16,10 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do
ref: merge_request.source_branch)
end
- before { project.team << [user, :master] }
+ before do
+ project.add_master(user)
+ merge_request.update(head_pipeline_id: pipeline.id)
+ end
context 'when there is active pipeline for merge request' do
background do
diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
index 449a60c1d05..5b2798af32f 100644
--- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb
@@ -3,7 +3,7 @@ require 'rails_helper'
feature 'Mini Pipeline Graph', :js, :feature do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
- let(:merge_request) { create(:merge_request, source_project: project) }
+ let(:merge_request) { create(:merge_request, source_project: project, head_pipeline: pipeline) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running', sha: project.commit.id) }
let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') }
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
index 187e927dac4..cdda0542c51 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb
@@ -31,6 +31,8 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu
status: status)
end
+ before { merge_request.update(head_pipeline: pipeline) }
+
context 'when merge requests can only be merged if the pipeline succeeds' do
before do
project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true)
diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb
index 3fcdc9f2c61..ae799584c0f 100644
--- a/spec/features/merge_requests/widget_spec.rb
+++ b/spec/features/merge_requests/widget_spec.rb
@@ -91,6 +91,8 @@ describe 'Merge request', :feature, :js do
statuses: [commit_status])
create(:ci_build, :pending, pipeline: pipeline)
+ merge_request.update(head_pipeline: pipeline)
+
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
@@ -103,10 +105,15 @@ describe 'Merge request', :feature, :js do
context 'when merge request is in the blocked pipeline state' do
before do
- create(:ci_pipeline, project: project,
- sha: merge_request.diff_head_sha,
- ref: merge_request.source_branch,
- status: :manual)
+ pipeline = create(
+ :ci_pipeline,
+ project: project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch,
+ status: :manual
+ )
+
+ merge_request.update(head_pipeline: pipeline)
visit namespace_project_merge_request_path(project.namespace,
project,
@@ -131,6 +138,8 @@ describe 'Merge request', :feature, :js do
statuses: [commit_status])
create(:ci_build, :pending, pipeline: pipeline)
+ merge_request.update(head_pipeline: pipeline)
+
visit namespace_project_merge_request_path(project.namespace, project, merge_request)
end
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index 47d904b865b..a746a776548 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -16,6 +16,16 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
sha: merge_request.diff_head_sha
)
end
+ let(:path) { "files/ruby/popen.rb" }
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs
+ )
+ end
render_views
@@ -39,6 +49,12 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_merge_request(example.description, merged_merge_request)
end
+ it 'merge_requests/diff_comment.html.raw' do |example|
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+ render_merge_request(example.description, merge_request)
+ end
+
private
def render_merge_request(fixture_file_name, merge_request)
diff --git a/spec/javascripts/merge_request_notes_spec.js b/spec/javascripts/merge_request_notes_spec.js
new file mode 100644
index 00000000000..e54acfa8e44
--- /dev/null
+++ b/spec/javascripts/merge_request_notes_spec.js
@@ -0,0 +1,61 @@
+/* global Notes */
+
+import 'vendor/autosize';
+import '~/gl_form';
+import '~/lib/utils/text_utility';
+import '~/render_gfm';
+import '~/render_math';
+import '~/notes';
+
+describe('Merge request notes', () => {
+ window.gon = window.gon || {};
+ window.gl = window.gl || {};
+ gl.utils = gl.utils || {};
+
+ const fixture = 'merge_requests/diff_comment.html.raw';
+ preloadFixtures(fixture);
+
+ beforeEach(() => {
+ loadFixtures(fixture);
+ gl.utils.disableButtonIfEmptyField = _.noop;
+ window.project_uploads_path = 'http://test.host/uploads';
+ $('body').data('page', 'projects:merge_requests:show');
+ window.gon.current_user_id = $('.note:last').data('author-id');
+
+ return new Notes('', []);
+ });
+
+ describe('up arrow', () => {
+ it('edits last comment when triggered in main form', () => {
+ const upArrowEvent = $.Event('keydown');
+ upArrowEvent.which = 38;
+
+ spyOnEvent('.note:last .js-note-edit', 'click');
+
+ $('.js-note-text').trigger(upArrowEvent);
+
+ expect('click').toHaveBeenTriggeredOn('.note:last .js-note-edit');
+ });
+
+ it('edits last comment in discussion when triggered in discussion form', (done) => {
+ const upArrowEvent = $.Event('keydown');
+ upArrowEvent.which = 38;
+
+ spyOnEvent('.note-discussion .js-note-edit', 'click');
+
+ $('.js-discussion-reply-button').click();
+
+ setTimeout(() => {
+ expect(
+ $('.note-discussion .js-note-text'),
+ ).toExist();
+
+ $('.note-discussion .js-note-text').trigger(upArrowEvent);
+
+ expect('click').toHaveBeenTriggeredOn('.note-discussion .js-note-edit');
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 0d56bdd0ebd..3610a0354e8 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -130,6 +130,8 @@ describe 'cycle analytics events' do
end
before do
+ merge_request.update(head_pipeline: pipeline)
+
create(:ci_build, pipeline: pipeline, status: :success, author: user)
create(:ci_build, pipeline: pipeline, status: :success, author: user)
@@ -226,6 +228,8 @@ describe 'cycle analytics events' do
end
before do
+ merge_request.update(head_pipeline: pipeline)
+
create(:ci_build, pipeline: pipeline, status: :success, author: user)
create(:ci_build, pipeline: pipeline, status: :success, author: user)
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 688e731bf15..34f617e23a5 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -85,6 +85,7 @@ merge_requests:
- merge_requests_closing_issues
- metrics
- timelogs
+- head_pipeline
merge_request_diff:
- merge_request
pipelines:
@@ -102,6 +103,7 @@ pipelines:
- manual_actions
- artifacts
- pipeline_schedule
+- merge_requests
statuses:
- project
- pipeline
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 3af2a172e6d..d2ceb1cf9ae 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -158,6 +158,7 @@ MergeRequest:
- time_estimate
- last_edited_at
- last_edited_by_id
+- head_pipeline_id
MergeRequestDiff:
- id
- state
diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
new file mode 100644
index 00000000000..bd5f85b901d
--- /dev/null
+++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
+
+describe AddHeadPipelineForEachMergeRequest do
+ let(:migration) { described_class.new }
+
+ let!(:project) { create(:empty_project) }
+ let!(:forked_project_link) { create(:forked_project_link, forked_from_project: project) }
+ let!(:other_project) { forked_project_link.forked_to_project }
+
+ let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") }
+ let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
+ let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") }
+ let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") }
+
+ let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") }
+ let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") }
+ let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") }
+ let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") }
+
+ context "#up" do
+ context "when source_project and source_branch of pipeline are the same of merge request" do
+ it "sets head_pipeline_id of given merge requests" do
+ migration.up
+
+ expect(mr_1.reload.head_pipeline_id).to eq(pipeline_1.id)
+ expect(mr_2.reload.head_pipeline_id).to eq(pipeline_3.id)
+ expect(mr_3.reload.head_pipeline_id).to eq(pipeline_4.id)
+ expect(mr_4.reload.head_pipeline_id).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 208c8cb1c3d..06e990a0574 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1044,8 +1044,8 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') }
it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
- merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)
allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' }
+ merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref)
expect(pipeline.merge_requests).to eq([merge_request])
end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index c2ba012a0e6..d0b919efcf9 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -14,6 +14,7 @@ describe 'CycleAnalytics#test', feature: true do
issue = context.create(:issue, project: context.project)
merge_request = context.create_merge_request_closing_issue(issue)
pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project)
+ merge_request.update(head_pipeline: pipeline)
{ pipeline: pipeline, issue: issue }
end,
start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 6cf3dd30ead..ef349530761 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -760,13 +760,8 @@ describe MergeRequest, models: true do
describe '#head_pipeline' do
describe 'when the source project exists' do
it 'returns the latest pipeline' do
- pipeline = double(:ci_pipeline, ref: 'master')
-
- allow(subject).to receive(:diff_head_sha).and_return('123abc')
-
- expect(subject.source_project).to receive(:pipeline_for).
- with('master', '123abc').
- and_return(pipeline)
+ pipeline = create(:ci_empty_pipeline, project: subject.source_project, ref: 'master', status: 'running', sha: "123abc")
+ subject.update(head_pipeline: pipeline)
expect(subject.head_pipeline).to eq(pipeline)
end
@@ -1504,11 +1499,15 @@ describe MergeRequest, models: true do
describe '#mergeable_with_slash_command?' do
def create_pipeline(status)
- create(:ci_pipeline_with_one_job,
+ pipeline = create(:ci_pipeline_with_one_job,
project: project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha,
status: status)
+
+ merge_request.update(head_pipeline: pipeline)
+
+ pipeline
end
let(:project) { create(:project, :public, :repository, only_allow_merge_if_pipeline_succeeds: true) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 63e71f5ff2f..c7ddd17872b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -344,6 +344,35 @@ describe User, models: true do
end
end
+ describe '#update_tracked_fields!', :redis do
+ let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") }
+ let(:user) { create(:user) }
+
+ it 'writes trackable attributes' do
+ expect do
+ user.update_tracked_fields!(request)
+ end.to change { user.reload.current_sign_in_at }
+ end
+
+ it 'does not write trackable attributes when called a second time within the hour' do
+ user.update_tracked_fields!(request)
+
+ expect do
+ user.update_tracked_fields!(request)
+ end.not_to change { user.reload.current_sign_in_at }
+ end
+
+ it 'writes trackable attributes for a different user' do
+ user2 = create(:user)
+
+ user.update_tracked_fields!(request)
+
+ expect do
+ user2.update_tracked_fields!(request)
+ end.to change { user2.reload.current_sign_in_at }
+ end
+ end
+
shared_context 'user keys' do
let(:user) { create(:user) }
let!(:key) { create(:key, user: user) }
diff --git a/spec/policies/environment_policy_spec.rb b/spec/policies/environment_policy_spec.rb
index 0e15beaa5e8..650432520bb 100644
--- a/spec/policies/environment_policy_spec.rb
+++ b/spec/policies/environment_policy_spec.rb
@@ -33,7 +33,7 @@ describe EnvironmentPolicy do
let(:project) { create(:project, :public) }
before do
- project.add_master(user)
+ project.add_developer(user)
end
context 'when team member has ability to stop environment' do
diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb
index c5a45949841..d92daa345b3 100644
--- a/spec/requests/projects/cycle_analytics_events_spec.rb
+++ b/spec/requests/projects/cycle_analytics_events_spec.rb
@@ -122,6 +122,7 @@ describe 'cycle analytics events', api: true do
mr = create_merge_request_closing_issue(issue, commit_message: "References #{issue.to_reference}")
pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha)
+ mr.update(head_pipeline_id: pipeline.id)
pipeline.run
create(:ci_build, pipeline: pipeline, status: :success, author: user)
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index fa5014cee07..1ff1438ba06 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -34,6 +34,42 @@ describe Ci::CreatePipelineService, services: true do
it { expect(pipeline).to have_attributes(status: 'pending') }
it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) }
+ context '#update_merge_requests_head_pipeline' do
+ it 'updates head pipeline of each merge request' do
+ merge_request_1 = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
+ merge_request_2 = create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
+
+ head_pipeline = pipeline
+
+ expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline)
+ expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline)
+ end
+
+ context 'when there is no pipeline for source branch' do
+ it "does not update merge request head pipeline" do
+ merge_request = create(:merge_request, source_branch: 'other_branch', target_branch: "branch_1", source_project: project)
+
+ head_pipeline = pipeline
+
+ expect(merge_request.reload.head_pipeline).not_to eq(head_pipeline)
+ end
+ end
+
+ context 'when merge request target project is different from source project' do
+ let!(:target_project) { create(:empty_project) }
+ let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) }
+
+ it 'updates head pipeline for merge request' do
+ merge_request =
+ create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project, target_project: target_project)
+
+ head_pipeline = pipeline
+
+ expect(merge_request.reload.head_pipeline).to eq(head_pipeline)
+ end
+ end
+ end
+
context 'auto-cancel enabled' do
before do
project.update(auto_cancel_pending_pipelines: 'enabled')
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index cf773866a6f..1d0a28210fb 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -268,6 +268,24 @@ describe Ci::ProcessPipelineService, '#execute', :services do
end
end
+ context 'when there are only manual actions in stages' do
+ before do
+ create_build('image', stage_idx: 0, when: 'manual', allow_failure: true)
+ create_build('build', stage_idx: 1, when: 'manual', allow_failure: true)
+ create_build('deploy', stage_idx: 2, when: 'manual')
+ create_build('check', stage_idx: 3)
+
+ process_pipeline
+ end
+
+ it 'processes all jobs until blocking actions encountered' do
+ expect(all_builds_statuses).to eq(%w[manual manual manual created])
+ expect(all_builds_names).to eq(%w[image build deploy check])
+
+ expect(pipeline.reload).to be_blocked
+ end
+ end
+
context 'when blocking manual actions are defined' do
before do
create_build('code:test', stage_idx: 0)
@@ -441,6 +459,10 @@ describe Ci::ProcessPipelineService, '#execute', :services do
builds.pluck(:name)
end
+ def all_builds_names
+ all_builds.pluck(:name)
+ end
+
def builds_statuses
builds.pluck(:status)
end
diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
index 769b3193275..3ef5135e6a3 100644
--- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb
@@ -82,6 +82,10 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
sha: merge_request_head, status: 'success')
end
+ before do
+ mr_merge_if_green_enabled.update(head_pipeline: triggering_pipeline)
+ end
+
it "merges all merge requests with merge when the pipeline succeeds enabled" do
expect(MergeWorker).to receive(:perform_async)
service.trigger(triggering_pipeline)
@@ -124,6 +128,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
sha: mr_conflict.diff_head_sha, status: 'success')
end
+ before { mr_conflict.update(head_pipeline: conflict_pipeline) }
+
it 'does not merge the merge request' do
expect(MergeWorker).not_to receive(:perform_async)
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 31487c0f794..07f5440cc36 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -174,11 +174,13 @@ describe MergeRequests::UpdateService, services: true do
context 'with active pipeline' do
before do
service_mock = double
- create(:ci_pipeline_with_one_job,
+ pipeline = create(:ci_pipeline_with_one_job,
project: project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
+ merge_request.update(head_pipeline: pipeline)
+
expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user).
and_return(service_mock)
expect(service_mock).to receive(:execute).with(merge_request)
diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb
index 5dbc0da95c2..ef71125c0b6 100644
--- a/spec/workers/pipeline_metrics_worker_spec.rb
+++ b/spec/workers/pipeline_metrics_worker_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe PipelineMetricsWorker do
let(:project) { create(:project, :repository) }
- let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref, head_pipeline: pipeline) }
let(:pipeline) do
create(:ci_empty_pipeline,