Commit 951cd3c3 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'ce-to-ee-2017-07-22' into 'master'

CE upstream: Friday

Closes gitaly#372, gitaly#396, gitlab-qa#57, and gitaly#381

See merge request !2496
parents 7dc28927 c1b85690
...@@ -75,7 +75,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.4', require: false ...@@ -75,7 +75,7 @@ gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
gem 'github-linguist', '~> 4.7.0', require: 'linguist' gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API # API
gem 'grape', '~> 0.19.0' gem 'grape', '~> 0.19.2'
gem 'grape-entity', '~> 0.6.0' gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
...@@ -402,7 +402,7 @@ gem 'sys-filesystem', '~> 1.1.6' ...@@ -402,7 +402,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp' gem 'net-ntp'
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly', '~> 0.17.0' gem 'gitaly', '~> 0.18.0'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -293,7 +293,7 @@ GEM ...@@ -293,7 +293,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly (0.17.0) gitaly (0.18.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -357,13 +357,13 @@ GEM ...@@ -357,13 +357,13 @@ GEM
multi_json (~> 1.11) multi_json (~> 1.11)
os (~> 0.9) os (~> 0.9)
signet (~> 0.7) signet (~> 0.7)
grape (0.19.1) grape (0.19.2)
activesupport activesupport
builder builder
hashie (>= 2.1.0) hashie (>= 2.1.0)
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
mustermann-grape (~> 0.4.0) mustermann-grape (~> 1.0.0)
rack (>= 1.3.0) rack (>= 1.3.0)
rack-accept rack-accept
virtus (>= 1.0.0) virtus (>= 1.0.0)
...@@ -491,10 +491,9 @@ GEM ...@@ -491,10 +491,9 @@ GEM
multi_json (1.12.1) multi_json (1.12.1)
multi_xml (0.6.0) multi_xml (0.6.0)
multipart-post (2.0.0) multipart-post (2.0.0)
mustermann (0.4.0) mustermann (1.0.0)
tool (~> 0.2) mustermann-grape (1.0.0)
mustermann-grape (0.4.0) mustermann (~> 1.0.0)
mustermann (= 0.4.0)
mysql2 (0.4.5) mysql2 (0.4.5)
net-ldap (0.12.1) net-ldap (0.12.1)
net-ntp (2.1.3) net-ntp (2.1.3)
...@@ -879,7 +878,6 @@ GEM ...@@ -879,7 +878,6 @@ GEM
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
toml-rb (0.3.15) toml-rb (0.3.15)
citrus (~> 3.0, > 3.0) citrus (~> 3.0, > 3.0)
tool (0.2.3)
truncato (0.7.8) truncato (0.7.8)
htmlentities (~> 4.3.1) htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1) nokogiri (~> 1.6.1)
...@@ -1006,7 +1004,7 @@ DEPENDENCIES ...@@ -1006,7 +1004,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.17.0) gitaly (~> 0.18.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
...@@ -1016,7 +1014,7 @@ DEPENDENCIES ...@@ -1016,7 +1014,7 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
google-api-client (~> 0.8.6) google-api-client (~> 0.8.6)
grape (~> 0.19.0) grape (~> 0.19.2)
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
gssapi gssapi
haml_lint (~> 0.21.0) haml_lint (~> 0.21.0)
......
...@@ -416,9 +416,6 @@ import AuditLogs from './audit_logs'; ...@@ -416,9 +416,6 @@ import AuditLogs from './audit_logs';
new AuditLogs(); new AuditLogs();
break; break;
case 'projects:settings:repository:show': case 'projects:settings:repository:show':
// Initialize Protected Branch Settings
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
new UsersSelect(); new UsersSelect();
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
......
...@@ -2,8 +2,9 @@ import Filter from '~/droplab/plugins/filter'; ...@@ -2,8 +2,9 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown'; import './filtered_search_dropdown';
class DropdownHint extends gl.FilteredSearchDropdown { class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, tokenKeys, filter) { constructor(options = {}) {
super(droplab, dropdown, input, filter); const { input, tokenKeys } = options;
super(options);
this.config = { this.config = {
Filter: { Filter: {
template: 'hint', template: 'hint',
......
...@@ -5,8 +5,9 @@ import Filter from '~/droplab/plugins/filter'; ...@@ -5,8 +5,9 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown'; import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown { class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, tokenKeys, filter, endpoint, symbol) { constructor(options = {}) {
super(droplab, dropdown, input, filter); const { input, endpoint, symbol } = options;
super(options);
this.symbol = symbol; this.symbol = symbol;
this.config = { this.config = {
Ajax: { Ajax: {
......
...@@ -5,8 +5,9 @@ import './filtered_search_dropdown'; ...@@ -5,8 +5,9 @@ import './filtered_search_dropdown';
import { addClassIfElementExists } from '../lib/utils/dom_utils'; import { addClassIfElementExists } from '../lib/utils/dom_utils';
class DropdownUser extends gl.FilteredSearchDropdown { class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, tokenKeys, filter) { constructor(options = {}) {
super(droplab, dropdown, input, filter); const { tokenKeys } = options;
super(options);
this.config = { this.config = {
AjaxFilter: { AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`, endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
......
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger'; const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown { class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor({ droplab, dropdown, input, filter }) {
this.droplab = droplab; this.droplab = droplab;
this.hookId = input && input.id; this.hookId = input && input.id;
this.input = input; this.input = input;
......
...@@ -46,13 +46,19 @@ class FilteredSearchDropdownManager { ...@@ -46,13 +46,19 @@ class FilteredSearchDropdownManager {
milestone: { milestone: {
reference: null, reference: null,
gl: 'DropdownNonUser', gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'], extraArguments: {
endpoint: `${this.baseEndpoint}/milestones.json`,
symbol: '%',
},
element: this.container.querySelector('#js-dropdown-milestone'), element: this.container.querySelector('#js-dropdown-milestone'),
}, },
label: { label: {
reference: null, reference: null,
gl: 'DropdownNonUser', gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/labels.json`, '~'], extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json`,
symbol: '~',
},
element: this.container.querySelector('#js-dropdown-label'), element: this.container.querySelector('#js-dropdown-label'),
}, },
hint: { hint: {
...@@ -109,13 +115,19 @@ class FilteredSearchDropdownManager { ...@@ -109,13 +115,19 @@ class FilteredSearchDropdownManager {
let forceShowList = false; let forceShowList = false;
if (!mappingKey.reference) { if (!mappingKey.reference) {
const dl = this.droplab; const defaultArguments = {
const defaultArguments = droplab: this.droplab,
[null, dl, element, this.filteredSearchInput, this.filteredSearchTokenKeys, key]; dropdown: element,
const glArguments = defaultArguments.concat(mappingKey.extraArguments || []); input: this.filteredSearchInput,
tokenKeys: this.filteredSearchTokenKeys,
filter: key,
};
const extraArguments = mappingKey.extraArguments || {};
const glArguments = Object.assign({}, defaultArguments, extraArguments);
// Passing glArguments to `new gl[glClass](<arguments>)` // Passing glArguments to `new gl[glClass](<arguments>)`
mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))(); mappingKey.reference =
new (Function.prototype.bind.apply(gl[glClass], [null, glArguments]))();
} }
if (firstLoad) { if (firstLoad) {
......
/* eslint-disable no-unused-vars */
import './protected_branch_access_dropdown'; import './protected_branch_access_dropdown';
import './protected_branch_create'; import './protected_branch_create';
import './protected_branch_dropdown'; import './protected_branch_dropdown';
import './protected_branch_edit'; import './protected_branch_edit';
import './protected_branch_edit_list'; import './protected_branch_edit_list';
$(() => {
const protectedBranchCreate = new gl.ProtectedBranchCreate();
const protectedBranchEditList = new gl.ProtectedBranchEditList();
});
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
gl.ProtectedBranchCreate = class { gl.ProtectedBranchCreate = class {
constructor() { constructor() {
this.$wrap = this.$form = $('#new_protected_branch'); this.$wrap = this.$form = $('.js-new-protected-branch');
this.buildDropdowns(); this.buildDropdowns();
this.$branchInput = this.$wrap.find('input[name="protected_branch[name]"]'); this.$branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
this.bindEvents(); this.bindEvents();
......
/* eslint-disable no-unused-vars */
import ProtectedBranchCreate from './protected_branch_create';
import ProtectedBranchEditList from './protected_branch_edit_list';
$(() => {
const protectedBranchCreate = new ProtectedBranchCreate();
const protectedBranchEditList = new ProtectedBranchEditList();
});
/* eslint-disable arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, max-len */ export default class ProtectedBranchAccessDropdown {
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchAccessDropdown = class {
constructor(options) { constructor(options) {
const { $dropdown, data, onSelect } = options; this.options = options;
this.initDropdown();
}
initDropdown() {
const { $dropdown, data, onSelect } = this.options;
$dropdown.glDropdown({ $dropdown.glDropdown({
data: data, data,
selectable: true, selectable: true,
inputId: $dropdown.data('input-id'), inputId: $dropdown.data('input-id'),
fieldName: $dropdown.data('field-name'), fieldName: $dropdown.data('field-name'),
toggleLabel(item, el) { toggleLabel(item, $el) {
if (el.is('.is-active')) { if ($el.is('.is-active')) {
return item.text; return item.text;
} else {
return 'Select';
} }
return 'Select';
}, },
clicked(opts) { clicked(options) {
const { e } = opts; options.e.preventDefault();
e.preventDefault();
onSelect(); onSelect();
} },
}); });
} }
}; }
})(window);
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */ import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
/* global ProtectedBranchDropdown */ import ProtectedBranchDropdown from './protected_branch_dropdown';
(global => { export default class ProtectedBranchCreate {
global.gl = global.gl || {};
gl.ProtectedBranchCreate = class {
constructor() { constructor() {
this.$wrap = this.$form = $('#new_protected_branch'); this.$form = $('.js-new-protected-branch');
this.buildDropdowns(); this.buildDropdowns();
} }
buildDropdowns() { buildDropdowns() {
const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push');
// Cache callback // Cache callback
this.onSelectCallback = this.onSelect.bind(this); this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Merge dropdown // Allowed to Merge dropdown
new gl.ProtectedBranchAccessDropdown({ this.protectedBranchMergeAccessDropdown = new ProtectedBranchAccessDropdown({
$dropdown: $allowedToMergeDropdown, $dropdown: $allowedToMergeDropdown,
data: gon.merge_access_levels, data: gon.merge_access_levels,
onSelect: this.onSelectCallback onSelect: this.onSelectCallback,
}); });
// Allowed to Push dropdown // Allowed to Push dropdown
new gl.ProtectedBranchAccessDropdown({ this.protectedBranchPushAccessDropdown = new ProtectedBranchAccessDropdown({
$dropdown: $allowedToPushDropdown, $dropdown: $allowedToPushDropdown,
data: gon.push_access_levels, data: gon.push_access_levels,
onSelect: this.onSelectCallback onSelect: this.onSelectCallback,
}); });
// Select default // Select default
...@@ -36,20 +33,19 @@ ...@@ -36,20 +33,19 @@
$allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0); $allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected branch dropdown // Protected branch dropdown
new ProtectedBranchDropdown({ this.protectedBranchDropdown = new ProtectedBranchDropdown({
$dropdown: this.$wrap.find('.js-protected-branch-select'), $dropdown: this.$form.find('.js-protected-branch-select'),
onSelect: this.onSelectCallback onSelect: this.onSelectCallback,
}); });
} }
// This will run after clicked callback // This will run after clicked callback
onSelect() { onSelect() {
// Enable submit button // Enable submit button
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]'); const $branchInput = this.$form.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]'); const $allowedToMergeInput = this.$form.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]'); const $allowedToPushInput = this.$form.find('input[name="protected_branch[push_access_levels_attributes][0][access_level]"]');
this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length)); this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
} }
}; }
})(window);
/* eslint-disable comma-dangle, no-unused-vars */ export default class ProtectedBranchDropdown {
/**
class ProtectedBranchDropdown { * @param {Object} options containing
* `$dropdown` target element
* `onSelect` event callback
* $dropdown must be an element created using `dropdown_branch()` rails helper
*/
constructor(options) { constructor(options) {
this.onSelect = options.onSelect; this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown; this.$dropdown = options.$dropdown;
...@@ -12,7 +16,7 @@ class ProtectedBranchDropdown { ...@@ -12,7 +16,7 @@ class ProtectedBranchDropdown {
this.bindEvents(); this.bindEvents();
// Hide footer // Hide footer
this.$dropdownFooter.addClass('hidden'); this.toggleFooter(true);
} }
buildDropdown() { buildDropdown() {
...@@ -21,7 +25,7 @@ class ProtectedBranchDropdown { ...@@ -21,7 +25,7 @@ class ProtectedBranchDropdown {
filterable: true, filterable: true,
remote: false, remote: false,
search: { search: {
fields: ['title'] fields: ['title'],
}, },
selectable: true, selectable: true,
toggleLabel(selected) { toggleLabel(selected) {
...@@ -36,10 +40,9 @@ class ProtectedBranchDropdown { ...@@ -36,10 +40,9 @@ class ProtectedBranchDropdown {
}, },
onFilter: this.toggleCreateNewButton.bind(this), onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => { clicked: (options) => {
const { $el, e } = options; options.e.preventDefault();
e.preventDefault();
this.onSelect(); this.onSelect();
} },
}); });
} }
...@@ -64,20 +67,22 @@ class ProtectedBranchDropdown { ...@@ -64,20 +67,22 @@ class ProtectedBranchDropdown {
} }
toggleCreateNewButton(branchName) { toggleCreateNewButton(branchName) {
if (branchName) {
this.selectedBranch = { this.selectedBranch = {
title: branchName, title: branchName,
id: branchName, id: branchName,
text: branchName text: branchName,
}; };
if (branchName) {
this.$dropdownContainer this.$dropdownContainer
.find('.js-create-new-protected-branch code') .find('.js-create-new-protected-branch code')
.text(branchName); .text(branchName);
} }
this.$dropdownFooter.toggleClass('hidden', !branchName); this.toggleFooter(!branchName);
} }
}
window.ProtectedBranchDropdown = ProtectedBranchDropdown; toggleFooter(toggleState) {
this.$dropdownFooter.toggleClass('hidden', toggleState);
}
}
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */ /* eslint-disable no-new */
/* global Flash */ /* global Flash */
(global => { import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
global.gl = global.gl || {};
gl.ProtectedBranchEdit = class { export default class ProtectedBranchEdit {
constructor(options) { constructor(options) {
this.$wrap = options.$wrap; this.$wrap = options.$wrap;
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
this.onSelectCallback = this.onSelect.bind(this);
this.buildDropdowns(); this.buildDropdowns();
} }
buildDropdowns() { buildDropdowns() {
// Allowed to merge dropdown // Allowed to merge dropdown
new gl.ProtectedBranchAccessDropdown({ this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToMergeDropdown, $dropdown: this.$allowedToMergeDropdown,
data: gon.merge_access_levels, data: gon.merge_access_levels,
onSelect: this.onSelect.bind(this) onSelect: this.onSelectCallback,
}); });
// Allowed to push dropdown // Allowed to push dropdown
new gl.ProtectedBranchAccessDropdown({ this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToPushDropdown, $dropdown: this.$allowedToPushDropdown,
data: gon.push_access_levels, data: gon.push_access_levels,
onSelect: this.onSelect.bind(this) onSelect: this.onSelectCallback,
}); });
} }
...@@ -48,22 +48,20 @@ ...@@ -48,22 +48,20 @@
protected_branch: { protected_branch: {
merge_access_levels_attributes: [{ merge_access_levels_attributes: [{
id: this.$allowedToMergeDropdown.data('access-level-id'), id: this.$allowedToMergeDropdown.data('access-level-id'),
access_level: $allowedToMergeInput.val() access_level: $allowedToMergeInput.val(),
}], }],
push_access_levels_attributes: [{ push_access_levels_attributes: [{
id: this.$allowedToPushDropdown.data('access-level-id'), id: this.$allowedToPushDropdown.data('access-level-id'),
access_level: $allowedToPushInput.val() access_level: $allowedToPushInput.val(),
}] }],
} },
}, },
error() { error() {
$.scrollTo(0); new Flash('Failed to update branch!', null, $('.js-protected-branches-list'));
new Flash('Failed to update branch!'); },
}
}).always(() => { }).always(() => {
this.$allowedToMergeDropdown.enable(); this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable(); this.$allowedToPushDropdown.enable();
}); });
} }
}; }
})(window);
/* eslint-disable arrow-parens, no-param-reassign, no-new, comma-dangle */ /* eslint-disable no-new */
(global => { import ProtectedBranchEdit from './protected_branch_edit';
global.gl = global.gl || {};
gl.ProtectedBranchEditList = class { export default class ProtectedBranchEditList {
constructor() { constructor() {
this.$wrap = $('.protected-branches-list'); this.$wrap = $('.protected-branches-list');
this.initEditForm();
}
// Build edit forms initEditForm() {
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => { this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new gl.ProtectedBranchEdit({ new ProtectedBranchEdit({
$wrap: $(el) $wrap: $(el),
}); });
}); });
} }
}; }
})(window);
import './protected_branch_access_dropdown';
import './protected_branch_create';
import './protected_branch_dropdown';
import './protected_branch_edit';
import './protected_branch_edit_list';
...@@ -274,7 +274,7 @@ header.navbar-gitlab-new { ...@@ -274,7 +274,7 @@ header.navbar-gitlab-new {
.breadcrumbs { .breadcrumbs {
display: flex; display: flex;
min-height: 60px; min-height: 61px;
color: $gl-text-color; color: $gl-text-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
......
...@@ -165,7 +165,6 @@ $new-sidebar-width: 220px; ...@@ -165,7 +165,6 @@ $new-sidebar-width: 220px;
> li { > li {
a { a {
font-size: 12px;
padding: 8px 16px 8px 24px; padding: 8px 16px 8px 24px;
&:hover, &:hover,
...@@ -262,7 +261,7 @@ $new-sidebar-width: 220px; ...@@ -262,7 +261,7 @@ $new-sidebar-width: 220px;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS height: 475px; // Needed for PhantomJS
// scss-lint:disable DuplicateProperty // scss-lint:disable DuplicateProperty
height: calc(100vh - 120px); height: calc(100vh - 180px);
// scss-lint:enable DuplicateProperty // scss-lint:enable DuplicateProperty
} }
} }
......
...@@ -770,15 +770,18 @@ a.allowed-to-push { ...@@ -770,15 +770,18 @@ a.allowed-to-push {
@extend .btn.disabled; @extend .btn.disabled;
} }
.protected-branches-list,
.protected-tags-list {
.flash-container {
padding: 0;
}
}
.protected-tags-list { .protected-tags-list {
.dropdown-menu-toggle { .dropdown-menu-toggle {
width: 100%; width: 100%;
max-width: 300px; max-width: 300px;
} }
.flash-container {
padding: 0;
}
} }
.custom-notifications-form { .custom-notifications-form {
......
...@@ -338,7 +338,9 @@ ...@@ -338,7 +338,9 @@
%fieldset %fieldset
%legend Metrics - Prometheus %legend Metrics - Prometheus
%p %p
Enable a Prometheus metrics endpoint at `#{metrics_path}` to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available Enable a Prometheus metrics endpoint at
%code= metrics_path
to expose a variety of statistics on the health and performance of GitLab. Additional information on authenticating and connecting to the metrics endpoint is available
= link_to 'here', admin_health_check_path = link_to 'here', admin_health_check_path
\. This setting requires a \. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab') = link_to 'restart', help_page_path('administration/restart_gitlab')
...@@ -353,7 +355,10 @@ ...@@ -353,7 +355,10 @@
- unless Gitlab::Metrics.metrics_folder_present? - unless Gitlab::Metrics.metrics_folder_present?
.help-block .help-block
%strong.cred WARNING: %strong.cred WARNING:
Environment variable `prometheus_multiproc_dir` does not exist or is not pointing to a valid directory. Environment variable
%code prometheus_multiproc_dir
does not exist or is not pointing to a valid directory.
= link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/gitlab_metrics', anchor: 'metrics-shared-directory')
%fieldset %fieldset
%legend Profiling - Performance Bar %legend Profiling - Performance Bar
......
...@@ -2,26 +2,6 @@ ...@@ -2,26 +2,6 @@
= render "admin/dashboard/head" = render "admin/dashboard/head"
%div{ class: container_class } %div{ class: container_class }
%p.prepend-top-default
%span
To register a new Runner you should enter the following registration
token.
With this token the Runner will request a unique Runner token and use
that for future communication.
%br
Registration token is
%code#runners-token= current_application_settings.runners_registration_token
.bs-callout.clearfix
.pull-left
%p
You can reset runners registration token by pressing a button below.
.prepend-top-10
= button_to "Reset runners registration token", reset_runners_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
data: { confirm: 'Are you sure you want to reset registration token?' }
.bs-callout .bs-callout
%p %p
A 'Runner' is a process which runs a job. A 'Runner' is a process which runs a job.
...@@ -46,6 +26,19 @@ ...@@ -46,6 +26,19 @@
%span.label.label-danger paused %span.label.label-danger paused
\- Runner will not receive any new jobs \- Runner will not receive any new jobs
.bs-callout.clearfix
.pull-left
%p
You can reset runners registration token by pressing a button below.
.prepend-top-10
= button_to _("Reset runners registration token"), reset_runners_token_admin_application_settings_path,
method: :put, class: 'btn btn-default',
data: { confirm: _("Are you sure you want to reset registration token?") }
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: current_application_settings.runners_registration_token,
type: 'shared' }
.append-bottom-20.clearfix .append-bottom-20.clearfix
.pull-left .pull-left
= form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do = form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do
......
- link = link_to _("GitLab Runner section"), 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'
.bs-callout.help-callout
%h4= _("How to setup a #{type} Runner for a new project")
%ol
%li
= _("Install a Runner compatible with GitLab CI")
= (_("(checkout the %{link} for information on how to install it).") % { link: link }).html_safe
%li
= _("Specify the following URL during the Runner setup:")
%code= root_url(only_path: false)
%li
= _("Use the following registration token during setup:")
%code#registration_token= registration_token
%li
= _("Start the Runner!")
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= pipeline_schedule.description = pipeline_schedule.description
%td.branch-name-cell %td.branch-name-cell
= icon('code-fork') = icon('code-fork')
- if pipeline_schedule.ref - if pipeline_schedule.ref.present?
= link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name" = link_to pipeline_schedule.ref, project_ref_path(@project, pipeline_schedule.ref), class: "ref-name"
%td %td
- if pipeline_schedule.last_pipeline - if pipeline_schedule.last_pipeline
......
.panel.panel-default.protected-branches-list .panel.panel-default.protected-branches-list.js-protected-branches-list
- if @protected_branches.empty? - if @protected_branches.empty?
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
- if can_admin_project - if can_admin_project
%th %th
%tbody %tbody
%tr
%td.flash-container{ colspan: 5 }
= yield = yield
= paginate @protected_branches, theme: 'gitlab' = paginate @protected_branches, theme: 'gitlab'
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f| = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'new-protected-branch js-new-protected-branch' } do |f|
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
......
.panel.panel-default.protected-tags-list .panel.panel-default.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty? - if @protected_tags.empty?
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
......
%h3 Specific Runners %h3 Specific Runners
.bs-callout.help-callout = render partial: 'ci/runner/how_to_setup_runner',
%h4 How to setup a specific Runner for a new project locals: { registration_token: @project.runners_token,
type: 'specific' }
%ol
%li
Install a Runner compatible with GitLab CI
(checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it).
%li
Specify the following URL during the Runner setup:
%code= root_url(only_path: false)
%li
Use the following registration token during setup:
%code= @project.runners_token
%li
Start the Runner!
- if @project_runners.any? - if @project_runners.any?
%h4.underlined-title Runners activated for this project %h4.underlined-title Runners activated for this project
......
---
title: Add coordinator url to admin area runner page
merge_request: 11603
author:
---
title: Extract "@request.env[devise.mapping] = Devise.mappings[:user]" to a test helper
merge_request: 12742
author: Jacopo Beschi @jacopo-beschi
---
title: Make font size of contextual sub menu items 14px
merge_request:
author:
---
title: Fix an infinite loop when handling user-supplied regular expressions
merge_request:
author:
---
title: Fix pipeline_schedules pages throwing error 500 (when ref is empty)
merge_request: 12983
author:
...@@ -13,7 +13,7 @@ unless Sidekiq.server? ...@@ -13,7 +13,7 @@ unless Sidekiq.server?
# Add request parameters to log output # Add request parameters to log output
config.lograge.custom_options = lambda do |event| config.lograge.custom_options = lambda do |event|
{ {
time: event.time, time: event.time.utc.iso8601(3),
params: event.payload[:params].except(%w(controller action format)) params: event.payload[:params].except(%w(controller action format))
} }
end end
......
...@@ -59,8 +59,8 @@ var config = { ...@@ -59,8 +59,8 @@ var config = {
pipelines_details: './pipelines/pipeline_details_bundle.js', pipelines_details: './pipelines/pipeline_details_bundle.js',
profile: './profile/profile_bundle.js', profile: './profile/profile_bundle.js',
prometheus_metrics: './prometheus_metrics', prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches/protected_branches_bundle.js', protected_branches: './protected_branches',
ee_protected_branches: './protected_branches/ee/protected_branches_bundle.js', ee_protected_branches: './protected_branches/ee',
protected_tags: './protected_tags', protected_tags: './protected_tags',
ee_protected_tags: './protected_tags/ee', ee_protected_tags: './protected_tags/ee',
service_desk: './projects/settings_service_desk/service_desk_bundle.js', service_desk: './projects/settings_service_desk/service_desk_bundle.js',
......
...@@ -46,6 +46,20 @@ In this experimental phase, only a few metrics are available: ...@@ -46,6 +46,20 @@ In this experimental phase, only a few metrics are available:
| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping | | redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in | | user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
## Metrics shared directory
GitLab's Prometheus client requires a directory to store metrics data shared between multi-process services.
Those files are shared among all instances running under Unicorn server.
The directory needs to be accessible to all running Unicorn's processes otherwise
metrics will not function correctly.
For best performance its advisable that this directory will be located in `tmpfs`.
Its location is configured using environment variable `prometheus_multiproc_dir`.
If GitLab is installed using Omnibus and `tmpfs` is available then metrics
directory will be automatically configured.
[← Back to the main Prometheus page](index.md) [← Back to the main Prometheus page](index.md)
[29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118 [29118]: https://gitlab.com/gitlab-org/gitlab-ce/issues/29118
......
...@@ -31,6 +31,18 @@ It's also possible for different migrations to be executed at the same time. ...@@ -31,6 +31,18 @@ It's also possible for different migrations to be executed at the same time.
This means that different background migrations should not migrate data in a This means that different background migrations should not migrate data in a
way that would cause conflicts. way that would cause conflicts.
## Idempotence
Background migrations are executed in a context of a Sidekiq process.
Usual Sidekiq rules apply, especially the rule that jobs should be small
and idempotent.
See [Sidekiq best practices guidelines](https://github.com/mperham/sidekiq/wiki/Best-Practices)
for more details.
Make sure that in case that your migration job is going to be retried data
integrity is guarateed.
## How It Works ## How It Works
Background migrations are simple classes that define a `perform` method. A Background migrations are simple classes that define a `perform` method. A
...@@ -212,3 +224,27 @@ end ...@@ -212,3 +224,27 @@ end
This migration will then process any jobs for the ExtractServicesUrl migration This migration will then process any jobs for the ExtractServicesUrl migration
and continue once all jobs have been processed. Once done you can safely remove and continue once all jobs have been processed. Once done you can safely remove
the `services.properties` column. the `services.properties` column.
## Testing
It is required to write tests for background migrations' scheduling migration
(either a regular migration or a post deployment migration), background
migration itself and a cleanup migration. You can use the `:migration` RSpec
tag when testing a regular / post deployment migration.
See [README][migrations-readme].
When you do that, keep in mind that `before` and `after` RSpec hooks are going
to migrate you database down and up, which can result in other background
migrations being called. That means that using `spy` test doubles with
`have_received` is encouraged, instead of using regular test doubles, because
your expectations defined in a `it` block can conflict with what is being
called in RSpec hooks. See [gitlab-org/gitlab-ce#35351][issue-rspec-hooks]
for more details.
## Best practices
1. Make sure that background migration jobs are idempotent.
1. Make sure that tests you write are not false positives.
[migrations-readme]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/migrations/README.md
[issue-rspec-hooks]: https://gitlab.com/gitlab-org/gitlab-ce/issues/35351
...@@ -64,7 +64,7 @@ up-to-date and install it. ...@@ -64,7 +64,7 @@ up-to-date and install it.
Install the required packages (needed to compile Ruby and native extensions to Ruby gems): Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
If you want to use Kerberos for user authentication, then install libkrb5-dev: If you want to use Kerberos for user authentication, then install libkrb5-dev:
......
...@@ -68,6 +68,7 @@ module API ...@@ -68,6 +68,7 @@ module API
delete ":id/access_requests/:user_id" do delete ":id/access_requests/:user_id" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
status 204
::Members::DestroyService.new(source, current_user, params) ::Members::DestroyService.new(source, current_user, params)
.execute(:requesters) .execute(:requesters)
end end
......
...@@ -88,6 +88,7 @@ module API ...@@ -88,6 +88,7 @@ module API
unauthorized! unless award.user == current_user || current_user.admin? unauthorized! unless award.user == current_user || current_user.admin?
status 204
award.destroy award.destroy
end end
end end
......
...@@ -91,6 +91,7 @@ module API ...@@ -91,6 +91,7 @@ module API
delete ':id' do delete ':id' do
message = find_message message = find_message
status 204
message.destroy message.destroy
end end
end end
......
...@@ -125,6 +125,7 @@ module API ...@@ -125,6 +125,7 @@ module API
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id]) key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
not_found!('Deploy Key') unless key not_found!('Deploy Key') unless key
status 204
key.destroy key.destroy
end end
end end
......
...@@ -79,6 +79,7 @@ module API ...@@ -79,6 +79,7 @@ module API
environment = user_project.environments.find(params[:environment_id]) environment = user_project.environments.find(params[:environment_id])
status 204
environment.destroy environment.destroy
end end
......
...@@ -157,6 +157,8 @@ module API ...@@ -157,6 +157,8 @@ module API
delete ":id" do delete ":id" do
group = find_group!(params[:id]) group = find_group!(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
status 204
::Groups::DestroyService.new(group, current_user).execute ::Groups::DestroyService.new(group, current_user).execute
end end
......
...@@ -230,6 +230,7 @@ module API ...@@ -230,6 +230,7 @@ module API
not_found!('Issue') unless issue not_found!('Issue') unless issue
authorize!(:destroy_issue, issue) authorize!(:destroy_issue, issue)
status 204
issue.destroy issue.destroy
end end
......
...@@ -56,6 +56,7 @@ module API ...@@ -56,6 +56,7 @@ module API
label = user_project.labels.find_by(title: params[:name]) label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label not_found!('Label') unless label
status 204
label.destroy label.destroy
end end
......
...@@ -38,6 +38,7 @@ module API ...@@ -38,6 +38,7 @@ module API
ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn]) ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn])
if ldap_group_link if ldap_group_link
ldap_group_link.destroy ldap_group_link.destroy
status 204
else else
render_api_error!('Linked LDAP group not found', 404) render_api_error!('Linked LDAP group not found', 404)
end end
...@@ -55,6 +56,7 @@ module API ...@@ -55,6 +56,7 @@ module API
ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn], provider: params[:provider]) ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn], provider: params[:provider])
if ldap_group_link if ldap_group_link
ldap_group_link.destroy ldap_group_link.destroy
status 204
else else
render_api_error!('Linked LDAP group not found', 404) render_api_error!('Linked LDAP group not found', 404)
end end
......
...@@ -102,6 +102,7 @@ module API ...@@ -102,6 +102,7 @@ module API
# Ensure the member exists # Ensure the member exists
source.members.find_by!(user_id: params[:user_id]) source.members.find_by!(user_id: params[:user_id])
status 204
::Members::DestroyService.new(source, current_user, declared_params).execute ::Members::DestroyService.new(source, current_user, declared_params).execute
end end
end end
......
...@@ -149,6 +149,7 @@ module API ...@@ -149,6 +149,7 @@ module API
merge_request = find_project_merge_request(params[:merge_request_iid]) merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request) authorize!(:destroy_merge_request, merge_request)
status 204
merge_request.destroy merge_request.destroy
end end
......
...@@ -131,6 +131,7 @@ module API ...@@ -131,6 +131,7 @@ module API
note = user_project.notes.find(params[:note_id]) note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note authorize! :admin_note, note
status 204
::Notes::DestroyService.new(user_project, current_user).execute(note) ::Notes::DestroyService.new(user_project, current_user).execute(note)
end end
end end
......
...@@ -96,6 +96,7 @@ module API ...@@ -96,6 +96,7 @@ module API
delete ":id/hooks/:hook_id" do delete ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id)) hook = user_project.hooks.find(params.delete(:hook_id))
status 204
hook.destroy hook.destroy
end end
end end
......
...@@ -70,6 +70,7 @@ module API ...@@ -70,6 +70,7 @@ module API
not_found!('Push Rule') unless push_rule not_found!('Push Rule') unless push_rule
push_rule.destroy push_rule.destroy
status 204
end end
end end
end end
......
...@@ -116,6 +116,7 @@ module API ...@@ -116,6 +116,7 @@ module API
not_found!('Snippet') unless snippet not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet authorize! :admin_project_snippet, snippet
status 204
snippet.destroy snippet.destroy
end end
......
...@@ -373,6 +373,7 @@ module API ...@@ -373,6 +373,7 @@ module API
authorize! :remove_fork_project, user_project authorize! :remove_fork_project, user_project
if user_project.forked? if user_project.forked?
status 204
user_project.forked_project_link.destroy user_project.forked_project_link.destroy
else else
not_modified! not_modified!
...@@ -417,6 +418,7 @@ module API ...@@ -417,6 +418,7 @@ module API
link = user_project.project_group_links.find_by(group_id: params[:group_id]) link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link not_found!('Group Link') unless link
status 204
link.destroy link.destroy
end end
......
...@@ -45,6 +45,7 @@ module API ...@@ -45,6 +45,7 @@ module API
end end
delete '/' do delete '/' do
authenticate_runner! authenticate_runner!
status 204
Ci::Runner.find_by_token(params[:token]).destroy Ci::Runner.find_by_token(params[:token]).destroy
end end
......
...@@ -79,6 +79,7 @@ module API ...@@ -79,6 +79,7 @@ module API
runner = get_runner(params[:id]) runner = get_runner(params[:id])
authenticate_delete_runner!(runner) authenticate_delete_runner!(runner)
status 204
runner.destroy! runner.destroy!
end end
end end
...@@ -134,6 +135,7 @@ module API ...@@ -134,6 +135,7 @@ module API
runner = runner_project.runner runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1 forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
status 204
runner_project.destroy runner_project.destroy
end end
end end
......
...@@ -123,6 +123,7 @@ module API ...@@ -123,6 +123,7 @@ module API
authorize! :destroy_personal_snippet, snippet authorize! :destroy_personal_snippet, snippet
status 204
snippet.destroy snippet.destroy
end end
......
...@@ -66,6 +66,7 @@ module API ...@@ -66,6 +66,7 @@ module API
hook = SystemHook.find_by(id: params[:id]) hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook not_found!('System hook') unless hook
status 204
hook.destroy hook.destroy
end end
end end
......
...@@ -140,6 +140,7 @@ module API ...@@ -140,6 +140,7 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id)) trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger return not_found!('Trigger') unless trigger
status 204
trigger.destroy trigger.destroy
end end
end end
......
...@@ -241,6 +241,7 @@ module API ...@@ -241,6 +241,7 @@ module API
key = user.keys.find_by(id: params[:key_id]) key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key not_found!('Key') unless key
status 204
key.destroy key.destroy
end end
...@@ -312,6 +313,7 @@ module API ...@@ -312,6 +313,7 @@ module API
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
not_found!('User') unless user not_found!('User') unless user
status 204
user.delete_async(deleted_by: current_user, params: params) user.delete_async(deleted_by: current_user, params: params)
end end
...@@ -412,6 +414,7 @@ module API ...@@ -412,6 +414,7 @@ module API
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token' requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end end
delete ':impersonation_token_id' do delete ':impersonation_token_id' do
status 204
find_impersonation_token.revoke! find_impersonation_token.revoke!
end end
end end
...@@ -489,6 +492,7 @@ module API ...@@ -489,6 +492,7 @@ module API
key = current_user.keys.find_by(id: params[:key_id]) key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key not_found!('Key') unless key
status 204
key.destroy key.destroy
end end
...@@ -540,6 +544,7 @@ module API ...@@ -540,6 +544,7 @@ module API
email = current_user.emails.find_by(id: params[:email_id]) email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email not_found!('Email') unless email
status 204
Emails::DestroyService.new(current_user, email: email.email).execute Emails::DestroyService.new(current_user, email: email.email).execute
end end
......
...@@ -102,6 +102,7 @@ module API ...@@ -102,6 +102,7 @@ module API
variable = user_project.variables.find_by(key: params[:key]) variable = user_project.variables.find_by(key: params[:key])
not_found!('Variable') unless variable not_found!('Variable') unless variable
status 204
variable.destroy variable.destroy
end end
end end
......
...@@ -237,6 +237,10 @@ module Gitlab ...@@ -237,6 +237,10 @@ module Gitlab
branch_name.parameterize << '.patch' branch_name.parameterize << '.patch'
end end
def patch_url
"https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/#{ENV['CI_JOB_ID']}/artifacts/raw/ee_compat_check/patches/#{ce_patch_name}"
end
def step(desc, cmd = nil) def step(desc, cmd = nil)
puts "\n=> #{desc}\n" puts "\n=> #{desc}\n"
...@@ -303,14 +307,11 @@ module Gitlab ...@@ -303,14 +307,11 @@ module Gitlab
2. Apply your branch's patch to EE 2. Apply your branch's patch to EE
# In the CE repo
$ git fetch origin master
$ git diff --binary origin/master...HEAD -- > #{ce_branch}.patch
# In the EE repo # In the EE repo
$ git fetch origin master $ git fetch origin master
$ git checkout -b #{ee_branch_prefix} origin/master $ git checkout -b #{ee_branch_prefix} origin/master
$ git apply --3way path/to/#{ce_branch}.patch $ wget #{patch_url}
$ git apply --3way #{ce_patch_name}
At this point you might have conflicts such as: At this point you might have conflicts such as:
...@@ -324,7 +325,7 @@ module Gitlab ...@@ -324,7 +325,7 @@ module Gitlab
If the patch couldn't be applied cleanly, use the following command: If the patch couldn't be applied cleanly, use the following command:
# In the EE repo # In the EE repo
$ git apply --reject path/to/#{ce_branch}.patch $ git apply --reject #{ce_patch_name}
This option makes git apply the parts of the patch that are applicable, This option makes git apply the parts of the patch that are applicable,
and leave the rejected hunks in corresponding `.rej` files. and leave the rejected hunks in corresponding `.rej` files.
......
...@@ -98,17 +98,13 @@ module Gitlab ...@@ -98,17 +98,13 @@ module Gitlab
# Commit.between(repo, '29eda46b', 'master') # Commit.between(repo, '29eda46b', 'master')
# #
def between(repo, base, head) def between(repo, base, head)
commits = Gitlab::GitalyClient.migrate(:commits_between) do |is_enabled| Gitlab::GitalyClient.migrate(:commits_between) do |is_enabled|
if is_enabled if is_enabled
repo.gitaly_commit_client.between(base, head) repo.gitaly_commit_client.between(base, head)
else else
repo.commits_between(base, head) repo.commits_between(base, head).map { |c| decorate(c) }
end end
end end
commits.map do |commit|
decorate(commit)
end
rescue Rugged::ReferenceError rescue Rugged::ReferenceError
[] []
end end
...@@ -135,6 +131,16 @@ module Gitlab ...@@ -135,6 +131,16 @@ module Gitlab
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/326 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/326
def find_all(repo, options = {}) def find_all(repo, options = {})
Gitlab::GitalyClient.migrate(:find_all_commits) do |is_enabled|
if is_enabled
find_all_by_gitaly(repo, options)
else
find_all_by_rugged(repo, options)
end
end
end
def find_all_by_rugged(repo, options = {})
actual_options = options.dup actual_options = options.dup
allowed_options = [:ref, :max_count, :skip, :order] allowed_options = [:ref, :max_count, :skip, :order]
...@@ -173,6 +179,10 @@ module Gitlab ...@@ -173,6 +179,10 @@ module Gitlab
[] []
end end
def find_all_by_gitaly(repo, options = {})
Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options)
end
def decorate(commit, ref = nil) def decorate(commit, ref = nil)
Gitlab::Git::Commit.new(commit, ref) Gitlab::Git::Commit.new(commit, ref)
end end
...@@ -214,11 +224,12 @@ module Gitlab ...@@ -214,11 +224,12 @@ module Gitlab
def initialize(raw_commit, head = nil) def initialize(raw_commit, head = nil)
raise "Nil as raw commit passed" unless raw_commit raise "Nil as raw commit passed" unless raw_commit
if raw_commit.is_a?(Hash) case raw_commit
when Hash
init_from_hash(raw_commit) init_from_hash(raw_commit)
elsif raw_commit.is_a?(Rugged::Commit) when Rugged::Commit
init_from_rugged(raw_commit) init_from_rugged(raw_commit)
elsif raw_commit.is_a?(Gitaly::GitCommit) when Gitlab::GitalyClient::Commit
init_from_gitaly(raw_commit) init_from_gitaly(raw_commit)
else else
raise "Invalid raw commit type: #{raw_commit.class}" raise "Invalid raw commit type: #{raw_commit.class}"
...@@ -298,7 +309,14 @@ module Gitlab ...@@ -298,7 +309,14 @@ module Gitlab
end end
def parents def parents
case raw_commit
when Rugged::Commit
raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) } raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
when Gitlab::GitalyClient::Commit
parent_ids.map { |oid| self.class.find(raw_commit.repository, oid) }.compact
else
raise NotImplementedError, "commit source doesn't support #parents"
end
end end
def stats def stats
......
...@@ -17,30 +17,13 @@ module Gitlab ...@@ -17,30 +17,13 @@ module Gitlab
def where(repository, sha, path = nil) def where(repository, sha, path = nil)
path = nil if path == '' || path == '/' path = nil if path == '' || path == '/'
commit = repository.lookup(sha) Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled|
root_tree = commit.tree if is_enabled
client = Gitlab::GitalyClient::CommitService.new(repository)
tree = if path client.tree_entries(repository, sha, path)
id = find_id_by_path(repository, root_tree.oid, path)
if id
repository.lookup(id)
else else
[] tree_entries_from_rugged(repository, sha, path)
end end
else
root_tree
end
tree.map do |entry|
new(
id: entry[:oid],
root_id: root_tree.oid,
name: entry[:name],
type: entry[:type],
mode: entry[:filemode].to_s(8),
path: path ? File.join(path, entry[:name]) : entry[:name],
commit_id: sha
)
end end
end end
...@@ -74,6 +57,34 @@ module Gitlab ...@@ -74,6 +57,34 @@ module Gitlab
entry[:oid] entry[:oid]
end end
end end
def tree_entries_from_rugged(repository, sha, path)
commit = repository.lookup(sha)
root_tree = commit.tree
tree = if path
id = find_id_by_path(repository, root_tree.oid, path)
if id
repository.lookup(id)
else
[]
end
else
root_tree
end
tree.map do |entry|
new(
id: entry[:oid],
root_id: root_tree.oid,
name: entry[:name],
type: entry[:type],
mode: entry[:filemode].to_s(8),
path: path ? File.join(path, entry[:name]) : entry[:name],
commit_id: sha
)
end
end
end end
def initialize(options) def initialize(options)
......
module Gitlab
module GitalyClient
class Commit
attr_reader :repository, :gitaly_commit
delegate :id, :subject, :body, :author, :committer, :parent_ids, to: :gitaly_commit
def initialize(repository, gitaly_commit)
@repository = repository
@gitaly_commit = gitaly_commit
end
end
end
end
...@@ -60,6 +60,31 @@ module Gitlab ...@@ -60,6 +60,31 @@ module Gitlab
entry entry
end end
def tree_entries(repository, revision, path)
request = Gitaly::GetTreeEntriesRequest.new(
repository: @gitaly_repo,
revision: revision,
path: path.presence || '.'
)
response = GitalyClient.call(@repository.storage, :commit_service, :get_tree_entries, request)
response.flat_map do |message|
message.entries.map do |gitaly_tree_entry|
entry_path = gitaly_tree_entry.path.dup
Gitlab::Git::Tree.new(
id: gitaly_tree_entry.oid,
root_id: gitaly_tree_entry.root_oid,
type: gitaly_tree_entry.type.downcase,
mode: gitaly_tree_entry.mode.to_s(8),
name: File.basename(entry_path),
path: entry_path,
commit_id: gitaly_tree_entry.commit_oid
)
end
end
end
def commit_count(ref) def commit_count(ref)
request = Gitaly::CountCommitsRequest.new( request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
...@@ -80,6 +105,19 @@ module Gitlab ...@@ -80,6 +105,19 @@ module Gitlab
consume_commits_response(response) consume_commits_response(response)
end end
def find_all_commits(opts = {})
request = Gitaly::FindAllCommitsRequest.new(
repository: @gitaly_repo,
revision: opts[:ref].to_s,
max_count: opts[:max_count].to_i,
skip: opts[:skip].to_i
)
request.order = opts[:order].upcase if opts[:order].present?
response = GitalyClient.call(@repository.storage, :commit_service, :find_all_commits, request)
consume_commits_response(response)
end
private private
def commit_diff_request_params(commit, options = {}) def commit_diff_request_params(commit, options = {})
...@@ -94,7 +132,12 @@ module Gitlab ...@@ -94,7 +132,12 @@ module Gitlab
end end
def consume_commits_response(response) def consume_commits_response(response)
response.flat_map { |r| r.commits } response.flat_map do |message|
message.commits.map do |gitaly_commit|
commit = GitalyClient::Commit.new(@repository, gitaly_commit)
Gitlab::Git::Commit.new(commit)
end
end
end end
end end
end end
......
...@@ -22,13 +22,28 @@ module Gitlab ...@@ -22,13 +22,28 @@ module Gitlab
end end
def scan(text) def scan(text)
scan_regexp.scan(text).map do |match| text = text.dup # modified in-place
if regexp.number_of_capturing_groups == 0 results = []
match.first
loop do
match = scan_regexp.match(text)
break unless match
# Ruby scan returns empty strings, not nil
groups = match.to_a.map(&:to_s)
results <<
if regexp.number_of_capturing_groups.zero?
groups[0]
else else
match groups[1..-1]
end end
text.slice!(0, match.end(0) || 1)
break unless text.present?
end end
results
end end
def replace(text, rewrite) def replace(text, rewrite)
...@@ -43,7 +58,7 @@ module Gitlab ...@@ -43,7 +58,7 @@ module Gitlab
# groups, so work around it # groups, so work around it
def scan_regexp def scan_regexp
@scan_regexp ||= @scan_regexp ||=
if regexp.number_of_capturing_groups == 0 if regexp.number_of_capturing_groups.zero?
RE2::Regexp.new('(' + regexp.source + ')') RE2::Regexp.new('(' + regexp.source + ')')
else else
regexp regexp
......
...@@ -153,7 +153,6 @@ module Gitlab ...@@ -153,7 +153,6 @@ module Gitlab
clone_repo(repo, target_dir) unless Dir.exist?(target_dir) clone_repo(repo, target_dir) unless Dir.exist?(target_dir)
checkout_version(version, target_dir) checkout_version(version, target_dir)
reset_to_version(version, target_dir)
end end
def clone_repo(repo, target_dir) def clone_repo(repo, target_dir)
...@@ -161,12 +160,8 @@ module Gitlab ...@@ -161,12 +160,8 @@ module Gitlab
end end
def checkout_version(version, target_dir) def checkout_version(version, target_dir)
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet]) run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --quiet origin #{version}])
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{version}]) run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout -f --quiet FETCH_HEAD --])
end
def reset_to_version(version, target_dir)
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{version}])
end end
end end
end end
...@@ -7,13 +7,13 @@ msgid "" ...@@ -7,13 +7,13 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: gitlab 1.0.0\n" "Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-06-28 13:32+0200\n" "POT-Creation-Date: 2017-07-05 08:50-0500\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-11 09:10-0400\n"
"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n"
"Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n" "Language-Team: Chinese (Taiwan) (https://translate.zanata.org/project/view/GitLab)\n"
"PO-Revision-Date: 2017-07-20 09:50-0400\n"
"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n"
"Language: zh-TW\n" "Language: zh-TW\n"
"X-Generator: Zanata 3.9.6\n" "X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n" "Plural-Forms: nplurals=1; plural=0\n"
...@@ -624,6 +624,12 @@ msgstr "所有" ...@@ -624,6 +624,12 @@ msgstr "所有"
msgid "PipelineSchedules|Inactive" msgid "PipelineSchedules|Inactive"
msgstr "未啟用" msgstr "未啟用"
msgid "PipelineSchedules|Input variable key"
msgstr "變數名稱"
msgid "PipelineSchedules|Input variable value"
msgstr "變數值"
msgid "PipelineSchedules|Next Run" msgid "PipelineSchedules|Next Run"
msgstr "下次執行時間" msgstr "下次執行時間"
...@@ -633,12 +639,18 @@ msgstr "無" ...@@ -633,12 +639,18 @@ msgstr "無"
msgid "PipelineSchedules|Provide a short description for this pipeline" msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "請簡單說明此流水線 (pipeline) " msgstr "請簡單說明此流水線 (pipeline) "
msgid "PipelineSchedules|Remove variable row"
msgstr "刪除變數"
msgid "PipelineSchedules|Take ownership" msgid "PipelineSchedules|Take ownership"
msgstr "取得所有權" msgstr "取得所有權"
msgid "PipelineSchedules|Target" msgid "PipelineSchedules|Target"
msgstr "目標" msgstr "目標"
msgid "PipelineSchedules|Variables"
msgstr "變數"
msgid "PipelineSheduleIntervalPattern|Custom" msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自訂" msgstr "自訂"
...@@ -759,13 +771,13 @@ msgid "Revert this merge request" ...@@ -759,13 +771,13 @@ msgid "Revert this merge request"
msgstr "還原此合併請求 (merge request) " msgstr "還原此合併請求 (merge request) "
msgid "Save pipeline schedule" msgid "Save pipeline schedule"
msgstr "存流水線 (pipeline) 排程" msgstr "存流水線 (pipeline) 排程"
msgid "Schedule a new pipeline" msgid "Schedule a new pipeline"
msgstr "建立流水線 (pipeline) 排程" msgstr "建立流水線 (pipeline) 排程"
msgid "Scheduling Pipelines" msgid "Scheduling Pipelines"
msgstr "流水線 (pipeline) 計劃" msgstr "流水線 (pipeline) 排程"
msgid "Search branches and tags" msgid "Search branches and tags"
msgstr "搜尋分支 (branch) 和標籤" msgstr "搜尋分支 (branch) 和標籤"
...@@ -849,8 +861,7 @@ msgid "" ...@@ -849,8 +861,7 @@ msgid ""
"specific branches or tags. Those scheduled pipelines will inherit limited " "specific branches or tags. Those scheduled pipelines will inherit limited "
"project access based on their associated user." "project access based on their associated user."
msgstr "" msgstr ""
"在指定了特定分支 (branch) 或標籤後,此處的流水線 (pipeline) 排程會不斷地重複執行。\n" "在指定了特定分支 (branch) 或標籤後,此處的流水線 (pipeline) 排程會不斷地重複執行。流水線排程的存取權限與專案本身相同。"
"流水線排程的存取權限與專案本身相同。"
msgid "" msgid ""
"The planning stage shows the time from the previous step to pushing your " "The planning stage shows the time from the previous step to pushing your "
...@@ -1096,6 +1107,14 @@ msgstr "因該階段的資料不足而無法顯示相關資訊" ...@@ -1096,6 +1107,14 @@ msgstr "因該階段的資料不足而無法顯示相關資訊"
msgid "Withdraw Access Request" msgid "Withdraw Access Request"
msgstr "取消權限申請" msgstr "取消權限申請"
msgid ""
"You are going to remove %{group_name}.\n"
"Removed groups CANNOT be restored!\n"
"Are you ABSOLUTELY sure?"
msgstr "即將要刪除 %{group_name}。\n"
"被刪除的群組完全無法救回來喔!\n"
"真的「100%確定」要這麼做嗎?"
msgid "" msgid ""
"You are going to remove %{project_name_with_namespace}.\n" "You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n" "Removed project CANNOT be restored!\n"
......
...@@ -48,7 +48,14 @@ module QA ...@@ -48,7 +48,14 @@ module QA
module Main module Main
autoload :Entry, 'qa/page/main/entry' autoload :Entry, 'qa/page/main/entry'
autoload :Menu, 'qa/page/main/menu' autoload :Menu, 'qa/page/main/menu'
autoload :Groups, 'qa/page/main/groups' end
module Dashboard
autoload :Groups, 'qa/page/dashboard/groups'
end
module Group
autoload :Show, 'qa/page/group/show'
end end
module Project module Project
......
module QA module QA
module Page module Page
module Main module Dashboard
class Groups < Page::Base class Groups < Page::Base
def prepare_test_namespace def prepare_test_namespace
return if page.has_content?(Runtime::Namespace.name) if page.has_content?(Runtime::Namespace.name)
return click_link(Runtime::Namespace.name)
end
click_on 'New group' click_on 'New group'
......
module QA
module Page
module Group
class Show < Page::Base
def go_to_new_project
click_link 'New Project'
end
end
end
end
end
...@@ -14,13 +14,6 @@ module QA ...@@ -14,13 +14,6 @@ module QA
within_user_menu { click_link 'Admin area' } within_user_menu { click_link 'Admin area' }
end end
def go_to_new_project
within_user_menu do
find('.header-new-dropdown-toggle').click
click_link('New project')
end
end
def sign_out def sign_out
within_user_menu do within_user_menu do
find('.header-user-dropdown-toggle').click find('.header-user-dropdown-toggle').click
......
...@@ -13,8 +13,8 @@ module QA ...@@ -13,8 +13,8 @@ module QA
def perform def perform
Page::Main::Menu.act { go_to_groups } Page::Main::Menu.act { go_to_groups }
Page::Main::Groups.act { prepare_test_namespace } Page::Dashboard::Groups.act { prepare_test_namespace }
Page::Main::Menu.act { go_to_new_project } Page::Group::Show.act { go_to_new_project }
Page::Project::New.perform do |page| Page::Project::New.perform do |page|
page.choose_test_namespace page.choose_test_namespace
......
require 'spec_helper' require 'spec_helper'
describe SessionsController do describe SessionsController do
include DeviseHelpers
describe '#new' do describe '#new' do
before do before do
@request.env['devise.mapping'] = Devise.mappings[:user] set_devise_mapping(context: @request)
end end
context 'when auto sign-in is enabled' do context 'when auto sign-in is enabled' do
...@@ -34,7 +36,7 @@ describe SessionsController do ...@@ -34,7 +36,7 @@ describe SessionsController do
describe '#create' do describe '#create' do
before do before do
@request.env['devise.mapping'] = Devise.mappings[:user] set_devise_mapping(context: @request)
end end
context 'when using standard authentications' do context 'when using standard authentications' do
...@@ -257,7 +259,7 @@ describe SessionsController do ...@@ -257,7 +259,7 @@ describe SessionsController do
describe '#new' do describe '#new' do
before do before do
@request.env['devise.mapping'] = Devise.mappings[:user] set_devise_mapping(context: @request)
end end
it 'redirects correctly for referer on same host with params' do it 'redirects correctly for referer on same host with params' do
......
...@@ -19,7 +19,7 @@ describe "Admin Runners" do ...@@ -19,7 +19,7 @@ describe "Admin Runners" do
end end
it 'has all necessary texts' do it 'has all necessary texts' do
expect(page).to have_text "To register a new Runner" expect(page).to have_text "How to setup"
expect(page).to have_text "Runners with last contact more than a minute ago: 1" expect(page).to have_text "Runners with last contact more than a minute ago: 1"
end end
...@@ -54,7 +54,7 @@ describe "Admin Runners" do ...@@ -54,7 +54,7 @@ describe "Admin Runners" do
end end
it 'has all necessary texts including no runner message' do it 'has all necessary texts including no runner message' do
expect(page).to have_text "To register a new Runner" expect(page).to have_text "How to setup"
expect(page).to have_text "Runners with last contact more than a minute ago: 0" expect(page).to have_text "Runners with last contact more than a minute ago: 0"
expect(page).to have_text 'No runners found' expect(page).to have_text 'No runners found'
end end
...@@ -163,12 +163,11 @@ describe "Admin Runners" do ...@@ -163,12 +163,11 @@ describe "Admin Runners" do
end end
it 'has a registration token' do it 'has a registration token' do
expect(page).to have_content("Registration token is #{token}") expect(page.find('#registration_token')).to have_content(token)
expect(page).to have_selector('#runners-token', text: token)
end end
describe 'reload registration token' do describe 'reload registration token' do
let(:page_token) { find('#runners-token').text } let(:page_token) { find('#registration_token').text }
before do before do
click_button 'Reset runners registration token' click_button 'Reset runners registration token'
......
...@@ -21,6 +21,16 @@ feature 'Issues > User uses quick actions', feature: true, js: true do ...@@ -21,6 +21,16 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
wait_for_requests wait_for_requests
end end
describe 'time tracking' do
let(:issue) { create(:issue, project: project) }
before do
visit project_issue_path(project, issue)
end
it_behaves_like 'issuable time tracker'
end
describe 'adding a due date from note' do describe 'adding a due date from note' do
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
...@@ -99,32 +109,6 @@ feature 'Issues > User uses quick actions', feature: true, js: true do ...@@ -99,32 +109,6 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
end end
end end
describe 'Issuable time tracking' do
let(:issue) { create(:issue, project: project) }
before do
project.team << [user, :developer]
end
context 'Issue' do
before do
visit project_issue_path(project, issue)
end
it_behaves_like 'issuable time tracker'
end
context 'Merge Request' do
let(:merge_request) { create(:merge_request, source_project: project) }
before do
visit project_merge_request_path(project, merge_request)
end
it_behaves_like 'issuable time tracker'
end
end
describe 'toggling the WIP prefix from the title from note' do describe 'toggling the WIP prefix from the title from note' do
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
......
...@@ -24,6 +24,14 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do ...@@ -24,6 +24,14 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
wait_for_requests wait_for_requests
end end
describe 'time tracking' do
before do
visit project_merge_request_path(project, merge_request)
end
it_behaves_like 'issuable time tracker'
end
describe 'toggling the WIP prefix in the title from note' do describe 'toggling the WIP prefix in the title from note' do
context 'when the current user can toggle the WIP prefix' do context 'when the current user can toggle the WIP prefix' do
it 'adds the WIP: prefix to the title' do it 'adds the WIP: prefix to the title' do
......
require 'spec_helper' require 'spec_helper'
feature 'OAuth Login', js: true do feature 'OAuth Login', js: true do
include DeviseHelpers
def enter_code(code) def enter_code(code)
fill_in 'user_otp_attempt', with: code fill_in 'user_otp_attempt', with: code
click_button 'Verify code' click_button 'Verify code'
...@@ -8,7 +10,7 @@ feature 'OAuth Login', js: true do ...@@ -8,7 +10,7 @@ feature 'OAuth Login', js: true do
def stub_omniauth_config(provider) def stub_omniauth_config(provider)
OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345")) OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
Rails.application.env_config['devise.mapping'] = Devise.mappings[:user] set_devise_mapping(context: Rails.application)
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider] Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
end end
......
...@@ -70,6 +70,17 @@ feature 'Pipeline Schedules', :feature, js: true do ...@@ -70,6 +70,17 @@ feature 'Pipeline Schedules', :feature, js: true do
expect(first('.branch-name-cell').text).to eq('') expect(first('.branch-name-cell').text).to eq('')
end end
end end
context 'when ref is empty' do
before do
pipeline_schedule.update_attribute(:ref, '')
visit_pipelines_schedules
end
it 'shows a list of the pipeline schedules with empty ref column' do
expect(first('.branch-name-cell').text).to eq('')
end
end
end end
describe 'POST /projects/pipeline_schedules/new' do describe 'POST /projects/pipeline_schedules/new' do
...@@ -128,6 +139,19 @@ feature 'Pipeline Schedules', :feature, js: true do ...@@ -128,6 +139,19 @@ feature 'Pipeline Schedules', :feature, js: true do
end end
end end
end end
context 'when ref is empty' do
before do
pipeline_schedule.update_attribute(:ref, '')
edit_pipeline_schedule
end
it 'shows the pipeline schedule with default ref' do
page.within('.js-target-branch-dropdown') do
expect(first('.dropdown-toggle-text').text).to eq('master')
end
end
end
end end
context 'when user creates a new pipeline schedule with variables' do context 'when user creates a new pipeline schedule with variables' do
......
...@@ -12,7 +12,9 @@ describe('Dropdown User', () => { ...@@ -12,7 +12,9 @@ describe('Dropdown User', () => {
spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {}); spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {}); spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
dropdownUser = new gl.DropdownUser(null, null, null, gl.FilteredSearchTokenKeys); dropdownUser = new gl.DropdownUser({
tokenKeys: gl.FilteredSearchTokenKeys,
});
}); });
it('should not return the double quote found in value', () => { it('should not return the double quote found in value', () => {
...@@ -78,7 +80,10 @@ describe('Dropdown User', () => { ...@@ -78,7 +80,10 @@ describe('Dropdown User', () => {
loadFixtures(fixtureTemplate); loadFixtures(fixtureTemplate);
authorFilterDropdownElement = document.querySelector('#js-dropdown-author'); authorFilterDropdownElement = document.querySelector('#js-dropdown-author');
const dummyInput = document.createElement('div'); const dummyInput = document.createElement('div');
dropdown = new gl.DropdownUser(null, authorFilterDropdownElement, dummyInput); dropdown = new gl.DropdownUser({
dropdown: authorFilterDropdownElement,
input: dummyInput,
});
}); });
const findCurrentUserElement = () => authorFilterDropdownElement.querySelector('.js-current-user'); const findCurrentUserElement = () => authorFilterDropdownElement.querySelector('.js-current-user');
......
...@@ -10,10 +10,12 @@ context 'U2F' do ...@@ -10,10 +10,12 @@ context 'U2F' do
end end
describe SessionsController, '(JavaScript fixtures)', type: :controller do describe SessionsController, '(JavaScript fixtures)', type: :controller do
include DeviseHelpers
render_views render_views
before do before do
@request.env['devise.mapping'] = Devise.mappings[:user] set_devise_mapping(context: @request)
end end
it 'u2f/authenticate.html.raw' do |example| it 'u2f/authenticate.html.raw' do |example|
......
...@@ -91,7 +91,7 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -91,7 +91,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
committer: committer committer: committer
) )
end end
let(:commit) { described_class.new(gitaly_commit) } let(:commit) { described_class.new(Gitlab::GitalyClient::Commit.new(repository, gitaly_commit)) }
it { expect(commit.short_id).to eq(id[0..10]) } it { expect(commit.short_id).to eq(id[0..10]) }
it { expect(commit.id).to eq(id) } it { expect(commit.id).to eq(id) }
...@@ -290,33 +290,13 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -290,33 +290,13 @@ describe Gitlab::Git::Commit, seed_helper: true do
end end
describe '.find_all' do describe '.find_all' do
shared_examples 'finding all commits' do
it 'should return a return a collection of commits' do it 'should return a return a collection of commits' do
commits = described_class.find_all(repository) commits = described_class.find_all(repository)
expect(commits).not_to be_empty
expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) ) expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
end end
context 'while applying a sort order based on the `order` option' do
it "allows ordering topologically (no parents shown before their children)" do
expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
described_class.find_all(repository, order: :topo)
end
it "allows ordering by date" do
expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO)
described_class.find_all(repository, order: :date)
end
it "applies no sorting by default" do
expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
described_class.find_all(repository)
end
end
context 'max_count' do context 'max_count' do
subject do subject do
commits = Gitlab::Git::Commit.find_all( commits = Gitlab::Git::Commit.find_all(
...@@ -324,15 +304,20 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -324,15 +304,20 @@ describe Gitlab::Git::Commit, seed_helper: true do
max_count: 50 max_count: 50
) )
commits.map { |c| c.id } commits.map(&:id)
end end
it 'has 31 elements' do it 'has 33 elements' do
expect(subject.size).to eq(33) expect(subject.size).to eq(33)
end end
it { is_expected.to include(SeedRepo::Commit::ID) }
it { is_expected.to include(SeedRepo::Commit::PARENT_ID) } it 'includes the expected commits' do
it { is_expected.to include(SeedRepo::FirstCommit::ID) } expect(subject).to include(
SeedRepo::Commit::ID,
SeedRepo::Commit::PARENT_ID,
SeedRepo::FirstCommit::ID
)
end
end end
context 'ref + max_count + skip' do context 'ref + max_count + skip' do
...@@ -344,15 +329,46 @@ describe Gitlab::Git::Commit, seed_helper: true do ...@@ -344,15 +329,46 @@ describe Gitlab::Git::Commit, seed_helper: true do
skip: 1 skip: 1
) )
commits.map { |c| c.id } commits.map(&:id)
end end
it 'has 23 elements' do it 'has 24 elements' do
expect(subject.size).to eq(24) expect(subject.size).to eq(24)
end end
it { is_expected.to include(SeedRepo::Commit::ID) }
it { is_expected.to include(SeedRepo::FirstCommit::ID) } it 'includes the expected commits' do
it { is_expected.not_to include(SeedRepo::LastCommit::ID) } expect(subject).to include(SeedRepo::Commit::ID, SeedRepo::FirstCommit::ID)
expect(subject).not_to include(SeedRepo::LastCommit::ID)
end
end
end
context 'when Gitaly find_all_commits feature is enabled' do
it_behaves_like 'finding all commits'
end
context 'when Gitaly find_all_commits feature is disabled', skip_gitaly_mock: true do
it_behaves_like 'finding all commits'
context 'while applying a sort order based on the `order` option' do
it "allows ordering topologically (no parents shown before their children)" do
expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_TOPO)
described_class.find_all(repository, order: :topo)
end
it "allows ordering by date" do
expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_DATE | Rugged::SORT_TOPO)
described_class.find_all(repository, order: :date)
end
it "applies no sorting by default" do
expect_any_instance_of(Rugged::Walker).to receive(:sorting).with(Rugged::SORT_NONE)
described_class.find_all(repository)
end
end
end end
end end
end end
......
require "spec_helper" require "spec_helper"
describe Gitlab::Git::Tree, seed_helper: true do describe Gitlab::Git::Tree, seed_helper: true do
context :repo do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) } let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
context :repo do
let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) } let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) }
it { expect(tree).to be_kind_of Array } it { expect(tree).to be_kind_of Array }
...@@ -74,4 +75,24 @@ describe Gitlab::Git::Tree, seed_helper: true do ...@@ -74,4 +75,24 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(submodule.name).to eq('gitlab-shell') } it { expect(submodule.name).to eq('gitlab-shell') }
end end
end end
describe '#where' do
context 'with gitaly disabled' do
before do
allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
end
it 'calls #tree_entries_from_rugged' do
expect(described_class).to receive(:tree_entries_from_rugged)
described_class.where(repository, SeedRepo::Commit::ID, '/')
end
end
it 'gets the tree entries from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries)
described_class.where(repository, SeedRepo::Commit::ID, '/')
end
end
end end
...@@ -2,9 +2,13 @@ require 'spec_helper' ...@@ -2,9 +2,13 @@ require 'spec_helper'
describe Gitlab::GitalyClient::CommitService do describe Gitlab::GitalyClient::CommitService do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.path_with_namespace + '.git' }
let(:repository) { project.repository } let(:repository) { project.repository }
let(:repository_message) { repository.gitaly_repository } let(:repository_message) { repository.gitaly_repository }
let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') } let(:revision) { '913c66a37b4a45b9769037c55c2d238bd0942d2e' }
let(:commit) { project.commit(revision) }
let(:client) { described_class.new(repository) }
describe '#diff_from_parent' do describe '#diff_from_parent' do
context 'when a commit has a parent' do context 'when a commit has a parent' do
...@@ -20,7 +24,7 @@ describe Gitlab::GitalyClient::CommitService do ...@@ -20,7 +24,7 @@ describe Gitlab::GitalyClient::CommitService do
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
described_class.new(repository).diff_from_parent(commit) client.diff_from_parent(commit)
end end
end end
...@@ -38,12 +42,12 @@ describe Gitlab::GitalyClient::CommitService do ...@@ -38,12 +42,12 @@ describe Gitlab::GitalyClient::CommitService do
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash)) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_diff).with(request, kind_of(Hash))
described_class.new(repository).diff_from_parent(initial_commit) client.diff_from_parent(initial_commit)
end end
end end
it 'returns a Gitlab::Git::DiffCollection' do it 'returns a Gitlab::Git::DiffCollection' do
ret = described_class.new(repository).diff_from_parent(commit) ret = client.diff_from_parent(commit)
expect(ret).to be_kind_of(Gitlab::Git::DiffCollection) expect(ret).to be_kind_of(Gitlab::Git::DiffCollection)
end end
...@@ -53,7 +57,7 @@ describe Gitlab::GitalyClient::CommitService do ...@@ -53,7 +57,7 @@ describe Gitlab::GitalyClient::CommitService do
expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options) expect(Gitlab::Git::DiffCollection).to receive(:new).with(kind_of(Enumerable), options)
described_class.new(repository).diff_from_parent(commit, options) client.diff_from_parent(commit, options)
end end
end end
...@@ -68,7 +72,7 @@ describe Gitlab::GitalyClient::CommitService do ...@@ -68,7 +72,7 @@ describe Gitlab::GitalyClient::CommitService do
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
described_class.new(repository).commit_deltas(commit) client.commit_deltas(commit)
end end
end end
...@@ -83,7 +87,7 @@ describe Gitlab::GitalyClient::CommitService do ...@@ -83,7 +87,7 @@ describe Gitlab::GitalyClient::CommitService do
expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([]) expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:commit_delta).with(request, kind_of(Hash)).and_return([])
described_class.new(repository).commit_deltas(initial_commit) client.commit_deltas(initial_commit)
end end
end end
end end
...@@ -91,6 +95,7 @@ describe Gitlab::GitalyClient::CommitService do ...@@ -91,6 +95,7 @@ describe Gitlab::GitalyClient::CommitService do
describe '#between' do describe '#between' do
let(:from) { 'master' } let(:from) { 'master' }
let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' } let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
it 'sends an RPC request' do it 'sends an RPC request' do
request = Gitaly::CommitsBetweenRequest.new( request = Gitaly::CommitsBetweenRequest.new(
repository: repository_message, from: from, to: to repository: repository_message, from: from, to: to
...@@ -102,4 +107,17 @@ describe Gitlab::GitalyClient::CommitService do ...@@ -102,4 +107,17 @@ describe Gitlab::GitalyClient::CommitService do
described_class.new(repository).between(from, to) described_class.new(repository).between(from, to)
end end
end end
describe '#tree_entries' do
let(:path) { '/' }
it 'sends a get_tree_entries message' do
expect_any_instance_of(Gitaly::CommitService::Stub)
.to receive(:get_tree_entries)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_return([])
client.tree_entries(repository, revision, path)
end
end
end end
...@@ -50,6 +50,24 @@ describe Gitlab::UntrustedRegexp do ...@@ -50,6 +50,24 @@ describe Gitlab::UntrustedRegexp do
include_examples 'malicious regexp' include_examples 'malicious regexp'
end end
context 'empty regexp' do
let(:regexp) { '' }
let(:text) { 'foo' }
it 'returns an array of empty matches' do
is_expected.to eq(['', '', ''])
end
end
context 'empty capture group regexp' do
let(:regexp) { '()' }
let(:text) { 'foo' }
it 'returns arrays of empty matches in an array' do
is_expected.to eq([[''], [''], ['']])
end
end
context 'no capture group' do context 'no capture group' do
let(:regexp) { '.+' } let(:regexp) { '.+' }
let(:text) { 'foo' } let(:text) { 'foo' }
......
module DeviseHelpers
# explicitly tells Devise which mapping to use
# this is needed when we are testing a Devise controller bypassing the router
def set_devise_mapping(context:)
env =
if context.respond_to?(:env_config)
context.env_config
elsif context.respond_to?(:env)
context.env
end
env['devise.mapping'] = Devise.mappings[:user] if env
end
end
module LoginHelpers module LoginHelpers
include DeviseHelpers
# Internal: Log in as a specific user or a new user of a specific role # Internal: Log in as a specific user or a new user of a specific role
# #
# user_or_role - User object, or a role to create (e.g., :admin, :user) # user_or_role - User object, or a role to create (e.g., :admin, :user)
...@@ -106,7 +108,7 @@ module LoginHelpers ...@@ -106,7 +108,7 @@ module LoginHelpers
end end
def stub_omniauth_saml_config(messages) def stub_omniauth_saml_config(messages)
Rails.application.env_config['devise.mapping'] = Devise.mappings[:user] set_devise_mapping(context: Rails.application)
Rails.application.routes.disable_clear_and_finalize = true Rails.application.routes.disable_clear_and_finalize = true
Rails.application.routes.draw do Rails.application.routes.draw do
post '/users/auth/saml' => 'omniauth_callbacks#saml' post '/users/auth/saml' => 'omniauth_callbacks#saml'
......
module ProtectedBranchHelpers module ProtectedBranchHelpers
def set_allowed_to(operation, option = 'Masters', form: '#new_protected_branch') def set_allowed_to(operation, option = 'Masters', form: '.js-new-protected-branch')
within form do within form do
find(".js-allowed-to-#{operation}").trigger('click') find(".js-allowed-to-#{operation}").trigger('click')
wait_for_requests wait_for_requests
......
...@@ -5,7 +5,7 @@ shared_examples "protected branches > access control > CE" do ...@@ -5,7 +5,7 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master') set_protected_branch_name('master')
within('.new_protected_branch') do within('.js-new-protected-branch') do
allowed_to_push_button = find(".js-allowed-to-push") allowed_to_push_button = find(".js-allowed-to-push")
unless allowed_to_push_button.text == access_type_name unless allowed_to_push_button.text == access_type_name
...@@ -50,7 +50,7 @@ shared_examples "protected branches > access control > CE" do ...@@ -50,7 +50,7 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master') set_protected_branch_name('master')
within('.new_protected_branch') do within('.js-new-protected-branch') do
allowed_to_merge_button = find(".js-allowed-to-merge") allowed_to_merge_button = find(".js-allowed-to-merge")
unless allowed_to_merge_button.text == access_type_name unless allowed_to_merge_button.text == access_type_name
......
...@@ -20,7 +20,6 @@ describe Gitlab::TaskHelpers do ...@@ -20,7 +20,6 @@ describe Gitlab::TaskHelpers do
it 'checkout the version and reset to it' do it 'checkout the version and reset to it' do
expect(subject).to receive(:checkout_version).with(tag, clone_path) expect(subject).to receive(:checkout_version).with(tag, clone_path)
expect(subject).to receive(:reset_to_version).with(tag, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path) subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end end
...@@ -31,7 +30,6 @@ describe Gitlab::TaskHelpers do ...@@ -31,7 +30,6 @@ describe Gitlab::TaskHelpers do
it 'checkout the version and reset to it with a branch name' do it 'checkout the version and reset to it with a branch name' do
expect(subject).to receive(:checkout_version).with(branch, clone_path) expect(subject).to receive(:checkout_version).with(branch, clone_path)
expect(subject).to receive(:reset_to_version).with(branch, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path) subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end end
...@@ -70,20 +68,11 @@ describe Gitlab::TaskHelpers do ...@@ -70,20 +68,11 @@ describe Gitlab::TaskHelpers do
describe '#checkout_version' do describe '#checkout_version' do
it 'clones the repo in the target dir' do it 'clones the repo in the target dir' do
expect(subject) expect(subject)
.to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet]) .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --quiet origin #{tag}])
expect(subject) expect(subject)
.to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}]) .to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout -f --quiet FETCH_HEAD --])
subject.checkout_version(tag, clone_path) subject.checkout_version(tag, clone_path)
end end
end end
describe '#reset_to_version' do
it 'resets --hard to the given version' do
expect(subject)
.to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}])
subject.reset_to_version(tag, clone_path)
end
end
end end
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