summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorFatih Acet <acetfatih@gmail.com>2016-10-04 18:06:30 +0300
committerFatih Acet <acetfatih@gmail.com>2016-10-04 18:06:30 +0300
commita04378a5e7332efc05fba2d31d77c1e306533147 (patch)
treec6c733642795b556a14c16ebf20202dce931d67d /app
parent94d887485a00b15657478619324e4cdc39c5c251 (diff)
parentb8005b6112d7322ff8b2cf0a1e55e6c56f0fcba3 (diff)
downloadgitlab-ce-a04378a5e7332efc05fba2d31d77c1e306533147.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into revert-c676283b-existing
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/blob/template_selector.js14
-rw-r--r--app/assets/javascripts/diff.js7
-rw-r--r--app/assets/javascripts/merge_conflict_data_provider.js.es616
-rw-r--r--app/assets/javascripts/merge_conflict_resolver.js.es65
-rw-r--r--app/assets/javascripts/merge_request.js9
-rw-r--r--app/assets/javascripts/merge_request_tabs.js34
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js.es618
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb15
-rw-r--r--app/controllers/concerns/membership_actions.rb13
-rw-r--r--app/controllers/groups/group_members_controller.rb5
-rw-r--r--app/controllers/projects/project_members_controller.rb6
-rw-r--r--app/helpers/page_layout_helper.rb8
-rw-r--r--app/helpers/todos_helper.rb20
-rw-r--r--app/models/concerns/mentionable.rb27
-rw-r--r--app/models/deployment.rb12
-rw-r--r--app/models/environment.rb4
-rw-r--r--app/models/merge_request.rb4
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/user.rb16
-rw-r--r--app/services/members/authorized_destroy_service.rb2
-rw-r--r--app/services/members/destroy_service.rb39
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/views/admin/dashboard/index.html.haml5
-rw-r--r--app/views/dashboard/todos/_todo.html.haml1
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/profiles/preferences/update.js.erb4
-rw-r--r--app/views/projects/diffs/_diffs.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml3
29 files changed, 209 insertions, 89 deletions
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index 95352164d76..6d41442cdfc 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -72,9 +72,17 @@
// To be implemented on the extending class
// e.g.
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
- TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
- this.editor.setValue(file.content, 1);
- if (!skipFocus) this.editor.focus();
+ TemplateSelector.prototype.requestFileSuccess = function(file, opts) {
+ var oldValue = this.editor.getValue();
+ var newValue = file.content;
+ if (opts == null) {
+ opts = {};
+ }
+ if (opts.append && oldValue.length && oldValue !== newValue) {
+ newValue = oldValue + '\n\n' + newValue;
+ }
+ this.editor.setValue(newValue, 1);
+ if (!opts.skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index c8634b78f2b..8086c10ad6b 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -7,6 +7,9 @@
function Diff() {
$('.files .diff-file').singleFileDiff();
this.filesCommentButton = $('.files .diff-file').filesCommentButton();
+ if (this.diffViewType() === 'parallel') {
+ $('.content-wrapper .container-fluid').removeClass('container-limited');
+ }
$(document).off('click', '.js-unfold');
$(document).on('click', '.js-unfold', (function(_this) {
return function(event) {
@@ -52,6 +55,10 @@
})(this));
}
+ Diff.prototype.diffViewType = function() {
+ return $('.inline-parallel-buttons a.active').data('view-type');
+ }
+
Diff.prototype.lineNumbers = function(line) {
if (!line.children().length) {
return [0, 0];
diff --git a/app/assets/javascripts/merge_conflict_data_provider.js.es6 b/app/assets/javascripts/merge_conflict_data_provider.js.es6
index cd92df8ddc5..13ee794ba38 100644
--- a/app/assets/javascripts/merge_conflict_data_provider.js.es6
+++ b/app/assets/javascripts/merge_conflict_data_provider.js.es6
@@ -7,13 +7,16 @@ const ORIGIN_BUTTON_TITLE = 'Use theirs';
class MergeConflictDataProvider {
getInitialData() {
+ // TODO: remove reliance on jQuery and DOM state introspection
const diffViewType = $.cookie('diff_view');
+ const fixedLayout = $('.content-wrapper .container-fluid').hasClass('container-limited');
return {
isLoading : true,
hasError : false,
isParallel : diffViewType === 'parallel',
diffViewType : diffViewType,
+ fixedLayout : fixedLayout,
isSubmitting : false,
conflictsData : {},
resolutionData : {}
@@ -192,14 +195,17 @@ class MergeConflictDataProvider {
updateViewType(newType) {
const vi = this.vueInstance;
- if (newType === vi.diffView || !(newType === 'parallel' || newType === 'inline')) {
+ if (newType === vi.diffViewType || !(newType === 'parallel' || newType === 'inline')) {
return;
}
- vi.diffView = newType;
- vi.isParallel = newType === 'parallel';
- $.cookie('diff_view', newType); // TODO: Make sure that cookie path added.
- $('.content-wrapper .container-fluid').toggleClass('container-limited');
+ vi.diffViewType = newType;
+ vi.isParallel = newType === 'parallel';
+ $.cookie('diff_view', newType, {
+ path: (gon && gon.relative_url_root) || '/'
+ });
+ $('.content-wrapper .container-fluid')
+ .toggleClass('container-limited', !vi.isParallel && vi.fixedLayout);
}
diff --git a/app/assets/javascripts/merge_conflict_resolver.js.es6 b/app/assets/javascripts/merge_conflict_resolver.js.es6
index b56fd5aa658..7e756433bf5 100644
--- a/app/assets/javascripts/merge_conflict_resolver.js.es6
+++ b/app/assets/javascripts/merge_conflict_resolver.js.es6
@@ -60,9 +60,8 @@ class MergeConflictResolver {
$('#conflicts .js-syntax-highlight').syntaxHighlight();
});
- if (this.vue.diffViewType === 'parallel') {
- $('.content-wrapper .container-fluid').removeClass('container-limited');
- }
+ $('.content-wrapper .container-fluid')
+ .toggleClass('container-limited', !this.vue.isParallel && this.vue.fixedLayout);
})
}
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 05644b3d03c..02ff5a382e2 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -36,13 +36,10 @@
};
MergeRequest.prototype.initTabs = function() {
- if (this.opts.action !== 'new') {
- // `MergeRequests#new` has no tab-persisting or lazy-loading behavior
- window.mrTabs = new MergeRequestTabs(this.opts);
- } else {
- // Show the first tab (Commits)
- return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show');
+ if (window.mrTabs) {
+ window.mrTabs.unbindEvents();
}
+ window.mrTabs = new MergeRequestTabs(this.opts);
};
MergeRequest.prototype.showAllCommits = function() {
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 18bbfa7a459..bec11a198a1 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -56,6 +56,8 @@
MergeRequestTabs.prototype.commitsLoaded = false;
+ MergeRequestTabs.prototype.fixedLayoutPref = null;
+
function MergeRequestTabs(opts) {
this.opts = opts != null ? opts : {};
this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true;
@@ -70,7 +72,12 @@
MergeRequestTabs.prototype.bindEvents = function() {
$(document).on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown);
- return $(document).on('click', '.js-show-tab', this.showTab);
+ $(document).on('click', '.js-show-tab', this.showTab);
+ };
+
+ MergeRequestTabs.prototype.unbindEvents = function() {
+ $(document).off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown);
+ $(document).off('click', '.js-show-tab', this.showTab);
};
MergeRequestTabs.prototype.showTab = function(event) {
@@ -85,11 +92,15 @@
if (action === 'commits') {
this.loadCommits($target.attr('href'));
this.expandView();
+ this.resetViewContainer();
} else if (action === 'diffs') {
this.loadDiff($target.attr('href'));
if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') {
this.shrinkView();
}
+ if (this.diffViewType() === 'parallel') {
+ this.expandViewContainer();
+ }
navBarHeight = $('.navbar-gitlab').outerHeight();
$.scrollTo(".merge-request-details .merge-request-tabs", {
offset: -navBarHeight
@@ -97,11 +108,14 @@
} else if (action === 'builds') {
this.loadBuilds($target.attr('href'));
this.expandView();
+ this.resetViewContainer();
} else if (action === 'pipelines') {
this.loadPipelines($target.attr('href'));
this.expandView();
+ this.resetViewContainer();
} else {
this.expandView();
+ this.resetViewContainer();
}
if (this.opts.setUrl) {
this.setCurrentAction(action);
@@ -126,7 +140,7 @@
if (action === 'show') {
action = 'notes';
}
- return $(".merge-request-tabs a[data-action='" + action + "']").tab('show');
+ $(".merge-request-tabs a[data-action='" + action + "']").tab('show').trigger('shown.bs.tab');
};
// Replaces the current Merge Request-specific action in the URL with a new one
@@ -209,7 +223,7 @@
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
$('#diffs .js-syntax-highlight').syntaxHighlight();
$('#diffs .diff-file').singleFileDiff();
- if (_this.diffViewType() === 'parallel') {
+ if (_this.diffViewType() === 'parallel' && _this.currentAction === 'diffs') {
_this.expandViewContainer();
}
_this.diffsLoaded = true;
@@ -308,11 +322,21 @@
MergeRequestTabs.prototype.diffViewType = function() {
return $('.inline-parallel-buttons a.active').data('view-type');
- // Returns diff view type
};
MergeRequestTabs.prototype.expandViewContainer = function() {
- return $('.container-fluid').removeClass('container-limited');
+ var $wrapper = $('.content-wrapper .container-fluid');
+ if (this.fixedLayoutPref === null) {
+ this.fixedLayoutPref = $wrapper.hasClass('container-limited');
+ }
+ $wrapper.removeClass('container-limited');
+ };
+
+ MergeRequestTabs.prototype.resetViewContainer = function() {
+ if (this.fixedLayoutPref !== null) {
+ $('.content-wrapper .container-fluid')
+ .toggleClass('container-limited', this.fixedLayoutPref);
+ }
};
MergeRequestTabs.prototype.shrinkView = function() {
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6
index c32ddf80219..017008c8438 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js.es6
+++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6
@@ -16,7 +16,7 @@
if (initialQuery.name) this.requestFile(initialQuery);
$('.reset-template', this.dropdown.parent()).on('click', () => {
- if (this.currentTemplate) this.setInputValueToTemplateContent();
+ if (this.currentTemplate) this.setInputValueToTemplateContent(false);
});
}
@@ -26,22 +26,24 @@
this.currentTemplate = currentTemplate;
if (err) return; // Error handled by global AJAX error handler
this.stopLoadingSpinner();
- this.setInputValueToTemplateContent();
+ this.setInputValueToTemplateContent(true);
});
return;
}
- setInputValueToTemplateContent() {
+ setInputValueToTemplateContent(append) {
// `this.requestFileSuccess` sets the value of the description input field
- // to the content of the template selected.
+ // to the content of the template selected. If `append` is true, the
+ // template content will be appended to the previous value of the field,
+ // separated by a blank line if the previous value is non-empty.
if (this.titleInput.val() === '') {
// If the title has not yet been set, focus the title input and
- // skip focusing the description input by setting `true` as the 2nd
- // argument to `requestFileSuccess`.
- this.requestFileSuccess(this.currentTemplate, true);
+ // skip focusing the description input by setting `true` as the
+ // `skipFocus` option to `requestFileSuccess`.
+ this.requestFileSuccess(this.currentTemplate, {skipFocus: true, append});
this.titleInput.focus();
} else {
- this.requestFileSuccess(this.currentTemplate);
+ this.requestFileSuccess(this.currentTemplate, {skipFocus: false, append});
}
return;
}
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index d5a8a962662..4c497711fc0 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -23,15 +23,24 @@ module AuthenticatesWithTwoFactor
#
# Returns nil
def prompt_for_two_factor(user)
+ return locked_user_redirect(user) if user.access_locked?
+
session[:otp_user_id] = user.id
setup_u2f_authentication(user)
render 'devise/sessions/two_factor'
end
+ def locked_user_redirect(user)
+ flash.now[:alert] = 'Invalid Login or password'
+ render 'devise/sessions/new'
+ end
+
def authenticate_with_two_factor
user = self.resource = find_user
- if user_params[:otp_attempt].present? && session[:otp_user_id]
+ if user.access_locked?
+ locked_user_redirect(user)
+ elsif user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user)
elsif user_params[:device_response].present? && session[:otp_user_id]
authenticate_with_two_factor_via_u2f(user)
@@ -50,8 +59,9 @@ module AuthenticatesWithTwoFactor
remember_me(user) if user_params[:remember_me] == '1'
sign_in(user)
else
+ user.increment_failed_attempts!
flash.now[:alert] = 'Invalid two-factor code.'
- render :two_factor
+ prompt_for_two_factor(user)
end
end
@@ -65,6 +75,7 @@ module AuthenticatesWithTwoFactor
remember_me(user) if user_params[:remember_me] == '1'
sign_in(user)
else
+ user.increment_failed_attempts!
flash.now[:alert] = 'Authentication via U2F device failed.'
prompt_for_two_factor(user)
end
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index b8ed2c159a7..c13333641d3 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -15,18 +15,17 @@ module MembershipActions
end
def leave
- @member = membershipable.members.find_by(user_id: current_user) ||
- membershipable.requesters.find_by(user_id: current_user)
- Members::DestroyService.new(@member, current_user).execute
+ member = Members::DestroyService.new(membershipable, current_user, user_id: current_user.id).
+ execute(:all)
- source_type = @member.real_source_type.humanize(capitalize: false)
+ source_type = membershipable.class.to_s.humanize(capitalize: false)
notice =
- if @member.request?
+ if member.request?
"Your access request to the #{source_type} has been withdrawn."
else
- "You left the \"#{@member.source.human_name}\" #{source_type}."
+ "You left the \"#{membershipable.human_name}\" #{source_type}."
end
- redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize]
+ redirect_path = member.request? ? member.source : [:dashboard, membershipable.class.to_s.tableize]
redirect_to redirect_path, notice: notice
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 9c323d7705a..18cd800c619 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -40,10 +40,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def destroy
- @group_member = @group.members.find_by(id: params[:id]) ||
- @group.requesters.find_by(id: params[:id])
-
- Members::DestroyService.new(@group_member, current_user).execute
+ Members::DestroyService.new(@group, current_user, id: params[:id]).execute(:all)
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 2343c7d20ec..f56b256984b 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -55,10 +55,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def destroy
- @project_member = @project.members.find_by(id: params[:id]) ||
- @project.requesters.find_by(id: params[:id])
-
- Members::DestroyService.new(@project_member, current_user).execute
+ Members::DestroyService.new(@project, current_user, params).
+ execute(:all)
respond_to do |format|
format.html do
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 22387d66451..7d4d049101a 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -92,12 +92,8 @@ module PageLayoutHelper
end
end
- def fluid_layout(enabled = false)
- if @fluid_layout.nil?
- @fluid_layout = (current_user && current_user.layout == "fluid") || enabled
- else
- @fluid_layout
- end
+ def fluid_layout
+ current_user && current_user.layout == "fluid"
end
def blank_container(enabled = false)
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 1e86f648203..a9db8bb2b82 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -114,6 +114,26 @@ module TodosHelper
selected_type ? selected_type[:text] : default_type
end
+ def todo_due_date(todo)
+ return unless todo.target.try(:due_date)
+
+ is_due_today = todo.target.due_date.today?
+ is_overdue = todo.target.overdue?
+ css_class =
+ if is_due_today
+ 'text-warning'
+ elsif is_overdue
+ 'text-danger'
+ else
+ ''
+ end
+
+ html = "&middot; ".html_safe
+ html << content_tag(:span, class: css_class) do
+ "Due #{is_due_today ? "today" : todo.target.due_date.to_s(:medium)}"
+ end
+ end
+
private
def show_todo_state?(todo)
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index ec9e0f1b1d0..eb2ff0428f6 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -43,19 +43,15 @@ module Mentionable
self
end
- def all_references(current_user = nil, text = nil, extractor: nil)
+ def all_references(current_user = nil, extractor: nil)
extractor ||= Gitlab::ReferenceExtractor.
new(project, current_user)
- if text
- extractor.analyze(text, author: author)
- else
- self.class.mentionable_attrs.each do |attr, options|
- text = __send__(attr)
- options = options.merge(cache_key: [self, attr], author: author)
+ self.class.mentionable_attrs.each do |attr, options|
+ text = __send__(attr)
+ options = options.merge(cache_key: [self, attr], author: author)
- extractor.analyze(text, options)
- end
+ extractor.analyze(text, options)
end
extractor
@@ -66,8 +62,8 @@ module Mentionable
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
- def referenced_mentionables(current_user = self.author, text = nil)
- refs = all_references(current_user, text)
+ def referenced_mentionables(current_user = self.author)
+ refs = all_references(current_user)
refs = (refs.issues + refs.merge_requests + refs.commits)
# We're using this method instead of Array diffing because that requires
@@ -77,8 +73,8 @@ module Mentionable
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in the +mentionable_attrs+.
- def create_cross_references!(author = self.author, without = [], text = nil)
- refs = referenced_mentionables(author, text)
+ def create_cross_references!(author = self.author, without = [])
+ refs = referenced_mentionables(author)
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
@@ -97,10 +93,7 @@ module Mentionable
return if changes.empty?
- original_text = changes.collect { |_, vals| vals.first }.join(' ')
-
- preexisting = referenced_mentionables(author, original_text)
- create_cross_references!(author, preexisting)
+ create_cross_references!(author)
end
private
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 07d7e19e70d..82b27b78229 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -11,7 +11,7 @@ class Deployment < ActiveRecord::Base
delegate :name, to: :environment, prefix: true
- after_save :keep_around_commit
+ after_save :create_ref
def commit
project.commit(sha)
@@ -29,8 +29,8 @@ class Deployment < ActiveRecord::Base
self == environment.last_deployment
end
- def keep_around_commit
- project.repository.keep_around(self.sha)
+ def create_ref
+ project.repository.create_ref(ref, ref_path)
end
def manual_actions
@@ -76,4 +76,10 @@ class Deployment < ActiveRecord::Base
where.not(id: self.id).
take
end
+
+ private
+
+ def ref_path
+ File.join(environment.ref_path, 'deployments', id.to_s)
+ end
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 49e0a20640c..f0f3ee23223 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -47,4 +47,8 @@ class Environment < ActiveRecord::Base
def update_merge_request_metrics?
self.name == "production"
end
+
+ def ref_path
+ "refs/environments/#{Shellwords.shellescape(name)}"
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index a431d46cc9e..071dfe54ef9 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -523,9 +523,13 @@ class MergeRequest < ActiveRecord::Base
# `MergeRequestsClosingIssues` model. This is a performance optimization.
# Calculating this information for a number of merge requests requires
# running `ReferenceExtractor` on each of them separately.
+ # This optimization does not apply to issues from external sources.
def cache_merge_request_closes_issues!(current_user = self.author)
+ return if project.has_external_issue_tracker?
+
transaction do
self.merge_requests_closing_issues.delete_all
+
closes_issues(current_user).each do |issue|
self.merge_requests_closing_issues.create!(issue: issue)
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 51557228ab9..eb574555df6 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -997,6 +997,10 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo)
end
+ def create_ref(ref, ref_path)
+ fetch_ref(path_to_repo, ref, ref_path)
+ end
+
def update_branch_with_hooks(current_user, branch)
update_autocrlf_option
diff --git a/app/models/service.rb b/app/models/service.rb
index 80de7175565..66c804f2b06 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -136,6 +136,7 @@ class Service < ActiveRecord::Base
end
def #{arg}=(value)
+ self.properties ||= {}
updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties['#{arg}'] = value
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 6996740eebd..7f5a8562907 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -827,6 +827,22 @@ class User < ActiveRecord::Base
todos_pending_count(force: true)
end
+ # This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth
+ # flow means we don't call that automatically (and can't conveniently do so).
+ #
+ # See:
+ # <https://github.com/plataformatec/devise/blob/v4.0.0/lib/devise/models/lockable.rb#L92>
+ #
+ def increment_failed_attempts!
+ self.failed_attempts ||= 0
+ self.failed_attempts += 1
+ if attempts_exceeded?
+ lock_access! unless access_locked?
+ else
+ save(validate: false)
+ end
+ end
+
private
def projects_union(min_access_level = nil)
diff --git a/app/services/members/authorized_destroy_service.rb b/app/services/members/authorized_destroy_service.rb
index ca9db59cac7..b7a244c2029 100644
--- a/app/services/members/authorized_destroy_service.rb
+++ b/app/services/members/authorized_destroy_service.rb
@@ -14,6 +14,8 @@ module Members
if member.request? && member.user != user
notification_service.decline_access_request(member)
end
+
+ member
end
end
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index 9a2bf82ef51..431da8372c9 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -1,17 +1,42 @@
module Members
class DestroyService < BaseService
- attr_accessor :member, :current_user
+ include MembersHelper
- def initialize(member, current_user)
- @member = member
+ attr_accessor :source
+
+ ALLOWED_SCOPES = %i[members requesters all]
+
+ def initialize(source, current_user, params = {})
+ @source = source
@current_user = current_user
+ @params = params
end
- def execute
- unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
- raise Gitlab::Access::AccessDeniedError
- end
+ def execute(scope = :members)
+ raise "scope :#{scope} is not allowed!" unless ALLOWED_SCOPES.include?(scope)
+
+ member = find_member!(scope)
+
+ raise Gitlab::Access::AccessDeniedError unless can_destroy_member?(member)
+
AuthorizedDestroyService.new(member, current_user).execute
end
+
+ private
+
+ def find_member!(scope)
+ condition = params[:user_id] ? { user_id: params[:user_id] } : { id: params[:id] }
+ case scope
+ when :all
+ source.members.find_by(condition) ||
+ source.requesters.find_by!(condition)
+ else
+ source.public_send(scope).find_by!(condition)
+ end
+ end
+
+ def can_destroy_member?(member)
+ member && can?(current_user, action_member_permission(:destroy, member), member)
+ end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index bf251816e7e..1ce66d50368 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -347,7 +347,7 @@ module SystemNoteService
notes = notes.where(noteable_id: noteable.id)
end
- notes_for_mentioner(mentioner, noteable, notes).count > 0
+ notes_for_mentioner(mentioner, noteable, notes).exists?
end
# Build an Array of lines detailing each commit added in a merge request
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index e6687f43816..90798c47d97 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -63,6 +63,11 @@
Reply by email
%span.light.pull-right
= boolean_to_icon Gitlab::IncomingEmail.enabled?
+ %p
+ Container Registry
+ %span.light.pull-right
+ = boolean_to_icon Gitlab.config.registry.enabled
+
.col-md-4
%h4
Components
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index b40395c74de..cc077fad32a 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -19,6 +19,7 @@
(removed)
&middot; #{time_ago_with_tooltip(todo.created_at)}
+ = todo_due_date(todo)
.todo-body
.todo-note
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 94c53882623..237280872f1 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,5 +1,5 @@
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- %div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
+ %div{ class: "container-fluid" }
.header-content
%button.side-nav-toggle{ type: 'button', "aria-label" => "Toggle global navigation" }
%span.sr-only Toggle navigation
diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb
index 4433cab7782..8966dd3fd86 100644
--- a/app/views/profiles/preferences/update.js.erb
+++ b/app/views/profiles/preferences/update.js.erb
@@ -4,9 +4,9 @@ $('body').addClass('<%= user_application_theme %>')
// Toggle container-fluid class
if ('<%= current_user.layout %>' === 'fluid') {
- $('.content-wrapper').find('.container-fluid').removeClass('container-limited')
+ $('.content-wrapper .container-fluid').removeClass('container-limited')
} else {
- $('.content-wrapper').find('.container-fluid').addClass('container-limited')
+ $('.content-wrapper .container-fluid').addClass('container-limited')
}
// Re-enable the "Save" button
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index 62aff36aadd..576e7ef021a 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -1,7 +1,5 @@
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- diff_files = diffs.diff_files
-- if diff_view == :parallel
- - fluid_layout true
.content-block.oneline-block.files-changed
.inline-parallel-buttons
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index d03ff9ec7e8..9f34ca9ff4e 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -4,9 +4,6 @@
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('diff_notes/diff_notes_bundle.js')
-- if diff_view == :parallel
- - fluid_layout true
-
.merge-request{'data-url' => merge_request_path(@merge_request)}
= render "projects/merge_requests/show/mr_title"