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
gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
gem 'grape', '~> 0.19.0'
gem 'grape', '~> 0.19.2'
gem 'grape-entity', '~> 0.6.0'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
......@@ -402,7 +402,7 @@ gem 'sys-filesystem', '~> 1.1.6'
gem 'net-ntp'
# Gitaly GRPC client
gem 'gitaly', '~> 0.17.0'
gem 'gitaly', '~> 0.18.0'
gem 'toml-rb', '~> 0.3.15', require: false
......
......@@ -293,7 +293,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly (0.17.0)
gitaly (0.18.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
......@@ -357,13 +357,13 @@ GEM
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
grape (0.19.1)
grape (0.19.2)
activesupport
builder
hashie (>= 2.1.0)
multi_json (>= 1.3.2)
multi_xml (>= 0.5.2)
mustermann-grape (~> 0.4.0)
mustermann-grape (~> 1.0.0)
rack (>= 1.3.0)
rack-accept
virtus (>= 1.0.0)
......@@ -491,10 +491,9 @@ GEM
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
mustermann (0.4.0)
tool (~> 0.2)
mustermann-grape (0.4.0)
mustermann (= 0.4.0)
mustermann (1.0.0)
mustermann-grape (1.0.0)
mustermann (~> 1.0.0)
mysql2 (0.4.5)
net-ldap (0.12.1)
net-ntp (2.1.3)
......@@ -879,7 +878,6 @@ GEM
timfel-krb5-auth (0.8.3)
toml-rb (0.3.15)
citrus (~> 3.0, > 3.0)
tool (0.2.3)
truncato (0.7.8)
htmlentities (~> 4.3.1)
nokogiri (~> 1.6.1)
......@@ -1006,7 +1004,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly (~> 0.17.0)
gitaly (~> 0.18.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......@@ -1016,7 +1014,7 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
google-api-client (~> 0.8.6)
grape (~> 0.19.0)
grape (~> 0.19.2)
grape-entity (~> 0.6.0)
gssapi
haml_lint (~> 0.21.0)
......
......@@ -416,9 +416,6 @@ import AuditLogs from './audit_logs';
new AuditLogs();
break;
case 'projects:settings:repository:show':
// Initialize Protected Branch Settings
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
new UsersSelect();
// Initialize expandable settings panels
initSettingsPanels();
......
......@@ -2,8 +2,9 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, tokenKeys, filter) {
super(droplab, dropdown, input, filter);
constructor(options = {}) {
const { input, tokenKeys } = options;
super(options);
this.config = {
Filter: {
template: 'hint',
......
......@@ -5,8 +5,9 @@ import Filter from '~/droplab/plugins/filter';
import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, tokenKeys, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
constructor(options = {}) {
const { input, endpoint, symbol } = options;
super(options);
this.symbol = symbol;
this.config = {
Ajax: {
......
......@@ -5,8 +5,9 @@ import './filtered_search_dropdown';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, tokenKeys, filter) {
super(droplab, dropdown, input, filter);
constructor(options = {}) {
const { tokenKeys } = options;
super(options);
this.config = {
AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
......
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
constructor({ droplab, dropdown, input, filter }) {
this.droplab = droplab;
this.hookId = input && input.id;
this.input = input;
......
......@@ -46,13 +46,19 @@ class FilteredSearchDropdownManager {
milestone: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
extraArguments: {
endpoint: `${this.baseEndpoint}/milestones.json`,
symbol: '%',
},
element: this.container.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json`,
symbol: '~',
},
element: this.container.querySelector('#js-dropdown-label'),
},
hint: {
......@@ -109,13 +115,19 @@ class FilteredSearchDropdownManager {
let forceShowList = false;
if (!mappingKey.reference) {
const dl = this.droplab;
const defaultArguments =
[null, dl, element, this.filteredSearchInput, this.filteredSearchTokenKeys, key];
const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
const defaultArguments = {
droplab: this.droplab,
dropdown: element,
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>)`
mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))();
mappingKey.reference =
new (Function.prototype.bind.apply(gl[glClass], [null, glArguments]))();
}
if (firstLoad) {
......
/* eslint-disable no-unused-vars */
import './protected_branch_access_dropdown';
import './protected_branch_create';
import './protected_branch_dropdown';
import './protected_branch_edit';
import './protected_branch_edit_list';
$(() => {
const protectedBranchCreate = new gl.ProtectedBranchCreate();
const protectedBranchEditList = new gl.ProtectedBranchEditList();
});
......@@ -18,7 +18,7 @@
gl.ProtectedBranchCreate = class {
constructor() {
this.$wrap = this.$form = $('#new_protected_branch');
this.$wrap = this.$form = $('.js-new-protected-branch');
this.buildDropdowns();
this.$branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
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 */
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchAccessDropdown = class {
export default class ProtectedBranchAccessDropdown {
constructor(options) {
const { $dropdown, data, onSelect } = options;
this.options = options;
this.initDropdown();
}
initDropdown() {
const { $dropdown, data, onSelect } = this.options;
$dropdown.glDropdown({
data: data,
data,
selectable: true,
inputId: $dropdown.data('input-id'),
fieldName: $dropdown.data('field-name'),
toggleLabel(item, el) {
if (el.is('.is-active')) {
toggleLabel(item, $el) {
if ($el.is('.is-active')) {
return item.text;
} else {
return 'Select';
}
return 'Select';
},
clicked(opts) {
const { e } = opts;
e.preventDefault();
clicked(options) {
options.e.preventDefault();
onSelect();
}
},
});
}
};
})(window);
}
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
/* global ProtectedBranchDropdown */
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import ProtectedBranchDropdown from './protected_branch_dropdown';
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchCreate = class {
export default class ProtectedBranchCreate {
constructor() {
this.$wrap = this.$form = $('#new_protected_branch');
this.$form = $('.js-new-protected-branch');
this.buildDropdowns();
}
buildDropdowns() {
const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push');
// Cache callback
this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Merge dropdown
new gl.ProtectedBranchAccessDropdown({
this.protectedBranchMergeAccessDropdown = new ProtectedBranchAccessDropdown({
$dropdown: $allowedToMergeDropdown,
data: gon.merge_access_levels,
onSelect: this.onSelectCallback
onSelect: this.onSelectCallback,
});
// Allowed to Push dropdown
new gl.ProtectedBranchAccessDropdown({
this.protectedBranchPushAccessDropdown = new ProtectedBranchAccessDropdown({
$dropdown: $allowedToPushDropdown,
data: gon.push_access_levels,
onSelect: this.onSelectCallback
onSelect: this.onSelectCallback,
});
// Select default
......@@ -36,20 +33,19 @@
$allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected branch dropdown
new ProtectedBranchDropdown({
$dropdown: this.$wrap.find('.js-protected-branch-select'),
onSelect: this.onSelectCallback
this.protectedBranchDropdown = new ProtectedBranchDropdown({
$dropdown: this.$form.find('.js-protected-branch-select'),
onSelect: this.onSelectCallback,
});
}
// This will run after clicked callback
onSelect() {
// Enable submit button
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$wrap.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 $branchInput = this.$form.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$form.find('input[name="protected_branch[merge_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));
}
};
})(window);
}
/* eslint-disable comma-dangle, no-unused-vars */
class ProtectedBranchDropdown {
export default 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) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
......@@ -12,7 +16,7 @@ class ProtectedBranchDropdown {
this.bindEvents();
// Hide footer
this.$dropdownFooter.addClass('hidden');
this.toggleFooter(true);
}
buildDropdown() {
......@@ -21,7 +25,7 @@ class ProtectedBranchDropdown {
filterable: true,
remote: false,
search: {
fields: ['title']
fields: ['title'],
},
selectable: true,
toggleLabel(selected) {
......@@ -36,10 +40,9 @@ class ProtectedBranchDropdown {
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
const { $el, e } = options;
e.preventDefault();
options.e.preventDefault();
this.onSelect();
}
},
});
}
......@@ -64,20 +67,22 @@ class ProtectedBranchDropdown {
}
toggleCreateNewButton(branchName) {
if (branchName) {
this.selectedBranch = {
title: branchName,
id: branchName,
text: branchName
text: branchName,
};
if (branchName) {
this.$dropdownContainer
.find('.js-create-new-protected-branch code')
.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 => {
global.gl = global.gl || {};
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
gl.ProtectedBranchEdit = class {
export default class ProtectedBranchEdit {
constructor(options) {
this.$wrap = options.$wrap;
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
this.onSelectCallback = this.onSelect.bind(this);
this.buildDropdowns();
}
buildDropdowns() {
// Allowed to merge dropdown
new gl.ProtectedBranchAccessDropdown({
this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToMergeDropdown,
data: gon.merge_access_levels,
onSelect: this.onSelect.bind(this)
onSelect: this.onSelectCallback,
});
// Allowed to push dropdown
new gl.ProtectedBranchAccessDropdown({
this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToPushDropdown,
data: gon.push_access_levels,
onSelect: this.onSelect.bind(this)
onSelect: this.onSelectCallback,
});
}
......@@ -48,22 +48,20 @@
protected_branch: {
merge_access_levels_attributes: [{
id: this.$allowedToMergeDropdown.data('access-level-id'),
access_level: $allowedToMergeInput.val()
access_level: $allowedToMergeInput.val(),
}],
push_access_levels_attributes: [{
id: this.$allowedToPushDropdown.data('access-level-id'),
access_level: $allowedToPushInput.val()
}]
}
access_level: $allowedToPushInput.val(),
}],
},
},
error() {
$.scrollTo(0);
new Flash('Failed to update branch!');
}
new Flash('Failed to update branch!', null, $('.js-protected-branches-list'));
},
}).always(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
});
}
};
})(window);
}
/* eslint-disable arrow-parens, no-param-reassign, no-new, comma-dangle */
/* eslint-disable no-new */
(global => {
global.gl = global.gl || {};
import ProtectedBranchEdit from './protected_branch_edit';
gl.ProtectedBranchEditList = class {
export default class ProtectedBranchEditList {
constructor() {
this.$wrap = $('.protected-branches-list');
this.initEditForm();
}
// Build edit forms
initEditForm() {
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new gl.ProtectedBranchEdit({
$wrap: $(el)
new ProtectedBranchEdit({
$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 {
.breadcrumbs {
display: flex;
min-height: 60px;
min-height: 61px;
color: $gl-text-color;
border-bottom: 1px solid $border-color;
......
......@@ -165,7 +165,6 @@ $new-sidebar-width: 220px;
> li {
a {
font-size: 12px;
padding: 8px 16px 8px 24px;
&:hover,
......@@ -262,7 +261,7 @@ $new-sidebar-width: 220px;
@media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS
// scss-lint:disable DuplicateProperty
height: calc(100vh - 120px);
height: calc(100vh - 180px);
// scss-lint:enable DuplicateProperty
}
}
......
......@@ -770,15 +770,18 @@ a.allowed-to-push {
@extend .btn.disabled;
}
.protected-branches-list,
.protected-tags-list {
.flash-container {
padding: 0;
}
}
.protected-tags-list {
.dropdown-menu-toggle {
width: 100%;
max-width: 300px;
}
.flash-container {
padding: 0;
}
}
.custom-notifications-form {
......
......@@ -338,7 +338,9 @@
%fieldset
%legend Metrics - Prometheus
%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
\. This setting requires a
= link_to 'restart', help_page_path('administration/restart_gitlab')
......@@ -353,7 +355,10 @@
- unless Gitlab::Metrics.metrics_folder_present?
.help-block
%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
%legend Profiling - Performance Bar
......
......@@ -2,26 +2,6 @@
= render "admin/dashboard/head"
%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
%p
A 'Runner' is a process which runs a job.
......@@ -46,6 +26,19 @@
%span.label.label-danger paused
\- 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
.pull-left
= 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 @@
= pipeline_schedule.description
%td.branch-name-cell
= 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"
%td
- 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?
.panel-heading
%h3.panel-title
......@@ -23,6 +23,8 @@
- if can_admin_project
%th
%tbody
%tr
%td.flash-container{ colspan: 5 }
= yield
= 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-heading
%h3.panel-title
......
.panel.panel-default.protected-tags-list
.panel.panel-default.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.panel-heading
%h3.panel-title
......
%h3 Specific Runners
.bs-callout.help-callout
%h4 How to setup a specific Runner for a new project
%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!
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @project.runners_token,
type: 'specific' }
- if @project_runners.any?
%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?
# Add request parameters to log output
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))
}
end
......
......@@ -59,8 +59,8 @@ var config = {
pipelines_details: './pipelines/pipeline_details_bundle.js',
profile: './profile/profile_bundle.js',
prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches/protected_branches_bundle.js',
ee_protected_branches: './protected_branches/ee/protected_branches_bundle.js',
protected_branches: './protected_branches',
ee_protected_branches: './protected_branches/ee',
protected_tags: './protected_tags',
ee_protected_tags: './protected_tags/ee',
service_desk: './projects/settings_service_desk/service_desk_bundle.js',
......
......@@ -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 |
| 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)
[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.
This means that different background migrations should not migrate data in a
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
Background migrations are simple classes that define a `perform` method. A
......@@ -212,3 +224,27 @@ end
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
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.
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:
......
......@@ -68,6 +68,7 @@ module API
delete ":id/access_requests/:user_id" do
source = find_source(source_type, params[:id])
status 204
::Members::DestroyService.new(source, current_user, params)
.execute(:requesters)
end
......
......@@ -88,6 +88,7 @@ module API
unauthorized! unless award.user == current_user || current_user.admin?
status 204
award.destroy
end
end
......
......@@ -91,6 +91,7 @@ module API
delete ':id' do
message = find_message
status 204
message.destroy
end
end
......
......@@ -125,6 +125,7 @@ module API
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
not_found!('Deploy Key') unless key
status 204
key.destroy
end
end
......
......@@ -79,6 +79,7 @@ module API
environment = user_project.environments.find(params[:environment_id])
status 204
environment.destroy
end
......
......@@ -157,6 +157,8 @@ module API
delete ":id" do
group = find_group!(params[:id])
authorize! :admin_group, group
status 204
::Groups::DestroyService.new(group, current_user).execute
end
......
......@@ -230,6 +230,7 @@ module API
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
status 204
issue.destroy
end
......
......@@ -56,6 +56,7 @@ module API
label = user_project.labels.find_by(title: params[:name])
not_found!('Label') unless label
status 204
label.destroy
end
......
......@@ -38,6 +38,7 @@ module API
ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn])
if ldap_group_link
ldap_group_link.destroy
status 204
else
render_api_error!('Linked LDAP group not found', 404)
end
......@@ -55,6 +56,7 @@ module API
ldap_group_link = group.ldap_group_links.find_by(cn: params[:cn], provider: params[:provider])
if ldap_group_link
ldap_group_link.destroy
status 204
else
render_api_error!('Linked LDAP group not found', 404)
end
......
......@@ -102,6 +102,7 @@ module API
# Ensure the member exists
source.members.find_by!(user_id: params[:user_id])
status 204
::Members::DestroyService.new(source, current_user, declared_params).execute
end
end
......
......@@ -149,6 +149,7 @@ module API
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request)
status 204
merge_request.destroy
end
......
......@@ -131,6 +131,7 @@ module API
note = user_project.notes.find(params[:note_id])
authorize! :admin_note, note
status 204
::Notes::DestroyService.new(user_project, current_user).execute(note)
end
end
......
......@@ -96,6 +96,7 @@ module API
delete ":id/hooks/:hook_id" do
hook = user_project.hooks.find(params.delete(:hook_id))
status 204
hook.destroy
end
end
......
......@@ -70,6 +70,7 @@ module API
not_found!('Push Rule') unless push_rule
push_rule.destroy
status 204
end
end
end
......
......@@ -116,6 +116,7 @@ module API
not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet
status 204
snippet.destroy
end
......
......@@ -373,6 +373,7 @@ module API
authorize! :remove_fork_project, user_project
if user_project.forked?
status 204
user_project.forked_project_link.destroy
else
not_modified!
......@@ -417,6 +418,7 @@ module API
link = user_project.project_group_links.find_by(group_id: params[:group_id])
not_found!('Group Link') unless link
status 204
link.destroy
end
......
......@@ -45,6 +45,7 @@ module API
end
delete '/' do
authenticate_runner!
status 204
Ci::Runner.find_by_token(params[:token]).destroy
end
......
......@@ -79,6 +79,7 @@ module API
runner = get_runner(params[:id])
authenticate_delete_runner!(runner)
status 204
runner.destroy!
end
end
......@@ -134,6 +135,7 @@ module API
runner = runner_project.runner
forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1
status 204
runner_project.destroy
end
end
......
......@@ -123,6 +123,7 @@ module API
authorize! :destroy_personal_snippet, snippet
status 204
snippet.destroy
end
......
......@@ -66,6 +66,7 @@ module API
hook = SystemHook.find_by(id: params[:id])
not_found!('System hook') unless hook
status 204
hook.destroy
end
end
......
......@@ -140,6 +140,7 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
status 204
trigger.destroy
end
end
......
......@@ -241,6 +241,7 @@ module API
key = user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
status 204
key.destroy
end
......@@ -312,6 +313,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user
status 204
user.delete_async(deleted_by: current_user, params: params)
end
......@@ -412,6 +414,7 @@ module API
requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
end
delete ':impersonation_token_id' do
status 204
find_impersonation_token.revoke!
end
end
......@@ -489,6 +492,7 @@ module API
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
status 204
key.destroy
end
......@@ -540,6 +544,7 @@ module API
email = current_user.emails.find_by(id: params[:email_id])
not_found!('Email') unless email
status 204
Emails::DestroyService.new(current_user, email: email.email).execute
end
......
......@@ -102,6 +102,7 @@ module API
variable = user_project.variables.find_by(key: params[:key])
not_found!('Variable') unless variable
status 204
variable.destroy
end
end
......
......@@ -237,6 +237,10 @@ module Gitlab
branch_name.parameterize << '.patch'
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)
puts "\n=> #{desc}\n"
......@@ -303,14 +307,11 @@ module Gitlab
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
$ git fetch 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:
......@@ -324,7 +325,7 @@ module Gitlab
If the patch couldn't be applied cleanly, use the following command:
# 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,
and leave the rejected hunks in corresponding `.rej` files.
......
......@@ -98,17 +98,13 @@ module Gitlab
# Commit.between(repo, '29eda46b', 'master')
#
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
repo.gitaly_commit_client.between(base, head)
else
repo.commits_between(base, head)
repo.commits_between(base, head).map { |c| decorate(c) }
end
end
commits.map do |commit|
decorate(commit)
end
rescue Rugged::ReferenceError
[]
end
......@@ -135,6 +131,16 @@ module Gitlab
#
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/326
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
allowed_options = [:ref, :max_count, :skip, :order]
......@@ -173,6 +179,10 @@ module Gitlab
[]
end
def find_all_by_gitaly(repo, options = {})
Gitlab::GitalyClient::CommitService.new(repo).find_all_commits(options)
end
def decorate(commit, ref = nil)
Gitlab::Git::Commit.new(commit, ref)
end
......@@ -214,11 +224,12 @@ module Gitlab
def initialize(raw_commit, head = nil)
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)
elsif raw_commit.is_a?(Rugged::Commit)
when Rugged::Commit
init_from_rugged(raw_commit)
elsif raw_commit.is_a?(Gitaly::GitCommit)
when Gitlab::GitalyClient::Commit
init_from_gitaly(raw_commit)
else
raise "Invalid raw commit type: #{raw_commit.class}"
......@@ -298,7 +309,14 @@ module Gitlab
end
def parents
case raw_commit
when Rugged::Commit
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
def stats
......
......@@ -17,30 +17,13 @@ module Gitlab
def where(repository, sha, path = nil)
path = nil if path == '' || 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)
Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled|
if is_enabled
client = Gitlab::GitalyClient::CommitService.new(repository)
client.tree_entries(repository, sha, path)
else
[]
tree_entries_from_rugged(repository, sha, path)
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
......@@ -74,6 +57,34 @@ module Gitlab
entry[:oid]
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
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
entry
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)
request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo,
......@@ -80,6 +105,19 @@ module Gitlab
consume_commits_response(response)
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
def commit_diff_request_params(commit, options = {})
......@@ -94,7 +132,12 @@ module Gitlab
end
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
......
......@@ -22,13 +22,28 @@ module Gitlab
end
def scan(text)
scan_regexp.scan(text).map do |match|
if regexp.number_of_capturing_groups == 0
match.first
text = text.dup # modified in-place
results = []
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
match
groups[1..-1]
end
text.slice!(0, match.end(0) || 1)
break unless text.present?
end
results
end
def replace(text, rewrite)
......@@ -43,7 +58,7 @@ module Gitlab
# groups, so work around it
def scan_regexp
@scan_regexp ||=
if regexp.number_of_capturing_groups == 0
if regexp.number_of_capturing_groups.zero?
RE2::Regexp.new('(' + regexp.source + ')')
else
regexp
......
......@@ -153,7 +153,6 @@ module Gitlab
clone_repo(repo, target_dir) unless Dir.exist?(target_dir)
checkout_version(version, target_dir)
reset_to_version(version, target_dir)
end
def clone_repo(repo, target_dir)
......@@ -161,12 +160,8 @@ module Gitlab
end
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} checkout --quiet #{version}])
end
def reset_to_version(version, target_dir)
run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{version}])
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 -f --quiet FETCH_HEAD --])
end
end
end
......@@ -7,13 +7,13 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\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"
"Content-Type: text/plain; charset=UTF-8\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"
"PO-Revision-Date: 2017-07-20 09:50-0400\n"
"Last-Translator: Lin Jen-Shin <godfat@godfat.org>\n"
"Language: zh-TW\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
......@@ -624,6 +624,12 @@ msgstr "所有"
msgid "PipelineSchedules|Inactive"
msgstr "未啟用"
msgid "PipelineSchedules|Input variable key"
msgstr "變數名稱"
msgid "PipelineSchedules|Input variable value"
msgstr "變數值"
msgid "PipelineSchedules|Next Run"
msgstr "下次執行時間"
......@@ -633,12 +639,18 @@ msgstr "無"
msgid "PipelineSchedules|Provide a short description for this pipeline"
msgstr "請簡單說明此流水線 (pipeline) "
msgid "PipelineSchedules|Remove variable row"
msgstr "刪除變數"
msgid "PipelineSchedules|Take ownership"
msgstr "取得所有權"
msgid "PipelineSchedules|Target"
msgstr "目標"
msgid "PipelineSchedules|Variables"
msgstr "變數"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "自訂"
......@@ -759,13 +771,13 @@ msgid "Revert this merge request"
msgstr "還原此合併請求 (merge request) "
msgid "Save pipeline schedule"
msgstr "存流水線 (pipeline) 排程"
msgstr "存流水線 (pipeline) 排程"
msgid "Schedule a new pipeline"
msgstr "建立流水線 (pipeline) 排程"
msgid "Scheduling Pipelines"
msgstr "流水線 (pipeline) 計劃"
msgstr "流水線 (pipeline) 排程"
msgid "Search branches and tags"
msgstr "搜尋分支 (branch) 和標籤"
......@@ -849,8 +861,7 @@ msgid ""
"specific branches or tags. Those scheduled pipelines will inherit limited "
"project access based on their associated user."
msgstr ""
"在指定了特定分支 (branch) 或標籤後,此處的流水線 (pipeline) 排程會不斷地重複執行。\n"
"流水線排程的存取權限與專案本身相同。"
"在指定了特定分支 (branch) 或標籤後,此處的流水線 (pipeline) 排程會不斷地重複執行。流水線排程的存取權限與專案本身相同。"
msgid ""
"The planning stage shows the time from the previous step to pushing your "
......@@ -1096,6 +1107,14 @@ msgstr "因該階段的資料不足而無法顯示相關資訊"
msgid "Withdraw Access Request"
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 ""
"You are going to remove %{project_name_with_namespace}.\n"
"Removed project CANNOT be restored!\n"
......
......@@ -48,7 +48,14 @@ module QA
module Main
autoload :Entry, 'qa/page/main/entry'
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
module Project
......
module QA
module Page
module Main
module Dashboard
class Groups < Page::Base
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'
......
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
within_user_menu { click_link 'Admin area' }
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
within_user_menu do
find('.header-user-dropdown-toggle').click
......
......@@ -13,8 +13,8 @@ module QA
def perform
Page::Main::Menu.act { go_to_groups }
Page::Main::Groups.act { prepare_test_namespace }
Page::Main::Menu.act { go_to_new_project }
Page::Dashboard::Groups.act { prepare_test_namespace }
Page::Group::Show.act { go_to_new_project }
Page::Project::New.perform do |page|
page.choose_test_namespace
......
require 'spec_helper'
describe SessionsController do
include DeviseHelpers
describe '#new' do
before do
@request.env['devise.mapping'] = Devise.mappings[:user]
set_devise_mapping(context: @request)
end
context 'when auto sign-in is enabled' do
......@@ -34,7 +36,7 @@ describe SessionsController do
describe '#create' do
before do
@request.env['devise.mapping'] = Devise.mappings[:user]
set_devise_mapping(context: @request)
end
context 'when using standard authentications' do
......@@ -257,7 +259,7 @@ describe SessionsController do
describe '#new' do
before do
@request.env['devise.mapping'] = Devise.mappings[:user]
set_devise_mapping(context: @request)
end
it 'redirects correctly for referer on same host with params' do
......
......@@ -19,7 +19,7 @@ describe "Admin Runners" do
end
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"
end
......@@ -54,7 +54,7 @@ describe "Admin Runners" do
end
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 'No runners found'
end
......@@ -163,12 +163,11 @@ describe "Admin Runners" do
end
it 'has a registration token' do
expect(page).to have_content("Registration token is #{token}")
expect(page).to have_selector('#runners-token', text: token)
expect(page.find('#registration_token')).to have_content(token)
end
describe 'reload registration token' do
let(:page_token) { find('#runners-token').text }
let(:page_token) { find('#registration_token').text }
before do
click_button 'Reset runners registration token'
......
......@@ -21,6 +21,16 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
wait_for_requests
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
let(:issue) { create(:issue, project: project) }
......@@ -99,32 +109,6 @@ feature 'Issues > User uses quick actions', feature: true, js: true do
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
let(:issue) { create(:issue, project: project) }
......
......@@ -24,6 +24,14 @@ feature 'Merge Requests > User uses quick actions', feature: true, js: true do
wait_for_requests
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
context 'when the current user can toggle the WIP prefix' do
it 'adds the WIP: prefix to the title' do
......
require 'spec_helper'
feature 'OAuth Login', js: true do
include DeviseHelpers
def enter_code(code)
fill_in 'user_otp_attempt', with: code
click_button 'Verify code'
......@@ -8,7 +10,7 @@ feature 'OAuth Login', js: true do
def stub_omniauth_config(provider)
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]
end
......
......@@ -70,6 +70,17 @@ feature 'Pipeline Schedules', :feature, js: true do
expect(first('.branch-name-cell').text).to eq('')
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
describe 'POST /projects/pipeline_schedules/new' do
......@@ -128,6 +139,19 @@ feature 'Pipeline Schedules', :feature, js: true do
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
context 'when user creates a new pipeline schedule with variables' do
......
......@@ -12,7 +12,9 @@ describe('Dropdown User', () => {
spyOn(gl.DropdownUser.prototype, 'getProjectId').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', () => {
......@@ -78,7 +80,10 @@ describe('Dropdown User', () => {
loadFixtures(fixtureTemplate);
authorFilterDropdownElement = document.querySelector('#js-dropdown-author');
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');
......
......@@ -10,10 +10,12 @@ context 'U2F' do
end
describe SessionsController, '(JavaScript fixtures)', type: :controller do
include DeviseHelpers
render_views
before do
@request.env['devise.mapping'] = Devise.mappings[:user]
set_devise_mapping(context: @request)
end
it 'u2f/authenticate.html.raw' do |example|
......
......@@ -91,7 +91,7 @@ describe Gitlab::Git::Commit, seed_helper: true do
committer: committer
)
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.id).to eq(id) }
......@@ -290,33 +290,13 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
describe '.find_all' do
shared_examples 'finding all commits' do
it 'should return a return a collection of commits' do
commits = described_class.find_all(repository)
expect(commits).not_to be_empty
expect(commits).to all( be_a_kind_of(Gitlab::Git::Commit) )
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
subject do
commits = Gitlab::Git::Commit.find_all(
......@@ -324,15 +304,20 @@ describe Gitlab::Git::Commit, seed_helper: true do
max_count: 50
)
commits.map { |c| c.id }
commits.map(&:id)
end
it 'has 31 elements' do
it 'has 33 elements' do
expect(subject.size).to eq(33)
end
it { is_expected.to include(SeedRepo::Commit::ID) }
it { is_expected.to include(SeedRepo::Commit::PARENT_ID) }
it { is_expected.to include(SeedRepo::FirstCommit::ID) }
it 'includes the expected commits' do
expect(subject).to include(
SeedRepo::Commit::ID,
SeedRepo::Commit::PARENT_ID,
SeedRepo::FirstCommit::ID
)
end
end
context 'ref + max_count + skip' do
......@@ -344,15 +329,46 @@ describe Gitlab::Git::Commit, seed_helper: true do
skip: 1
)
commits.map { |c| c.id }
commits.map(&:id)
end
it 'has 23 elements' do
it 'has 24 elements' do
expect(subject.size).to eq(24)
end
it { is_expected.to include(SeedRepo::Commit::ID) }
it { is_expected.to include(SeedRepo::FirstCommit::ID) }
it { is_expected.not_to include(SeedRepo::LastCommit::ID) }
it 'includes the expected commits' do
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
......
require "spec_helper"
describe Gitlab::Git::Tree, seed_helper: true do
context :repo do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH) }
context :repo do
let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) }
it { expect(tree).to be_kind_of Array }
......@@ -74,4 +75,24 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(submodule.name).to eq('gitlab-shell') }
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
......@@ -2,9 +2,13 @@ require 'spec_helper'
describe Gitlab::GitalyClient::CommitService do
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_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
context 'when a commit has a parent' 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))
described_class.new(repository).diff_from_parent(commit)
client.diff_from_parent(commit)
end
end
......@@ -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))
described_class.new(repository).diff_from_parent(initial_commit)
client.diff_from_parent(initial_commit)
end
end
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)
end
......@@ -53,7 +57,7 @@ describe Gitlab::GitalyClient::CommitService do
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
......@@ -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([])
described_class.new(repository).commit_deltas(commit)
client.commit_deltas(commit)
end
end
......@@ -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([])
described_class.new(repository).commit_deltas(initial_commit)
client.commit_deltas(initial_commit)
end
end
end
......@@ -91,6 +95,7 @@ describe Gitlab::GitalyClient::CommitService do
describe '#between' do
let(:from) { 'master' }
let(:to) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
it 'sends an RPC request' do
request = Gitaly::CommitsBetweenRequest.new(
repository: repository_message, from: from, to: to
......@@ -102,4 +107,17 @@ describe Gitlab::GitalyClient::CommitService do
described_class.new(repository).between(from, to)
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
......@@ -50,6 +50,24 @@ describe Gitlab::UntrustedRegexp do
include_examples 'malicious regexp'
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
let(:regexp) { '.+' }
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
include DeviseHelpers
# 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)
......@@ -106,7 +108,7 @@ module LoginHelpers
end
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.draw do
post '/users/auth/saml' => 'omniauth_callbacks#saml'
......
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
find(".js-allowed-to-#{operation}").trigger('click')
wait_for_requests
......
......@@ -5,7 +5,7 @@ shared_examples "protected branches > access control > CE" do
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")
unless allowed_to_push_button.text == access_type_name
......@@ -50,7 +50,7 @@ shared_examples "protected branches > access control > CE" do
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")
unless allowed_to_merge_button.text == access_type_name
......
......@@ -20,7 +20,6 @@ describe Gitlab::TaskHelpers do
it 'checkout the version and reset to it' do
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)
end
......@@ -31,7 +30,6 @@ describe Gitlab::TaskHelpers 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(:reset_to_version).with(branch, clone_path)
subject.checkout_or_clone_version(version: version, repo: repo, target_dir: clone_path)
end
......@@ -70,20 +68,11 @@ describe Gitlab::TaskHelpers do
describe '#checkout_version' do
it 'clones the repo in the target dir' do
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)
.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)
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
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