Commit cbbe6d5d authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into bootstrap4

parents 644246da ba58a66a
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
return { return {
pipelineId: '', pipelineId: '',
endpoint: '', endpoint: '',
cancelingPipeline: null,
}; };
}, },
computed: { computed: {
...@@ -64,6 +65,7 @@ ...@@ -64,6 +65,7 @@
}, },
onSubmit() { onSubmit() {
eventHub.$emit('postAction', this.endpoint); eventHub.$emit('postAction', this.endpoint);
this.cancelingPipeline = this.pipelineId;
}, },
}, },
}; };
...@@ -106,6 +108,7 @@ ...@@ -106,6 +108,7 @@
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath" :auto-devops-help-path="autoDevopsHelpPath"
:view-type="viewType" :view-type="viewType"
:canceling-pipeline="cancelingPipeline"
/> />
<modal <modal
......
...@@ -46,12 +46,16 @@ ...@@ -46,12 +46,16 @@
type: String, type: String,
required: true, required: true,
}, },
cancelingPipeline: {
type: String,
required: false,
default: null,
},
}, },
pipelinesTable: PIPELINES_TABLE, pipelinesTable: PIPELINES_TABLE,
data() { data() {
return { return {
isRetrying: false, isRetrying: false,
isCancelling: false,
}; };
}, },
computed: { computed: {
...@@ -227,12 +231,14 @@ ...@@ -227,12 +231,14 @@
isChildView() { isChildView() {
return this.viewType === 'child'; return this.viewType === 'child';
}, },
isCancelling() {
return this.cancelingPipeline === this.pipeline.id;
},
}, },
methods: { methods: {
handleCancelClick() { handleCancelClick() {
this.isCancelling = true;
eventHub.$emit('openConfirmationModal', { eventHub.$emit('openConfirmationModal', {
pipelineId: this.pipeline.id, pipelineId: this.pipeline.id,
endpoint: this.pipeline.cancel_path, endpoint: this.pipeline.cancel_path,
......
...@@ -26,11 +26,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -26,11 +26,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Extend the standard message generation to accept our custom exception # Extend the standard message generation to accept our custom exception
def failure_message def failure_message
exception = env["omniauth.error"] exception = request.env["omniauth.error"]
error = exception.error_reason if exception.respond_to?(:error_reason) error = exception.error_reason if exception.respond_to?(:error_reason)
error ||= exception.error if exception.respond_to?(:error) error ||= exception.error if exception.respond_to?(:error)
error ||= exception.message if exception.respond_to?(:message) error ||= exception.message if exception.respond_to?(:message)
error ||= env["omniauth.error.type"].to_s error ||= request.env["omniauth.error.type"].to_s
error.to_s.humanize if error error.to_s.humanize if error
end end
......
...@@ -5,9 +5,10 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll ...@@ -5,9 +5,10 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll
before_action :authorize_create_cluster!, only: [:create] before_action :authorize_create_cluster!, only: [:create]
def create def create
Clusters::Applications::ScheduleInstallationService.new(project, current_user, application = @application_class.find_or_create_by!(cluster: @cluster)
application_class: @application_class,
cluster: @cluster).execute Clusters::Applications::ScheduleInstallationService.new(project, current_user).execute(application)
head :no_content head :no_content
rescue StandardError rescue StandardError
head :bad_request head :bad_request
......
...@@ -63,7 +63,7 @@ module CommitsHelper ...@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link # Returns a link formatted as a commit branch link
def commit_branch_link(url, text) def commit_branch_link(url, text)
link_to(url, class: 'badge badge-gray ref-name branch-link') do link_to(url, class: 'badge badge-gray ref-name branch-link') do
sprite_icon('fork', size: 12, css_class: 'fork-svg') + "#{text}" sprite_icon('branch', size: 12, css_class: 'fork-svg') + "#{text}"
end end
end end
...@@ -77,7 +77,7 @@ module CommitsHelper ...@@ -77,7 +77,7 @@ module CommitsHelper
# Returns a link formatted as a commit tag link # Returns a link formatted as a commit tag link
def commit_tag_link(url, text) def commit_tag_link(url, text)
link_to(url, class: 'badge badge-gray ref-name') do link_to(url, class: 'badge badge-gray ref-name') do
icon('tag', class: 'append-right-5') + "#{text}" sprite_icon('tag', size: 12, css_class: 'append-right-5 vertical-align-middle') + "#{text}"
end end
end end
......
...@@ -56,6 +56,14 @@ module Emails ...@@ -56,6 +56,14 @@ module Emails
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end end
def merge_request_unmergeable_email(recipient_id, merge_request_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@reasons = MergeRequestPresenter.new(@merge_request, current_user: current_user).unmergeable_reasons
mail_answer_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id, reason))
end
def resolved_all_discussions_email(recipient_id, merge_request_id, resolved_by_user_id, reason = nil) def resolved_all_discussions_email(recipient_id, merge_request_id, resolved_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id) setup_merge_request_mail(merge_request_id, recipient_id)
......
...@@ -32,7 +32,7 @@ module ResolvableNote ...@@ -32,7 +32,7 @@ module ResolvableNote
# Keep this method in sync with the `potentially_resolvable` scope # Keep this method in sync with the `potentially_resolvable` scope
def potentially_resolvable? def potentially_resolvable?
RESOLVABLE_TYPES.include?(self.class.name) && noteable.supports_resolvable_notes? RESOLVABLE_TYPES.include?(self.class.name) && noteable&.supports_resolvable_notes?
end end
# Keep this method in sync with the `resolvable` scope # Keep this method in sync with the `resolvable` scope
......
...@@ -104,24 +104,35 @@ class MergeRequest < ActiveRecord::Base ...@@ -104,24 +104,35 @@ class MergeRequest < ActiveRecord::Base
state_machine :merge_status, initial: :unchecked do state_machine :merge_status, initial: :unchecked do
event :mark_as_unchecked do event :mark_as_unchecked do
transition [:can_be_merged, :cannot_be_merged] => :unchecked transition [:can_be_merged, :unchecked] => :unchecked
transition [:cannot_be_merged, :cannot_be_merged_recheck] => :cannot_be_merged_recheck
end end
event :mark_as_mergeable do event :mark_as_mergeable do
transition [:unchecked, :cannot_be_merged] => :can_be_merged transition [:unchecked, :cannot_be_merged_recheck] => :can_be_merged
end end
event :mark_as_unmergeable do event :mark_as_unmergeable do
transition [:unchecked, :can_be_merged] => :cannot_be_merged transition [:unchecked, :cannot_be_merged_recheck] => :cannot_be_merged
end end
state :unchecked state :unchecked
state :cannot_be_merged_recheck
state :can_be_merged state :can_be_merged
state :cannot_be_merged state :cannot_be_merged
around_transition do |merge_request, transition, block| around_transition do |merge_request, transition, block|
Gitlab::Timeless.timeless(merge_request, &block) Gitlab::Timeless.timeless(merge_request, &block)
end end
after_transition unchecked: :cannot_be_merged do |merge_request, transition|
NotificationService.new.merge_request_unmergeable(merge_request)
TodoService.new.merge_request_became_unmergeable(merge_request)
end
def check_state?(merge_status)
[:unchecked, :cannot_be_merged_recheck].include?(merge_status.to_sym)
end
end end
validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?] validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
...@@ -327,6 +338,16 @@ class MergeRequest < ActiveRecord::Base ...@@ -327,6 +338,16 @@ class MergeRequest < ActiveRecord::Base
update_column(:merge_jid, jid) update_column(:merge_jid, jid)
end end
def merge_participants
participants = [author]
if merge_when_pipeline_succeeds? && !participants.include?(merge_user)
participants << merge_user
end
participants
end
def first_commit def first_commit
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end end
...@@ -602,7 +623,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -602,7 +623,7 @@ class MergeRequest < ActiveRecord::Base
end end
def check_if_can_be_merged def check_if_can_be_merged
return unless unchecked? && Gitlab::Database.read_write? return unless self.class.state_machines[:merge_status].check_state?(merge_status) && Gitlab::Database.read_write?
can_be_merged = can_be_merged =
!broken? && project.repository.can_be_merged?(diff_head_sha, target_branch) !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch)
......
...@@ -1431,8 +1431,8 @@ class Project < ActiveRecord::Base ...@@ -1431,8 +1431,8 @@ class Project < ActiveRecord::Base
self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end end
def open_issues_count def open_issues_count(current_user = nil)
Projects::OpenIssuesCountService.new(self).count Projects::OpenIssuesCountService.new(self, current_user).count
end end
def open_merge_requests_count def open_merge_requests_count
......
...@@ -115,6 +115,6 @@ class DroneCiService < CiService ...@@ -115,6 +115,6 @@ class DroneCiService < CiService
def merge_request_valid?(data) def merge_request_valid?(data)
data[:object_attributes][:state] == 'opened' && data[:object_attributes][:state] == 'opened' &&
data[:object_attributes][:merge_status] == 'unchecked' MergeRequest.state_machines[:merge_status].check_state?(data[:object_attributes][:merge_status])
end end
end end
...@@ -20,6 +20,17 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -20,6 +20,17 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end end
end end
def unmergeable_reasons
strong_memoize(:unmergeable_reasons) do
reasons = []
reasons << "no commits" if merge_request.has_no_commits?
reasons << "source branch is missing" unless merge_request.source_branch_exists?
reasons << "target branch is missing" unless merge_request.target_branch_exists?
reasons << "has merge conflicts" unless merge_request.project.repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch)
reasons
end
end
def cancel_merge_when_pipeline_succeeds_path def cancel_merge_when_pipeline_succeeds_path
if can_cancel_merge_when_pipeline_succeeds?(current_user) if can_cancel_merge_when_pipeline_succeeds?(current_user)
cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request) cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request)
......
module Clusters module Clusters
module Applications module Applications
class ScheduleInstallationService < ::BaseService class ScheduleInstallationService < ::BaseService
def execute def execute(application)
application_class.find_or_create_by!(cluster: cluster).try do |application| application.make_scheduled!
application.make_scheduled!
ClusterInstallAppWorker.perform_async(application.name, application.id)
end
end
private
def application_class
params[:application_class]
end
def cluster ClusterInstallAppWorker.perform_async(application.name, application.id)
params[:cluster]
end end
end end
end end
......
...@@ -24,11 +24,7 @@ module MergeRequests ...@@ -24,11 +24,7 @@ module MergeRequests
pipeline_merge_requests(pipeline) do |merge_request| pipeline_merge_requests(pipeline) do |merge_request|
next unless merge_request.merge_when_pipeline_succeeds? next unless merge_request.merge_when_pipeline_succeeds?
next unless merge_request.mergeable?
unless merge_request.mergeable?
todo_service.merge_request_became_unmergeable(merge_request)
next
end
merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params) merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params)
end end
......
...@@ -3,6 +3,12 @@ module MergeRequests ...@@ -3,6 +3,12 @@ module MergeRequests
def execute(oldrev, newrev, ref) def execute(oldrev, newrev, ref)
return true unless Gitlab::Git.branch_ref?(ref) return true unless Gitlab::Git.branch_ref?(ref)
do_execute(oldrev, newrev, ref)
end
private
def do_execute(oldrev, newrev, ref)
@oldrev, @newrev = oldrev, newrev @oldrev, @newrev = oldrev, newrev
@branch_name = Gitlab::Git.ref_name(ref) @branch_name = Gitlab::Git.ref_name(ref)
...@@ -28,8 +34,6 @@ module MergeRequests ...@@ -28,8 +34,6 @@ module MergeRequests
true true
end end
private
def close_upon_missing_source_branch_ref def close_upon_missing_source_branch_ref
# MergeRequest#reload_diff ignores not opened MRs. This means it won't # MergeRequest#reload_diff ignores not opened MRs. This means it won't
# create an `empty` diff for `closed` MRs without a source branch, keeping # create an `empty` diff for `closed` MRs without a source branch, keeping
......
...@@ -18,6 +18,10 @@ module NotificationRecipientService ...@@ -18,6 +18,10 @@ module NotificationRecipientService
Builder::NewNote.new(*a).notification_recipients Builder::NewNote.new(*a).notification_recipients
end end
def self.build_merge_request_unmergeable_recipients(*a)
Builder::MergeRequestUnmergeable.new(*a).notification_recipients
end
module Builder module Builder
class Base class Base
def initialize(*) def initialize(*)
...@@ -330,5 +334,26 @@ module NotificationRecipientService ...@@ -330,5 +334,26 @@ module NotificationRecipientService
note.author note.author
end end
end end
class MergeRequestUnmergeable < Base
attr_reader :target
def initialize(merge_request)
@target = merge_request
end
def build!
target.merge_participants.each do |user|
add_recipients(user, :participating, nil)
end
end
def custom_action
:unmergeable_merge_request
end
def acting_user
nil
end
end
end end
end end
...@@ -129,6 +129,7 @@ class NotificationService ...@@ -129,6 +129,7 @@ class NotificationService
# When create a merge request we should send an email to: # When create a merge request we should send an email to:
# #
# * mr author
# * mr assignee if their notification level is not Disabled # * mr assignee if their notification level is not Disabled
# * project team members with notification level higher then Participating # * project team members with notification level higher then Participating
# * watchers of the mr's labels # * watchers of the mr's labels
...@@ -148,6 +149,15 @@ class NotificationService ...@@ -148,6 +149,15 @@ class NotificationService
end end
end end
# When a merge request is found to be unmergeable, we should send an email to:
#
# * mr author
# * mr merge user if set
#
def merge_request_unmergeable(merge_request)
merge_request_unmergeable_email(merge_request)
end
# When merge request text is updated, we should send an email to: # When merge request text is updated, we should send an email to:
# #
# * newly mentioned project team members with notification level higher than Participating # * newly mentioned project team members with notification level higher than Participating
...@@ -484,6 +494,14 @@ class NotificationService ...@@ -484,6 +494,14 @@ class NotificationService
end end
end end
def merge_request_unmergeable_email(merge_request)
recipients = NotificationRecipientService.build_merge_request_unmergeable_recipients(merge_request)
recipients.each do |recipient|
mailer.merge_request_unmergeable_email(recipient.user.id, merge_request.id).deliver_later
end
end
def mailer def mailer
Notify Notify
end end
......
...@@ -2,14 +2,42 @@ module Projects ...@@ -2,14 +2,42 @@ module Projects
# Service class for counting and caching the number of open issues of a # Service class for counting and caching the number of open issues of a
# project. # project.
class OpenIssuesCountService < Projects::CountService class OpenIssuesCountService < Projects::CountService
include Gitlab::Utils::StrongMemoize
def initialize(project, user = nil)
@user = user
super(project)
end
def cache_key_name def cache_key_name
'open_issues_count' public_only? ? 'public_open_issues_count' : 'total_open_issues_count'
end
def public_only?
!user_is_at_least_reporter?
end
def relation_for_count
self.class.query(@project, public_only: public_only?)
end
def user_is_at_least_reporter?
strong_memoize(:user_is_at_least_reporter) do
@user && @project.team.member?(@user, Gitlab::Access::REPORTER)
end
end end
def self.query(project_ids) # We only show total issues count for reporters
# We don't include confidential issues in this number since this would # which are allowed to view confidential issues
# expose the number of confidential issues to non project members. # This will still show a discrepancy on issues number but should be less than before.
Issue.opened.public_only.where(project: project_ids) # Check https://gitlab.com/gitlab-org/gitlab-ce/issues/38418 description.
def self.query(projects, public_only: true)
if public_only
Issue.opened.public_only.where(project: projects)
else
Issue.opened.where(project: projects)
end
end end
end end
end end
...@@ -98,12 +98,12 @@ class TodoService ...@@ -98,12 +98,12 @@ class TodoService
# When a build fails on the HEAD of a merge request we should: # When a build fails on the HEAD of a merge request we should:
# #
# * create a todo for author of MR to fix it # * create a todo for each merge participant
# * create a todo for merge_user to keep an eye on it
# #
def merge_request_build_failed(merge_request) def merge_request_build_failed(merge_request)
create_build_failed_todo(merge_request, merge_request.author) merge_request.merge_participants.each do |user|
create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? create_build_failed_todo(merge_request, user)
end
end end
# When a new commit is pushed to a merge request we should: # When a new commit is pushed to a merge request we should:
...@@ -116,20 +116,22 @@ class TodoService ...@@ -116,20 +116,22 @@ class TodoService
# When a build is retried to a merge request we should: # When a build is retried to a merge request we should:
# #
# * mark all pending todos related to the merge request for the author as done # * mark all pending todos related to the merge request as done for each merge participant
# * mark all pending todos related to the merge request for the merge_user as done
# #
def merge_request_build_retried(merge_request) def merge_request_build_retried(merge_request)
mark_pending_todos_as_done(merge_request, merge_request.author) merge_request.merge_participants.each do |user|
mark_pending_todos_as_done(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? mark_pending_todos_as_done(merge_request, user)
end
end end
# When a merge request could not be automatically merged due to its unmergeable state we should: # When a merge request could not be merged due to its unmergeable state we should:
# #
# * create a todo for a merge_user # * create a todo for each merge participant
# #
def merge_request_became_unmergeable(merge_request) def merge_request_became_unmergeable(merge_request)
create_unmergeable_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? merge_request.merge_participants.each do |user|
create_unmergeable_todo(merge_request, user)
end
end end
# When create a note we should: # When create a note we should:
...@@ -275,9 +277,9 @@ class TodoService ...@@ -275,9 +277,9 @@ class TodoService
create_todos(todo_author, attributes) create_todos(todo_author, attributes)
end end
def create_unmergeable_todo(merge_request, merge_user) def create_unmergeable_todo(merge_request, todo_author)
attributes = attributes_for_todo(merge_request.project, merge_request, merge_user, Todo::UNMERGEABLE) attributes = attributes_for_todo(merge_request.project, merge_request, todo_author, Todo::UNMERGEABLE)
create_todos(merge_user, attributes) create_todos(todo_author, attributes)
end end
def attributes_for_target(target) def attributes_for_target(target)
......
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
= _('Issues') = _('Issues')
- if @project.issues_enabled? - if @project.issues_enabled?
%span.badge.badge-pill.count.issue_counter %span.badge.badge-pill.count.issue_counter
= number_with_delimiter(@project.open_issues_count) = number_with_delimiter(@project.open_issues_count(current_user))
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items
= nav_link(controller: :issues, html_options: { class: "fly-out-top-item" } ) do = nav_link(controller: :issues, html_options: { class: "fly-out-top-item" } ) do
...@@ -98,7 +98,7 @@ ...@@ -98,7 +98,7 @@
= _('Issues') = _('Issues')
- if @project.issues_enabled? - if @project.issues_enabled?
%span.badge.badge-pill.count.issue_counter.fly-out-badge %span.badge.badge-pill.count.issue_counter.fly-out-badge
= number_with_delimiter(@project.open_issues_count) = number_with_delimiter(@project.open_issues_count(current_user))
%li.divider.fly-out-top-item %li.divider.fly-out-top-item
= nav_link(controller: :issues, action: :index) do = nav_link(controller: :issues, action: :index) do
= link_to project_issues_path(@project), title: 'Issues' do = link_to project_issues_path(@project), title: 'Issues' do
......
%p
Merge Request #{link_to @merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request)} can no longer be merged due to the following #{pluralize(@reasons, 'reason')}:
%ul
- @reasons.each do |reason|
%li= reason
Merge Request #{@merge_request.to_reference} can no longer be merged due to the following #{pluralize(@reasons, 'reason')}:
- @reasons.each do |reason|
* #{reason}
Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
Author: #{@merge_request.author_name}
Assignee: #{@merge_request.assignee_name}
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
- if @branches.any? || @tags.any? || @tags_limit_exceeded - if @branches.any? || @tags.any? || @tags_limit_exceeded
%span %span
= link_to "…", "#", class: "js-details-expand label badge-gray" = link_to "#", class: "js-details-expand label label-gray ref-name" do
= sprite_icon('ellipsis_h', size: 12, css_class: 'vertical-align-middle')
%span.js-details-content.hide %span.js-details-content.hide
= commit_branches_links(@project, @branches) = commit_branches_links(@project, @branches)
- if @tags_limit_exceeded - if @tags_limit_exceeded
......
#modal_merge_info.modal #modal_merge_info.modal{ tabindex: '-1' }
.modal-dialog .modal-dialog
.modal-content .modal-content
.modal-header .modal-header
......
---
title: Closes MR check out branch modal with escape
merge_request: Jacopo Beschi @jacopo-beschi
author: 19050
type: added
---
title: Updated icons for branch and tag names in commit details
merge_request: 18953
author: Constance Okoghenun
type: changed
---
title: Fix issue count on sidebar
merge_request:
author:
type: other
---
title: Fix resolvable check if note's commit could not be found.
merge_request:
author:
type: fixed
---
title: Support restoring repositories into gitaly
merge_request:
author:
type: changed
---
title: When MR becomes unmergeable, notify and create todo for author and merge user
merge_request: 18042
author:
type: added
---
title: Fix backup creation and restore for specific Rake tasks
merge_request:
author:
type: fixed
...@@ -306,7 +306,10 @@ module.exports = { ...@@ -306,7 +306,10 @@ module.exports = {
host: DEV_SERVER_HOST, host: DEV_SERVER_HOST,
port: DEV_SERVER_PORT, port: DEV_SERVER_PORT,
disableHostCheck: true, disableHostCheck: true,
headers: { 'Access-Control-Allow-Origin': '*' }, headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
},
stats: 'errors-only', stats: 'errors-only',
hot: DEV_SERVER_LIVERELOAD, hot: DEV_SERVER_LIVERELOAD,
inline: DEV_SERVER_LIVERELOAD, inline: DEV_SERVER_LIVERELOAD,
......
...@@ -467,7 +467,7 @@ POST /projects/:id/issues ...@@ -467,7 +467,7 @@ POST /projects/:id/issues
| `description` | string | no | The description of an issue | | `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. | | `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. |
| `assignee_ids` | Array[integer] | no | The ID of the users to assign issue | | `assignee_ids` | Array[integer] | no | The ID of the users to assign issue |
| `milestone_id` | integer | no | The ID of a milestone to assign issue | | `milestone_id` | integer | no | The global ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue | | `labels` | string | no | Comma-separated label names for an issue |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | | `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
...@@ -548,7 +548,7 @@ PUT /projects/:id/issues/:issue_iid ...@@ -548,7 +548,7 @@ PUT /projects/:id/issues/:issue_iid
| `description` | string | no | The description of an issue | | `description` | string | no | The description of an issue |
| `confidential` | boolean | no | Updates an issue to be confidential | | `confidential` | boolean | no | Updates an issue to be confidential |
| `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. | | `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. |
| `milestone_id` | integer | no | The ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.| | `milestone_id` | integer | no | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.|
| `labels` | string | no | Comma-separated label names for an issue. Set to an empty string to unassign all labels. | | `labels` | string | no | Comma-separated label names for an issue. Set to an empty string to unassign all labels. |
| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | | `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) |
......
...@@ -539,7 +539,7 @@ POST /projects/:id/merge_requests ...@@ -539,7 +539,7 @@ POST /projects/:id/merge_requests
| `description` | string | no | Description of MR | | `description` | string | no | Description of MR |
| `target_project_id` | integer | no | The target project (numeric id) | | `target_project_id` | integer | no | The target project (numeric id) |
| `labels` | string | no | Labels for MR as a comma-separated list | | `labels` | string | no | Labels for MR as a comma-separated list |
| `milestone_id` | integer | no | The ID of a milestone | | `milestone_id` | integer | no | The global ID of a milestone |
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging | | `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging |
| `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | | `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch |
...@@ -622,7 +622,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid ...@@ -622,7 +622,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| `target_branch` | string | no | The target branch | | `target_branch` | string | no | The target branch |
| `title` | string | no | Title of MR | | `title` | string | no | Title of MR |
| `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. | | `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. |
| `milestone_id` | integer | no | The ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.| | `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.|
| `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. | | `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. |
| `description` | string | no | Description of MR | | `description` | string | no | Description of MR |
| `state_event` | string | no | New state (close/reopen) | | `state_event` | string | no | New state (close/reopen) |
......
...@@ -5,10 +5,10 @@ GitLab CI and Docker. ...@@ -5,10 +5,10 @@ GitLab CI and Docker.
First, you need GitLab Runner with [docker-in-docker executor][dind]. First, you need GitLab Runner with [docker-in-docker executor][dind].
Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `codequality`: Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `code_quality`:
```yaml ```yaml
codequality: code_quality:
image: docker:stable image: docker:stable
variables: variables:
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
...@@ -23,20 +23,27 @@ codequality: ...@@ -23,20 +23,27 @@ codequality:
--volume /var/run/docker.sock:/var/run/docker.sock --volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts: artifacts:
paths: [codeclimate.json] paths: [gl-code-quality-report.json]
``` ```
The above example will create a `codequality` job in your CI/CD pipeline which The above example will create a `code_quality` job in your CI/CD pipeline which
will scan your source code for code quality issues. The report will be saved will scan your source code for code quality issues. The report will be saved
as an artifact that you can later download and analyze. as an artifact that you can later download and analyze.
TIP: **Tip:** TIP: **Tip:**
Starting with [GitLab Starter][ee] 9.3, this information will Starting with [GitLab Starter][ee] 9.3, this information will
be automatically extracted and shown right in the merge request widget. To do be automatically extracted and shown right in the merge request widget. To do
so, the CI/CD job must be named `codequality` and the artifact path must be so, the CI/CD job must be named `code_quality` and the artifact path must be
`codeclimate.json`. `gl-code-quality-report.json`.
[Learn more on code quality diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html). [Learn more on code quality diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html).
CAUTION: **Caution:**
Code Quality was previously using `codeclimate` and `codequality` for job name and
`codeclimate.json` for the artifact name. While these old names
are still maintained they have been deprecated with GitLab 11.0 and may be removed
in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml`
configuration to reflect that change.
[cli]: https://github.com/codeclimate/codeclimate [cli]: https://github.com/codeclimate/codeclimate
[dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor [dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor
[ee]: https://about.gitlab.com/products/ [ee]: https://about.gitlab.com/products/
...@@ -7,10 +7,10 @@ for Vulnerability Static Analysis for containers. ...@@ -7,10 +7,10 @@ for Vulnerability Static Analysis for containers.
All you need is a GitLab Runner with the Docker executor (the shared Runners on All you need is a GitLab Runner with the Docker executor (the shared Runners on
GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`, GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
called `sast:container`: called `container_scanning`:
```yaml ```yaml
sast:container: container_scanning:
image: docker:stable image: docker:stable
variables: variables:
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
...@@ -34,12 +34,12 @@ sast:container: ...@@ -34,12 +34,12 @@ sast:container:
- retries=0 - retries=0
- echo "Waiting for clair daemon to start" - echo "Waiting for clair daemon to start"
- while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
- ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
artifacts: artifacts:
paths: [gl-sast-container-report.json] paths: [gl-container-scanning-report.json]
``` ```
The above example will create a `sast:container` job in your CI/CD pipeline, pull The above example will create a `container_scanning` job in your CI/CD pipeline, pull
the image from the [Container Registry](../../user/project/container_registry.md) the image from the [Container Registry](../../user/project/container_registry.md)
(whose name is defined from the two `CI_APPLICATION_` variables) and scan it (whose name is defined from the two `CI_APPLICATION_` variables) and scan it
for possible vulnerabilities. The report will be saved as an artifact that you for possible vulnerabilities. The report will be saved as an artifact that you
...@@ -52,8 +52,15 @@ in our case its named `clair-whitelist.yml`. ...@@ -52,8 +52,15 @@ in our case its named `clair-whitelist.yml`.
TIP: **Tip:** TIP: **Tip:**
Starting with [GitLab Ultimate][ee] 10.4, this information will Starting with [GitLab Ultimate][ee] 10.4, this information will
be automatically extracted and shown right in the merge request widget. To do be automatically extracted and shown right in the merge request widget. To do
so, the CI/CD job must be named `sast:container` and the artifact path must be so, the CI/CD job must be named `container_scanning` and the artifact path must be
`gl-sast-container-report.json`. `gl-container-scanning-report.json`.
[Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html). [Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html).
CAUTION: **Caution:**
Container Scanning was previously using `sast:container` for job name and
`gl-sast-container-report.json` for the artifact name. While these old names
are still maintained they have been deprecated with GitLab 11.0 and may be removed
in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml`
configuration to reflect that change.
[ee]: https://about.gitlab.com/products/ [ee]: https://about.gitlab.com/products/
...@@ -27,8 +27,8 @@ Please see the [installation from source guide](installation.md) and the [instal ...@@ -27,8 +27,8 @@ Please see the [installation from source guide](installation.md) and the [instal
### Non-Unix operating systems such as Windows ### Non-Unix operating systems such as Windows
GitLab is developed for Unix operating systems. GitLab is developed for Unix operating systems.
GitLab does **not** run on Windows and we have no plans of supporting it in the near future. It does **not** run on Windows, and we have no plans to support it in the near future. For the latest development status view this [issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/46567).
Please consider using a virtual machine to run GitLab. Please consider using a virtual machine to run GitLab.
## Ruby versions ## Ruby versions
......
...@@ -31,8 +31,8 @@ sudo apt-get install -y rsync ...@@ -31,8 +31,8 @@ sudo apt-get install -y rsync
>**Note:** >**Note:**
In GitLab 9.2 the timestamp format was changed from `EPOCH_YYYY_MM_DD` to In GitLab 9.2 the timestamp format was changed from `EPOCH_YYYY_MM_DD` to
`EPOCH_YYYY_MM_DD_GitLab version`, for example `1493107454_2017_04_25` `EPOCH_YYYY_MM_DD_GitLab_version`, for example `1493107454_2018_04_25`
would become `1493107454_2017_04_25_9.1.0`. would become `1493107454_2018_04_25_10.6.4-ce`.
The backup archive will be saved in `backup_path`, which is specified in the The backup archive will be saved in `backup_path`, which is specified in the
`config/gitlab.yml` file. `config/gitlab.yml` file.
...@@ -41,8 +41,8 @@ identifies the time at which each backup was created, plus the GitLab version. ...@@ -41,8 +41,8 @@ identifies the time at which each backup was created, plus the GitLab version.
The timestamp is needed if you need to restore GitLab and multiple backups are The timestamp is needed if you need to restore GitLab and multiple backups are
available. available.
For example, if the backup name is `1493107454_2017_04_25_9.1.0_gitlab_backup.tar`, For example, if the backup name is `1493107454_2018_04_25_10.6.4-ce_gitlab_backup.tar`,
then the timestamp is `1493107454_2017_04_25_9.1.0`. then the timestamp is `1493107454_2018_04_25_10.6.4-ce`.
### Creating a backup of the GitLab system ### Creating a backup of the GitLab system
...@@ -574,7 +574,7 @@ First make sure your backup tar file is in the backup directory described in the ...@@ -574,7 +574,7 @@ First make sure your backup tar file is in the backup directory described in the
`/var/opt/gitlab/backups`. `/var/opt/gitlab/backups`.
```shell ```shell
sudo cp 1493107454_2017_04_25_9.1.0_gitlab_backup.tar /var/opt/gitlab/backups/ sudo cp 11493107454_2018_04_25_10.6.4-ce_gitlab_backup.tar /var/opt/gitlab/backups/
``` ```
Stop the processes that are connected to the database. Leave the rest of GitLab Stop the processes that are connected to the database. Leave the rest of GitLab
...@@ -592,7 +592,7 @@ restore: ...@@ -592,7 +592,7 @@ restore:
```shell ```shell
# This command will overwrite the contents of your GitLab database! # This command will overwrite the contents of your GitLab database!
sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2017_04_25_9.1.0 sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2018_04_25_10.6.4-ce
``` ```
Next, restore `/etc/gitlab/gitlab-secrets.json` if necessary as mentioned above. Next, restore `/etc/gitlab/gitlab-secrets.json` if necessary as mentioned above.
......
...@@ -220,8 +220,8 @@ tests, it's up to you to add them. ...@@ -220,8 +220,8 @@ tests, it's up to you to add them.
### Auto Code Quality ### Auto Code Quality
Auto Code Quality uses the open source Auto Code Quality uses the
[`codeclimate` image](https://hub.docker.com/r/codeclimate/codeclimate/) to run [Code Quality image](https://gitlab.com/gitlab-org/security-products/codequality) to run
static analysis and other code checks on the current code. The report is static analysis and other code checks on the current code. The report is
created, and is uploaded as an artifact which you can later download and check created, and is uploaded as an artifact which you can later download and check
out. out.
......
...@@ -126,10 +126,10 @@ Next, a pipeline needs to be triggered. Since the test project doesn't have a ...@@ -126,10 +126,10 @@ Next, a pipeline needs to be triggered. Since the test project doesn't have a
manually visit `https://gitlab.com/<username>/minimal-ruby-app/pipelines/new`, manually visit `https://gitlab.com/<username>/minimal-ruby-app/pipelines/new`,
where `<username>` is your username. where `<username>` is your username.
This will create a new pipeline with several jobs: `build`, `test`, `codequality`, This will create a new pipeline with several jobs: `build`, `test`, `code_quality`,
and `production`. The `build` job will create a Docker image with your new and `production`. The `build` job will create a Docker image with your new
change and push it to the Container Registry. The `test` job will test your change and push it to the Container Registry. The `test` job will test your
changes, whereas the `codequality` job will run static analysis on your changes. changes, whereas the `code_quality` job will run static analysis on your changes.
Finally, the `production` job will deploy your changes to a production application. Finally, the `production` job will deploy your changes to a production application.
Once the deploy job succeeds you should be able to see your application by Once the deploy job succeeds you should be able to see your application by
......
This document was moved to [user/project/merge_requests](../user/project/merge_requests.md). This document was moved to [user/project/merge_requests/index.md](../user/project/merge_requests/index.md).
...@@ -106,6 +106,10 @@ by yourself (except when an issue is due). You will only receive automatic ...@@ -106,6 +106,10 @@ by yourself (except when an issue is due). You will only receive automatic
notifications when somebody else comments or adds changes to the ones that notifications when somebody else comments or adds changes to the ones that
you've created or mentions you. you've created or mentions you.
If a merge request becomes unmergeable, its author will be notified about the cause.
If a user has also set the merge request to automatically merge once pipeline succeeds,
then that user will also be notified.
### Email Headers ### Email Headers
Notification emails include headers that provide extra content about the notification received: Notification emails include headers that provide extra content about the notification received:
......
...@@ -31,6 +31,9 @@ A Todo appears in your Todos dashboard when: ...@@ -31,6 +31,9 @@ A Todo appears in your Todos dashboard when:
- you are `@mentioned` in a comment on a commit, - you are `@mentioned` in a comment on a commit,
- a job in the CI pipeline running for your merge request failed, but this - a job in the CI pipeline running for your merge request failed, but this
job is not allowed to fail. job is not allowed to fail.
- a merge request becomes unmergeable, and you are either:
- the author, or
- have set it to automatically merge once pipeline succeeds.
### Directly addressed Todos ### Directly addressed Todos
......
...@@ -2,7 +2,11 @@ require 'backup/files' ...@@ -2,7 +2,11 @@ require 'backup/files'
module Backup module Backup
class Artifacts < Files class Artifacts < Files
def initialize attr_reader :progress
def initialize(progress)
@progress = progress
super('artifacts', JobArtifactUploader.root) super('artifacts', JobArtifactUploader.root)
end end
end end
......
...@@ -2,7 +2,11 @@ require 'backup/files' ...@@ -2,7 +2,11 @@ require 'backup/files'
module Backup module Backup
class Builds < Files class Builds < Files
def initialize attr_reader :progress
def initialize(progress)
@progress = progress
super('builds', Settings.gitlab_ci.builds_path) super('builds', Settings.gitlab_ci.builds_path)
end end
end end
......
...@@ -2,9 +2,11 @@ require 'yaml' ...@@ -2,9 +2,11 @@ require 'yaml'
module Backup module Backup
class Database class Database
attr_reader :progress
attr_reader :config, :db_file_name attr_reader :config, :db_file_name
def initialize def initialize(progress)
@progress = progress
@config = YAML.load_file(File.join(Rails.root, 'config', 'database.yml'))[Rails.env] @config = YAML.load_file(File.join(Rails.root, 'config', 'database.yml'))[Rails.env]
@db_file_name = File.join(Gitlab.config.backup.path, 'db', 'database.sql.gz') @db_file_name = File.join(Gitlab.config.backup.path, 'db', 'database.sql.gz')
end end
...@@ -19,12 +21,12 @@ module Backup ...@@ -19,12 +21,12 @@ module Backup
dump_pid = dump_pid =
case config["adapter"] case config["adapter"]
when /^mysql/ then when /^mysql/ then
$progress.print "Dumping MySQL database #{config['database']} ... " progress.print "Dumping MySQL database #{config['database']} ... "
# Workaround warnings from MySQL 5.6 about passwords on cmd line # Workaround warnings from MySQL 5.6 about passwords on cmd line
ENV['MYSQL_PWD'] = config["password"].to_s if config["password"] ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
spawn('mysqldump', *mysql_args, config['database'], out: compress_wr) spawn('mysqldump', *mysql_args, config['database'], out: compress_wr)
when "postgresql" then when "postgresql" then
$progress.print "Dumping PostgreSQL database #{config['database']} ... " progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env pg_env
pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump. pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
if Gitlab.config.backup.pg_schema if Gitlab.config.backup.pg_schema
...@@ -53,12 +55,12 @@ module Backup ...@@ -53,12 +55,12 @@ module Backup
restore_pid = restore_pid =
case config["adapter"] case config["adapter"]
when /^mysql/ then when /^mysql/ then
$progress.print "Restoring MySQL database #{config['database']} ... " progress.print "Restoring MySQL database #{config['database']} ... "
# Workaround warnings from MySQL 5.6 about passwords on cmd line # Workaround warnings from MySQL 5.6 about passwords on cmd line
ENV['MYSQL_PWD'] = config["password"].to_s if config["password"] ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
spawn('mysql', *mysql_args, config['database'], in: decompress_rd) spawn('mysql', *mysql_args, config['database'], in: decompress_rd)
when "postgresql" then when "postgresql" then
$progress.print "Restoring PostgreSQL database #{config['database']} ... " progress.print "Restoring PostgreSQL database #{config['database']} ... "
pg_env pg_env
spawn('psql', config['database'], in: decompress_rd) spawn('psql', config['database'], in: decompress_rd)
end end
...@@ -111,9 +113,9 @@ module Backup ...@@ -111,9 +113,9 @@ module Backup
def report_success(success) def report_success(success)
if success if success
$progress.puts '[DONE]'.color(:green) progress.puts '[DONE]'.color(:green)
else else
$progress.puts '[FAILED]'.color(:red) progress.puts '[FAILED]'.color(:red)
end end
end end
end end
......
...@@ -2,7 +2,11 @@ require 'backup/files' ...@@ -2,7 +2,11 @@ require 'backup/files'
module Backup module Backup
class Lfs < Files class Lfs < Files
def initialize attr_reader :progress
def initialize(progress)
@progress = progress
super('lfs', Settings.lfs.storage_path) super('lfs', Settings.lfs.storage_path)
end end
end end
......
...@@ -4,6 +4,12 @@ module Backup ...@@ -4,6 +4,12 @@ module Backup
FOLDERS_TO_BACKUP = %w[repositories db].freeze FOLDERS_TO_BACKUP = %w[repositories db].freeze
FILE_NAME_SUFFIX = '_gitlab_backup.tar'.freeze FILE_NAME_SUFFIX = '_gitlab_backup.tar'.freeze
attr_reader :progress
def initialize(progress)
@progress = progress
end
def pack def pack
# Make sure there is a connection # Make sure there is a connection
ActiveRecord::Base.connection.reconnect! ActiveRecord::Base.connection.reconnect!
...@@ -14,11 +20,11 @@ module Backup ...@@ -14,11 +20,11 @@ module Backup
end end
# create archive # create archive
$progress.print "Creating backup archive: #{tar_file} ... " progress.print "Creating backup archive: #{tar_file} ... "
# Set file permissions on open to prevent chmod races. # Set file permissions on open to prevent chmod races.
tar_system_options = { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] } tar_system_options = { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] }
if Kernel.system('tar', '-cf', '-', *backup_contents, tar_system_options) if Kernel.system('tar', '-cf', '-', *backup_contents, tar_system_options)
$progress.puts "done".color(:green) progress.puts "done".color(:green)
else else
puts "creating archive #{tar_file} failed".color(:red) puts "creating archive #{tar_file} failed".color(:red)
abort 'Backup failed' abort 'Backup failed'
...@@ -29,11 +35,11 @@ module Backup ...@@ -29,11 +35,11 @@ module Backup
end end
def upload def upload
$progress.print "Uploading backup archive to remote storage #{remote_directory} ... " progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
connection_settings = Gitlab.config.backup.upload.connection connection_settings = Gitlab.config.backup.upload.connection
if connection_settings.blank? if connection_settings.blank?
$progress.puts "skipped".color(:yellow) progress.puts "skipped".color(:yellow)
return return
end end
...@@ -43,7 +49,7 @@ module Backup ...@@ -43,7 +49,7 @@ module Backup
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size, multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
encryption: Gitlab.config.backup.upload.encryption, encryption: Gitlab.config.backup.upload.encryption,
storage_class: Gitlab.config.backup.upload.storage_class) storage_class: Gitlab.config.backup.upload.storage_class)
$progress.puts "done".color(:green) progress.puts "done".color(:green)
else else
puts "uploading backup to #{remote_directory} failed".color(:red) puts "uploading backup to #{remote_directory} failed".color(:red)
abort 'Backup failed' abort 'Backup failed'
...@@ -51,13 +57,13 @@ module Backup ...@@ -51,13 +57,13 @@ module Backup
end end
def cleanup def cleanup
$progress.print "Deleting tmp directories ... " progress.print "Deleting tmp directories ... "
backup_contents.each do |dir| backup_contents.each do |dir|
next unless File.exist?(File.join(backup_path, dir)) next unless File.exist?(File.join(backup_path, dir))
if FileUtils.rm_rf(File.join(backup_path, dir)) if FileUtils.rm_rf(File.join(backup_path, dir))
$progress.puts "done".color(:green) progress.puts "done".color(:green)
else else
puts "deleting tmp directory '#{dir}' failed".color(:red) puts "deleting tmp directory '#{dir}' failed".color(:red)
abort 'Backup failed' abort 'Backup failed'
...@@ -67,7 +73,7 @@ module Backup ...@@ -67,7 +73,7 @@ module Backup
def remove_old def remove_old
# delete backups # delete backups
$progress.print "Deleting old backups ... " progress.print "Deleting old backups ... "
keep_time = Gitlab.config.backup.keep_time.to_i keep_time = Gitlab.config.backup.keep_time.to_i
if keep_time > 0 if keep_time > 0
...@@ -88,31 +94,32 @@ module Backup ...@@ -88,31 +94,32 @@ module Backup
FileUtils.rm(file) FileUtils.rm(file)
removed += 1 removed += 1
rescue => e rescue => e
$progress.puts "Deleting #{file} failed: #{e.message}".color(:red) progress.puts "Deleting #{file} failed: #{e.message}".color(:red)
end end
end end
end end
end end
$progress.puts "done. (#{removed} removed)".color(:green) progress.puts "done. (#{removed} removed)".color(:green)
else else
$progress.puts "skipping".color(:yellow) progress.puts "skipping".color(:yellow)
end end
end end
# rubocop: disable Metrics/AbcSize
def unpack def unpack
Dir.chdir(backup_path) do Dir.chdir(backup_path) do
# check for existing backups in the backup dir # check for existing backups in the backup dir
if backup_file_list.empty? if backup_file_list.empty?
$progress.puts "No backups found in #{backup_path}" progress.puts "No backups found in #{backup_path}"
$progress.puts "Please make sure that file name ends with #{FILE_NAME_SUFFIX}" progress.puts "Please make sure that file name ends with #{FILE_NAME_SUFFIX}"
exit 1 exit 1
elsif backup_file_list.many? && ENV["BACKUP"].nil? elsif backup_file_list.many? && ENV["BACKUP"].nil?
$progress.puts 'Found more than one backup:' progress.puts 'Found more than one backup:'
# print list of available backups # print list of available backups
$progress.puts " " + available_timestamps.join("\n ") progress.puts " " + available_timestamps.join("\n ")
$progress.puts 'Please specify which one you want to restore:' progress.puts 'Please specify which one you want to restore:'
$progress.puts 'rake gitlab:backup:restore BACKUP=timestamp_of_backup' progress.puts 'rake gitlab:backup:restore BACKUP=timestamp_of_backup'
exit 1 exit 1
end end
...@@ -123,31 +130,31 @@ module Backup ...@@ -123,31 +130,31 @@ module Backup
end end
unless File.exist?(tar_file) unless File.exist?(tar_file)
$progress.puts "The backup file #{tar_file} does not exist!" progress.puts "The backup file #{tar_file} does not exist!"
exit 1 exit 1
end end
$progress.print 'Unpacking backup ... ' progress.print 'Unpacking backup ... '
unless Kernel.system(*%W(tar -xf #{tar_file})) unless Kernel.system(*%W(tar -xf #{tar_file}))
$progress.puts 'unpacking backup failed'.color(:red) progress.puts 'unpacking backup failed'.color(:red)
exit 1 exit 1
else else
$progress.puts 'done'.color(:green) progress.puts 'done'.color(:green)
end end
ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0 ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0
# restoring mismatching backups can lead to unexpected problems # restoring mismatching backups can lead to unexpected problems
if settings[:gitlab_version] != Gitlab::VERSION if settings[:gitlab_version] != Gitlab::VERSION
$progress.puts(<<~HEREDOC.color(:red)) progress.puts(<<~HEREDOC.color(:red))
GitLab version mismatch: GitLab version mismatch:
Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup! Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup!
Please switch to the following version and try again: Please switch to the following version and try again:
version: #{settings[:gitlab_version]} version: #{settings[:gitlab_version]}
HEREDOC HEREDOC
$progress.puts progress.puts
$progress.puts "Hint: git checkout v#{settings[:gitlab_version]}" progress.puts "Hint: git checkout v#{settings[:gitlab_version]}"
exit 1 exit 1
end end
end end
......
...@@ -2,7 +2,11 @@ require 'backup/files' ...@@ -2,7 +2,11 @@ require 'backup/files'
module Backup module Backup
class Pages < Files class Pages < Files
def initialize attr_reader :progress
def initialize(progress)
@progress = progress
super('pages', Gitlab.config.pages.path) super('pages', Gitlab.config.pages.path)
end end
end end
......
...@@ -2,7 +2,11 @@ require 'backup/files' ...@@ -2,7 +2,11 @@ require 'backup/files'
module Backup module Backup
class Registry < Files class Registry < Files
def initialize attr_reader :progress
def initialize(progress)
@progress = progress
super('registry', Settings.registry.path) super('registry', Settings.registry.path)
end end
end end
......
...@@ -6,6 +6,12 @@ module Backup ...@@ -6,6 +6,12 @@ module Backup
include Backup::Helper include Backup::Helper
# rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/AbcSize
attr_reader :progress
def initialize(progress)
@progress = progress
end
def dump def dump
prepare prepare
...@@ -67,6 +73,9 @@ module Backup ...@@ -67,6 +73,9 @@ module Backup
end end
def prepare_directories def prepare_directories
# TODO: Need to find a way to do this for gitaly
# Gitaly discussion issue: https://gitlab.com/gitlab-org/gitaly/issues/1194
Gitlab.config.repositories.storages.each do |name, repository_storage| Gitlab.config.repositories.storages.each do |name, repository_storage|
path = repository_storage.legacy_disk_path path = repository_storage.legacy_disk_path
next unless File.exist?(path) next unless File.exist?(path)
...@@ -87,70 +96,65 @@ module Backup ...@@ -87,70 +96,65 @@ module Backup
end end
end end
def restore_custom_hooks(project)
# TODO: Need to find a way to do this for gitaly
# Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/1195
in_path(path_to_tars(project)) do |dir|
path_to_project_repo = path_to_repo(project)
cmd = %W(tar -xf #{path_to_tars(project, dir)} -C #{path_to_project_repo} #{dir})
output, status = Gitlab::Popen.popen(cmd)
unless status.zero?
progress_warn(project, cmd.join(' '), output)
end
end
end
def restore def restore
prepare_directories prepare_directories
gitlab_shell = Gitlab::Shell.new
Project.find_each(batch_size: 1000) do |project| Project.find_each(batch_size: 1000) do |project|
progress.print " * #{display_repo_path(project)} ... " progress.print " * #{project.full_path} ... "
path_to_project_repo = path_to_repo(project)
path_to_project_bundle = path_to_bundle(project) path_to_project_bundle = path_to_bundle(project)
project.ensure_storage_path_exists project.ensure_storage_path_exists
cmd = if File.exist?(path_to_project_bundle) restore_repo_success = nil
%W(#{Gitlab.config.git.bin_path} clone --bare --mirror #{path_to_project_bundle} #{path_to_project_repo}) if File.exist?(path_to_project_bundle)
else begin
%W(#{Gitlab.config.git.bin_path} init --bare #{path_to_project_repo}) gitlab_shell.remove_repository(project.repository_storage, project.disk_path) if project.repository_exists?
end project.repository.create_from_bundle path_to_project_bundle
restore_repo_success = true
rescue => e
restore_repo_success = false
progress.puts "Error: #{e}".color(:red)
end
else
restore_repo_success = gitlab_shell.create_repository(project.repository_storage, project.disk_path)
end
output, status = Gitlab::Popen.popen(cmd) if restore_repo_success
if status.zero?
progress.puts "[DONE]".color(:green) progress.puts "[DONE]".color(:green)
else else
progress_warn(project, cmd.join(' '), output) progress.puts "[Failed] restoring #{project.full_path} repository".color(:red)
end end
in_path(path_to_tars(project)) do |dir| restore_custom_hooks(project)
cmd = %W(tar -xf #{path_to_tars(project, dir)} -C #{path_to_project_repo} #{dir})
output, status = Gitlab::Popen.popen(cmd)
unless status.zero?
progress_warn(project, cmd.join(' '), output)
end
end
wiki = ProjectWiki.new(project) wiki = ProjectWiki.new(project)
path_to_wiki_repo = path_to_repo(wiki)
path_to_wiki_bundle = path_to_bundle(wiki) path_to_wiki_bundle = path_to_bundle(wiki)
if File.exist?(path_to_wiki_bundle) if File.exist?(path_to_wiki_bundle)
progress.print " * #{display_repo_path(wiki)} ... " progress.print " * #{wiki.full_path} ... "
begin
# If a wiki bundle exists, first remove the empty repo gitlab_shell.remove_repository(wiki.repository_storage, wiki.disk_path) if wiki.repository_exists?
# that was initialized with ProjectWiki.new() and then wiki.repository.create_from_bundle(path_to_wiki_bundle)
# try to restore with 'git clone --bare'. progress.puts "[DONE]".color(:green)
FileUtils.rm_rf(path_to_wiki_repo) rescue => e
cmd = %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_wiki_bundle} #{path_to_wiki_repo}) progress.puts "[Failed] restoring #{wiki.full_path} wiki".color(:red)
progress.puts "Error #{e}".color(:red)
output, status = Gitlab::Popen.popen(cmd)
if status.zero?
progress.puts " [DONE]".color(:green)
else
progress_warn(project, cmd.join(' '), output)
end end
end end
end end
progress.print 'Put GitLab hooks in repositories dirs'.color(:yellow)
cmd = %W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args
output, status = Gitlab::Popen.popen(cmd)
if status.zero?
progress.puts " [DONE]".color(:green)
else
puts " [FAILED]".color(:red)
puts "failed: #{cmd}"
puts output
end
end end
# rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/AbcSize
...@@ -217,10 +221,6 @@ module Backup ...@@ -217,10 +221,6 @@ module Backup
Gitlab.config.repositories.storages.values.map { |rs| rs.legacy_disk_path } Gitlab.config.repositories.storages.values.map { |rs| rs.legacy_disk_path }
end end
def progress
$progress
end
def display_repo_path(project) def display_repo_path(project)
project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path
end end
......
...@@ -2,7 +2,11 @@ require 'backup/files' ...@@ -2,7 +2,11 @@ require 'backup/files'
module Backup module Backup
class Uploads < Files class Uploads < Files
def initialize attr_reader :progress
def initialize(progress)
@progress = progress
super('uploads', Rails.root.join('public/uploads')) super('uploads', Rails.root.join('public/uploads'))
end end
end end
......
...@@ -6,7 +6,6 @@ namespace :gitlab do ...@@ -6,7 +6,6 @@ namespace :gitlab do
desc "GitLab | Create a backup of the GitLab system" desc "GitLab | Create a backup of the GitLab system"
task create: :gitlab_environment do task create: :gitlab_environment do
warn_user_is_not_gitlab warn_user_is_not_gitlab
configure_cron_mode
Rake::Task["gitlab:backup:db:create"].invoke Rake::Task["gitlab:backup:db:create"].invoke
Rake::Task["gitlab:backup:repo:create"].invoke Rake::Task["gitlab:backup:repo:create"].invoke
...@@ -17,7 +16,7 @@ namespace :gitlab do ...@@ -17,7 +16,7 @@ namespace :gitlab do
Rake::Task["gitlab:backup:lfs:create"].invoke Rake::Task["gitlab:backup:lfs:create"].invoke
Rake::Task["gitlab:backup:registry:create"].invoke Rake::Task["gitlab:backup:registry:create"].invoke
backup = Backup::Manager.new backup = Backup::Manager.new(progress)
backup.pack backup.pack
backup.cleanup backup.cleanup
backup.remove_old backup.remove_old
...@@ -27,9 +26,8 @@ namespace :gitlab do ...@@ -27,9 +26,8 @@ namespace :gitlab do
desc 'GitLab | Restore a previously created backup' desc 'GitLab | Restore a previously created backup'
task restore: :gitlab_environment do task restore: :gitlab_environment do
warn_user_is_not_gitlab warn_user_is_not_gitlab
configure_cron_mode
backup = Backup::Manager.new backup = Backup::Manager.new(progress)
backup.unpack backup.unpack
unless backup.skipped?('db') unless backup.skipped?('db')
...@@ -49,9 +47,9 @@ namespace :gitlab do ...@@ -49,9 +47,9 @@ namespace :gitlab do
# Drop all tables Load the schema to ensure we don't have any newer tables # Drop all tables Load the schema to ensure we don't have any newer tables
# hanging out from a failed upgrade # hanging out from a failed upgrade
$progress.puts 'Cleaning the database ... '.color(:blue) progress.puts 'Cleaning the database ... '.color(:blue)
Rake::Task['gitlab:db:drop_tables'].invoke Rake::Task['gitlab:db:drop_tables'].invoke
$progress.puts 'done'.color(:green) progress.puts 'done'.color(:green)
Rake::Task['gitlab:backup:db:restore'].invoke Rake::Task['gitlab:backup:db:restore'].invoke
rescue Gitlab::TaskAbortedByUserError rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".color(:red) puts "Quitting...".color(:red)
...@@ -74,173 +72,173 @@ namespace :gitlab do ...@@ -74,173 +72,173 @@ namespace :gitlab do
namespace :repo do namespace :repo do
task create: :gitlab_environment do task create: :gitlab_environment do
$progress.puts "Dumping repositories ...".color(:blue) progress.puts "Dumping repositories ...".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("repositories") if ENV["SKIP"] && ENV["SKIP"].include?("repositories")
$progress.puts "[SKIPPED]".color(:cyan) progress.puts "[SKIPPED]".color(:cyan)
else else
Backup::Repository.new.dump Backup::Repository.new(progress).dump
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
task restore: :gitlab_environment do task restore: :gitlab_environment do
$progress.puts "Restoring repositories ...".color(:blue) progress.puts "Restoring repositories ...".color(:blue)
Backup::Repository.new.restore Backup::Repository.new(progress).restore
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
namespace :db do namespace :db do
task create: :gitlab_environment do task create: :gitlab_environment do
$progress.puts "Dumping database ... ".color(:blue) progress.puts "Dumping database ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("db") if ENV["SKIP"] && ENV["SKIP"].include?("db")
$progress.puts "[SKIPPED]".color(:cyan) progress.puts "[SKIPPED]".color(:cyan)
else else
Backup::Database.new.dump Backup::Database.new(progress).dump
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
task restore: :gitlab_environment do task restore: :gitlab_environment do
$progress.puts "Restoring database ... ".color(:blue) progress.puts "Restoring database ... ".color(:blue)
Backup::Database.new.restore Backup::Database.new(progress).restore
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
namespace :builds do namespace :builds do
task create: :gitlab_environment do task create: :gitlab_environment do
$progress.puts "Dumping builds ... ".color(:blue) progress.puts "Dumping builds ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("builds") if ENV["SKIP"] && ENV["SKIP"].include?("builds")
$progress.puts "[SKIPPED]".color(:cyan) progress.puts "[SKIPPED]".color(:cyan)
else else
Backup::Builds.new.dump Backup::Builds.new(progress).dump
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
task restore: :gitlab_environment do task restore: :gitlab_environment do
$progress.puts "Restoring builds ... ".color(:blue) progress.puts "Restoring builds ... ".color(:blue)
Backup::Builds.new.restore Backup::Builds.new(progress).restore
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
namespace :uploads do namespace :uploads do
task create: :gitlab_environment do task create: :gitlab_environment do
$progress.puts "Dumping uploads ... ".color(:blue) progress.puts "Dumping uploads ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("uploads") if ENV["SKIP"] && ENV["SKIP"].include?("uploads")
$progress.puts "[SKIPPED]".color(:cyan) progress.puts "[SKIPPED]".color(:cyan)
else else
Backup::Uploads.new.dump Backup::Uploads.new(progress).dump
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
task restore: :gitlab_environment do task restore: :gitlab_environment do
$progress.puts "Restoring uploads ... ".color(:blue) progress.puts "Restoring uploads ... ".color(:blue)
Backup::Uploads.new.restore Backup::Uploads.new(progress).restore
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
namespace :artifacts do namespace :artifacts do
task create: :gitlab_environment do task create: :gitlab_environment do
$progress.puts "Dumping artifacts ... ".color(:blue) progress.puts "Dumping artifacts ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("artifacts") if ENV["SKIP"] && ENV["SKIP"].include?("artifacts")
$progress.puts "[SKIPPED]".color(:cyan) progress.puts "[SKIPPED]".color(:cyan)
else else
Backup::Artifacts.new.dump Backup::Artifacts.new(progress).dump
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
task restore: :gitlab_environment do task restore: :gitlab_environment do
$progress.puts "Restoring artifacts ... ".color(:blue) progress.puts "Restoring artifacts ... ".color(:blue)
Backup::Artifacts.new.restore Backup::Artifacts.new(progress).restore
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
namespace :pages do namespace :pages do
task create: :gitlab_environment do task create: :gitlab_environment do
$progress.puts "Dumping pages ... ".color(:blue) progress.puts "Dumping pages ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("pages") if ENV["SKIP"] && ENV["SKIP"].include?("pages")
$progress.puts "[SKIPPED]".color(:cyan) progress.puts "[SKIPPED]".color(:cyan)
else else
Backup::Pages.new.dump Backup::Pages.new(progress).dump
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
task restore: :gitlab_environment do task restore: :gitlab_environment do
$progress.puts "Restoring pages ... ".color(:blue) progress.puts "Restoring pages ... ".color(:blue)
Backup::Pages.new.restore Backup::Pages.new(progress).restore
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
namespace :lfs do namespace :lfs do
task create: :gitlab_environment do task create: :gitlab_environment do
$progress.puts "Dumping lfs objects ... ".color(:blue) progress.puts "Dumping lfs objects ... ".color(:blue)
if ENV["SKIP"] && ENV["SKIP"].include?("lfs") if ENV["SKIP"] && ENV["SKIP"].include?("lfs")
$progress.puts "[SKIPPED]".color(:cyan) progress.puts "[SKIPPED]".color(:cyan)
else else
Backup::Lfs.new.dump Backup::Lfs.new(progress).dump
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
task restore: :gitlab_environment do task restore: :gitlab_environment do
$progress.puts "Restoring lfs objects ... ".color(:blue) progress.puts "Restoring lfs objects ... ".color(:blue)
Backup::Lfs.new.restore Backup::Lfs.new(progress).restore
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
end end
namespace :registry do namespace :registry do
task create: :gitlab_environment do task create: :gitlab_environment do
$progress.puts "Dumping container registry images ... ".color(:blue) progress.puts "Dumping container registry images ... ".color(:blue)
if Gitlab.config.registry.enabled if Gitlab.config.registry.enabled
if ENV["SKIP"] && ENV["SKIP"].include?("registry") if ENV["SKIP"] && ENV["SKIP"].include?("registry")
$progress.puts "[SKIPPED]".color(:cyan) progress.puts "[SKIPPED]".color(:cyan)
else else
Backup::Registry.new.dump Backup::Registry.new(progress).dump
$progress.puts "done".color(:green) progress.puts "done".color(:green)
end end
else else
$progress.puts "[DISABLED]".color(:cyan) progress.puts "[DISABLED]".color(:cyan)
end end
end end
task restore: :gitlab_environment do task restore: :gitlab_environment do
$progress.puts "Restoring container registry images ... ".color(:blue) progress.puts "Restoring container registry images ... ".color(:blue)
if Gitlab.config.registry.enabled if Gitlab.config.registry.enabled
Backup::Registry.new.restore Backup::Registry.new(progress).restore
$progress.puts "done".color(:green) progress.puts "done".color(:green)
else else
$progress.puts "[DISABLED]".color(:cyan) progress.puts "[DISABLED]".color(:cyan)
end end
end end
end end
def configure_cron_mode def progress
if ENV['CRON'] if ENV['CRON']
# We need an object we can say 'puts' and 'print' to; let's use a # We need an object we can say 'puts' and 'print' to; let's use a
# StringIO. # StringIO.
require 'stringio' require 'stringio'
$progress = StringIO.new StringIO.new
else else
$progress = $stdout $stdout
end end
end end
end # namespace end: backup end # namespace end: backup
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
}, },
"dependencies": { "dependencies": {
"@gitlab-org/gitlab-svgs": "^1.22.0", "@gitlab-org/gitlab-svgs": "^1.23.0",
"autosize": "^4.0.0", "autosize": "^4.0.0",
"axios": "^0.17.1", "axios": "^0.17.1",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
......
...@@ -5,7 +5,7 @@ describe Projects::MergeRequests::ConflictsController do ...@@ -5,7 +5,7 @@ describe Projects::MergeRequests::ConflictsController do
let(:user) { project.owner } let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
let(:merge_request_with_conflicts) do let(:merge_request_with_conflicts) do
create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr| create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr|
mr.mark_as_unmergeable mr.mark_as_unmergeable
end end
end end
......
...@@ -7,7 +7,7 @@ describe Projects::MergeRequestsController do ...@@ -7,7 +7,7 @@ describe Projects::MergeRequestsController do
let(:user) { project.owner } let(:user) { project.owner }
let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
let(:merge_request_with_conflicts) do let(:merge_request_with_conflicts) do
create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr| create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr|
mr.mark_as_unmergeable mr.mark_as_unmergeable
end end
end end
......
...@@ -10,7 +10,7 @@ describe 'Merge request > User resolves conflicts', :js do ...@@ -10,7 +10,7 @@ describe 'Merge request > User resolves conflicts', :js do
end end
def create_merge_request(source_branch) def create_merge_request(source_branch)
create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project) do |mr| create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr|
mr.mark_as_unmergeable mr.mark_as_unmergeable
end end
end end
......
require 'rails_helper'
describe 'Merge request > User sees Check out branch modal', :js do
let(:project) { create(:project, :public, :repository) }
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project) }
before do
sign_in(user)
visit project_merge_request_path(project, merge_request)
wait_for_requests
click_button('Check out branch')
end
it 'shows the check out branch modal' do
expect(page).to have_content('Check out, review, and merge locally')
end
it 'closes the check out branch model with Escape keypress' do
find('#modal_merge_info').send_keys(:escape)
expect(page).not_to have_content('Check out, review, and merge locally')
end
end
...@@ -217,7 +217,7 @@ export const pipelineWithStages = { ...@@ -217,7 +217,7 @@ export const pipelineWithStages = {
browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/browse', browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/browse',
}, },
{ {
name: 'codequality', name: 'code_quality',
expired: false, expired: false,
expire_at: '2018-04-18T14:16:24.484Z', expire_at: '2018-04-18T14:16:24.484Z',
path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/download', path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/download',
......
...@@ -182,7 +182,16 @@ describe('Pipelines Table Row', () => { ...@@ -182,7 +182,16 @@ describe('Pipelines Table Row', () => {
}); });
component.$el.querySelector('.js-pipelines-cancel-button').click(); component.$el.querySelector('.js-pipelines-cancel-button').click();
expect(component.isCancelling).toEqual(true); });
it('renders a loading icon when `cancelingPipeline` matches pipeline id', done => {
component.cancelingPipeline = pipeline.id;
component.$nextTick()
.then(() => {
expect(component.isCancelling).toEqual(true);
})
.then(done)
.catch(done.fail);
}); });
}); });
}); });
...@@ -5,6 +5,8 @@ describe Backup::Manager do ...@@ -5,6 +5,8 @@ describe Backup::Manager do
let(:progress) { StringIO.new } let(:progress) { StringIO.new }
subject { described_class.new(progress) }
before do before do
allow(progress).to receive(:puts) allow(progress).to receive(:puts)
allow(progress).to receive(:print) allow(progress).to receive(:print)
......
...@@ -3,6 +3,7 @@ require 'spec_helper' ...@@ -3,6 +3,7 @@ require 'spec_helper'
describe Backup::Repository do describe Backup::Repository do
let(:progress) { StringIO.new } let(:progress) { StringIO.new }
let!(:project) { create(:project, :wiki_repo) } let!(:project) { create(:project, :wiki_repo) }
subject { described_class.new(progress) }
before do before do
allow(progress).to receive(:puts) allow(progress).to receive(:puts)
...@@ -24,14 +25,12 @@ describe Backup::Repository do ...@@ -24,14 +25,12 @@ describe Backup::Repository do
end end
it 'does not raise error' do it 'does not raise error' do
expect { described_class.new.dump }.not_to raise_error expect { subject.dump }.not_to raise_error
end end
end end
end end
describe '#restore' do describe '#restore' do
subject { described_class.new }
let(:timestamp) { Time.utc(2017, 3, 22) } let(:timestamp) { Time.utc(2017, 3, 22) }
let(:temp_dirs) do let(:temp_dirs) do
Gitlab.config.repositories.storages.map do |name, storage| Gitlab.config.repositories.storages.map do |name, storage|
...@@ -49,14 +48,14 @@ describe Backup::Repository do ...@@ -49,14 +48,14 @@ describe Backup::Repository do
describe 'command failure' do describe 'command failure' do
before do before do
allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1]) allow_any_instance_of(Gitlab::Shell).to receive(:create_repository).and_return(false)
end end
context 'hashed storage' do context 'hashed storage' do
it 'shows the appropriate error' do it 'shows the appropriate error' do
subject.restore subject.restore
expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} (#{project.disk_path}) - error") expect(progress).to have_received(:puts).with("[Failed] restoring #{project.full_path} repository")
end end
end end
...@@ -66,33 +65,10 @@ describe Backup::Repository do ...@@ -66,33 +65,10 @@ describe Backup::Repository do
it 'shows the appropriate error' do it 'shows the appropriate error' do
subject.restore subject.restore
expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error") expect(progress).to have_received(:puts).with("[Failed] restoring #{project.full_path} repository")
end end
end end
end end
describe 'folders without permissions' do
before do
allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
end
it 'shows error message' do
expect(subject).to receive(:access_denied_error)
subject.restore
end
end
describe 'folder that is a mountpoint' do
before do
allow(FileUtils).to receive(:mv).and_raise(Errno::EBUSY)
end
it 'shows error message' do
expect(subject).to receive(:resource_busy_error).and_call_original
expect { subject.restore }.to raise_error(/is a mountpoint/)
end
end
end end
describe '#empty_repo?' do describe '#empty_repo?' do
...@@ -102,20 +78,20 @@ describe Backup::Repository do ...@@ -102,20 +78,20 @@ describe Backup::Repository do
it 'invalidates the emptiness cache' do it 'invalidates the emptiness cache' do
expect(wiki.repository).to receive(:expire_emptiness_caches).once expect(wiki.repository).to receive(:expire_emptiness_caches).once
described_class.new.send(:empty_repo?, wiki) subject.send(:empty_repo?, wiki)
end end
context 'wiki repo has content' do context 'wiki repo has content' do
let!(:wiki_page) { create(:wiki_page, wiki: wiki) } let!(:wiki_page) { create(:wiki_page, wiki: wiki) }
it 'returns true, regardless of bad cache value' do it 'returns true, regardless of bad cache value' do
expect(described_class.new.send(:empty_repo?, wiki)).to be(false) expect(subject.send(:empty_repo?, wiki)).to be(false)
end end
end end
context 'wiki repo does not have content' do context 'wiki repo does not have content' do
it 'returns true, regardless of bad cache value' do it 'returns true, regardless of bad cache value' do
expect(described_class.new.send(:empty_repo?, wiki)).to be_truthy expect(subject.send(:empty_repo?, wiki)).to be_truthy
end end
end end
end end
......
...@@ -390,6 +390,46 @@ describe Notify do ...@@ -390,6 +390,46 @@ describe Notify do
end end
end end
describe 'that are unmergeable' do
set(:merge_request) do
create(:merge_request, :conflict,
source_project: project,
target_project: project,
author: current_user,
assignee: assignee,
description: 'Awesome description')
end
subject { described_class.merge_request_unmergeable_email(recipient.id, merge_request.id) }
it_behaves_like 'a multiple recipients email'
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
let(:model) { merge_request }
end
it_behaves_like 'it should show Gmail Actions View Merge request link'
it_behaves_like 'an unsubscribeable thread'
it 'is sent as the merge request author' do
sender = subject.header[:from].addrs[0]
expect(sender.display_name).to eq(merge_request.author.name)
expect(sender.address).to eq(gitlab_sender)
end
it 'has the correct subject and body' do
reasons = %w[foo bar]
allow_any_instance_of(MergeRequestPresenter).to receive(:unmergeable_reasons).and_return(reasons)
aggregate_failures do
is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text(project_merge_request_path(project, merge_request))
is_expected.to have_body_text('reasons:')
reasons.each do |reason|
is_expected.to have_body_text(reason)
end
end
end
end
shared_examples 'a push to an existing merge request' do shared_examples 'a push to an existing merge request' do
let(:push_user) { create(:user) } let(:push_user) { create(:user) }
......
...@@ -326,4 +326,12 @@ describe Note, ResolvableNote do ...@@ -326,4 +326,12 @@ describe Note, ResolvableNote do
end end
end end
end end
describe "#potentially_resolvable?" do
it 'returns false if noteable could not be found' do
allow(subject).to receive(:noteable).and_return(nil)
expect(subject.potentially_resolvable?).to be_falsey
end
end
end end
...@@ -1245,38 +1245,50 @@ describe MergeRequest do ...@@ -1245,38 +1245,50 @@ describe MergeRequest do
describe '#check_if_can_be_merged' do describe '#check_if_can_be_merged' do
let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) } let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
subject { create(:merge_request, source_project: project, merge_status: :unchecked) } shared_examples 'checking if can be merged' do
context 'when it is not broken and has no conflicts' do
before do
allow(subject).to receive(:broken?) { false }
allow(project.repository).to receive(:can_be_merged?).and_return(true)
end
context 'when it is not broken and has no conflicts' do it 'is marked as mergeable' do
before do expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged')
allow(subject).to receive(:broken?) { false } end
allow(project.repository).to receive(:can_be_merged?).and_return(true)
end end
it 'is marked as mergeable' do context 'when broken' do
expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') before do
end allow(subject).to receive(:broken?) { true }
end end
context 'when broken' do it 'becomes unmergeable' do
before do expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
allow(subject).to receive(:broken?) { true } end
end end
it 'becomes unmergeable' do context 'when it has conflicts' do
expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') before do
allow(subject).to receive(:broken?) { false }
allow(project.repository).to receive(:can_be_merged?).and_return(false)
end
it 'becomes unmergeable' do
expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged')
end
end end
end end
context 'when it has conflicts' do context 'when merge_status is unchecked' do
before do subject { create(:merge_request, source_project: project, merge_status: :unchecked) }
allow(subject).to receive(:broken?) { false }
allow(project.repository).to receive(:can_be_merged?).and_return(false)
end
it 'becomes unmergeable' do it_behaves_like 'checking if can be merged'
expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') end
end
context 'when merge_status is unchecked' do
subject { create(:merge_request, source_project: project, merge_status: :cannot_be_merged_recheck) }
it_behaves_like 'checking if can be merged'
end end
end end
...@@ -2064,6 +2076,53 @@ describe MergeRequest do ...@@ -2064,6 +2076,53 @@ describe MergeRequest do
expect(subject.merge_jid).to be_nil expect(subject.merge_jid).to be_nil
end end
end end
describe 'transition to cannot_be_merged' do
let(:notification_service) { double(:notification_service) }
let(:todo_service) { double(:todo_service) }
subject { create(:merge_request, merge_status: :unchecked) }
before do
allow(NotificationService).to receive(:new).and_return(notification_service)
allow(TodoService).to receive(:new).and_return(todo_service)
end
it 'notifies, but does not notify again if rechecking still results in cannot_be_merged' do
expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once
expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once
subject.mark_as_unmergeable
subject.mark_as_unchecked
subject.mark_as_unmergeable
end
it 'notifies whenever merge request is newly unmergeable' do
expect(notification_service).to receive(:merge_request_unmergeable).with(subject).twice
expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).twice
subject.mark_as_unmergeable
subject.mark_as_unchecked
subject.mark_as_mergeable
subject.mark_as_unchecked
subject.mark_as_unmergeable
end
end
describe 'check_state?' do
it 'indicates whether MR is still checking for mergeability' do
state_machine = described_class.state_machines[:merge_status]
check_states = [:unchecked, :cannot_be_merged_recheck]
check_states.each do |merge_status|
expect(state_machine.check_state?(merge_status)).to be true
end
(state_machine.states.map(&:name) - check_states).each do |merge_status|
expect(state_machine.check_state?(merge_status)).to be false
end
end
end
end end
describe '#should_be_rebased?' do describe '#should_be_rebased?' do
...@@ -2195,4 +2254,39 @@ describe MergeRequest do ...@@ -2195,4 +2254,39 @@ describe MergeRequest do
expect(merge_request.can_allow_maintainer_to_push?(user)).to be_truthy expect(merge_request.can_allow_maintainer_to_push?(user)).to be_truthy
end end
end end
describe '#merge_participants' do
it 'contains author' do
expect(subject.merge_participants).to eq([subject.author])
end
describe 'when merge_when_pipeline_succeeds? is true' do
describe 'when merge user is author' do
let(:user) { create(:user) }
subject do
create(:merge_request,
merge_when_pipeline_succeeds: true,
merge_user: user,
author: user)
end
it 'contains author only' do
expect(subject.merge_participants).to eq([subject.author])
end
end
describe 'when merge user and author are different users' do
let(:merge_user) { create(:user) }
subject do
create(:merge_request,
merge_when_pipeline_succeeds: true,
merge_user: merge_user)
end
it 'contains author and merge user' do
expect(subject.merge_participants).to eq([subject.author, merge_user])
end
end
end
end
end end
...@@ -70,6 +70,41 @@ describe MergeRequestPresenter do ...@@ -70,6 +70,41 @@ describe MergeRequestPresenter do
end end
end end
describe "#unmergeable_reasons" do
let(:presenter) { described_class.new(resource, current_user: user) }
before do
# Mergeable base state
allow(resource).to receive(:has_no_commits?).and_return(false)
allow(resource).to receive(:source_branch_exists?).and_return(true)
allow(resource).to receive(:target_branch_exists?).and_return(true)
allow(resource.project.repository).to receive(:can_be_merged?).and_return(true)
end
it "handles mergeable request" do
expect(presenter.unmergeable_reasons).to eq([])
end
it "handles no commit" do
allow(resource).to receive(:has_no_commits?).and_return(true)
expect(presenter.unmergeable_reasons).to eq(["no commits"])
end
it "handles branches missing" do
allow(resource).to receive(:source_branch_exists?).and_return(false)
allow(resource).to receive(:target_branch_exists?).and_return(false)
expect(presenter.unmergeable_reasons).to eq(["source branch is missing", "target branch is missing"])
end
it "handles merge conflict" do
allow(resource.project.repository).to receive(:can_be_merged?).and_return(false)
expect(presenter.unmergeable_reasons).to eq(["has merge conflicts"])
end
end
describe '#conflict_resolution_path' do describe '#conflict_resolution_path' do
let(:project) { create :project } let(:project) { create :project }
let(:user) { create :user } let(:user) { create :user }
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Clusters::Applications::ScheduleInstallationService do describe Clusters::Applications::ScheduleInstallationService do
def count_scheduled def count_scheduled
application_class&.with_status(:scheduled)&.count || 0 application&.class&.with_status(:scheduled)&.count || 0
end end
shared_examples 'a failing service' do shared_examples 'a failing service' do
...@@ -10,45 +10,42 @@ describe Clusters::Applications::ScheduleInstallationService do ...@@ -10,45 +10,42 @@ describe Clusters::Applications::ScheduleInstallationService do
expect(ClusterInstallAppWorker).not_to receive(:perform_async) expect(ClusterInstallAppWorker).not_to receive(:perform_async)
count_before = count_scheduled count_before = count_scheduled
expect { service.execute }.to raise_error(StandardError) expect { service.execute(application) }.to raise_error(StandardError)
expect(count_scheduled).to eq(count_before) expect(count_scheduled).to eq(count_before)
end end
end end
describe '#execute' do describe '#execute' do
let(:application_class) { Clusters::Applications::Helm } let(:project) { double(:project) }
let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:service) { described_class.new(project, nil) }
let(:project) { cluster.project }
let(:service) { described_class.new(project, nil, cluster: cluster, application_class: application_class) }
it 'creates a new application' do context 'when application is installable' do
allow(ClusterInstallAppWorker).to receive(:perform_async) let(:application) { create(:clusters_applications_helm, :installable) }
expect { service.execute }.to change { application_class.count }.by(1) it 'make the application scheduled' do
end expect(ClusterInstallAppWorker).to receive(:perform_async).with(application.name, kind_of(Numeric)).once
it 'make the application scheduled' do
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application_class.application_name, kind_of(Numeric)).once
expect { service.execute }.to change { application_class.with_status(:scheduled).count }.by(1) expect { service.execute(application) }.to change { application.class.with_status(:scheduled).count }.by(1)
end
end end
context 'when installation is already in progress' do context 'when installation is already in progress' do
let(:application) { create(:clusters_applications_helm, :installing) } let(:application) { create(:clusters_applications_helm, :installing) }
let(:cluster) { application.cluster }
it_behaves_like 'a failing service' it_behaves_like 'a failing service'
end end
context 'when application_class is nil' do context 'when application is nil' do
let(:application_class) { nil } let(:application) { nil }
it_behaves_like 'a failing service' it_behaves_like 'a failing service'
end end
context 'when application cannot be persisted' do context 'when application cannot be persisted' do
let(:application) { create(:clusters_applications_helm) }
before do before do
expect_any_instance_of(application_class).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid) expect(application).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid)
end end
it_behaves_like 'a failing service' it_behaves_like 'a failing service'
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe MergeRequests::Conflicts::ListService do describe MergeRequests::Conflicts::ListService do
describe '#can_be_resolved_in_ui?' do describe '#can_be_resolved_in_ui?' do
def create_merge_request(source_branch) def create_merge_request(source_branch)
create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start') do |mr| create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', merge_status: :unchecked) do |mr|
mr.mark_as_unmergeable mr.mark_as_unmergeable
end end
end end
......
...@@ -112,32 +112,6 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do ...@@ -112,32 +112,6 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do
service.trigger(unrelated_pipeline) service.trigger(unrelated_pipeline)
end end
end end
context 'when the merge request is not mergeable' do
let(:mr_conflict) do
create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user,
source_branch: 'master', target_branch: 'feature-conflict',
source_project: project, target_project: project)
end
let(:conflict_pipeline) do
create(:ci_pipeline, project: project, ref: mr_conflict.source_branch,
sha: mr_conflict.diff_head_sha, status: 'success',
head_pipeline_of: mr_conflict)
end
it 'does not merge the merge request' do
expect(MergeWorker).not_to receive(:perform_async)
service.trigger(conflict_pipeline)
end
it 'creates todos for unmergeability' do
expect_any_instance_of(TodoService).to receive(:merge_request_became_unmergeable).with(mr_conflict)
service.trigger(conflict_pipeline)
end
end
end end
describe "#cancel" do describe "#cancel" do
......
...@@ -1224,6 +1224,32 @@ describe NotificationService, :mailer do ...@@ -1224,6 +1224,32 @@ describe NotificationService, :mailer do
end end
end end
describe '#merge_request_unmergeable' do
it "sends email to merge request author" do
notification.merge_request_unmergeable(merge_request)
should_email(merge_request.author)
expect(email_recipients.size).to eq(1)
end
describe 'when merge_when_pipeline_succeeds is true' do
before do
merge_request.update_attributes(
merge_when_pipeline_succeeds: true,
merge_user: create(:user)
)
end
it "sends email to merge request author and merge_user" do
notification.merge_request_unmergeable(merge_request)
should_email(merge_request.author)
should_email(merge_request.merge_user)
expect(email_recipients.size).to eq(2)
end
end
end
describe '#closed_merge_request' do describe '#closed_merge_request' do
before do before do
update_custom_notification(:close_merge_request, @u_guest_custom, resource: project) update_custom_notification(:close_merge_request, @u_guest_custom, resource: project)
......
...@@ -2,20 +2,53 @@ require 'spec_helper' ...@@ -2,20 +2,53 @@ require 'spec_helper'
describe Projects::OpenIssuesCountService do describe Projects::OpenIssuesCountService do
describe '#count' do describe '#count' do
it 'returns the number of open issues' do let(:project) { create(:project) }
project = create(:project)
create(:issue, :opened, project: project)
expect(described_class.new(project).count).to eq(1) context 'when user is nil' do
it 'does not include confidential issues in the issue count' do
create(:issue, :opened, project: project)
create(:issue, :opened, confidential: true, project: project)
expect(described_class.new(project).count).to eq(1)
end
end end
it 'does not include confidential issues in the issue count' do context 'when user is provided' do
project = create(:project) let(:user) { create(:user) }
context 'when user can read confidential issues' do
before do
project.add_reporter(user)
end
it 'returns the right count with confidential issues' do
create(:issue, :opened, project: project)
create(:issue, :opened, confidential: true, project: project)
expect(described_class.new(project, user).count).to eq(2)
end
it 'uses total_open_issues_count cache key' do
expect(described_class.new(project, user).cache_key_name).to eq('total_open_issues_count')
end
end
context 'when user cannot read confidential issues' do
before do
project.add_guest(user)
end
it 'does not include confidential issues' do
create(:issue, :opened, project: project)
create(:issue, :opened, confidential: true, project: project)
create(:issue, :opened, project: project) expect(described_class.new(project, user).count).to eq(1)
create(:issue, :opened, confidential: true, project: project) end
expect(described_class.new(project).count).to eq(1) it 'uses public_open_issues_count cache key' do
expect(described_class.new(project, user).cache_key_name).to eq('public_open_issues_count')
end
end
end end
end end
end end
...@@ -721,17 +721,18 @@ describe TodoService do ...@@ -721,17 +721,18 @@ describe TodoService do
end end
describe '#merge_request_build_failed' do describe '#merge_request_build_failed' do
it 'creates a pending todo for the merge request author' do let(:merge_participants) { [mr_unassigned.author, admin] }
service.merge_request_build_failed(mr_unassigned)
should_create_todo(user: author, target: mr_unassigned, action: Todo::BUILD_FAILED) before do
allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants)
end end
it 'creates a pending todo for merge_user' do it 'creates a pending todo for each merge_participant' do
mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin)
service.merge_request_build_failed(mr_unassigned) service.merge_request_build_failed(mr_unassigned)
should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::BUILD_FAILED) merge_participants.each do |participant|
should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::BUILD_FAILED)
end
end end
end end
...@@ -747,11 +748,19 @@ describe TodoService do ...@@ -747,11 +748,19 @@ describe TodoService do
end end
describe '#merge_request_became_unmergeable' do describe '#merge_request_became_unmergeable' do
it 'creates a pending todo for a merge_user' do let(:merge_participants) { [admin, create(:user)] }
before do
allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants)
end
it 'creates a pending todo for each merge_participant' do
mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin) mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin)
service.merge_request_became_unmergeable(mr_unassigned) service.merge_request_became_unmergeable(mr_unassigned)
should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::UNMERGEABLE) merge_participants.each do |participant|
should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::UNMERGEABLE)
end
end end
end end
......
...@@ -132,6 +132,14 @@ module LoginHelpers ...@@ -132,6 +132,14 @@ module LoginHelpers
env['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym] env['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym]
end end
def stub_omniauth_failure(strategy, message_key, exception = nil)
env = @request.env
env['omniauth.error'] = exception
env['omniauth.error.type'] = message_key.to_sym
env['omniauth.error.strategy'] = strategy
end
def stub_omniauth_saml_config(messages) def stub_omniauth_saml_config(messages)
set_devise_mapping(context: Rails.application) set_devise_mapping(context: Rails.application)
Rails.application.routes.disable_clear_and_finalize = true Rails.application.routes.disable_clear_and_finalize = true
......
module RoutesHelpers
def fake_routes(&block)
@routes = @routes.dup
@routes.formatter.clear
ActionDispatch::Routing::Mapper.new(@routes).instance_exec(&block)
end
end
...@@ -125,6 +125,16 @@ describe 'gitlab:app namespace rake task' do ...@@ -125,6 +125,16 @@ describe 'gitlab:app namespace rake task' do
expect(Dir.entries(File.join(project.repository.path, 'custom_hooks'))).to include("dummy.txt") expect(Dir.entries(File.join(project.repository.path, 'custom_hooks'))).to include("dummy.txt")
end end
end end
context 'specific backup tasks' do
let(:task_list) { %w(db repo uploads builds artifacts pages lfs registry) }
it 'prints a progress message to stdout' do
task_list.each do |task|
expect { run_rake_task("gitlab:backup:#{task}:create") }.to output(/Dumping /).to_stdout
end
end
end
end end
context 'tar creation' do context 'tar creation' do
......
...@@ -18,8 +18,13 @@ describe UpdateMergeRequestsWorker do ...@@ -18,8 +18,13 @@ describe UpdateMergeRequestsWorker do
end end
it 'executes MergeRequests::RefreshService with expected values' do it 'executes MergeRequests::RefreshService with expected values' do
expect(MergeRequests::RefreshService).to receive(:new).with(project, user).and_call_original expect(MergeRequests::RefreshService).to receive(:new)
expect_any_instance_of(MergeRequests::RefreshService).to receive(:execute).with(oldrev, newrev, ref) .with(project, user).and_wrap_original do |method, *args|
method.call(*args).tap do |refresh_service|
expect(refresh_service)
.to receive(:execute).with(oldrev, newrev, ref)
end
end
perform perform
end end
......
...@@ -77,7 +77,7 @@ test: ...@@ -77,7 +77,7 @@ test:
only: only:
- branches - branches
codequality: code_quality:
image: docker:stable image: docker:stable
variables: variables:
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
...@@ -86,9 +86,9 @@ codequality: ...@@ -86,9 +86,9 @@ codequality:
- docker:stable-dind - docker:stable-dind
script: script:
- setup_docker - setup_docker
- codeclimate - code_quality
artifacts: artifacts:
paths: [codeclimate.json] paths: [gl-code-quality-report.json]
performance: performance:
stage: performance stage: performance
...@@ -136,7 +136,7 @@ dependency_scanning: ...@@ -136,7 +136,7 @@ dependency_scanning:
artifacts: artifacts:
paths: [gl-dependency-scanning-report.json] paths: [gl-dependency-scanning-report.json]
sast:container: container_scanning:
image: docker:stable image: docker:stable
variables: variables:
DOCKER_DRIVER: overlay2 DOCKER_DRIVER: overlay2
...@@ -145,9 +145,9 @@ sast:container: ...@@ -145,9 +145,9 @@ sast:container:
- docker:stable-dind - docker:stable-dind
script: script:
- setup_docker - setup_docker
- sast_container - container_scanning
artifacts: artifacts:
paths: [gl-sast-container-report.json] paths: [gl-container-scanning-report.json]
dast: dast:
stage: dast stage: dast
...@@ -388,7 +388,7 @@ rollout 100%: ...@@ -388,7 +388,7 @@ rollout 100%:
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
function sast_container() { function container_scanning() {
if [[ -n "$CI_REGISTRY_USER" ]]; then if [[ -n "$CI_REGISTRY_USER" ]]; then
echo "Logging to GitLab Container Registry with CI credentials..." echo "Logging to GitLab Container Registry with CI credentials..."
docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
...@@ -406,10 +406,10 @@ rollout 100%: ...@@ -406,10 +406,10 @@ rollout 100%:
retries=0 retries=0
echo "Waiting for clair daemon to start" echo "Waiting for clair daemon to start"
while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
} }
function codeclimate() { function code_quality() {
docker run --env SOURCE_CODE="$PWD" \ docker run --env SOURCE_CODE="$PWD" \
--volume "$PWD":/code \ --volume "$PWD":/code \
--volume /var/run/docker.sock:/var/run/docker.sock \ --volume /var/run/docker.sock:/var/run/docker.sock \
......
...@@ -54,9 +54,9 @@ ...@@ -54,9 +54,9 @@
lodash "^4.2.0" lodash "^4.2.0"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@gitlab-org/gitlab-svgs@^1.22.0": "@gitlab-org/gitlab-svgs@^1.23.0":
version "1.22.0" version "1.23.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.22.0.tgz#9f2daefebcda911cba8341313c8c464c8087fe44" resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.23.0.tgz#42047aeedcc06bc12d417ed1efadad1749af9670"
"@mrmlnc/readdir-enhanced@^2.2.1": "@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1" version "2.2.1"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment