Commit 51cd05e9 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce-to-ee-2017-12-16' into 'master'

CE upstream - Saturday

Closes gitlab-ce#38019, gitlab-qa#86, #4125, and gitaly#808

See merge request gitlab-org/gitlab-ee!3803
parents 577f1260 b679480c
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git-2.13-chrome-62.0-node-8.x-yarn-1.2-postgresql-9.6"
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.5-golang-1.8-git-2.14-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
.dedicated-runner: &dedicated-runner
retry: 1
......
......@@ -416,7 +416,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.59.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.61.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -308,7 +308,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.59.0)
gitaly-proto (0.61.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -1077,7 +1077,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.59.0)
gitaly-proto (~> 0.61.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......
/* eslint-disable no-param-reassign, class-methods-use-this */
/* global Pager */
import Cookies from 'js-cookie';
import Pager from './pager';
import { localTimeAgo } from './lib/utils/datetime_utility';
export default class Activities {
......
/* eslint-disable func-names, wrap-iife, consistent-return,
no-return-assign, no-param-reassign, one-var-declaration-per-line, no-unused-vars,
prefer-template, object-shorthand, prefer-arrow-callback */
/* global Pager */
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
export default (function () {
const CommitsList = {};
......
......@@ -7,8 +7,8 @@ import IssuableForm from './issuable_form';
import LabelsSelect from './labels_select';
/* global MilestoneSelect */
import NewBranchForm from './new_branch_form';
/* global NotificationsForm */
/* global NotificationsDropdown */
import NotificationsForm from './notifications_form';
import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
import LineHighlighter from './line_highlighter';
......@@ -455,7 +455,7 @@ import initGroupAnalytics from './init_group_analytics';
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
shortcut_handler = new ShortcutsNavigation();
new NotificationsForm();
new NotificationsDropdown();
notificationsDropdown();
new ProjectsList();
if (newGroupChildWrapper) {
......@@ -712,7 +712,7 @@ import initGroupAnalytics from './init_group_analytics';
break;
case 'profiles':
new NotificationsForm();
new NotificationsDropdown();
notificationsDropdown();
break;
case 'projects':
new Project();
......@@ -736,7 +736,7 @@ import initGroupAnalytics from './init_group_analytics';
case 'show':
new Star();
new ProjectNew();
new NotificationsDropdown();
notificationsDropdown();
break;
case 'wikis':
new Wikis();
......
......@@ -34,16 +34,11 @@ import { getLocationHash, visitUrl } from './lib/utils/url_utility';
import './behaviors/';
// everything else
import './activities';
import './admin';
import loadAwardsHandler from './awards_handler';
import bp from './breakpoints';
import './confirm_danger_modal';
import Flash, { removeFlashClickListener } from './flash';
import './gl_dropdown';
import './gl_field_error';
import './gl_field_errors';
import './gl_form';
import initTodoToggle from './header';
import initImporterStatus from './importer_status';
import './layout_nav';
......@@ -51,11 +46,7 @@ import LazyLoader from './lazy_loader';
import './line_highlighter';
import initLogoAnimation from './logo';
import './milestone_select';
import './notifications_dropdown';
import './notifications_form';
import './pager';
import './preview_markdown';
import './project_import';
import './projects_dropdown';
import './render_gfm';
import initBreadcrumbs from './breadcrumb';
......
......@@ -3,7 +3,7 @@
import 'vendor/jquery.waitforimages';
import TaskList from './task_list';
import './merge_request_tabs';
import MergeRequestTabs from './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
import { addDelimiter } from './lib/utils/text_utility';
......@@ -49,7 +49,7 @@ MergeRequest.prototype.initTabs = function() {
if (window.mrTabs) {
window.mrTabs.unbindEvents();
}
window.mrTabs = new gl.MergeRequestTabs(this.opts);
window.mrTabs = new MergeRequestTabs(this.opts);
};
MergeRequest.prototype.showAllCommits = function() {
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, max-len */
import Flash from './flash';
(function() {
this.NotificationsDropdown = (function() {
function NotificationsDropdown() {
$(document).off('click', '.update-notification').on('click', '.update-notification', function(e) {
var form, label, notificationLevel;
e.preventDefault();
if ($(this).is('.is-active') && $(this).data('notification-level') === 'custom') {
return;
}
notificationLevel = $(this).data('notification-level');
label = $(this).data('notification-title');
form = $(this).parents('.notification-form:first');
form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner');
form.find('#notification_setting_level').val(notificationLevel);
return form.submit();
});
$(document).off('ajax:success', '.notification-form').on('ajax:success', '.notification-form', function(e, data) {
if (data.saved) {
return $(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html);
} else {
return new Flash('Failed to save new settings', 'alert');
}
});
export default function notificationsDropdown() {
$(document).on('click', '.update-notification', function updateNotificationCallback(e) {
e.preventDefault();
if ($(this).is('.is-active') && $(this).data('notification-level') === 'custom') {
return;
}
return NotificationsDropdown;
})();
}).call(window);
const notificationLevel = $(this).data('notification-level');
const form = $(this).parents('.notification-form:first');
form.find('.js-notification-loading').toggleClass('fa-bell fa-spin fa-spinner');
form.find('#notification_setting_level').val(notificationLevel);
form.submit();
});
$(document).on('ajax:success', '.notification-form', (e, data) => {
if (data.saved) {
$(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html);
} else {
Flash('Failed to save new settings', 'alert');
}
});
}
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, newline-per-chained-call, comma-dangle, consistent-return, prefer-arrow-callback, max-len */
(function() {
this.NotificationsForm = (function() {
function NotificationsForm() {
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.removeEventListeners();
this.initEventListeners();
}
export default class NotificationsForm {
constructor() {
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.initEventListeners();
}
NotificationsForm.prototype.removeEventListeners = function() {
return $(document).off('change', '.js-custom-notification-event');
};
initEventListeners() {
$(document).on('change', '.js-custom-notification-event', this.toggleCheckbox);
}
NotificationsForm.prototype.initEventListeners = function() {
return $(document).on('change', '.js-custom-notification-event', this.toggleCheckbox);
};
toggleCheckbox(e) {
const $checkbox = $(e.currentTarget);
const $parent = $checkbox.closest('.checkbox');
NotificationsForm.prototype.toggleCheckbox = function(e) {
var $checkbox, $parent;
$checkbox = $(e.currentTarget);
$parent = $checkbox.closest('.checkbox');
return this.saveEvent($checkbox, $parent);
};
this.saveEvent($checkbox, $parent);
}
NotificationsForm.prototype.showCheckboxLoadingSpinner = function($parent) {
return $parent.addClass('is-loading').find('.custom-notification-event-loading').removeClass('fa-check').addClass('fa-spin fa-spinner').removeClass('is-done');
};
// eslint-disable-next-line class-methods-use-this
showCheckboxLoadingSpinner($parent) {
$parent.addClass('is-loading')
.find('.custom-notification-event-loading')
.removeClass('fa-check')
.addClass('fa-spin fa-spinner')
.removeClass('is-done');
}
NotificationsForm.prototype.saveEvent = function($checkbox, $parent) {
var form;
form = $parent.parents('form:first');
return $.ajax({
url: form.attr('action'),
method: form.attr('method'),
dataType: 'json',
data: form.serialize(),
beforeSend: (function(_this) {
return function() {
return _this.showCheckboxLoadingSpinner($parent);
};
})(this)
}).done(function(data) {
$checkbox.enable();
if (data.saved) {
$parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
return setTimeout(function() {
return $parent.removeClass('is-loading').find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
}, 2000);
}
});
};
saveEvent($checkbox, $parent) {
const form = $parent.parents('form:first');
return NotificationsForm;
})();
}).call(window);
return $.ajax({
url: form.attr('action'),
method: form.attr('method'),
dataType: 'json',
data: form.serialize(),
beforeSend: () => {
this.showCheckboxLoadingSpinner($parent);
},
}).done((data) => {
$checkbox.enable();
if (data.saved) {
$parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
setTimeout(() => {
$parent.removeClass('is-loading')
.find('.custom-notification-event-loading')
.toggleClass('fa-spin fa-spinner fa-check is-done');
}, 2000);
}
});
}
}
import { getParameterByName } from '~/lib/utils/common_utils';
import { removeParams } from './lib/utils/url_utility';
(() => {
const ENDLESS_SCROLL_BOTTOM_PX = 400;
const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000;
const ENDLESS_SCROLL_BOTTOM_PX = 400;
const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000;
const Pager = {
init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
this.limit = limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable;
this.prepareData = prepareData;
this.callback = callback;
this.loading = $('.loading').first();
if (preload) {
this.offset = 0;
this.getOld();
}
this.initLoadMore();
},
export default {
init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
this.limit = limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable;
this.prepareData = prepareData;
this.callback = callback;
this.loading = $('.loading').first();
if (preload) {
this.offset = 0;
this.getOld();
}
this.initLoadMore();
},
getOld() {
this.loading.show();
$.ajax({
type: 'GET',
url: this.url,
data: `limit=${this.limit}&offset=${this.offset}`,
dataType: 'json',
error: () => this.loading.hide(),
success: (data) => {
this.append(data.count, this.prepareData(data.html));
this.callback();
getOld() {
this.loading.show();
$.ajax({
type: 'GET',
url: this.url,
data: `limit=${this.limit}&offset=${this.offset}`,
dataType: 'json',
error: () => this.loading.hide(),
success: (data) => {
this.append(data.count, this.prepareData(data.html));
this.callback();
// keep loading until we've filled the viewport height
if (!this.disable && !this.isScrollable()) {
this.getOld();
} else {
this.loading.hide();
}
},
});
},
// keep loading until we've filled the viewport height
if (!this.disable && !this.isScrollable()) {
this.getOld();
} else {
this.loading.hide();
}
},
});
},
append(count, html) {
$('.content_list').append(html);
if (count > 0) {
this.offset += count;
} else {
this.disable = true;
}
},
append(count, html) {
$('.content_list').append(html);
if (count > 0) {
this.offset += count;
} else {
this.disable = true;
}
},
isScrollable() {
const $w = $(window);
return $(document).height() > $w.height() + $w.scrollTop() + ENDLESS_SCROLL_BOTTOM_PX;
},
isScrollable() {
const $w = $(window);
return $(document).height() > $w.height() + $w.scrollTop() + ENDLESS_SCROLL_BOTTOM_PX;
},
initLoadMore() {
$(document).unbind('scroll');
$(document).endlessScroll({
bottomPixels: ENDLESS_SCROLL_BOTTOM_PX,
fireDelay: ENDLESS_SCROLL_FIRE_DELAY_MS,
fireOnce: true,
ceaseFire: () => this.disable === true,
callback: () => {
if (!this.loading.is(':visible')) {
this.loading.show();
this.getOld();
}
},
});
},
};
window.Pager = Pager;
})();
initLoadMore() {
$(document).unbind('scroll');
$(document).endlessScroll({
bottomPixels: ENDLESS_SCROLL_BOTTOM_PX,
fireDelay: ENDLESS_SCROLL_FIRE_DELAY_MS,
fireOnce: true,
ceaseFire: () => this.disable === true,
callback: () => {
if (!this.loading.is(':visible')) {
this.loading.show();
this.getOld();
}
},
});
},
};
......@@ -65,10 +65,12 @@ export default {
<div class="mr-widget-body media">
<status-icon status="success" />
<div class="media-body">
<h4>
Set by
<mr-widget-author :author="mr.setToMWPSBy" />
to be merged automatically when the pipeline succeeds
<h4 class="flex-container-block">
<span class="append-right-10">
Set by
<mr-widget-author :author="mr.setToMWPSBy" />
to be merged automatically when the pipeline succeeds
</span>
<a
v-if="mr.canCancelAutomaticMerge"
@click.prevent="cancelAutomaticMerge"
......@@ -94,8 +96,13 @@ export default {
<p v-if="mr.shouldRemoveSourceBranch">
The source branch will be removed
</p>
<p v-else>
The source branch will not be removed
<p
v-else
class="flex-container-block"
>
<span class="append-right-10">
The source branch will not be removed
</span>
<a
v-if="canRemoveSourceBranch"
:disabled="isRemovingSourceBranch"
......
<script>
import { s__ } from '../../locale';
import icon from './icon.vue';
import loadingIcon from './loading_icon.vue';
const ICON_ON = 'status_success_borderless';
const ICON_OFF = 'status_failed_borderless';
const LABEL_ON = s__('ToggleButton|Toggle Status: ON');
const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
export default {
props: {
name: {
......@@ -22,19 +29,10 @@
required: false,
default: false,
},
enabledText: {
type: String,
required: false,
default: 'Enabled',
},
disabledText: {
type: String,
required: false,
default: 'Disabled',
},
},
components: {
icon,
loadingIcon,
},
......@@ -43,6 +41,15 @@
event: 'change',
},
computed: {
toggleIcon() {
return this.value ? ICON_ON : ICON_OFF;
},
ariaLabel() {
return this.value ? LABEL_ON : LABEL_OFF;
},
},
methods: {
toggleFeature() {
if (!this.disabledInput) this.$emit('change', !this.value);
......@@ -60,10 +67,8 @@
/>
<button
type="button"
aria-label="Toggle"
class="project-feature-toggle"
:data-enabled-text="enabledText"
:data-disabled-text="disabledText"
:aria-label="ariaLabel"
:class="{
'is-checked': value,
'is-disabled': disabledInput,
......@@ -72,6 +77,11 @@
@click="toggleFeature"
>
<loadingIcon class="loading-icon" />
<span class="toggle-icon">
<icon
css-classes="toggle-icon-svg"
:name="toggleIcon"/>
</span>
</button>
</label>
</template>
......@@ -396,3 +396,8 @@ span.idiff {
.file-fork-suggestion-note {
margin-right: 1.5em;
}
.label-lfs {
color: $common-gray-light;
border: 1px solid $common-gray-light;
}
......@@ -27,7 +27,7 @@
border: 0;
outline: 0;
display: block;
width: 100px;
width: 50px;
height: 24px;
cursor: pointer;
user-select: none;
......@@ -42,31 +42,31 @@
background: none;
}
&::before {
color: $feature-toggle-text-color;
font-size: 12px;
line-height: 24px;
position: absolute;
top: 0;
left: 25px;
right: 5px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
animation: animate-disabled .2s ease-in;
content: attr(data-disabled-text);
}
&::after {
.toggle-icon {
position: relative;
display: block;
content: "";
width: 22px;
height: 18px;
left: 0;
border-radius: 9px;
background: $feature-toggle-color;
transition: all .2s ease;
&,
.toggle-icon-svg {
width: 18px;
height: 18px;
}
.toggle-icon-svg {
fill: $feature-toggle-color-disabled;
}
.toggle-status-checked {
display: none;
}
.toggle-status-unchecked {
display: inline;
}
}
.loading-icon {
......@@ -77,11 +77,10 @@
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&.is-loading {
&::before {
.toggle-icon {
display: none;
}
......@@ -100,15 +99,20 @@
&.is-checked {
background: $feature-toggle-color-enabled;
&::before {
left: 5px;
right: 25px;
animation: animate-enabled .2s ease-in;
content: attr(data-enabled-text);
}
.toggle-icon {
left: calc(100% - 18px);
&::after {
left: calc(100% - 22px);
.toggle-icon-svg {
fill: $feature-toggle-color-enabled;
}
.toggle-status-checked {
display: inline;
}
.toggle-status-unchecked {
display: none;
}
}
}
......
......@@ -110,7 +110,7 @@ class Projects::JobsController < Projects::ApplicationController
def erase
if @build.erase(erased_by: current_user)
redirect_to project_job_path(project, @build),
notice: "Build has been successfully erased!"
notice: "Job has been successfully erased!"
else
respond_422
end
......
......@@ -11,7 +11,10 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
before_action :build_merge_request, except: [:create]
def new
define_new_vars
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/40934
Gitlab::GitalyClient.allow_n_plus_1_calls do
define_new_vars
end
end
def create
......
......@@ -26,6 +26,7 @@ class Projects::TreeController < Projects::ApplicationController
respond_to do |format|
format.html do
lfs_blob_ids
@last_commit = @repository.last_commit_for_path(@commit.id, @tree.path) || @commit
end
......
......@@ -11,6 +11,7 @@ class ProjectsController < Projects::ApplicationController
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
before_action :assign_tree_vars, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
# Authorize
......
......@@ -16,11 +16,11 @@ class Identity < ActiveRecord::Base
end
def ldap?
provider.starts_with?('ldap')
Gitlab::OAuth::Provider.ldap_provider?(provider)
end
def self.normalize_uid(provider, uid)
if provider.to_s.starts_with?('ldap')
if Gitlab::OAuth::Provider.ldap_provider?(provider)
Gitlab::LDAP::Person.normalize_dn(uid)
else
uid.to_s
......
......@@ -995,7 +995,7 @@ class Repository
def merge_base(first_commit_id, second_commit_id)
first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
rugged.merge_base(first_commit_id, second_commit_id)
raw_repository.merge_base(first_commit_id, second_commit_id)
rescue Rugged::ReferenceError
nil
end
......
......@@ -757,7 +757,7 @@ class User < ActiveRecord::Base
def ldap_user?
if identities.loaded?
identities.find { |identity| identity.provider.start_with?('ldap') && !identity.extern_uid.nil? }
identities.find { |identity| Gitlab::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
else
identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end
......
......@@ -6,11 +6,11 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base
SYNCABLE_ATTRIBUTES = %i[name email location].freeze
def read_only?(attribute)
Gitlab.config.omniauth.sync_profile_from_provider && synced?(attribute)
sync_profile_from_provider? && synced?(attribute)
end
def read_only_attributes
return [] unless Gitlab.config.omniauth.sync_profile_from_provider
return [] unless sync_profile_from_provider?
SYNCABLE_ATTRIBUTES.select { |key| synced?(key) }
end
......@@ -22,4 +22,10 @@ class UserSyncedAttributesMetadata < ActiveRecord::Base
def set_attribute_synced(attribute, value)
write_attribute("#{attribute}_synced", value)
end
private
def sync_profile_from_provider?
Gitlab::OAuth::Provider.sync_profile_from_provider?(provider)
end
end
......@@ -15,7 +15,7 @@
Unable to collect CPU info
.col-sm-4
.light-well
%h4 Memory
%h4 Memory Usage
.data
- if @memory
%h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
......@@ -24,7 +24,7 @@
Unable to collect memory info
.col-sm-4
.light-well
%h4 Disks
%h4 Disk Usage
.data
- @disks.each do |disk|
%h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
......@@ -34,4 +34,4 @@
.light-well
%h4 Uptime
.data
%h1= time_ago_with_tooltip(Rails.application.config.booted_at)
%h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at)
......@@ -161,7 +161,6 @@
%ul
%li User will not be able to login
%li User will not be able to access git repositories
%li User will be removed from joined projects and groups
%li Personal projects will be left
%li Owned groups will be left
%br
......
......@@ -8,7 +8,7 @@
%br
%span.descr
Pipelines need to be configured to enable this feature.
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds')
= link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds'), target: '_blank'
.checkbox
= form.label :only_allow_merge_if_all_discussions_are_resolved do
= form.check_box :only_allow_merge_if_all_discussions_are_resolved
......
......@@ -8,3 +8,6 @@
%small
= number_to_human_size(blob.raw_size)
- if blob.stored_externally? && blob.external_storage == :lfs
%span.label.label-lfs.append-right-5 LFS
......@@ -16,7 +16,8 @@
class: "js-toggle-cluster-list project-feature-toggle #{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}",
"aria-label": s_("ClusterIntegration|Toggle Cluster"),
disabled: !cluster.can_toggle_cluster?,
data: { "enabled-text": s_("ClusterIntegration|Active"),
"disabled-text": s_("ClusterIntegration|Inactive"),
endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
= icon("spinner spin", class: "loading-icon")
%span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
......@@ -7,8 +7,10 @@
%button{ type: 'button',
class: "js-toggle-cluster project-feature-toggle #{'is-checked' unless !@cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}",
"aria-label": s_("ClusterIntegration|Toggle Cluster"),
disabled: !can?(current_user, :update_cluster, @cluster),
data: { "enabled-text": s_("ClusterIntegration|Active"), "disabled-text": s_("ClusterIntegration|Inactive"), } }
disabled: !can?(current_user, :update_cluster, @cluster) }
%span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
- if can?(current_user, :update_cluster, @cluster)
.form-group
......
- is_lfs_blob = @lfs_blob_ids.include?(blob_item.id)
%tr{ class: "tree-item #{tree_hex_class(blob_item)}" }
%td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name)
- file_name = blob_item.name
= link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), class: 'str-truncated', title: file_name do
%span= file_name
- if is_lfs_blob
%span.label.label-lfs.prepend-left-5 LFS
%td.hidden-xs.tree-commit
%td.tree-time-ago.cgray.text-right
= render 'projects/tree/spinner'
---
title: Fixes the wording of headers in system info page
merge_request: 15802
author: Gilbert Roulot
type: fixed
---
title: Update feature toggle design to use icons and make it i18n friendly
merge_request: 15904
author:
type: changed
---
title: fix button alignment on MWPS component
merge_request:
author:
type: fixed
---
title: Make sure user email is read only when synced with LDAP
merge_request: 15915
author:
type: fixed
---
title: Added badge to tree & blob views to indicate LFS tracked files
merge_request:
author:
type: added
---
title: Removed incorrect guidance stating blocked users will be removed from groups
and project as members
merge_request: 15947
author: CesarApodaca
type: fixed
......@@ -497,6 +497,7 @@ production: &base
# Sync user's profile from the specified Omniauth providers every time the user logs in (default: empty).
# Define the allowed providers using an array, e.g. ["cas3", "saml", "twitter"],
# or as true/false to allow all providers or none.
# When authenticating using LDAP, the user's email is always synced.
# sync_profile_from_provider: []
# Select which info to sync from the providers above. (default: email).
......
......@@ -80,7 +80,7 @@ Make sure you have the right version of Git installed
# Install Git
sudo apt-get install -y git-core
# Make sure Git is version 2.13.6 or higher
# Make sure Git is version 2.14.3 or higher
git --version
Is the system packaged Git too old? Remove it and compile from source.
......
......@@ -229,16 +229,18 @@ In order to enable/disable an OmniAuth provider, go to Admin Area -> Settings ->
## Keep OmniAuth user profiles up to date
You can enable profile syncing from selected OmniAuth providers and for all or for specific user information.
When authenticating using LDAP, the user's email is always synced.
```ruby
gitlab_rails['sync_profile_from_provider'] = ['twitter', 'google_oauth2']
gitlab_rails['sync_profile_attributes'] = ['name', 'email', 'location']
```
**For installations from source**
```yaml
omniauth:
sync_profile_from_provider: ['twitter', 'google_oauth2']
sync_profile_claims_from_provider: ['email', 'location']
```
\ No newline at end of file
sync_profile_attributes: ['email', 'location']
```
......@@ -147,6 +147,10 @@ has a `.gitlab-ci.yml` or not:
All you need to do is remove your existing `.gitlab-ci.yml`, and you can even
do that in a branch to test Auto DevOps before committing to `master`.
NOTE: **Note:**
Starting with GitLab 10.3, when enabling Auto DevOps, a pipeline is
automatically run on the default branch.
NOTE: **Note:**
If you are a GitLab Administrator, you can enable Auto DevOps instance wide
in **Admin Area > Settings > Continuous Integration and Deployment**. Doing that,
......@@ -211,6 +215,18 @@ check out.
Any security warnings are also [shown in the merge request widget](../../user/project/merge_requests/sast.md).
### Auto SAST
> Introduced in [GitLab Enterprise Edition Ultimate][ee] 10.3.
Static Application Security Testing (SAST) uses the
[gl-sast Docker image](https://gitlab.com/gitlab-org/gl-sast) to run static
analysis on the current code and checks for potential security issues. Once the
report is created, it's uploaded as an artifact which you can later download and
check out.
Any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html).
### Auto Review Apps
NOTE: **Note:**
......
......@@ -456,6 +456,7 @@ module API
warden.try(:authenticate) if verified_request?
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def initial_current_user
return @initial_current_user if defined?(@initial_current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
......@@ -465,6 +466,7 @@ module API
unauthorized!
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def sudo!
return unless sudo_identifier
......
......@@ -128,7 +128,6 @@ module ExtractsPath
@hex_path = Digest::SHA1.hexdigest(@path)
@logs_path = logs_file_project_ref_path(@project, @ref, @path)
rescue RuntimeError, NoMethodError, InvalidPathError
render_404
end
......@@ -138,6 +137,11 @@ module ExtractsPath
@tree ||= @repo.tree(@commit.id, @path) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def lfs_blob_ids
blob_ids = tree.blobs.map(&:id)
@lfs_blob_ids = Gitlab::Git::Blob.batch_lfs_pointers(@project.repository, blob_ids).map(&:id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
private
# overriden in subclasses, do not remove
......
......@@ -4,11 +4,11 @@ module Gitlab
attr_reader :merge_request, :resolver
def initialize(merge_request)
source_repo = merge_request.source_project.repository.raw
our_commit = merge_request.source_branch_head.raw
their_commit = merge_request.target_branch_head.raw
target_repo = merge_request.target_project.repository.raw
@resolver = Gitlab::Git::Conflict::Resolver.new(source_repo, our_commit, target_repo, their_commit)
@source_repo = merge_request.source_project.repository.raw
@resolver = Gitlab::Git::Conflict::Resolver.new(target_repo, our_commit.id, their_commit.id)
@merge_request = merge_request
end
......@@ -18,7 +18,7 @@ module Gitlab
target_branch: merge_request.target_branch,
commit_message: commit_message || default_commit_message
}
resolver.resolve_conflicts(user, files, args)
resolver.resolve_conflicts(@source_repo, user, files, args)
ensure
@merge_request.clear_memoized_shas
end
......
......@@ -5,38 +5,31 @@ module Gitlab
ConflictSideMissing = Class.new(StandardError)
ResolutionError = Class.new(StandardError)
def initialize(repository, our_commit, target_repository, their_commit)
@repository = repository
@our_commit = our_commit.rugged_commit
def initialize(target_repository, our_commit_oid, their_commit_oid)
@target_repository = target_repository
@their_commit = their_commit.rugged_commit
@our_commit_oid = our_commit_oid
@their_commit_oid = their_commit_oid
end
def conflicts
@conflicts ||= begin
target_index = @target_repository.rugged.merge_commits(@our_commit, @their_commit)
target_index = @target_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid)
# We don't need to do `with_repo_branch_commit` here, because the target
# project always fetches source refs when creating merge request diffs.
target_index.conflicts.map do |conflict|
raise ConflictSideMissing unless conflict[:theirs] && conflict[:ours]
Gitlab::Git::Conflict::File.new(
@target_repository,
@our_commit.oid,
conflict,
target_index.merge_file(conflict[:ours][:path])[:data]
)
end
conflict_files(@target_repository, target_index)
end
end
def resolve_conflicts(user, files, source_branch:, target_branch:, commit_message:)
@repository.with_repo_branch_commit(@target_repository, target_branch) do
def resolve_conflicts(source_repository, user, files, source_branch:, target_branch:, commit_message:)
source_repository.with_repo_branch_commit(@target_repository, target_branch) do
index = source_repository.rugged.merge_commits(@our_commit_oid, @their_commit_oid)
conflicts = conflict_files(source_repository, index)
files.each do |file_params|
conflict_file = conflict_for_path(file_params[:old_path], file_params[:new_path])
conflict_file = conflict_for_path(conflicts, file_params[:old_path], file_params[:new_path])
write_resolved_file_to_index(conflict_file, file_params)
write_resolved_file_to_index(source_repository, index, conflict_file, file_params)
end
unless index.conflicts.empty?
......@@ -47,14 +40,14 @@ module Gitlab
commit_params = {
message: commit_message,
parents: [@our_commit, @their_commit].map(&:oid)
parents: [@our_commit_oid, @their_commit_oid]
}
@repository.commit_index(user, source_branch, index, commit_params)
source_repository.commit_index(user, source_branch, index, commit_params)
end
end
def conflict_for_path(old_path, new_path)
def conflict_for_path(conflicts, old_path, new_path)
conflicts.find do |conflict|
conflict.their_path == old_path && conflict.our_path == new_path
end
......@@ -62,15 +55,20 @@ module Gitlab
private
# We can only write when getting the merge index from the source
# project, because we will write to that project. We don't use this all
# the time because this fetches a ref into the source project, which
# isn't needed for reading.
def index
@index ||= @repository.rugged.merge_commits(@our_commit, @their_commit)
def conflict_files(repository, index)
index.conflicts.map do |conflict|
raise ConflictSideMissing unless conflict[:theirs] && conflict[:ours]
Gitlab::Git::Conflict::File.new(
repository,
@our_commit_oid,
conflict,
index.merge_file(conflict[:ours][:path])[:data]
)
end
end
def write_resolved_file_to_index(file, params)
def write_resolved_file_to_index(repository, index, file, params)
if params[:sections]
resolved_lines = file.resolve_lines(params[:sections])
new_file = resolved_lines.map { |line| line[:full_line] }.join("\n")
......@@ -82,7 +80,8 @@ module Gitlab
our_path = file.our_path
index.add(path: our_path, oid: @repository.rugged.write(new_file, :blob), mode: file.our_mode)
oid = repository.rugged.write(new_file, :blob)
index.add(path: our_path, oid: oid, mode: file.our_mode)
index.conflict_remove(our_path)
end
end
......
......@@ -131,7 +131,7 @@ module Gitlab
oldrev = branch.target
if oldrev == repository.rugged.merge_base(newrev, branch.target)
if oldrev == repository.merge_base(newrev, branch.target)
oldrev
else
raise Gitlab::Git::CommitError.new('Branch diverged')
......
......@@ -538,8 +538,15 @@ module Gitlab
# Returns the SHA of the most recent common ancestor of +from+ and +to+
def merge_base_commit(from, to)
rugged.merge_base(from, to)
gitaly_migrate(:merge_base) do |is_enabled|
if is_enabled
gitaly_repository_client.find_merge_base(from, to)
else
rugged.merge_base(from, to)
end
end
end
alias_method :merge_base, :merge_base_commit
# Gitaly note: JV: check gitlab-ee before removing this method.
def rugged_is_ancestor?(ancestor_id, descendant_id)
......
......@@ -69,6 +69,16 @@ module Gitlab
response.value
end
def find_merge_base(*revisions)
request = Gitaly::FindMergeBaseRequest.new(
repository: @gitaly_repo,
revisions: revisions.map { |r| GitalyClient.encode(r) }
)
response = GitalyClient.call(@storage, :repository_service, :find_merge_base, request)
response.base.presence
end
def fetch_source_branch(source_repository, source_branch, local_ref)
request = Gitaly::FetchSourceBranchRequest.new(
repository: @gitaly_repo,
......
......@@ -38,10 +38,6 @@ module Gitlab
ldap_config.block_auto_created_users
end
def sync_profile_from_provider?
true
end
def allowed?
Gitlab::LDAP::Access.allowed?(gl_user)
end
......
......@@ -19,6 +19,18 @@ module Gitlab
name.to_s.start_with?('ldap')
end
def self.sync_profile_from_provider?(provider)
return true if ldap_provider?(provider)
providers = Gitlab.config.omniauth.sync_profile_from_provider
if providers.is_a?(Array)
providers.include?(provider)
else
providers
end
end
def self.config_for(name)
name = name.to_s
if ldap_provider?(name)
......
......@@ -14,7 +14,7 @@ module Gitlab
def initialize(auth_hash)
self.auth_hash = auth_hash
update_profile if sync_profile_from_provider?
update_profile
add_or_update_user_identities
end
......@@ -197,29 +197,31 @@ module Gitlab
end
def sync_profile_from_provider?
providers = Gitlab.config.omniauth.sync_profile_from_provider
if providers.is_a?(Array)
providers.include?(auth_hash.provider)
else
providers
end
Gitlab::OAuth::Provider.sync_profile_from_provider?(auth_hash.provider)
end
def update_profile
user_synced_attributes_metadata = gl_user.user_synced_attributes_metadata || gl_user.build_user_synced_attributes_metadata
UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key|
if auth_hash.has_attribute?(key) && gl_user.sync_attribute?(key)
gl_user[key] = auth_hash.public_send(key) # rubocop:disable GitlabSecurity/PublicSend
user_synced_attributes_metadata.set_attribute_synced(key, true)
else
user_synced_attributes_metadata.set_attribute_synced(key, false)
return unless sync_profile_from_provider? || creating_linked_ldap_user?
metadata = gl_user.user_synced_attributes_metadata || gl_user.build_user_synced_attributes_metadata
if sync_profile_from_provider?
UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key|
if auth_hash.has_attribute?(key) && gl_user.sync_attribute?(key)
gl_user[key] = auth_hash.public_send(key) # rubocop:disable GitlabSecurity/PublicSend
metadata.set_attribute_synced(key, true)
else
metadata.set_attribute_synced(key, false)
end
end
metadata.provider = auth_hash.provider
end
user_synced_attributes_metadata.provider = auth_hash.provider
gl_user.user_synced_attributes_metadata = user_synced_attributes_metadata
if creating_linked_ldap_user? && gl_user.email == ldap_person.email.first
metadata.set_attribute_synced(:email, true)
metadata.provider = ldap_person.provider
end
end
def log
......
......@@ -385,6 +385,8 @@ module Gitlab
success
end
# Delete branch from remote repository
#
# storage - project's storage path
# project_name - project's disk path
# remote_name - remote name
......
......@@ -21,6 +21,7 @@ module Omnibus
if id
puts "Triggered https://gitlab.com/#{Omnibus::PROJECT_PATH}/pipelines/#{id}"
puts "Waiting for downstream pipeline status"
else
raise "Trigger failed! The response from the trigger is: #{res.body}"
end
......@@ -39,7 +40,9 @@ module Omnibus
"ref" => ENV["OMNIBUS_BRANCH"] || "master",
"variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"],
"variables[ALTERNATIVE_SOURCES]" => true,
"variables[ee]" => ee? ? 'true' : 'false'
"variables[ee]" => ee? ? 'true' : 'false',
"variables[TRIGGERED_USER]" => ENV["GITLAB_USER_NAME"],
"variables[TRIGGER_SOURCE]" => "https://gitlab.com/gitlab-org/#{ENV['CI_PROJECT_NAME']}/-/jobs/#{ENV['CI_JOB_ID']}"
}
end
......@@ -63,14 +66,14 @@ module Omnibus
def wait!
loop do
raise 'Pipeline timeout!' if timeout?
raise "Pipeline timed out after waiting for #{duration} minutes!" if timeout?
case status
when :created, :pending, :running
puts "Waiting another #{INTERVAL} seconds ..."
print "."
sleep INTERVAL
when :success
puts "Omnibus pipeline succeeded!"
puts "Omnibus pipeline succeeded in #{duration} minutes!"
break
else
raise "Omnibus pipeline did not succeed!"
......@@ -84,6 +87,10 @@ module Omnibus
Time.now.to_i > (@start + MAX_DURATION)
end
def duration
(Time.now.to_i - @start) / 60
end
def status
req = Net::HTTP::Get.new(@uri)
req['PRIVATE-TOKEN'] = ENV['GITLAB_QA_ACCESS_TOKEN']
......
......@@ -18,8 +18,8 @@ describe 'Admin System Info' do
it 'shows system info page' do
expect(page).to have_content 'CPU 2 cores'
expect(page).to have_content 'Memory 4 GB / 16 GB'
expect(page).to have_content 'Disks'
expect(page).to have_content 'Memory Usage 4 GB / 16 GB'
expect(page).to have_content 'Disk Usage'
expect(page).to have_content 'Uptime'
end
end
......@@ -33,8 +33,8 @@ describe 'Admin System Info' do
it 'shows system info page with no CPU info' do
expect(page).to have_content 'CPU Unable to collect CPU info'
expect(page).to have_content 'Memory 4 GB / 16 GB'
expect(page).to have_content 'Disks'
expect(page).to have_content 'Memory Usage 4 GB / 16 GB'
expect(page).to have_content 'Disk Usage'
expect(page).to have_content 'Uptime'
end
end
......@@ -48,8 +48,8 @@ describe 'Admin System Info' do
it 'shows system info page with no CPU info' do
expect(page).to have_content 'CPU 2 cores'
expect(page).to have_content 'Memory Unable to collect memory info'
expect(page).to have_content 'Disks'
expect(page).to have_content 'Memory Usage Unable to collect memory info'
expect(page).to have_content 'Disk Usage'
expect(page).to have_content 'Uptime'
end
end
......
require 'spec_helper'
feature 'Projects tree' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
before do
project.add_master(user)
sign_in(user)
visit project_tree_path(project, 'master')
end
it 'renders tree table' do
expect(page).to have_selector('.tree-item')
expect(page).not_to have_selector('.label-lfs', text: 'LFS')
end
context 'LFS' do
before do
visit project_tree_path(project, File.join('master', 'files/lfs'))
end
it 'renders LFS badge on blob item' do
expect(page).to have_selector('.label-lfs', text: 'LFS')
end
end
end
......@@ -9,6 +9,7 @@ describe TreeHelper do
before do
@id = sha
@project = project
@lfs_blob_ids = []
end
it 'displays all entries without a warning' do
......
/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow, max-len */
import 'vendor/jquery.endless-scroll';
import '~/pager';
import Activities from '~/activities';
(() => {
......
import 'vendor/jquery.endless-scroll';
import '~/pager';
import CommitsList from '~/commits';
describe('Commits List', () => {
......
/* eslint-disable no-var, comma-dangle, object-shorthand */
import * as urlUtils from '~/lib/utils/url_utility';
import '~/merge_request_tabs';
import MergeRequestTabs from '~/merge_request_tabs';
import '~/commit/pipelines/pipelines_bundle';
import '~/breakpoints';
import '~/lib/utils/common_utils';
......@@ -31,7 +31,7 @@ import 'vendor/jquery.scrollTo';
);
beforeEach(function () {
this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
this.class = new MergeRequestTabs({ stubLocation: stubLocation });
setLocation();
this.spies = {
......
/* global fixture */
import * as utils from '~/lib/utils/url_utility';
import '~/pager';
import Pager from '~/pager';
describe('pager', () => {
const Pager = window.Pager;
it('is defined on window', () => {
expect(window.Pager).toBeDefined();
});
describe('init', () => {
const originalHref = window.location.href;
......
......@@ -30,9 +30,9 @@ describe('Toggle Button', () => {
expect(vm.$el.querySelector('input').getAttribute('value')).toEqual('true');
});
it('renders Enabled and Disabled text data attributes', () => {
expect(vm.$el.querySelector('button').getAttribute('data-enabled-text')).toEqual('Enabled');
expect(vm.$el.querySelector('button').getAttribute('data-disabled-text')).toEqual('Disabled');
it('renders input status icon', () => {
expect(vm.$el.querySelectorAll('span.toggle-icon').length).toEqual(1);
expect(vm.$el.querySelectorAll('svg.s16.toggle-icon-svg').length).toEqual(1);
});
});
......@@ -49,6 +49,14 @@ describe('Toggle Button', () => {
expect(vm.$el.querySelector('button').classList.contains('is-checked')).toEqual(true);
});
it('sets aria-label representing toggle state', () => {
vm.value = true;
expect(vm.ariaLabel).toEqual('Toggle Status: ON');
vm.value = false;
expect(vm.ariaLabel).toEqual('Toggle Status: OFF');
});
it('emits change event when clicked', () => {
vm.$el.querySelector('button').click();
......
......@@ -41,7 +41,8 @@ describe Gitlab::Git::GitlabProjects do
end
it "fails if the source path doesn't exist" do
expect(logger).to receive(:error).with("mv-project failed: source path <#{tmp_repos_path}/bad-src.git> does not exist.")
expected_source_path = File.join(tmp_repos_path, 'bad-src.git')
expect(logger).to receive(:error).with("mv-project failed: source path <#{expected_source_path}> does not exist.")
result = build_gitlab_projects(tmp_repos_path, 'bad-src.git').mv_project('repo.git')
expect(result).to be_falsy
......@@ -50,7 +51,8 @@ describe Gitlab::Git::GitlabProjects do
it 'fails if the destination path already exists' do
FileUtils.mkdir_p(File.join(tmp_repos_path, 'already-exists.git'))
message = "mv-project failed: destination path <#{tmp_repos_path}/already-exists.git> already exists."
expected_distination_path = File.join(tmp_repos_path, 'already-exists.git')
message = "mv-project failed: destination path <#{expected_distination_path}> already exists."
expect(logger).to receive(:error).with(message)
expect(gl_projects.mv_project('already-exists.git')).to be_falsy
......
......@@ -41,7 +41,6 @@ describe Gitlab::LDAP::User do
it "does not mark existing ldap user as changed" do
create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain')
ldap_user.gl_user.user_synced_attributes_metadata(provider: 'ldapmain', email: true)
expect(ldap_user.changed?).to be_falsey
end
end
......@@ -147,11 +146,15 @@ describe Gitlab::LDAP::User do
expect(ldap_user.gl_user.email).to eq(info[:email])
end
it "has user_synced_attributes_metadata email set to true" do
it "has email set as synced" do
expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_truthy
end
it "has synced_attribute_provider set to ldapmain" do
it "has email set as read-only" do
expect(ldap_user.gl_user.read_only_attribute?(:email)).to be_truthy
end
it "has synced attributes provider set to ldapmain" do
expect(ldap_user.gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain'
end
end
......@@ -165,9 +168,13 @@ describe Gitlab::LDAP::User do
expect(ldap_user.gl_user.temp_oauth_email?).to be_truthy
end
it "has synced attribute email set to false" do
it "has email set as not synced" do
expect(ldap_user.gl_user.user_synced_attributes_metadata.email_synced).to be_falsey
end
it "does not have email set as read-only" do
expect(ldap_user.gl_user.read_only_attribute?(:email)).to be_falsey
end
end
end
......
......@@ -202,11 +202,13 @@ describe Gitlab::OAuth::User do
end
context "and no account for the LDAP user" do
it "creates a user with dual LDAP and omniauth identities" do
before do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
end
it "creates a user with dual LDAP and omniauth identities" do
expect(gl_user).to be_valid
expect(gl_user.username).to eql uid
expect(gl_user.email).to eql 'johndoe@example.com'
......@@ -219,6 +221,18 @@ describe Gitlab::OAuth::User do
]
)
end
it "has email set as synced" do
expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy
end
it "has email set as read-only" do
expect(gl_user.read_only_attribute?(:email)).to be_truthy
end
it "has synced attributes provider set to ldapmain" do
expect(gl_user.user_synced_attributes_metadata.provider).to eql 'ldapmain'
end
end
context "and LDAP user has an account already" do
......@@ -440,11 +454,15 @@ describe Gitlab::OAuth::User do
expect(gl_user.email).to eq(info_hash[:email])
end
it "has external_attributes set to true" do
expect(gl_user.user_synced_attributes_metadata).not_to be_nil
it "has email set as synced" do
expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy
end
it "has email set as read-only" do
expect(gl_user.read_only_attribute?(:email)).to be_truthy
end
it "has attributes_provider set to my-provider" do
it "has synced attributes provider set to my-provider" do
expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider'
end
end
......@@ -458,10 +476,13 @@ describe Gitlab::OAuth::User do
expect(gl_user.email).not_to eq(info_hash[:email])
end
it "has user_synced_attributes_metadata set to nil" do
expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider'
it "has email set as not synced" do
expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey
end
it "does not have email set as read-only" do
expect(gl_user.read_only_attribute?(:email)).to be_falsey
end
end
end
......@@ -508,11 +529,15 @@ describe Gitlab::OAuth::User do
expect(gl_user.email).to eq(info_hash[:email])
end
it "has email_synced_attribute set to true" do
it "has email set as synced" do
expect(gl_user.user_synced_attributes_metadata.email_synced).to be(true)
end
it "has my-provider as attributes_provider" do
it "has email set as read-only" do
expect(gl_user.read_only_attribute?(:email)).to be_truthy
end
it "has synced attributes provider set to my-provider" do
expect(gl_user.user_synced_attributes_metadata.provider).to eql 'my-provider'
end
end
......@@ -524,7 +549,14 @@ describe Gitlab::OAuth::User do
it "does not update the user email" do
expect(gl_user.email).not_to eq(info_hash[:email])
expect(gl_user.user_synced_attributes_metadata.email_synced).to be(false)
end
it "has email set as not synced" do
expect(gl_user.user_synced_attributes_metadata.email_synced).to be_falsey
end
it "does not have email set as read-only" do
expect(gl_user.read_only_attribute?(:email)).to be_falsey
end
end
end
......
......@@ -1061,7 +1061,7 @@ describe Repository do
it 'runs without errors' do
# old_rev is an ancestor of new_rev
expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev)
expect(repository.merge_base(old_rev, new_rev)).to eq(old_rev)
# old_rev is not a direct ancestor (parent) of new_rev
expect(repository.rugged.lookup(new_rev).parent_ids).not_to include(old_rev)
......@@ -1083,7 +1083,7 @@ describe Repository do
it 'raises an exception' do
# The 'master' branch is NOT an ancestor of new_rev.
expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev)
expect(repository.merge_base(old_rev, new_rev)).not_to eq(old_rev)
# Updating 'master' to new_rev would lose the commits on 'master' that
# are not contained in new_rev. This should not be allowed.
......
......@@ -213,7 +213,7 @@ describe MergeRequests::Conflicts::ResolveService do
MergeRequests::Conflicts::ListService.new(merge_request).conflicts.resolver
end
let(:regex_conflict) do
resolver.conflict_for_path('files/ruby/regex.rb', 'files/ruby/regex.rb')
resolver.conflict_for_path(resolver.conflicts, 'files/ruby/regex.rb', 'files/ruby/regex.rb')
end
let(:invalid_params) do
......
require 'spec_helper'
describe 'projects/tree/_blob_item' do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:blob_item) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID, 'files/ruby').first }
before do
assign(:project, project)
assign(:repository, repository)
assign(:id, File.join('master', ''))
assign(:lfs_blob_ids, [])
end
it 'renders blob item' do
render_partial(blob_item)
expect(rendered).to have_content(blob_item.name)
expect(rendered).not_to have_selector('.label-lfs', text: 'LFS')
end
describe 'LFS blob' do
before do
assign(:lfs_blob_ids, [blob_item].map(&:id))
render_partial(blob_item)
end
it 'renders LFS badge' do
expect(rendered).to have_selector('.label-lfs', text: 'LFS')
end
end
def render_partial(blob_item)
render partial: 'projects/tree/blob_item', locals: {
blob_item: blob_item,
type: 'blob'
}
end
end
......@@ -9,6 +9,7 @@ describe 'projects/tree/show' do
before do
assign(:project, project)
assign(:repository, repository)
assign(:lfs_blob_ids, [])
allow(view).to receive(:can?).and_return(true)
allow(view).to receive(:can_collaborate_with_project?).and_return(true)
......
......@@ -54,9 +54,9 @@
lodash "^4.2.0"
to-fast-properties "^2.0.0"
"@gitlab-org/gitlab-svgs@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.2.0.tgz#0b1181b5d2dd56a959528529750417c5f49159ca"
"@gitlab-org/gitlab-svgs@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.3.0.tgz#07f2aa75d6e0e857eaa20c38a3bad7e6c22c420c"
"@types/jquery@^2.0.40":
version "2.0.48"
......
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