summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-11-17 14:47:42 +0000
committerFilipa Lacerda <filipa@gitlab.com>2017-11-17 14:47:48 +0000
commit2686626dbf0479bd829f9f89114ec53704fc5444 (patch)
tree618d3434c3a9f3ceda9a2e4eb865712299bb9ed1
parent9a826fcd31430fcaec95cd31755a5712a9504833 (diff)
parente68ee8af4d981cb7b83fae76c0a94059add495fb (diff)
downloadgitlab-ce-36400-trigger-job.tar.gz
[ci skip] Merge branch 'master' into 36400-trigger-job36400-trigger-job
* master: (21 commits) Changing OAuth lookup to be case insensitive Delete orphaned fork networks in a migration Delete the fork network when removing the last membership Resolve "Performance issues when loading large number of wiki pages" Exports a couple of project related code as es6 modules Fix go-import meta data when enabled_git_access_protocol is a blank string Add dropdowns documentation Convert migration to populate latest merge request ID into a background migration Set 0.69.0 instead of latest for codeclimate image Fix hashed storage with project transfers to another namespace De-duplicate background migration matchers defined in spec/support/migrations_helpers.rb Update database_debugging.md Update database_debugging.md Move installation of apps higher Change to Google Kubernetes Cluster and add internal links Add Ingress description from official docs Add info on creating your own k8s cluster from the cluster page Add info about the installed apps in the Cluster docs Update HA README.md to clarify GitLab support does not troubleshoot DRBD. Optimise getting the pipeline status of commits ...
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/dispatcher.js18
-rw-r--r--app/assets/javascripts/main.js5
-rw-r--r--app/assets/javascripts/project.js4
-rw-r--r--app/assets/javascripts/project_label_subscription.js79
-rw-r--r--app/assets/javascripts/project_new.js272
-rw-r--r--app/assets/javascripts/project_select.js134
-rw-r--r--app/assets/javascripts/project_show.js11
-rw-r--r--app/assets/javascripts/project_variables.js60
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb6
-rw-r--r--app/controllers/projects/commits_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/controllers/projects/wikis_controller.rb8
-rw-r--r--app/helpers/ci_status_helper.rb5
-rw-r--r--app/models/ci/pipeline.rb66
-rw-r--r--app/models/commit.rb9
-rw-r--r--app/models/commit_collection.rb44
-rw-r--r--app/models/fork_network_member.rb10
-rw-r--r--app/models/identity.rb15
-rw-r--r--app/models/merge_request_diff.rb4
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/repository.rb16
-rw-r--r--app/models/user.rb3
-rw-r--r--app/models/wiki_page.rb19
-rw-r--r--app/services/projects/transfer_service.rb26
-rw-r--r--app/views/admin/runners/show.html.haml2
-rw-r--r--app/views/projects/wikis/_pages_wiki_page.html.haml2
-rw-r--r--app/views/projects/wikis/history.html.haml3
-rw-r--r--app/views/projects/wikis/show.html.haml4
-rw-r--r--changelogs/unreleased/34600-performance-wiki-pages.yml5
-rw-r--r--changelogs/unreleased/38822-oauth-search-case-insensitive.yml5
-rw-r--r--changelogs/unreleased/bvl-delete-empty-fork-networks.yml5
-rw-r--r--changelogs/unreleased/ci-pipeline-status-query.yml5
-rw-r--r--changelogs/unreleased/sh-port-hashed-storage-transfer-fix.yml5
-rw-r--r--config/initializers/gollum.rb26
-rw-r--r--db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb27
-rw-r--r--db/post_migrate/20171026082505_schedule_merge_request_latest_merge_request_diff_id_migrations.rb29
-rw-r--r--db/post_migrate/20171114104051_remove_empty_fork_networks.rb36
-rw-r--r--db/schema.rb2
-rw-r--r--doc/administration/high_availability/README.md4
-rw-r--r--doc/development/database_debugging.md14
-rw-r--r--doc/development/fe_guide/dropdowns.md38
-rw-r--r--doc/development/fe_guide/index.md2
-rw-r--r--doc/user/project/clusters/img/cluster-applications.pngbin39115 -> 0 bytes
-rw-r--r--doc/user/project/clusters/index.md80
-rw-r--r--lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id.rb30
-rw-r--r--lib/gitlab/git/wiki.rb56
-rw-r--r--lib/gitlab/ldap/user.rb5
-rw-r--r--lib/gitlab/middleware/go.rb5
-rw-r--r--lib/gitlab/o_auth/user.rb2
-rw-r--r--spec/factories/fork_network_members.rb8
-rw-r--r--spec/features/commits_spec.rb22
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js1
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb (renamed from spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb)13
-rw-r--r--spec/lib/gitlab/middleware/go_spec.rb8
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb9
-rw-r--r--spec/migrations/remove_empty_fork_networks_spec.rb24
-rw-r--r--spec/migrations/schedule_merge_request_diff_migrations_spec.rb19
-rw-r--r--spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb19
-rw-r--r--spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb64
-rw-r--r--spec/models/ci/pipeline_spec.rb122
-rw-r--r--spec/models/commit_collection_spec.rb59
-rw-r--r--spec/models/commit_spec.rb11
-rw-r--r--spec/models/fork_network_member_spec.rb18
-rw-r--r--spec/models/identity_spec.rb10
-rw-r--r--spec/models/wiki_page_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb32
67 files changed, 1105 insertions, 552 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 65f2bc7045f..ba19d69745c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -578,7 +578,7 @@ codequality:
script:
- cp .rubocop.yml .rubocop.yml.bak
- grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml
- - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json
+ - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 analyze -f json > raw_codeclimate.json
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
- mv .rubocop.yml.bak .rubocop.yml
artifacts:
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index d716218d9a4..5ff7edb6077 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,6 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
import { s__ } from './locale';
-/* global ProjectSelect */
+import projectSelect from './project_select';
import IssuableIndex from './issuable_index';
/* global Milestone */
import IssuableForm from './issuable_form';
@@ -26,8 +26,7 @@ import projectAvatar from './project_avatar';
/* global Compare */
/* global CompareAutocomplete */
/* global ProjectFindFile */
-/* global ProjectNew */
-/* global ProjectShow */
+import ProjectNew from './project_new';
import projectImport from './project_import';
import Labels from './labels';
import LabelManager from './label_manager';
@@ -91,6 +90,8 @@ import Members from './members';
import memberExpirationDate from './member_expiration_date';
import DueDateSelectors from './due_date_select';
import Diff from './diff';
+import ProjectLabelSubscription from './project_label_subscription';
+import ProjectVariables from './project_variables';
(function() {
var Dispatcher;
@@ -187,7 +188,7 @@ import Diff from './diff';
initIssuableSidebar();
break;
case 'dashboard:milestones:index':
- new ProjectSelect();
+ projectSelect();
break;
case 'projects:milestones:show':
case 'groups:milestones:show':
@@ -197,7 +198,7 @@ import Diff from './diff';
break;
case 'dashboard:issues':
case 'dashboard:merge_requests':
- new ProjectSelect();
+ projectSelect();
initLegacyFilters();
break;
case 'groups:issues':
@@ -206,7 +207,7 @@ import Diff from './diff';
const filteredSearchManager = new gl.FilteredSearchManager(page === 'groups:issues' ? 'issues' : 'merge_requests');
filteredSearchManager.setup();
}
- new ProjectSelect();
+ projectSelect();
break;
case 'dashboard:todos:index':
new Todos();
@@ -484,7 +485,7 @@ import Diff from './diff';
if ($el.find('.dropdown-group-label').length) {
new GroupLabelSubscription($el);
} else {
- new gl.ProjectLabelSubscription($el);
+ new ProjectLabelSubscription($el);
}
});
break;
@@ -520,7 +521,7 @@ import Diff from './diff';
// Initialize expandable settings panels
initSettingsPanels();
case 'groups:settings:ci_cd:show':
- new gl.ProjectVariables();
+ new ProjectVariables();
break;
case 'ci:lints:create':
case 'ci:lints:show':
@@ -623,7 +624,6 @@ import Diff from './diff';
case 'show':
new Star();
new ProjectNew();
- new ProjectShow();
new NotificationsDropdown();
break;
case 'wikis':
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 0035dd23011..cef79eec273 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -71,11 +71,6 @@ import './pager';
import './preview_markdown';
import './project_find_file';
import './project_import';
-import './project_label_subscription';
-import './project_new';
-import './project_select';
-import './project_show';
-import './project_variables';
import './projects_dropdown';
import './projects_list';
import './syntax_highlight';
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index ddb78aaeea1..36b6a5ed376 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -1,7 +1,7 @@
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
-/* global ProjectSelect */
import Cookies from 'js-cookie';
+import projectSelect from './project_select';
export default class Project {
constructor() {
@@ -46,7 +46,7 @@ export default class Project {
}
static projectSelectDropdown () {
- new ProjectSelect();
+ projectSelect();
$('.project-item-select').on('click', e => Project.changeProject($(e.currentTarget).val()));
}
diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js
index 0a811627600..b65521b278f 100644
--- a/app/assets/javascripts/project_label_subscription.js
+++ b/app/assets/javascripts/project_label_subscription.js
@@ -1,55 +1,50 @@
-/* eslint-disable wrap-iife, func-names, space-before-function-paren, object-shorthand, comma-dangle, one-var, one-var-declaration-per-line, no-restricted-syntax, max-len, no-param-reassign */
+export default class ProjectLabelSubscription {
+ constructor(container) {
+ this.$container = $(container);
+ this.$buttons = this.$container.find('.js-subscribe-button');
-(function(global) {
- class ProjectLabelSubscription {
- constructor(container) {
- this.$container = $(container);
- this.$buttons = this.$container.find('.js-subscribe-button');
-
- this.$buttons.on('click', this.toggleSubscription.bind(this));
- }
+ this.$buttons.on('click', this.toggleSubscription.bind(this));
+ }
- toggleSubscription(event) {
- event.preventDefault();
+ toggleSubscription(event) {
+ event.preventDefault();
- const $btn = $(event.currentTarget);
- const $span = $btn.find('span');
- const url = $btn.attr('data-url');
- const oldStatus = $btn.attr('data-status');
+ const $btn = $(event.currentTarget);
+ const $span = $btn.find('span');
+ const url = $btn.attr('data-url');
+ const oldStatus = $btn.attr('data-status');
- $btn.addClass('disabled');
- $span.toggleClass('hidden');
+ $btn.addClass('disabled');
+ $span.toggleClass('hidden');
- $.ajax({
- type: 'POST',
- url: url
- }).done(() => {
- let newStatus, newAction;
+ $.ajax({
+ type: 'POST',
+ url,
+ }).done(() => {
+ let newStatus;
+ let newAction;
- if (oldStatus === 'unsubscribed') {
- [newStatus, newAction] = ['subscribed', 'Unsubscribe'];
- } else {
- [newStatus, newAction] = ['unsubscribed', 'Subscribe'];
- }
+ if (oldStatus === 'unsubscribed') {
+ [newStatus, newAction] = ['subscribed', 'Unsubscribe'];
+ } else {
+ [newStatus, newAction] = ['unsubscribed', 'Subscribe'];
+ }
- $span.toggleClass('hidden');
- $btn.removeClass('disabled');
+ $span.toggleClass('hidden');
+ $btn.removeClass('disabled');
- this.$buttons.attr('data-status', newStatus);
- this.$buttons.find('> span').text(newAction);
+ this.$buttons.attr('data-status', newStatus);
+ this.$buttons.find('> span').text(newAction);
- this.$buttons.map((button) => {
- const $button = $(button);
+ this.$buttons.map((button) => {
+ const $button = $(button);
- if ($button.attr('data-original-title')) {
- $button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle');
- }
+ if ($button.attr('data-original-title')) {
+ $button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle');
+ }
- return button;
- });
+ return button;
});
- }
+ });
}
-
- global.ProjectLabelSubscription = ProjectLabelSubscription;
-})(window.gl || (window.gl = {}));
+}
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index fd89a1a85c3..ca548d011b6 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */
+/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
import VisibilitySelect from './visibility_select';
@@ -7,153 +7,145 @@ function highlightChanges($elm) {
setTimeout(() => $elm.removeClass('highlight-changes'), 10);
}
-(function() {
- this.ProjectNew = (function() {
- function ProjectNew() {
- this.toggleSettings = this.toggleSettings.bind(this);
- this.$selects = $('.features select');
- this.$repoSelects = this.$selects.filter('.js-repo-select');
- this.$projectSelects = this.$selects.not('.js-repo-select');
-
- $('.project-edit-container').on('ajax:before', (function(_this) {
- return function() {
- $('.project-edit-container').hide();
- return $('.save-project-loader').show();
- };
- })(this));
-
- this.initVisibilitySelect();
-
- this.toggleSettings();
- this.toggleSettingsOnclick();
- this.toggleRepoVisibility();
- }
-
- ProjectNew.prototype.initVisibilitySelect = function() {
- const visibilityContainer = document.querySelector('.js-visibility-select');
- if (!visibilityContainer) return;
- const visibilitySelect = new VisibilitySelect(visibilityContainer);
- visibilitySelect.init();
-
- const $visibilitySelect = $(visibilityContainer).find('select');
- let projectVisibility = $visibilitySelect.val();
- const PROJECT_VISIBILITY_PRIVATE = '0';
-
- $visibilitySelect.on('change', () => {
- const newProjectVisibility = $visibilitySelect.val();
-
- if (projectVisibility !== newProjectVisibility) {
- this.$projectSelects.each((idx, select) => {
- const $select = $(select);
- const $options = $select.find('option');
- const values = $.map($options, e => e.value);
-
- // if switched to "private", limit visibility options
- if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) {
- if ($select.val() !== values[0] && $select.val() !== values[1]) {
- $select.val(values[1]).trigger('change');
- highlightChanges($select);
- }
- $options.slice(2).disable();
+export default class ProjectNew {
+ constructor() {
+ this.toggleSettings = this.toggleSettings.bind(this);
+ this.$selects = $('.features select');
+ this.$repoSelects = this.$selects.filter('.js-repo-select');
+ this.$projectSelects = this.$selects.not('.js-repo-select');
+
+ $('.project-edit-container').on('ajax:before', () => {
+ $('.project-edit-container').hide();
+ return $('.save-project-loader').show();
+ });
+
+ this.initVisibilitySelect();
+
+ this.toggleSettings();
+ this.toggleSettingsOnclick();
+ this.toggleRepoVisibility();
+ }
+
+ initVisibilitySelect() {
+ const visibilityContainer = document.querySelector('.js-visibility-select');
+ if (!visibilityContainer) return;
+ const visibilitySelect = new VisibilitySelect(visibilityContainer);
+ visibilitySelect.init();
+
+ const $visibilitySelect = $(visibilityContainer).find('select');
+ let projectVisibility = $visibilitySelect.val();
+ const PROJECT_VISIBILITY_PRIVATE = '0';
+
+ $visibilitySelect.on('change', () => {
+ const newProjectVisibility = $visibilitySelect.val();
+
+ if (projectVisibility !== newProjectVisibility) {
+ this.$projectSelects.each((idx, select) => {
+ const $select = $(select);
+ const $options = $select.find('option');
+ const values = $.map($options, e => e.value);
+
+ // if switched to "private", limit visibility options
+ if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) {
+ if ($select.val() !== values[0] && $select.val() !== values[1]) {
+ $select.val(values[1]).trigger('change');
+ highlightChanges($select);
}
+ $options.slice(2).disable();
+ }
- // if switched from "private", increase visibility for non-disabled options
- if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) {
- $options.enable();
- if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) {
- $select.val(values[values.length - 1]).trigger('change');
- highlightChanges($select);
- }
+ // if switched from "private", increase visibility for non-disabled options
+ if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) {
+ $options.enable();
+ if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) {
+ $select.val(values[values.length - 1]).trigger('change');
+ highlightChanges($select);
}
- });
+ }
+ });
- projectVisibility = newProjectVisibility;
- }
- });
- };
-
- ProjectNew.prototype.toggleSettings = function() {
- var self = this;
-
- this.$selects.each(function () {
- var $select = $(this);
- var className = $select.data('field')
- .replace(/_/g, '-')
- .replace('access-level', 'feature');
- self._showOrHide($select, '.' + className);
- });
- };
-
- ProjectNew.prototype.toggleSettingsOnclick = function() {
- this.$selects.on('change', this.toggleSettings);
- };
-
- ProjectNew.prototype._showOrHide = function(checkElement, container) {
- var $container = $(container);
-
- if ($(checkElement).val() !== '0') {
- return $container.show();
- } else {
- return $container.hide();
+ projectVisibility = newProjectVisibility;
}
- };
-
- ProjectNew.prototype.toggleRepoVisibility = function () {
- var $repoAccessLevel = $('.js-repo-access-level select');
- var $lfsEnabledOption = $('.js-lfs-enabled select');
- var containerRegistry = document.querySelectorAll('.js-container-registry')[0];
- var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
- var prevSelectedVal = parseInt($repoAccessLevel.val(), 10);
-
- this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
- .nextAll()
- .hide();
-
- $repoAccessLevel.off('change')
- .on('change', function () {
- var selectedVal = parseInt($repoAccessLevel.val(), 10);
-
- this.$repoSelects.each(function () {
- var $this = $(this);
- var repoSelectVal = parseInt($this.val(), 10);
-
- $this.find('option').enable();
-
- if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) {
- $this.val(selectedVal).trigger('change');
- highlightChanges($this);
- }
-
- $this.find("option[value='" + selectedVal + "']").nextAll().disable();
- });
+ });
+ }
+
+ toggleSettings() {
+ this.$selects.each(function () {
+ var $select = $(this);
+ var className = $select.data('field')
+ .replace(/_/g, '-')
+ .replace('access-level', 'feature');
+ ProjectNew._showOrHide($select, '.' + className);
+ });
+ }
+
+ toggleSettingsOnclick() {
+ this.$selects.on('change', this.toggleSettings);
+ }
+
+ static _showOrHide(checkElement, container) {
+ const $container = $(container);
+
+ if ($(checkElement).val() !== '0') {
+ return $container.show();
+ }
+ return $container.hide();
+ }
+
+ toggleRepoVisibility() {
+ var $repoAccessLevel = $('.js-repo-access-level select');
+ var $lfsEnabledOption = $('.js-lfs-enabled select');
+ var containerRegistry = document.querySelectorAll('.js-container-registry')[0];
+ var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
+ var prevSelectedVal = parseInt($repoAccessLevel.val(), 10);
+
+ this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
+ .nextAll()
+ .hide();
+
+ $repoAccessLevel
+ .off('change')
+ .on('change', function () {
+ var selectedVal = parseInt($repoAccessLevel.val(), 10);
+
+ this.$repoSelects.each(function () {
+ var $this = $(this);
+ var repoSelectVal = parseInt($this.val(), 10);
+
+ $this.find('option').enable();
+
+ if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) {
+ $this.val(selectedVal).trigger('change');
+ highlightChanges($this);
+ }
- if (selectedVal) {
- this.$repoSelects.removeClass('disabled');
+ $this.find("option[value='" + selectedVal + "']").nextAll().disable();
+ });
- if ($lfsEnabledOption.length) {
- $lfsEnabledOption.removeClass('disabled');
- highlightChanges($lfsEnabledOption);
- }
- if (containerRegistry) {
- containerRegistry.style.display = '';
- }
- } else {
- this.$repoSelects.addClass('disabled');
+ if (selectedVal) {
+ this.$repoSelects.removeClass('disabled');
- if ($lfsEnabledOption.length) {
- $lfsEnabledOption.val('false').addClass('disabled');
- highlightChanges($lfsEnabledOption);
- }
- if (containerRegistry) {
- containerRegistry.style.display = 'none';
- containerRegistryCheckbox.checked = false;
- }
+ if ($lfsEnabledOption.length) {
+ $lfsEnabledOption.removeClass('disabled');
+ highlightChanges($lfsEnabledOption);
+ }
+ if (containerRegistry) {
+ containerRegistry.style.display = '';
}
+ } else {
+ this.$repoSelects.addClass('disabled');
- prevSelectedVal = selectedVal;
- }.bind(this));
- };
+ if ($lfsEnabledOption.length) {
+ $lfsEnabledOption.val('false').addClass('disabled');
+ highlightChanges($lfsEnabledOption);
+ }
+ if (containerRegistry) {
+ containerRegistry.style.display = 'none';
+ containerRegistryCheckbox.checked = false;
+ }
+ }
- return ProjectNew;
- })();
-}).call(window);
+ prevSelectedVal = selectedVal;
+ }.bind(this));
+ }
+}
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index bffc85e6315..07a49d1506c 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -2,79 +2,73 @@
import Api from './api';
import ProjectSelectComboButton from './project_select_combo_button';
-(function () {
- this.ProjectSelect = (function () {
- function ProjectSelect() {
- $('.ajax-project-select').each(function(i, select) {
- var placeholder;
- const simpleFilter = $(select).data('simple-filter') || false;
- this.groupId = $(select).data('group-id');
- this.includeGroups = $(select).data('include-groups');
- this.allProjects = $(select).data('all-projects') || false;
- this.orderBy = $(select).data('order-by') || 'id';
- this.withIssuesEnabled = $(select).data('with-issues-enabled');
- this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled');
+export default function projectSelect() {
+ $('.ajax-project-select').each(function(i, select) {
+ var placeholder;
+ const simpleFilter = $(select).data('simple-filter') || false;
+ this.groupId = $(select).data('group-id');
+ this.includeGroups = $(select).data('include-groups');
+ this.allProjects = $(select).data('all-projects') || false;
+ this.orderBy = $(select).data('order-by') || 'id';
+ this.withIssuesEnabled = $(select).data('with-issues-enabled');
+ this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled');
- placeholder = "Search for project";
- if (this.includeGroups) {
- placeholder += " or group";
- }
+ placeholder = "Search for project";
+ if (this.includeGroups) {
+ placeholder += " or group";
+ }
- $(select).select2({
- placeholder: placeholder,
- minimumInputLength: 0,
- query: (function (_this) {
- return function (query) {
- var finalCallback, projectsCallback;
- finalCallback = function (projects) {
+ $(select).select2({
+ placeholder: placeholder,
+ minimumInputLength: 0,
+ query: (function (_this) {
+ return function (query) {
+ var finalCallback, projectsCallback;
+ finalCallback = function (projects) {
+ var data;
+ data = {
+ results: projects
+ };
+ return query.callback(data);
+ };
+ if (_this.includeGroups) {
+ projectsCallback = function (projects) {
+ var groupsCallback;
+ groupsCallback = function (groups) {
var data;
- data = {
- results: projects
- };
- return query.callback(data);
+ data = groups.concat(projects);
+ return finalCallback(data);
};
- if (_this.includeGroups) {
- projectsCallback = function (projects) {
- var groupsCallback;
- groupsCallback = function (groups) {
- var data;
- data = groups.concat(projects);
- return finalCallback(data);
- };
- return Api.groups(query.term, {}, groupsCallback);
- };
- } else {
- projectsCallback = finalCallback;
- }
- if (_this.groupId) {
- return Api.groupProjects(_this.groupId, query.term, projectsCallback);
- } else {
- return Api.projects(query.term, {
- order_by: _this.orderBy,
- with_issues_enabled: _this.withIssuesEnabled,
- with_merge_requests_enabled: _this.withMergeRequestsEnabled,
- membership: !_this.allProjects,
- }, projectsCallback);
- }
+ return Api.groups(query.term, {}, groupsCallback);
};
- })(this),
- id: function(project) {
- if (simpleFilter) return project.id;
- return JSON.stringify({
- name: project.name,
- url: project.web_url,
- });
- },
- text: function (project) {
- return project.name_with_namespace || project.name;
- },
- dropdownCssClass: "ajax-project-dropdown"
+ } else {
+ projectsCallback = finalCallback;
+ }
+ if (_this.groupId) {
+ return Api.groupProjects(_this.groupId, query.term, projectsCallback);
+ } else {
+ return Api.projects(query.term, {
+ order_by: _this.orderBy,
+ with_issues_enabled: _this.withIssuesEnabled,
+ with_merge_requests_enabled: _this.withMergeRequestsEnabled,
+ membership: !_this.allProjects,
+ }, projectsCallback);
+ }
+ };
+ })(this),
+ id: function(project) {
+ if (simpleFilter) return project.id;
+ return JSON.stringify({
+ name: project.name,
+ url: project.web_url,
});
- if (simpleFilter) return select;
- return new ProjectSelectComboButton(select);
- });
- }
-
- return ProjectSelect;
- })();
-}).call(window);
+ },
+ text: function (project) {
+ return project.name_with_namespace || project.name;
+ },
+ dropdownCssClass: "ajax-project-dropdown"
+ });
+ if (simpleFilter) return select;
+ return new ProjectSelectComboButton(select);
+ });
+}
diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js
deleted file mode 100644
index 3a51c1f26ac..00000000000
--- a/app/assets/javascripts/project_show.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife */
-
-(function() {
- this.ProjectShow = (function() {
- function ProjectShow() {}
-
- return ProjectShow;
- })();
-}).call(window);
-
-// I kept class for future
diff --git a/app/assets/javascripts/project_variables.js b/app/assets/javascripts/project_variables.js
index 4ee2e49306d..567c311f119 100644
--- a/app/assets/javascripts/project_variables.js
+++ b/app/assets/javascripts/project_variables.js
@@ -1,43 +1,39 @@
-(() => {
- const HIDDEN_VALUE_TEXT = '******';
- class ProjectVariables {
- constructor() {
- this.$revealBtn = $('.js-btn-toggle-reveal-values');
- this.$revealBtn.on('click', this.toggleRevealState.bind(this));
- }
+const HIDDEN_VALUE_TEXT = '******';
+
+export default class ProjectVariables {
+ constructor() {
+ this.$revealBtn = $('.js-btn-toggle-reveal-values');
+ this.$revealBtn.on('click', this.toggleRevealState.bind(this));
+ }
- toggleRevealState(e) {
- e.preventDefault();
+ toggleRevealState(e) {
+ e.preventDefault();
- const oldStatus = this.$revealBtn.attr('data-status');
- let newStatus = 'hidden';
- let newAction = 'Reveal Values';
+ const oldStatus = this.$revealBtn.attr('data-status');
+ let newStatus = 'hidden';
+ let newAction = 'Reveal Values';
- if (oldStatus === 'hidden') {
- newStatus = 'revealed';
- newAction = 'Hide Values';
- }
+ if (oldStatus === 'hidden') {
+ newStatus = 'revealed';
+ newAction = 'Hide Values';
+ }
- this.$revealBtn.attr('data-status', newStatus);
+ this.$revealBtn.attr('data-status', newStatus);
- const $variables = $('.variable-value');
+ const $variables = $('.variable-value');
- $variables.each((_, variable) => {
- const $variable = $(variable);
- let newText = HIDDEN_VALUE_TEXT;
+ $variables.each((_, variable) => {
+ const $variable = $(variable);
+ let newText = HIDDEN_VALUE_TEXT;
- if (newStatus === 'revealed') {
- newText = $variable.attr('data-value');
- }
+ if (newStatus === 'revealed') {
+ newText = $variable.attr('data-value');
+ }
- $variable.text(newText);
- });
+ $variable.text(newText);
+ });
- this.$revealBtn.text(newAction);
- }
+ this.$revealBtn.text(newAction);
}
-
- window.gl = window.gl || {};
- window.gl.ProjectVariables = ProjectVariables;
-})();
+}
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 9612b8d8514..56baa19f864 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -54,7 +54,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if current_user
log_audit_event(current_user, with: :saml)
# Update SAML identity if data has changed.
- identity = current_user.identities.find_by(extern_uid: oauth['uid'], provider: :saml)
+ identity = current_user.identities.with_extern_uid(:saml, oauth['uid']).take
if identity.nil?
current_user.identities.create(extern_uid: oauth['uid'], provider: :saml)
redirect_to profile_account_path, notice: 'Authentication method updated'
@@ -98,7 +98,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_omniauth
if current_user
# Add new authentication method
- current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
+ current_user.identities
+ .with_extern_uid(oauth['provider'], oauth['uid'])
+ .first_or_create(extern_uid: oauth['uid'])
log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
else
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 28920877635..5f4afd2cdee 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -57,6 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController
@repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end
+ @commits = @commits.with_pipeline_status
@commits = prepare_commits_for_rendering(@commits)
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 22de6680511..abe4e5245b1 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -80,7 +80,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def commits
# Get commits from repository
# or from cache if already merged
- @commits = prepare_commits_for_rendering(@merge_request.commits)
+ @commits =
+ prepare_commits_for_rendering(@merge_request.commits.with_pipeline_status)
render json: { html: view_to_html_string('projects/merge_requests/_commits') }
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index f7a9c98629d..b51261a49e0 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -74,7 +74,11 @@ class Projects::WikisController < Projects::ApplicationController
def history
@page = @project_wiki.find_page(params[:id])
- unless @page
+ if @page
+ @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page]),
+ total_count: @page.count_versions)
+ .page(params[:page])
+ else
redirect_to(
project_wiki_path(@project, :home),
notice: "Page not found"
@@ -101,7 +105,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki
- @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages.first(15))
+ @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15))
rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project)
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 4dd573c61f1..636316da80a 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -6,11 +6,6 @@
# See 'detailed_status?` method and `Gitlab::Ci::Status` module.
#
module CiStatusHelper
- def ci_status_path(pipeline)
- project = pipeline.project
- project_pipeline_path(project, pipeline)
- end
-
def ci_label_for_status(status)
if detailed_status?(status)
return status.label
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 19814864e50..c77c837ff3f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -149,34 +149,70 @@ module Ci
end
end
- # ref can't be HEAD or SHA, can only be branch/tag name
- scope :latest, ->(ref = nil) do
- max_id = unscope(:select)
- .select("max(#{quoted_table_name}.id)")
- .group(:ref, :sha)
-
- if ref
- where(ref: ref, id: max_id.where(ref: ref))
- else
- where(id: max_id)
- end
- end
scope :internal, -> { where(source: internal_sources) }
+ # Returns the pipelines in descending order (= newest first), optionally
+ # limited to a number of references.
+ #
+ # ref - The name (or names) of the branch(es)/tag(s) to limit the list of
+ # pipelines to.
+ def self.newest_first(ref = nil)
+ relation = order(id: :desc)
+
+ ref ? relation.where(ref: ref) : relation
+ end
+
def self.latest_status(ref = nil)
- latest(ref).status
+ newest_first(ref).pluck(:status).first
end
def self.latest_successful_for(ref)
- success.latest(ref).order(id: :desc).first
+ newest_first(ref).success.take
end
def self.latest_successful_for_refs(refs)
- success.latest(refs).order(id: :desc).each_with_object({}) do |pipeline, hash|
+ relation = newest_first(refs).success
+
+ relation.each_with_object({}) do |pipeline, hash|
hash[pipeline.ref] ||= pipeline
end
end
+ # Returns a Hash containing the latest pipeline status for every given
+ # commit.
+ #
+ # The keys of this Hash are the commit SHAs, the values the statuses.
+ #
+ # commits - The list of commit SHAs to get the status for.
+ # ref - The ref to scope the data to (e.g. "master"). If the ref is not
+ # given we simply get the latest status for the commits, regardless
+ # of what refs their pipelines belong to.
+ def self.latest_status_per_commit(commits, ref = nil)
+ p1 = arel_table
+ p2 = arel_table.alias
+
+ # This LEFT JOIN will filter out all but the newest row for every
+ # combination of (project_id, sha) or (project_id, sha, ref) if a ref is
+ # given.
+ cond = p1[:sha].eq(p2[:sha])
+ .and(p1[:project_id].eq(p2[:project_id]))
+ .and(p1[:id].lt(p2[:id]))
+
+ cond = cond.and(p1[:ref].eq(p2[:ref])) if ref
+ join = p1.join(p2, Arel::Nodes::OuterJoin).on(cond)
+
+ relation = select(:sha, :status)
+ .where(sha: commits)
+ .where(p2[:id].eq(nil))
+ .joins(join.join_sources)
+
+ relation = relation.where(ref: ref) if ref
+
+ relation.each_with_object({}) do |row, hash|
+ hash[row[:sha]] = row[:status]
+ end
+ end
+
def self.truncate_sha(sha)
sha[0...8]
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6dba154a6ea..a31ebe9cc87 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -80,6 +80,7 @@ class Commit
@raw = raw_commit
@project = project
+ @statuses = {}
end
def id
@@ -236,11 +237,13 @@ class Commit
end
def status(ref = nil)
- @statuses ||= {}
-
return @statuses[ref] if @statuses.key?(ref)
- @statuses[ref] = pipelines.latest_status(ref)
+ @statuses[ref] = project.pipelines.latest_status_per_commit(id, ref)[id]
+ end
+
+ def set_status_for_ref(ref, status)
+ @statuses[ref] = status
end
def signature
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
new file mode 100644
index 00000000000..dd93af9df64
--- /dev/null
+++ b/app/models/commit_collection.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+# A collection of Commit instances for a specific project and Git reference.
+class CommitCollection
+ include Enumerable
+
+ attr_reader :project, :ref, :commits
+
+ # project - The project the commits belong to.
+ # commits - The Commit instances to store.
+ # ref - The name of the ref (e.g. "master").
+ def initialize(project, commits, ref = nil)
+ @project = project
+ @commits = commits
+ @ref = ref
+ end
+
+ def each(&block)
+ commits.each(&block)
+ end
+
+ # Sets the pipeline status for every commit.
+ #
+ # Setting this status ahead of time removes the need for running a query for
+ # every commit we're displaying.
+ def with_pipeline_status
+ statuses = project.pipelines.latest_status_per_commit(map(&:id), ref)
+
+ each do |commit|
+ commit.set_status_for_ref(ref, statuses[commit.id])
+ end
+
+ self
+ end
+
+ def respond_to_missing?(message, inc_private = false)
+ commits.respond_to?(message, inc_private)
+ end
+
+ # rubocop:disable GitlabSecurity/PublicSend
+ def method_missing(message, *args, &block)
+ commits.public_send(message, *args, &block)
+ end
+end
diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb
index 6a9b52a1ef8..eb9417dc34f 100644
--- a/app/models/fork_network_member.rb
+++ b/app/models/fork_network_member.rb
@@ -4,4 +4,14 @@ class ForkNetworkMember < ActiveRecord::Base
belongs_to :forked_from_project, class_name: 'Project'
validates :fork_network, :project, presence: true
+
+ after_destroy :cleanup_fork_network
+
+ private
+
+ def cleanup_fork_network
+ # Explicitly using `#count` makes sure we have the correct number if the
+ # relation was loaded in the fork_network.
+ fork_network.destroy if fork_network.fork_network_members.count == 0
+ end
end
diff --git a/app/models/identity.rb b/app/models/identity.rb
index ac8094b610e..ff811e19f8a 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -1,18 +1,27 @@
class Identity < ActiveRecord::Base
include Sortable
include CaseSensitivity
+
belongs_to :user
validates :provider, presence: true
- validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider }
+ validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider, case_sensitive: false }
validates :user_id, uniqueness: { scope: :provider }
+ scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do
- extern_uid = Gitlab::LDAP::Person.normalize_dn(extern_uid) if provider.starts_with?('ldap')
- where(extern_uid: extern_uid, provider: provider)
+ iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
end
def ldap?
provider.starts_with?('ldap')
end
+
+ def self.normalize_uid(provider, uid)
+ if provider.to_s.starts_with?('ldap')
+ Gitlab::LDAP::Person.normalize_dn(uid)
+ else
+ uid.to_s
+ end
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 1eda0f9cbbd..5382f5cc627 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -284,8 +284,10 @@ class MergeRequestDiff < ActiveRecord::Base
def load_commits
commits = st_commits.presence || merge_request_diff_commits
+ commits = commits.map { |commit| Commit.from_hash(commit.to_hash, project) }
- commits.map { |commit| Commit.from_hash(commit.to_hash, project) }
+ CommitCollection
+ .new(merge_request.source_project, commits, merge_request.source_branch)
end
def save_diffs
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 3eecbea8cbf..a0af749a93f 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -76,8 +76,8 @@ class ProjectWiki
# Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages.
- def pages
- wiki.pages.map { |page| WikiPage.new(self, page, true) }
+ def pages(limit: nil)
+ wiki.pages(limit: limit).map { |page| WikiPage.new(self, page, true) }
end
# Finds a page within the repository based on a tile
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 3a89fa9264b..35ee12bdfa1 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -132,7 +132,8 @@ class Repository
commits = Gitlab::Git::Commit.where(options)
commits = Commit.decorate(commits, @project) if commits.present?
- commits
+
+ CommitCollection.new(project, commits, ref)
end
def commits_between(from, to)
@@ -148,11 +149,14 @@ class Repository
end
raw_repository.gitaly_migrate(:commits_by_message) do |is_enabled|
- if is_enabled
- find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
- else
- find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
- end
+ commits =
+ if is_enabled
+ find_commits_by_message_by_gitaly(query, ref, path, limit, offset)
+ else
+ find_commits_by_message_by_shelling_out(query, ref, path, limit, offset)
+ end
+
+ CommitCollection.new(project, commits, ref)
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index be8112749bf..71c34766451 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -269,8 +269,7 @@ class User < ActiveRecord::Base
end
def for_github_id(id)
- joins(:identities)
- .where(identities: { provider: :github, extern_uid: id.to_s })
+ joins(:identities).merge(Identity.with_extern_uid(:github, id))
end
# Find a User by their primary email or any associated secondary email
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 5f710961f95..bdfef677ef3 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -127,19 +127,24 @@ class WikiPage
@version ||= @page.version
end
- # Returns an array of Gitlab Commit instances.
- def versions
+ def versions(options = {})
return [] unless persisted?
- wiki.wiki.page_versions(@page.path)
+ wiki.wiki.page_versions(@page.path, options)
end
- def commit
- versions.first
+ def count_versions
+ return [] unless persisted?
+
+ wiki.wiki.count_page_versions(@page.path)
+ end
+
+ def last_version
+ @last_version ||= versions(limit: 1).first
end
def last_commit_sha
- commit&.sha
+ last_version&.sha
end
# Returns the Date that this latest version was
@@ -151,7 +156,7 @@ class WikiPage
# Returns boolean True or False if this instance
# is an old version of the page.
def historical?
- @page.historical? && versions.first.sha != version.sha
+ @page.historical? && last_version.sha != version.sha
end
# Returns boolean True or False if this instance
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 5957f612e84..d3a8ae8d7c6 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -60,15 +60,8 @@ module Projects
# Notifications
project.send_move_instructions(@old_path)
- # Move main repository
- # TODO: check storage type and NOOP when not using Legacy
- unless move_repo_folder(@old_path, @new_path)
- raise TransferError.new('Cannot move project')
- end
-
- # Move wiki repo also if present
- # TODO: check storage type and NOOP when not using Legacy
- move_repo_folder("#{@old_path}.wiki", "#{@new_path}.wiki")
+ # Directories on disk
+ move_project_folders(project)
# Move missing group labels to project
Labels::TransferService.new(current_user, @old_group, project).execute
@@ -131,5 +124,20 @@ module Projects
def execute_system_hooks
SystemHooksService.new.execute_hooks_for(project, :transfer)
end
+
+ def move_project_folders(project)
+ return if project.hashed_storage?(:repository)
+
+ # Move main repository
+ unless move_repo_folder(@old_path, @new_path)
+ raise TransferError.new("Cannot move project")
+ end
+
+ # Disk path is changed; we need to ensure we reload it
+ project.reload_repository!
+
+ # Move wiki repo also if present
+ move_repo_folder("#{@old_path}.wiki", "#{@new_path}.wiki")
+ end
end
end
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index df2bf27be9d..6d8fad0eb8d 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -99,7 +99,7 @@
%td.build-link
- if project
- = link_to ci_status_path(build.pipeline) do
+ = link_to pipeline_path(build.pipeline) do
%strong= build.pipeline.short_sha
%td.timestamp
diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml
index 0a1ccbc5f1c..efa16d38f84 100644
--- a/app/views/projects/wikis/_pages_wiki_page.html.haml
+++ b/app/views/projects/wikis/_pages_wiki_page.html.haml
@@ -2,4 +2,4 @@
= link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%small (#{wiki_page.format})
.pull-right
- %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.commit.authored_date) }).html_safe
+ %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 9ee09262324..969a1677d9a 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -21,7 +21,7 @@
%th= _("Last updated")
%th= _("Format")
%tbody
- - @page.versions.each_with_index do |version, index|
+ - @page_versions.each_with_index do |version, index|
- commit = version
%tr
%td
@@ -37,5 +37,6 @@
%td
%strong
= version.format
+= paginate @page_versions, theme: 'gitlab'
= render 'sidebar'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index de15fc99eda..b3b83cee81a 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -11,8 +11,8 @@
.nav-text
%h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by
- = (_("Last edited by %{name}") % { name: "<strong>#{@page.commit.author_name}</strong>" }).html_safe
- #{time_ago_with_tooltip(@page.commit.authored_date)}
+ = (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
+ #{time_ago_with_tooltip(@page.last_version.authored_date)}
.nav-controls
= render 'main_links'
diff --git a/changelogs/unreleased/34600-performance-wiki-pages.yml b/changelogs/unreleased/34600-performance-wiki-pages.yml
new file mode 100644
index 00000000000..541ae8f8e60
--- /dev/null
+++ b/changelogs/unreleased/34600-performance-wiki-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Performance issues when loading large number of wiki pages
+merge_request: 15276
+author:
+type: performance
diff --git a/changelogs/unreleased/38822-oauth-search-case-insensitive.yml b/changelogs/unreleased/38822-oauth-search-case-insensitive.yml
new file mode 100644
index 00000000000..d84360b4c5c
--- /dev/null
+++ b/changelogs/unreleased/38822-oauth-search-case-insensitive.yml
@@ -0,0 +1,5 @@
+---
+title: OAuth identity lookups case-insensitive
+merge_request: 15312
+author:
+type: fixed
diff --git a/changelogs/unreleased/bvl-delete-empty-fork-networks.yml b/changelogs/unreleased/bvl-delete-empty-fork-networks.yml
new file mode 100644
index 00000000000..3bbb4cf6e3c
--- /dev/null
+++ b/changelogs/unreleased/bvl-delete-empty-fork-networks.yml
@@ -0,0 +1,5 @@
+---
+title: Clean up empty fork networks
+merge_request: 15373
+author:
+type: other
diff --git a/changelogs/unreleased/ci-pipeline-status-query.yml b/changelogs/unreleased/ci-pipeline-status-query.yml
new file mode 100644
index 00000000000..a464e501418
--- /dev/null
+++ b/changelogs/unreleased/ci-pipeline-status-query.yml
@@ -0,0 +1,5 @@
+---
+title: Optimise getting the pipeline status of commits
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-port-hashed-storage-transfer-fix.yml b/changelogs/unreleased/sh-port-hashed-storage-transfer-fix.yml
new file mode 100644
index 00000000000..c32afc90f64
--- /dev/null
+++ b/changelogs/unreleased/sh-port-hashed-storage-transfer-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fix hashed storage with project transfers to another namespace
+merge_request:
+author:
+type: fixed
diff --git a/config/initializers/gollum.rb b/config/initializers/gollum.rb
index 1ebe3c7a742..2ca32791bb1 100644
--- a/config/initializers/gollum.rb
+++ b/config/initializers/gollum.rb
@@ -10,4 +10,30 @@ module Gollum
index.send(name, *args)
end
end
+
+ class Wiki
+ def pages(treeish = nil, limit: nil)
+ tree_list((treeish || @ref), limit: limit)
+ end
+
+ def tree_list(ref, limit: nil)
+ if (sha = @access.ref_to_sha(ref))
+ commit = @access.commit(sha)
+ tree_map_for(sha).inject([]) do |list, entry|
+ next list unless @page_class.valid_page_name?(entry.name)
+ list << entry.page(self, commit)
+ break list if limit && list.size >= limit
+ list
+ end
+ else
+ []
+ end
+ end
+ end
+end
+
+Rails.application.configure do
+ config.after_initialize do
+ Gollum::Page.per_page = Kaminari.config.default_per_page
+ end
end
diff --git a/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb b/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb
deleted file mode 100644
index a7ebbbf34c0..00000000000
--- a/db/post_migrate/20171026082505_populate_merge_requests_latest_merge_request_diff_id.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-class PopulateMergeRequestsLatestMergeRequestDiffId < ActiveRecord::Migration
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
- BATCH_SIZE = 1_000
-
- class MergeRequest < ActiveRecord::Base
- self.table_name = 'merge_requests'
-
- include ::EachBatch
- end
-
- disable_ddl_transaction!
-
- def up
- update = '
- latest_merge_request_diff_id = (
- SELECT MAX(id)
- FROM merge_request_diffs
- WHERE merge_requests.id = merge_request_diffs.merge_request_id
- )'.squish
-
- MergeRequest.where(latest_merge_request_diff_id: nil).each_batch(of: BATCH_SIZE) do |relation|
- relation.update_all(update)
- end
- end
-end
diff --git a/db/post_migrate/20171026082505_schedule_merge_request_latest_merge_request_diff_id_migrations.rb b/db/post_migrate/20171026082505_schedule_merge_request_latest_merge_request_diff_id_migrations.rb
new file mode 100644
index 00000000000..7a63382cc6d
--- /dev/null
+++ b/db/post_migrate/20171026082505_schedule_merge_request_latest_merge_request_diff_id_migrations.rb
@@ -0,0 +1,29 @@
+class ScheduleMergeRequestLatestMergeRequestDiffIdMigrations < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 50_000
+ MIGRATION = 'PopulateMergeRequestsLatestMergeRequestDiffId'
+
+ disable_ddl_transaction!
+
+ class MergeRequest < ActiveRecord::Base
+ self.table_name = 'merge_requests'
+
+ include ::EachBatch
+ end
+
+ # On GitLab.com, we saw that we generated about 500,000 dead tuples over 5 minutes.
+ # To keep replication lag from ballooning, we'll aim for 50,000 updates over 5 minutes.
+ #
+ # Assuming that there are 5 million rows affected (which is more than on
+ # GitLab.com), and that each batch of 50,000 rows takes up to 5 minutes, then
+ # we can migrate all the rows in 8.5 hours.
+ def up
+ MergeRequest.where(latest_merge_request_diff_id: nil).each_batch(of: BATCH_SIZE) do |relation, index|
+ range = relation.pluck('MIN(id)', 'MAX(id)').first
+
+ BackgroundMigrationWorker.perform_in(index * 5.minutes, MIGRATION, range)
+ end
+ end
+end
diff --git a/db/post_migrate/20171114104051_remove_empty_fork_networks.rb b/db/post_migrate/20171114104051_remove_empty_fork_networks.rb
new file mode 100644
index 00000000000..2fe99a1b9c1
--- /dev/null
+++ b/db/post_migrate/20171114104051_remove_empty_fork_networks.rb
@@ -0,0 +1,36 @@
+class RemoveEmptyForkNetworks < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10_000
+
+ class MigrationForkNetwork < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'fork_networks'
+ end
+
+ class MigrationForkNetworkMembers < ActiveRecord::Base
+ self.table_name = 'fork_network_members'
+ end
+
+ disable_ddl_transaction!
+
+ def up
+ say 'Deleting empty ForkNetworks in batches'
+
+ has_members = MigrationForkNetworkMembers
+ .where('fork_network_members.fork_network_id = fork_networks.id')
+ .select(1)
+ MigrationForkNetwork.where('NOT EXISTS (?)', has_members)
+ .each_batch(of: BATCH_SIZE) do |networks|
+ deleted = networks.delete_all
+
+ say "Deleted #{deleted} rows in batch"
+ end
+ end
+
+ def down
+ # nothing
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 37e08d453c8..53299792a39 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20171106180641) do
+ActiveRecord::Schema.define(version: 20171114104051) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index 4d3be0ab8f6..a88e67bfeb5 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -53,7 +53,9 @@ or in different cloud availability zones.
> **Note:** GitLab recommends against choosing this HA method because of the
complexity of managing DRBD and crafting automatic failover. This is
- *compatible* with GitLab, but not officially *supported*.
+ *compatible* with GitLab, but not officially *supported*. If you are
+ an EE customer, support will help you with GitLab related problems, but if the
+ root cause is identified as DRBD, we will not troubleshoot further.
Components/Servers Required: 2 servers/virtual machines (one active/one passive)
diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md
index 4acfbef3020..50eb8005b44 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -9,18 +9,24 @@ An easy first step is to search for your error in Slack or google "GitLab <my er
Available `RAILS_ENV`
- - `production` (not sure if in GDK)
+ - `production` (generally not for your main GDK db, but you may need this for e.g. omnibus)
- `development` (this is your main GDK db)
- `test` (used for tests like rspec and spinach)
## Nuke everything and start over
-If you just want to delete everything and start over,
+If you just want to delete everything and start over with an empty DB (~1 minute):
- - `bundle exec rake db:drop RAILS_ENV=development`
- - `bundle exec rake db:setup RAILS_ENV=development`
+ - `bundle exec rake db:reset RAILS_ENV=development`
+If you just want to delete everything and start over with dummy data (~40 minutes). This also does `db:reset` and runs DB-specific migrations:
+
+ - `bundle exec rake dev:setup RAILS_ENV=development`
+
+If your test DB is giving you problems, it is safe to nuke it because it doesn't contain important data:
+
+ - `bundle exec rake db:reset RAILS_ENV=test`
## Migration wrangling
diff --git a/doc/development/fe_guide/dropdowns.md b/doc/development/fe_guide/dropdowns.md
new file mode 100644
index 00000000000..e1660ac5caa
--- /dev/null
+++ b/doc/development/fe_guide/dropdowns.md
@@ -0,0 +1,38 @@
+# Dropdowns
+
+
+## How to style a bootstrap dropdown
+1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
+1. Add a specific class to the top level `.dropdown` element
+
+
+ ```Haml
+ .dropdown.my-dropdown
+ %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
+ %span.dropdown-toggle-text
+ Toggle Dropdown
+ = icon('chevron-down')
+
+ %ul.dropdown-menu
+ %li
+ %a
+ item!
+ ```
+
+ Or use the helpers
+ ```Haml
+ .dropdown.my-dropdown
+ = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
+ = dropdown_content
+ %li
+ %a
+ item!
+ ```
+
+1. Include the mixin in CSS
+
+ ```SCSS
+ @include new-style-dropdown('.my-dropdown ');
+ ```
+
+[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 8f956681693..73a03c07812 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -77,6 +77,8 @@ Vue resource specific practices and gotchas.
## [Icons](icons.md)
How we use SVG for our Icons.
+## [Dropdowns](dropdowns.md)
+How we use dropdowns.
---
## Style Guides
diff --git a/doc/user/project/clusters/img/cluster-applications.png b/doc/user/project/clusters/img/cluster-applications.png
deleted file mode 100644
index 7c82d19297e..00000000000
--- a/doc/user/project/clusters/img/cluster-applications.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 27b4b49c207..cf0c7c109a8 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -1,14 +1,15 @@
-# Connecting GitLab with GKE
+# Connecting GitLab with a Kubernetes cluster
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in 10.1.
CAUTION: **Warning:**
The Cluster integration is currently in **Beta**.
-Connect your project to Google Container Engine (GKE) in a few steps.
-
With a cluster associated to your project, you can use Review Apps, deploy your
-applications, run your pipelines, and much more in an easy way.
+applications, run your pipelines, and much more, in an easy way.
+
+Connect your project to Google Kubernetes Engine (GKE) or your own Kubernetes
+cluster in a few steps.
NOTE: **Note:**
The Cluster integration will eventually supersede the
@@ -30,36 +31,58 @@ prerequisites must be met:
- You must have Master [permissions] in order to be able to access the **Cluster**
page.
-If all of the above requirements are met, you can proceed to add a new cluster.
+If all of the above requirements are met, you can proceed to add a new GKE
+cluster.
## Adding a cluster
NOTE: **Note:**
You need Master [permissions] and above to add a cluster.
+There are two options when adding a new cluster; either use Google Kubernetes
+Engine (GKE) or provide the credentials to your own Kubernetes cluster.
+
To add a new cluster:
-1. Navigate to your project's **CI/CD > Cluster** page.
-1. Connect your Google account if you haven't done already by clicking the
- "Sign-in with Google" button.
-1. Fill in the requested values:
- - **Cluster name** (required) - The name you wish to give the cluster.
- - **GCP project ID** (required) - The ID of the project you created in your GCP
- console that will host the Kubernetes cluster. This must **not** be confused
- with the project name. Learn more about [Google Cloud Platform projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects).
- - **Zone** - The zone under which the cluster will be created. Read more about
- [the available zones](https://cloud.google.com/compute/docs/regions-zones/).
- - **Number of nodes** - The number of nodes you wish the cluster to have.
- - **Machine type** - The machine type of the Virtual Machine instance that
- the cluster will be based on. Read more about [the available machine types](https://cloud.google.com/compute/docs/machine-types).
- - **Project namespace** - The unique namespace for this project. By default you
- don't have to fill it in; by leaving it blank, GitLab will create one for you.
-1. Click the **Create cluster** button.
-
-After a few moments your cluster should be created. If something goes wrong,
+1. Navigate to your project's **CI/CD > Cluster** page
+1. If you want to let GitLab create a cluster on GKE for you, go through the
+ following steps, otherwise skip to the next one.
+ 1. Click on **Create with GKE**
+ 1. Connect your Google account if you haven't done already by clicking the
+ **Sign in with Google** button
+ 1. Fill in the requested values:
+ - **Cluster name** (required) - The name you wish to give the cluster.
+ - **GCP project ID** (required) - The ID of the project you created in your GCP
+ console that will host the Kubernetes cluster. This must **not** be confused
+ with the project name. Learn more about [Google Cloud Platform projects](https://cloud.google.com/resource-manager/docs/creating-managing-projects).
+ - **Zone** - The [zone](https://cloud.google.com/compute/docs/regions-zones/)
+ under which the cluster will be created.
+ - **Number of nodes** - The number of nodes you wish the cluster to have.
+ - **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
+ of the Virtual Machine instance that the cluster will be based on.
+ - **Project namespace** - The unique namespace for this project. By default you
+ don't have to fill it in; by leaving it blank, GitLab will create one for you.
+1. If you want to use your own existing Kubernetes cluster, click on
+ **Add an existing cluster** and fill in the details as described in the
+ [Kubernetes integration](../integrations/kubernetes.md) documentation.
+1. Finally, click the **Create cluster** button
+
+After a few moments, your cluster should be created. If something goes wrong,
you will be notified.
-Now, you can proceed to [enable the Cluster integration](#enabling-or-disabling-the-cluster-integration).
+You can now proceed to install some pre-defined applications and then
+enable the Cluster integration.
+
+## Installing applications
+
+GitLab provides a one-click install for various applications which will be
+added directly to your configured cluster. Those applications are needed for
+[Review Apps](../../../ci/review_apps/index.md) and [deployments](../../../ci/environments.md).
+
+| Application | GitLab version | Description |
+| ----------- | :------------: | ----------- |
+| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It will be automatically installed as a dependency when you try to install a different app. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. |
+| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. |
## Enabling or disabling the Cluster integration
@@ -88,12 +111,3 @@ To remove the Cluster integration from your project, simply click on the
and [add a cluster](#adding-a-cluster) again.
[permissions]: ../../permissions.md
-
-## Installing applications
-
-GitLab provides a one-click install for
-[Helm Tiller](https://docs.helm.sh/) and
-[Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)
-which will be added directly to your configured cluster.
-
-![Cluster application settings](img/cluster-applications.png)
diff --git a/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id.rb b/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id.rb
new file mode 100644
index 00000000000..7e109e96e73
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id.rb
@@ -0,0 +1,30 @@
+module Gitlab
+ module BackgroundMigration
+ class PopulateMergeRequestsLatestMergeRequestDiffId
+ BATCH_SIZE = 1_000
+
+ class MergeRequest < ActiveRecord::Base
+ self.table_name = 'merge_requests'
+
+ include ::EachBatch
+ end
+
+ def perform(start_id, stop_id)
+ update = '
+ latest_merge_request_diff_id = (
+ SELECT MAX(id)
+ FROM merge_request_diffs
+ WHERE merge_requests.id = merge_request_diffs.merge_request_id
+ )'.squish
+
+ MergeRequest
+ .where(id: start_id..stop_id)
+ .where(latest_merge_request_diff_id: nil)
+ .each_batch(of: BATCH_SIZE) do |relation|
+
+ relation.update_all(update)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb
index 022d1f249a9..d4a53d32c28 100644
--- a/lib/gitlab/git/wiki.rb
+++ b/lib/gitlab/git/wiki.rb
@@ -58,12 +58,12 @@ module Gitlab
end
end
- def pages
- @repository.gitaly_migrate(:wiki_get_all_pages) do |is_enabled|
+ def pages(limit: nil)
+ @repository.gitaly_migrate(:wiki_get_all_pages, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled
gitaly_get_all_pages
else
- gollum_get_all_pages
+ gollum_get_all_pages(limit: limit)
end
end
end
@@ -88,14 +88,23 @@ module Gitlab
end
end
- def page_versions(page_path)
+ # options:
+ # :page - The Integer page number.
+ # :per_page - The number of items per page.
+ # :limit - Total number of items to return.
+ def page_versions(page_path, options = {})
current_page = gollum_page_by_path(page_path)
- current_page.versions.map do |gollum_git_commit|
- gollum_page = gollum_wiki.page(current_page.title, gollum_git_commit.id)
- new_version(gollum_page, gollum_git_commit.id)
+
+ commits_from_page(current_page, options).map do |gitlab_git_commit|
+ gollum_page = gollum_wiki.page(current_page.title, gitlab_git_commit.id)
+ Gitlab::Git::WikiPageVersion.new(gitlab_git_commit, gollum_page&.format)
end
end
+ def count_page_versions(page_path)
+ @repository.count_commits(ref: 'HEAD', path: page_path)
+ end
+
def preview_slug(title, format)
# Adapted from gollum gem (Gollum::Wiki#preview_page) to avoid
# using Rugged through a Gollum::Wiki instance
@@ -110,6 +119,22 @@ module Gitlab
private
+ # options:
+ # :page - The Integer page number.
+ # :per_page - The number of items per page.
+ # :limit - Total number of items to return.
+ def commits_from_page(gollum_page, options = {})
+ unless options[:limit]
+ options[:offset] = ([1, options.delete(:page).to_i].max - 1) * Gollum::Page.per_page
+ options[:limit] = (options.delete(:per_page) || Gollum::Page.per_page).to_i
+ end
+
+ @repository.log(ref: gollum_page.last_version.id,
+ path: gollum_page.path,
+ limit: options[:limit],
+ offset: options[:offset])
+ end
+
def gollum_wiki
@gollum_wiki ||= Gollum::Wiki.new(@repository.path)
end
@@ -126,8 +151,17 @@ module Gitlab
end
def new_version(gollum_page, commit_id)
- commit = Gitlab::Git::Commit.find(@repository, commit_id)
- Gitlab::Git::WikiPageVersion.new(commit, gollum_page&.format)
+ Gitlab::Git::WikiPageVersion.new(version(commit_id), gollum_page&.format)
+ end
+
+ def version(commit_id)
+ commit_find_proc = -> { Gitlab::Git::Commit.find(@repository, commit_id) }
+
+ if RequestStore.active?
+ RequestStore.fetch([:wiki_version_commit, commit_id]) { commit_find_proc.call }
+ else
+ commit_find_proc.call
+ end
end
def assert_type!(object, klass)
@@ -185,8 +219,8 @@ module Gitlab
Gitlab::Git::WikiFile.new(gollum_file)
end
- def gollum_get_all_pages
- gollum_wiki.pages.map { |gollum_page| new_page(gollum_page) }
+ def gollum_get_all_pages(limit: nil)
+ gollum_wiki.pages(limit: limit).map { |gollum_page| new_page(gollum_page) }
end
def gitaly_write_page(name, format, content, commit_details)
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 4d5c67ed892..3945df27eed 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -9,11 +9,8 @@ module Gitlab
class User < Gitlab::OAuth::User
class << self
def find_by_uid_and_provider(uid, provider)
- uid = Gitlab::LDAP::Person.normalize_dn(uid)
+ identity = ::Identity.with_extern_uid(provider, uid).take
- identity = ::Identity
- .where(provider: provider)
- .where(extern_uid: uid).last
identity && identity.user
end
end
diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb
index cfc6b2a2029..1edda50e4b0 100644
--- a/lib/gitlab/middleware/go.rb
+++ b/lib/gitlab/middleware/go.rb
@@ -42,12 +42,11 @@ module Gitlab
project_url = URI.join(config.gitlab.url, path)
import_prefix = strip_url(project_url.to_s)
- repository_url = case current_application_settings.enabled_git_access_protocol
- when 'ssh'
+ repository_url = if current_application_settings.enabled_git_access_protocol == 'ssh'
shell = config.gitlab_shell
port = ":#{shell.ssh_port}" unless shell.ssh_port == 22
"ssh://#{shell.ssh_user}@#{shell.ssh_host}#{port}/#{path}.git"
- when 'http', nil
+ else
"#{project_url}.git"
end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index b4b3b00c84d..552133234a3 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -157,7 +157,7 @@ module Gitlab
end
def find_by_uid_and_provider
- identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid)
+ identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take
identity && identity.user
end
diff --git a/spec/factories/fork_network_members.rb b/spec/factories/fork_network_members.rb
new file mode 100644
index 00000000000..509c4e1fa1c
--- /dev/null
+++ b/spec/factories/fork_network_members.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :fork_network_member do
+ association :project
+ association :fork_network
+
+ forked_from_project { fork_network.root_project }
+ end
+end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 479fb713297..b163ca8dc75 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
describe 'Commits' do
- include CiStatusHelper
-
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
@@ -33,7 +31,7 @@ describe 'Commits' do
describe 'Commit builds' do
before do
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
end
it { expect(page).to have_content pipeline.sha[0..7] }
@@ -79,7 +77,7 @@ describe 'Commits' do
describe 'Commit builds', :js do
before do
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
end
it 'shows pipeline`s data' do
@@ -95,7 +93,7 @@ describe 'Commits' do
end
it do
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
click_on 'Download artifacts'
expect(page.response_headers['Content-Type']).to eq(artifacts_file.content_type)
end
@@ -103,7 +101,7 @@ describe 'Commits' do
describe 'Cancel all builds' do
it 'cancels commit', :js do
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
click_on 'Cancel running'
expect(page).to have_content 'canceled'
end
@@ -111,7 +109,7 @@ describe 'Commits' do
describe 'Cancel build' do
it 'cancels build', :js do
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
find('.js-btn-cancel-pipeline').click
expect(page).to have_content 'canceled'
end
@@ -120,13 +118,13 @@ describe 'Commits' do
describe '.gitlab-ci.yml not found warning' do
context 'ci builds enabled' do
it "does not show warning" do
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
end
it 'shows warning' do
stub_ci_pipeline_yaml_file(nil)
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
expect(page).to have_content '.gitlab-ci.yml not found in this commit'
end
end
@@ -135,7 +133,7 @@ describe 'Commits' do
before do
stub_ci_builds_disabled
stub_ci_pipeline_yaml_file(nil)
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
end
it 'does not show warning' do
@@ -149,7 +147,7 @@ describe 'Commits' do
before do
project.team << [user, :reporter]
build.update_attributes(artifacts_file: artifacts_file)
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
end
it 'Renders header', :js do
@@ -171,7 +169,7 @@ describe 'Commits' do
visibility_level: Gitlab::VisibilityLevel::INTERNAL,
public_builds: false)
build.update_attributes(artifacts_file: artifacts_file)
- visit ci_status_path(pipeline)
+ visit pipeline_path(pipeline)
end
it do
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 0795d0aaa82..1ad7c2d8efa 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -202,7 +202,6 @@ export default {
"revert_in_fork_path": "/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1",
"email_patches_path": "/root/acets-app/merge_requests/22.patch",
"plain_diff_path": "/root/acets-app/merge_requests/22.diff",
- "ci_status_path": "/root/acets-app/merge_requests/22/ci_status",
"status_path": "/root/acets-app/merge_requests/22.json",
"merge_check_path": "/root/acets-app/merge_requests/22/merge_check",
"ci_environments_status_url": "/root/acets-app/merge_requests/22/ci_environments_status",
diff --git a/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb
index 4ea7f441f7c..0cb753c5853 100644
--- a/spec/migrations/populate_merge_requests_latest_merge_request_diff_id_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb
@@ -1,7 +1,6 @@
require 'spec_helper'
-require Rails.root.join('db', 'post_migrate', '20171026082505_populate_merge_requests_latest_merge_request_diff_id')
-describe PopulateMergeRequestsLatestMergeRequestDiffId, :migration do
+describe Gitlab::BackgroundMigration::PopulateMergeRequestsLatestMergeRequestDiffId, :migration, schema: 20171026082505 do
let(:projects_table) { table(:projects) }
let(:merge_requests_table) { table(:merge_requests) }
let(:merge_request_diffs_table) { table(:merge_request_diffs) }
@@ -27,30 +26,32 @@ describe PopulateMergeRequestsLatestMergeRequestDiffId, :migration do
merge_request_diffs_table.where(merge_request_id: merge_request.id)
end
- describe '#up' do
+ describe '#perform' do
it 'ignores MRs without diffs' do
merge_request_without_diff = create_mr!('without_diff')
+ mr_id = merge_request_without_diff.id
expect(merge_request_without_diff.latest_merge_request_diff_id).to be_nil
- expect { migrate! }
+ expect { subject.perform(mr_id, mr_id) }
.not_to change { merge_request_without_diff.reload.latest_merge_request_diff_id }
end
it 'ignores MRs that have a diff ID already set' do
merge_request_with_multiple_diffs = create_mr!('with_multiple_diffs', diffs: 3)
diff_id = diffs_for(merge_request_with_multiple_diffs).minimum(:id)
+ mr_id = merge_request_with_multiple_diffs.id
merge_request_with_multiple_diffs.update!(latest_merge_request_diff_id: diff_id)
- expect { migrate! }
+ expect { subject.perform(mr_id, mr_id) }
.not_to change { merge_request_with_multiple_diffs.reload.latest_merge_request_diff_id }
end
it 'migrates multiple MR diffs to the correct values' do
merge_requests = Array.new(3).map.with_index { |_, i| create_mr!(i, diffs: 3) }
- migrate!
+ subject.perform(merge_requests.first.id, merge_requests.last.id)
merge_requests.each do |merge_request|
expect(merge_request.reload.latest_merge_request_diff_id)
diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb
index 67121937398..60a134be939 100644
--- a/spec/lib/gitlab/middleware/go_spec.rb
+++ b/spec/lib/gitlab/middleware/go_spec.rb
@@ -127,6 +127,14 @@ describe Gitlab::Middleware::Go do
include_examples 'go-get=1', enabled_protocol: nil
end
+
+ context 'with nothing disabled (blank string)' do
+ before do
+ stub_application_setting(enabled_git_access_protocol: '')
+ end
+
+ include_examples 'go-get=1', enabled_protocol: nil
+ end
end
def go
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index c7471a21fda..2f19fb7312d 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -662,4 +662,13 @@ describe Gitlab::OAuth::User do
end
end
end
+
+ describe '.find_by_uid_and_provider' do
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
+
+ it 'normalizes extern_uid' do
+ allow(oauth_user.auth_hash).to receive(:uid).and_return('MY-UID')
+ expect(oauth_user.find_user).to eql gl_user
+ end
+ end
end
diff --git a/spec/migrations/remove_empty_fork_networks_spec.rb b/spec/migrations/remove_empty_fork_networks_spec.rb
new file mode 100644
index 00000000000..cf6ae5cda74
--- /dev/null
+++ b/spec/migrations/remove_empty_fork_networks_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20171114104051_remove_empty_fork_networks.rb')
+
+describe RemoveEmptyForkNetworks, :migration do
+ let!(:fork_networks) { table(:fork_networks) }
+
+ let(:deleted_project) { create(:project) }
+ let!(:empty_network) { create(:fork_network, id: 1, root_project_id: deleted_project.id) }
+ let!(:other_network) { create(:fork_network, id: 2, root_project_id: create(:project).id) }
+
+ before do
+ deleted_project.destroy!
+ end
+
+ it 'deletes only the fork network without members' do
+ expect(fork_networks.count).to eq(2)
+
+ migrate!
+
+ expect(fork_networks.find_by(id: empty_network.id)).to be_nil
+ expect(fork_networks.find_by(id: other_network.id)).not_to be_nil
+ expect(fork_networks.count).to eq(1)
+ end
+end
diff --git a/spec/migrations/schedule_merge_request_diff_migrations_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb
index f95bd6e3511..76afb6c19cf 100644
--- a/spec/migrations/schedule_merge_request_diff_migrations_spec.rb
+++ b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb
@@ -2,19 +2,6 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170703130158_schedule_merge_request_diff_migrations')
describe ScheduleMergeRequestDiffMigrations, :migration, :sidekiq do
- matcher :be_scheduled_migration do |time, *expected|
- match do |migration|
- BackgroundMigrationWorker.jobs.any? do |job|
- job['args'] == [migration, expected] &&
- job['at'].to_i == time.to_i
- end
- end
-
- failure_message do |migration|
- "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!"
- end
- end
-
let(:merge_request_diffs) { table(:merge_request_diffs) }
let(:merge_requests) { table(:merge_requests) }
let(:projects) { table(:projects) }
@@ -37,9 +24,9 @@ describe ScheduleMergeRequestDiffMigrations, :migration, :sidekiq do
Timecop.freeze do
migrate!
- expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes.from_now, 1, 1)
- expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes.from_now, 2, 2)
- expect(described_class::MIGRATION).to be_scheduled_migration(15.minutes.from_now, 4, 4)
+ expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, 1, 1)
+ expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes, 2, 2)
+ expect(described_class::MIGRATION).to be_scheduled_migration(15.minutes, 4, 4)
expect(BackgroundMigrationWorker.jobs.size).to eq 3
end
end
diff --git a/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb
index 4ab1bb67058..cf323973384 100644
--- a/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb
+++ b/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb
@@ -2,19 +2,6 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170926150348_schedule_merge_request_diff_migrations_take_two')
describe ScheduleMergeRequestDiffMigrationsTakeTwo, :migration, :sidekiq do
- matcher :be_scheduled_migration do |time, *expected|
- match do |migration|
- BackgroundMigrationWorker.jobs.any? do |job|
- job['args'] == [migration, expected] &&
- job['at'].to_i == time.to_i
- end
- end
-
- failure_message do |migration|
- "Migration `#{migration}` with args `#{expected.inspect}` not scheduled!"
- end
- end
-
let(:merge_request_diffs) { table(:merge_request_diffs) }
let(:merge_requests) { table(:merge_requests) }
let(:projects) { table(:projects) }
@@ -37,9 +24,9 @@ describe ScheduleMergeRequestDiffMigrationsTakeTwo, :migration, :sidekiq do
Timecop.freeze do
migrate!
- expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes.from_now, 1, 1)
- expect(described_class::MIGRATION).to be_scheduled_migration(20.minutes.from_now, 2, 2)
- expect(described_class::MIGRATION).to be_scheduled_migration(30.minutes.from_now, 4, 4)
+ expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes, 1, 1)
+ expect(described_class::MIGRATION).to be_scheduled_migration(20.minutes, 2, 2)
+ expect(described_class::MIGRATION).to be_scheduled_migration(30.minutes, 4, 4)
expect(BackgroundMigrationWorker.jobs.size).to eq 3
end
end
diff --git a/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb b/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb
new file mode 100644
index 00000000000..158d0bc02ed
--- /dev/null
+++ b/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20171026082505_schedule_merge_request_latest_merge_request_diff_id_migrations')
+
+describe ScheduleMergeRequestLatestMergeRequestDiffIdMigrations, :migration, :sidekiq do
+ let(:projects_table) { table(:projects) }
+ let(:merge_requests_table) { table(:merge_requests) }
+ let(:merge_request_diffs_table) { table(:merge_request_diffs) }
+
+ let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') }
+
+ let!(:merge_request_1) { create_mr!('mr_1', diffs: 1) }
+ let!(:merge_request_2) { create_mr!('mr_2', diffs: 2) }
+ let!(:merge_request_migrated) { create_mr!('merge_request_migrated', diffs: 3) }
+ let!(:merge_request_4) { create_mr!('mr_4', diffs: 3) }
+
+ def create_mr!(name, diffs: 0)
+ merge_request =
+ merge_requests_table.create!(target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: name,
+ title: name)
+
+ diffs.times do
+ merge_request_diffs_table.create!(merge_request_id: merge_request.id)
+ end
+
+ merge_request
+ end
+
+ def diffs_for(merge_request)
+ merge_request_diffs_table.where(merge_request_id: merge_request.id)
+ end
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+
+ diff_id = diffs_for(merge_request_migrated).minimum(:id)
+ merge_request_migrated.update!(latest_merge_request_diff_id: diff_id)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION).to be_scheduled_migration(5.minutes, merge_request_1.id, merge_request_1.id)
+ expect(described_class::MIGRATION).to be_scheduled_migration(10.minutes, merge_request_2.id, merge_request_2.id)
+ expect(described_class::MIGRATION).to be_scheduled_migration(15.minutes, merge_request_4.id, merge_request_4.id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 3
+ end
+ end
+ end
+
+ it 'schedules background migrations' do
+ Sidekiq::Testing.inline! do
+ expect(merge_requests_table.where(latest_merge_request_diff_id: nil).count).to eq 3
+
+ migrate!
+
+ expect(merge_requests_table.where(latest_merge_request_diff_id: nil).count).to eq 0
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 2c9e7013b77..b89b0e555d9 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -625,38 +625,29 @@ describe Ci::Pipeline, :mailer do
shared_context 'with some outdated pipelines' do
before do
- create_pipeline(:canceled, 'ref', 'A')
- create_pipeline(:success, 'ref', 'A')
- create_pipeline(:failed, 'ref', 'B')
- create_pipeline(:skipped, 'feature', 'C')
+ create_pipeline(:canceled, 'ref', 'A', project)
+ create_pipeline(:success, 'ref', 'A', project)
+ create_pipeline(:failed, 'ref', 'B', project)
+ create_pipeline(:skipped, 'feature', 'C', project)
end
- def create_pipeline(status, ref, sha)
- create(:ci_empty_pipeline, status: status, ref: ref, sha: sha)
+ def create_pipeline(status, ref, sha, project)
+ create(
+ :ci_empty_pipeline,
+ status: status,
+ ref: ref,
+ sha: sha,
+ project: project
+ )
end
end
- describe '.latest' do
+ describe '.newest_first' do
include_context 'with some outdated pipelines'
- context 'when no ref is specified' do
- let(:pipelines) { described_class.latest.all }
-
- it 'returns the latest pipeline for the same ref and different sha' do
- expect(pipelines.map(&:sha)).to contain_exactly('A', 'B', 'C')
- expect(pipelines.map(&:status))
- .to contain_exactly('success', 'failed', 'skipped')
- end
- end
-
- context 'when ref is specified' do
- let(:pipelines) { described_class.latest('ref').all }
-
- it 'returns the latest pipeline for ref and different sha' do
- expect(pipelines.map(&:sha)).to contain_exactly('A', 'B')
- expect(pipelines.map(&:status))
- .to contain_exactly('success', 'failed')
- end
+ it 'returns the pipelines from new to old' do
+ expect(described_class.newest_first.pluck(:status))
+ .to eq(%w[skipped failed success canceled])
end
end
@@ -664,20 +655,14 @@ describe Ci::Pipeline, :mailer do
include_context 'with some outdated pipelines'
context 'when no ref is specified' do
- let(:latest_status) { described_class.latest_status }
-
- it 'returns the latest status for the same ref and different sha' do
- expect(latest_status).to eq(described_class.latest.status)
- expect(latest_status).to eq('failed')
+ it 'returns the status of the latest pipeline' do
+ expect(described_class.latest_status).to eq('skipped')
end
end
context 'when ref is specified' do
- let(:latest_status) { described_class.latest_status('ref') }
-
- it 'returns the latest status for ref and different sha' do
- expect(latest_status).to eq(described_class.latest_status('ref'))
- expect(latest_status).to eq('failed')
+ it 'returns the status of the latest pipeline for the given ref' do
+ expect(described_class.latest_status('ref')).to eq('failed')
end
end
end
@@ -686,7 +671,7 @@ describe Ci::Pipeline, :mailer do
include_context 'with some outdated pipelines'
let!(:latest_successful_pipeline) do
- create_pipeline(:success, 'ref', 'D')
+ create_pipeline(:success, 'ref', 'D', project)
end
it 'returns the latest successful pipeline' do
@@ -698,8 +683,13 @@ describe Ci::Pipeline, :mailer do
describe '.latest_successful_for_refs' do
include_context 'with some outdated pipelines'
- let!(:latest_successful_pipeline1) { create_pipeline(:success, 'ref1', 'D') }
- let!(:latest_successful_pipeline2) { create_pipeline(:success, 'ref2', 'D') }
+ let!(:latest_successful_pipeline1) do
+ create_pipeline(:success, 'ref1', 'D', project)
+ end
+
+ let!(:latest_successful_pipeline2) do
+ create_pipeline(:success, 'ref2', 'D', project)
+ end
it 'returns the latest successful pipeline for both refs' do
refs = %w(ref1 ref2 ref3)
@@ -708,6 +698,62 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.latest_status_per_commit' do
+ let(:project) { create(:project) }
+
+ before do
+ pairs = [
+ %w[success ref1 123],
+ %w[manual master 123],
+ %w[failed ref 456]
+ ]
+
+ pairs.each do |(status, ref, sha)|
+ create(
+ :ci_empty_pipeline,
+ status: status,
+ ref: ref,
+ sha: sha,
+ project: project
+ )
+ end
+ end
+
+ context 'without a ref' do
+ it 'returns a Hash containing the latest status per commit for all refs' do
+ expect(described_class.latest_status_per_commit(%w[123 456]))
+ .to eq({ '123' => 'manual', '456' => 'failed' })
+ end
+
+ it 'only includes the status of the given commit SHAs' do
+ expect(described_class.latest_status_per_commit(%w[123]))
+ .to eq({ '123' => 'manual' })
+ end
+
+ context 'when there are two pipelines for a ref and SHA' do
+ it 'returns the status of the latest pipeline' do
+ create(
+ :ci_empty_pipeline,
+ status: 'failed',
+ ref: 'master',
+ sha: '123',
+ project: project
+ )
+
+ expect(described_class.latest_status_per_commit(%w[123]))
+ .to eq({ '123' => 'failed' })
+ end
+ end
+ end
+
+ context 'with a ref' do
+ it 'only includes the pipelines for the given ref' do
+ expect(described_class.latest_status_per_commit(%w[123 456], 'master'))
+ .to eq({ '123' => 'manual' })
+ end
+ end
+ end
+
describe '.internal_sources' do
subject { described_class.internal_sources }
diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb
new file mode 100644
index 00000000000..066fe7d154e
--- /dev/null
+++ b/spec/models/commit_collection_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+
+describe CommitCollection do
+ let(:project) { create(:project, :repository) }
+ let(:commit) { project.commit }
+
+ describe '#each' do
+ it 'yields every commit' do
+ collection = described_class.new(project, [commit])
+
+ expect { |b| collection.each(&b) }.to yield_with_args(commit)
+ end
+ end
+
+ describe '#with_pipeline_status' do
+ it 'sets the pipeline status for every commit so no additional queries are necessary' do
+ create(
+ :ci_empty_pipeline,
+ ref: 'master',
+ sha: commit.id,
+ status: 'success',
+ project: project
+ )
+
+ collection = described_class.new(project, [commit])
+ collection.with_pipeline_status
+
+ recorder = ActiveRecord::QueryRecorder.new do
+ expect(commit.status).to eq('success')
+ end
+
+ expect(recorder.count).to be_zero
+ end
+ end
+
+ describe '#respond_to_missing?' do
+ it 'returns true when the underlying Array responds to the message' do
+ collection = described_class.new(project, [])
+
+ expect(collection.respond_to?(:last)).to eq(true)
+ end
+
+ it 'returns false when the underlying Array does not respond to the message' do
+ collection = described_class.new(project, [])
+
+ expect(collection.respond_to?(:foo)).to eq(false)
+ end
+ end
+
+ describe '#method_missing' do
+ it 'delegates undefined methods to the underlying Array' do
+ collection = described_class.new(project, [commit])
+
+ expect(collection.length).to eq(1)
+ expect(collection.last).to eq(commit)
+ expect(collection).not_to be_empty
+ end
+ end
+end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index e3cfa149e3a..d18a5c9dfa6 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -351,12 +351,19 @@ eos
end
it 'gives compound status from latest pipelines if ref is nil' do
- expect(commit.status(nil)).to eq(Ci::Pipeline.latest_status)
- expect(commit.status(nil)).to eq('failed')
+ expect(commit.status(nil)).to eq(pipeline_from_fix.status)
end
end
end
+ describe '#set_status_for_ref' do
+ it 'sets the status for a given reference' do
+ commit.set_status_for_ref('master', 'failed')
+
+ expect(commit.status('master')).to eq('failed')
+ end
+ end
+
describe '#participants' do
let(:user1) { build(:user) }
let(:user2) { build(:user) }
diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb
index 532ca1fca8c..25bf596fddc 100644
--- a/spec/models/fork_network_member_spec.rb
+++ b/spec/models/fork_network_member_spec.rb
@@ -5,4 +5,22 @@ describe ForkNetworkMember do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:fork_network) }
end
+
+ describe 'destroying a ForkNetworkMember' do
+ let(:fork_network_member) { create(:fork_network_member) }
+ let(:fork_network) { fork_network_member.fork_network }
+
+ it 'removes the fork network if it was the last member' do
+ fork_network.fork_network_members.destroy_all
+
+ expect(ForkNetwork.count).to eq(0)
+ end
+
+ it 'does not destroy the fork network if there are members left' do
+ fork_network_member.destroy!
+
+ # The root of the fork network is left
+ expect(ForkNetwork.count).to eq(1)
+ end
+ end
end
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index 3ed048744de..a45a6088831 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -33,5 +33,15 @@ describe Identity do
expect(identity).to eq(ldap_identity)
end
end
+
+ context 'any other provider' do
+ let!(:test_entity) { create(:identity, provider: 'test_provider', extern_uid: 'test_uid') }
+
+ it 'the extern_uid lookup is case insensitive' do
+ identity = described_class.with_extern_uid('test_provider', 'TEST_UID').first
+
+ expect(identity).to eq(test_entity)
+ end
+ end
end
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index a7227b38850..ea75434e399 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -373,7 +373,7 @@ describe WikiPage do
end
it 'returns commit sha' do
- expect(@page.last_commit_sha).to eq @page.commit.sha
+ expect(@page.last_commit_sha).to eq @page.last_version.sha
end
it 'is changed after page updated' do
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 2459f371a91..2b1337bee7e 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -42,6 +42,18 @@ describe Projects::TransferService do
expect(service).to receive(:execute_system_hooks)
end
end
+
+ it 'disk path has moved' do
+ old_path = project.repository.disk_path
+ old_full_path = project.repository.full_path
+
+ transfer_project(project, user, group)
+
+ expect(project.repository.disk_path).not_to eq(old_path)
+ expect(project.repository.full_path).not_to eq(old_full_path)
+ expect(project.disk_path).not_to eq(old_path)
+ expect(project.disk_path).to start_with(group.path)
+ end
end
context 'when transfer fails' do
@@ -188,6 +200,26 @@ describe Projects::TransferService do
end
end
+ context 'when hashed storage in use' do
+ let(:hashed_project) { create(:project, :repository, :hashed, namespace: user.namespace) }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it 'does not move the directory' do
+ old_path = hashed_project.repository.disk_path
+ old_full_path = hashed_project.repository.full_path
+
+ transfer_project(hashed_project, user, group)
+ project.reload
+
+ expect(hashed_project.repository.disk_path).to eq(old_path)
+ expect(hashed_project.repository.full_path).to eq(old_full_path)
+ expect(hashed_project.disk_path).to eq(old_path)
+ end
+ end
+
describe 'refreshing project authorizations' do
let(:group) { create(:group) }
let(:owner) { project.namespace.owner }