summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClement Ho <clemmakesapps@gmail.com>2018-01-18 18:04:40 +0000
committerClement Ho <clemmakesapps@gmail.com>2018-01-18 18:04:40 +0000
commit088b01d099f6ca90725c752c9eaf331be7f54bb3 (patch)
tree4ed7ba4647f8adf34cb6d797318bcee9ecef6cda
parent93a012810d56d9f962df9516dba5229d065ad3bb (diff)
parent7b0872c7237a21eacd076b09be5712a4ef63b99f (diff)
downloadgitlab-ce-dispatcher-projects-mr-creation-new.tar.gz
Merge branch 'master' into 'dispatcher-projects-mr-creation-new'dispatcher-projects-mr-creation-new
# Conflicts: # app/assets/javascripts/dispatcher.js
-rw-r--r--CHANGELOG.md12
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/boards/utils/query_data.js2
-rw-r--r--app/assets/javascripts/dispatcher.js52
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue47
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/index.js29
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/new/index.js9
-rw-r--r--app/assets/javascripts/pages/projects/project.js (renamed from app/assets/javascripts/project.js)4
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue (renamed from app/assets/javascripts/projects/permissions/components/project_feature_setting.vue)2
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue (renamed from app/assets/javascripts/projects/permissions/components/project_setting_row.vue)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue (renamed from app/assets/javascripts/projects/permissions/components/settings_panel.vue)2
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/constants.js (renamed from app/assets/javascripts/projects/permissions/constants.js)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/external.js (renamed from app/assets/javascripts/projects/permissions/external.js)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/index.js (renamed from app/assets/javascripts/projects/permissions/index.js)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_avatar.js (renamed from app/assets/javascripts/project_avatar.js)0
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_new.js (renamed from app/assets/javascripts/project_new.js)2
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js11
-rw-r--r--app/assets/javascripts/pages/projects/wikis/wikis.js (renamed from app/assets/javascripts/wikis.js)4
-rw-r--r--app/assets/javascripts/pages/search/show/search.js2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue4
-rw-r--r--app/assets/javascripts/projects/project_new.js2
-rw-r--r--app/assets/javascripts/render_mermaid.js6
-rw-r--r--app/assets/javascripts/shortcuts.js6
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js2
-rw-r--r--app/assets/stylesheets/framework/files.scss4
-rw-r--r--app/assets/stylesheets/framework/modal.scss1
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss41
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss2
-rw-r--r--app/controllers/admin/hooks_controller.rb6
-rw-r--r--app/controllers/admin/jobs_controller.rb2
-rw-r--r--app/controllers/projects/commits_controller.rb46
-rw-r--r--app/controllers/projects/hooks_controller.rb10
-rw-r--r--app/finders/labels_finder.rb2
-rw-r--r--app/helpers/issuables_helper.rb6
-rw-r--r--app/models/concerns/triggerable_hooks.rb40
-rw-r--r--app/models/hooks/project_hook.rb26
-rw-r--r--app/models/hooks/system_hook.rb16
-rw-r--r--app/models/project.rb4
-rw-r--r--app/services/system_hooks_service.rb2
-rw-r--r--app/services/test_hooks/base_service.rb2
-rw-r--r--app/services/test_hooks/system_service.rb7
-rw-r--r--app/views/admin/hooks/_form.html.haml7
-rw-r--r--app/views/admin/hooks/edit.html.haml2
-rw-r--r--app/views/admin/hooks/index.html.haml4
-rw-r--r--app/views/admin/jobs/index.html.haml7
-rw-r--r--app/views/profiles/preferences/show.html.haml17
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml35
-rw-r--r--app/views/projects/hooks/edit.html.haml2
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/settings/integrations/_project_hook.html.haml4
-rw-r--r--app/views/shared/issuable/_filter.html.haml2
-rw-r--r--app/views/shared/web_hooks/_form.html.haml2
-rw-r--r--changelogs/unreleased/34252-trailing-plus.yml5
-rw-r--r--changelogs/unreleased/41743-unused-selectors-for-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/feature-merge-request-system-hook.yml5
-rw-r--r--changelogs/unreleased/file-content-large-screen-padding.yml5
-rw-r--r--changelogs/unreleased/issue_37143_2.yml5
-rw-r--r--changelogs/unreleased/sh-fix-mermaid-start-on-load-typo.yml5
-rw-r--r--changelogs/unreleased/winh-search-page-filters.yml5
-rw-r--r--config/webpack.config.js1
-rw-r--r--db/migrate/20170425112128_create_pipeline_schedules_table.rb2
-rw-r--r--db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb48
-rw-r--r--db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb48
-rw-r--r--doc/README.md8
-rw-r--r--doc/api/project_snippets.md9
-rw-r--r--doc/api/system_hooks.md3
-rw-r--r--doc/articles/index.md5
-rw-r--r--doc/ci/examples/test-phoenix-application.md1
-rw-r--r--doc/ci/runners/README.md22
-rw-r--r--doc/install/requirements.md1
-rw-r--r--doc/system_hooks/system_hooks.md129
-rw-r--r--doc/user/admin_area/monitoring/img/convdev_index.pngbin116112 -> 119743 bytes
-rw-r--r--doc/workflow/lfs/img/lfs-icon.pngbin0 -> 4317 bytes
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md5
-rw-r--r--doc/workflow/notifications.md2
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/system_hooks.rb1
-rw-r--r--lib/gitlab/git/commit.rb18
-rw-r--r--lib/gitlab/git/repository.rb27
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb17
-rw-r--r--lib/gitlab/gpg/commit.rb8
-rw-r--r--spec/controllers/admin/hooks_controller_spec.rb6
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb26
-rw-r--r--spec/features/admin/admin_builds_spec.rb16
-rw-r--r--spec/features/admin/admin_hooks_spec.rb65
-rw-r--r--spec/features/boards/boards_spec.rb11
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb12
-rw-r--r--spec/helpers/issuables_helper_spec.rb29
-rw-r--r--spec/javascripts/boards/utils/query_data_spec.js27
-rw-r--r--spec/javascripts/fixtures/search.rb18
-rw-r--r--spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js63
-rw-r--r--spec/javascripts/search_spec.js40
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb78
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb38
-rw-r--r--spec/lib/gitlab/gpg/commit_spec.rb24
-rw-r--r--spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb4
-rw-r--r--spec/migrations/fix_wrongly_renamed_routes_spec.rb65
-rw-r--r--spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb43
-rw-r--r--spec/migrations/update_upload_paths_to_system_spec.rb58
-rw-r--r--spec/models/concerns/triggerable_hooks_spec.rb43
-rw-r--r--spec/models/hooks/system_hook_spec.rb3
-rw-r--r--spec/models/project_spec.rb19
-rw-r--r--spec/requests/api/project_snippets_spec.rb13
-rw-r--r--spec/requests/api/system_hooks_spec.rb20
-rw-r--r--spec/services/test_hooks/system_service_spec.rb20
110 files changed, 1234 insertions, 425 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87260744190..7fbe6e47e24 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.3.5 (2018-01-18)
+
+- No changes.
+
## 10.3.4 (2018-01-10)
### Security (7 changes, 1 of them is from the community)
@@ -193,6 +197,10 @@ entry.
- Clean up schema of the "merge_requests" table.
+## 10.2.7 (2018-01-18)
+
+- No changes.
+
## 10.2.6 (2018-01-11)
### Security (9 changes, 1 of them is from the community)
@@ -474,6 +482,10 @@ entry.
- Add Gitaly metrics to the performance bar.
+## 10.1.7 (2018-01-18)
+
+- No changes.
+
## 10.1.6 (2018-01-11)
### Security (8 changes, 1 of them is from the community)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 106d4ac0005..534b316aef6 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.69.0
+0.70.0
diff --git a/app/assets/javascripts/boards/utils/query_data.js b/app/assets/javascripts/boards/utils/query_data.js
index 2cd3c146f11..65315979df7 100644
--- a/app/assets/javascripts/boards/utils/query_data.js
+++ b/app/assets/javascripts/boards/utils/query_data.js
@@ -5,7 +5,7 @@ export default (path, extraData) => path.split('&').reduce((dataParam, filterPar
const paramSplit = filterParam.split('=');
const paramKeyNormalized = paramSplit[0].replace('[]', '');
const isArray = paramSplit[0].indexOf('[]');
- const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' ');
+ const value = decodeURIComponent(paramSplit[1].replace(/\+/g, ' '));
if (isArray !== -1) {
if (!data[paramKeyNormalized]) {
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 71ce8955033..158a4a994dd 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -9,10 +9,7 @@ import notificationsDropdown from './notifications_dropdown';
import groupAvatar from './group_avatar';
import GroupLabelSubscription from './group_label_subscription';
import LineHighlighter from './line_highlighter';
-import Project from './project';
-import projectAvatar from './project_avatar';
import MergeRequest from './merge_request';
-import ProjectNew from './project_new';
import Labels from './labels';
import LabelManager from './label_manager';
import Sidebar from './right_sidebar';
@@ -23,19 +20,16 @@ import SecretValues from './behaviors/secret_values';
import Group from './group';
import ProjectsList from './projects_list';
import UserCallout from './user_callout';
-import ShortcutsWiki from './shortcuts_wiki';
import BlobViewer from './blob/viewer/index';
import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
import Star from './star';
import TreeView from './tree';
-import Wikis from './wikis';
import ZenMode from './zen_mode';
import initSettingsPanels from './settings_panels';
import PerformanceBar from './performance_bar';
import initNotes from './init_notes';
import initIssuableSidebar from './init_issuable_sidebar';
-import initProjectVisibilitySelector from './project_visibility';
import NewGroupChild from './groups/new_group_child';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
import GlFieldErrors from './gl_field_errors';
@@ -156,6 +150,11 @@ import Activities from './activities';
case 'dashboard:todos:index':
import('./pages/dashboard/todos/index').then(callDefault).catch(fail);
break;
+ case 'admin:jobs:index':
+ import('./pages/admin/jobs/index')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'dashboard:projects:index':
case 'dashboard:projects:starred':
import('./pages/dashboard/projects')
@@ -295,7 +294,6 @@ import Activities from './activities';
window.mergeRequest = new MergeRequest({
action: mrShowNode.dataset.mrAction,
});
-
shortcut_handler = new ShortcutsIssuable(true);
break;
case 'dashboard:activity':
@@ -631,57 +629,39 @@ import Activities from './activities';
.catch(fail);
break;
case 'projects':
- new Project();
- projectAvatar();
+ import('./pages/projects')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
switch (path[1]) {
case 'compare':
import('./pages/projects/compare')
.then(callDefault)
.catch(fail);
break;
- case 'edit':
- shortcut_handler = new ShortcutsNavigation();
- new ProjectNew();
- import(/* webpackChunkName: 'project_permissions' */ './projects/permissions')
+ case 'create':
+ case 'new':
+ import('./pages/projects/new')
.then(callDefault)
.catch(fail);
break;
- case 'new':
- new ProjectNew();
- initProjectVisibilitySelector();
- break;
case 'show':
new Star();
- new ProjectNew();
notificationsDropdown();
break;
case 'wikis':
- new Wikis();
- shortcut_handler = new ShortcutsWiki();
- new ZenMode();
- new GLForm($('.wiki-form'), true);
+ import('./pages/projects/wikis')
+ .then(callDefault)
+ .catch(fail);
+ shortcut_handler = true;
break;
case 'snippets':
- shortcut_handler = new ShortcutsNavigation();
if (path[2] === 'show') {
new ZenMode();
new LineHighlighter();
new BlobViewer();
}
break;
- case 'labels':
- case 'graphs':
- case 'compare':
- case 'pipelines':
- case 'forks':
- case 'milestones':
- case 'project_members':
- case 'deploy_keys':
- case 'builds':
- case 'hooks':
- case 'services':
- case 'protected_branches':
- shortcut_handler = new ShortcutsNavigation();
}
break;
}
diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
new file mode 100644
index 00000000000..555725cbe12
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
@@ -0,0 +1,47 @@
+<script>
+ import axios from '~/lib/utils/axios_utils';
+ import Flash from '~/flash';
+ import modal from '~/vue_shared/components/modal.vue';
+ import { s__ } from '~/locale';
+ import { redirectTo } from '~/lib/utils/url_utility';
+
+ export default {
+ components: {
+ modal,
+ },
+ props: {
+ url: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ text() {
+ return s__('AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running.');
+ },
+ },
+ methods: {
+ onSubmit() {
+ return axios.post(this.url)
+ .then((response) => {
+ // follow the rediect to refresh the page
+ redirectTo(response.request.responseURL);
+ })
+ .catch((error) => {
+ Flash(s__('AdminArea|Stopping jobs failed'));
+ throw error;
+ });
+ },
+ },
+ };
+</script>
+
+<template>
+ <modal
+ id="stop-jobs-modal"
+ :title="s__('AdminArea|Stop all jobs?')"
+ :text="text"
+ kind="danger"
+ :primary-button-label="s__('AdminArea|Stop jobs')"
+ @submit="onSubmit" />
+</template>
diff --git a/app/assets/javascripts/pages/admin/jobs/index/index.js b/app/assets/javascripts/pages/admin/jobs/index/index.js
new file mode 100644
index 00000000000..0e004bd9174
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/jobs/index/index.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+
+import Translate from '~/vue_shared/translate';
+
+import stopJobsModal from './components/stop_jobs_modal.vue';
+
+Vue.use(Translate);
+
+export default () => {
+ const stopJobsButton = document.getElementById('stop-jobs-button');
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#stop-jobs-modal',
+ components: {
+ stopJobsModal,
+ },
+ mounted() {
+ stopJobsButton.classList.remove('disabled');
+ },
+ render(createElement) {
+ return createElement('stop-jobs-modal', {
+ props: {
+ url: stopJobsButton.dataset.url,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 7f662ef6b6a..9edf36d66b1 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -1,8 +1,14 @@
import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
+import ProjectNew from '../shared/project_new';
+import projectAvatar from '../shared/project_avatar';
+import initProjectPermissionsSettings from '../shared/permissions';
export default () => {
+ new ProjectNew(); // eslint-disable-line no-new
setupProjectEdit();
// Initialize expandable settings panels
initSettingsPanels();
+ projectAvatar();
+ initProjectPermissionsSettings();
};
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
new file mode 100644
index 00000000000..9b1d52692a3
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -0,0 +1,7 @@
+import Project from './project';
+import ShortcutsNavigation from '../../shortcuts_navigation';
+
+export default () => {
+ new Project(); // eslint-disable-line no-new
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/pages/projects/new/index.js b/app/assets/javascripts/pages/projects/new/index.js
new file mode 100644
index 00000000000..71c49deb9d0
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/new/index.js
@@ -0,0 +1,9 @@
+import ProjectNew from '../shared/project_new';
+import initProjectVisibilitySelector from '../../../project_visibility';
+import initProjectNew from '../../../projects/project_new';
+
+export default () => {
+ new ProjectNew(); // eslint-disable-line no-new
+ initProjectVisibilitySelector();
+ initProjectNew.bindEvents();
+};
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/pages/projects/project.js
index d4f26b81f30..e30d558726b 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -1,8 +1,8 @@
/* 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 */
import Cookies from 'js-cookie';
-import { visitUrl } from './lib/utils/url_utility';
-import projectSelect from './project_select';
+import { visitUrl } from '../../lib/utils/url_utility';
+import projectSelect from '../../project_select';
export default class Project {
constructor() {
diff --git a/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
index 3ebfe82597a..9b13b2a524f 100644
--- a/app/assets/javascripts/projects/permissions/components/project_feature_setting.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
@@ -1,5 +1,5 @@
<script>
- import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
+ import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
export default {
components: {
diff --git a/app/assets/javascripts/projects/permissions/components/project_setting_row.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
index 25a88f846eb..25a88f846eb 100644
--- a/app/assets/javascripts/projects/permissions/components/project_setting_row.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
diff --git a/app/assets/javascripts/projects/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index c96ce12d9fb..380c6c3ea7d 100644
--- a/app/assets/javascripts/projects/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -1,6 +1,6 @@
<script>
import projectFeatureSetting from './project_feature_setting.vue';
- import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
+ import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
import projectSettingRow from './project_setting_row.vue';
import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
import { toggleHiddenClassBySelector } from '../external';
diff --git a/app/assets/javascripts/projects/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
index ce47562f259..ce47562f259 100644
--- a/app/assets/javascripts/projects/permissions/constants.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
diff --git a/app/assets/javascripts/projects/permissions/external.js b/app/assets/javascripts/pages/projects/shared/permissions/external.js
index 460af4a2111..460af4a2111 100644
--- a/app/assets/javascripts/projects/permissions/external.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/external.js
diff --git a/app/assets/javascripts/projects/permissions/index.js b/app/assets/javascripts/pages/projects/shared/permissions/index.js
index dbde8dda634..dbde8dda634 100644
--- a/app/assets/javascripts/projects/permissions/index.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/index.js
diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/pages/projects/shared/project_avatar.js
index 56627aa155c..56627aa155c 100644
--- a/app/assets/javascripts/project_avatar.js
+++ b/app/assets/javascripts/pages/projects/shared/project_avatar.js
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/pages/projects/shared/project_new.js
index ca548d011b6..86faba0b910 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/pages/projects/shared/project_new.js
@@ -1,6 +1,6 @@
/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
-import VisibilitySelect from './visibility_select';
+import VisibilitySelect from '../../../visibility_select';
function highlightChanges($elm) {
$elm.addClass('highlight-changes');
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
new file mode 100644
index 00000000000..eb14c7a0e78
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -0,0 +1,11 @@
+import Wikis from './wikis';
+import ShortcutsWiki from '../../../shortcuts_wiki';
+import ZenMode from '../../../zen_mode';
+import GLForm from '../../../gl_form';
+
+export default () => {
+ new Wikis(); // eslint-disable-line no-new
+ new ShortcutsWiki(); // eslint-disable-line no-new
+ new ZenMode(); // eslint-disable-line no-new
+ new GLForm($('.wiki-form'), true); // eslint-disable-line no-new
+};
diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js
index 7a865587444..34a12ef76a1 100644
--- a/app/assets/javascripts/wikis.js
+++ b/app/assets/javascripts/pages/projects/wikis/wikis.js
@@ -1,5 +1,5 @@
-import bp from './breakpoints';
-import { slugify } from './lib/utils/text_utility';
+import bp from '../../../breakpoints';
+import { slugify } from '../../../lib/utils/text_utility';
export default class Wikis {
constructor() {
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index d44195f6b72..dc621bc87c0 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -15,6 +15,7 @@ export default class Search {
$groupDropdown.glDropdown({
selectable: true,
filterable: true,
+ filterRemote: true,
fieldName: 'group_id',
search: {
fields: ['full_name'],
@@ -43,6 +44,7 @@ export default class Search {
$projectDropdown.glDropdown({
selectable: true,
filterable: true,
+ filterRemote: true,
fieldName: 'project_id',
search: {
fields: ['name'],
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index c6638cdcf1e..6681b89e629 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -50,13 +50,13 @@
Pipeline
</div>
<div
- class="table-section section-25 js-pipeline-commit pipeline-commit"
+ class="table-section section-20 js-pipeline-commit pipeline-commit"
role="rowheader"
>
Commit
</div>
<div
- class="table-section section-15 js-pipeline-stages pipeline-stages"
+ class="table-section section-20 js-pipeline-stages pipeline-stages"
role="rowheader"
>
Stages
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index d87e24cc8a7..d0e4cf7ff40 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -239,7 +239,7 @@
:auto-devops-help-path="autoDevopsHelpPath"
/>
- <div class="table-section section-25">
+ <div class="table-section section-20">
<div
class="table-mobile-header"
role="rowheader">
@@ -258,7 +258,7 @@
</div>
</div>
- <div class="table-section section-wrap section-15 stage-cell">
+ <div class="table-section section-wrap section-20 stage-cell">
<div
class="table-mobile-header"
role="rowheader">
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 4710e70d619..f5133111d04 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -100,8 +100,6 @@ const bindEvents = () => {
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
};
-document.addEventListener('DOMContentLoaded', bindEvents);
-
export default {
bindEvents,
deriveProjectPathFromUrl,
diff --git a/app/assets/javascripts/render_mermaid.js b/app/assets/javascripts/render_mermaid.js
index b7cde6fb092..31c7a772cf4 100644
--- a/app/assets/javascripts/render_mermaid.js
+++ b/app/assets/javascripts/render_mermaid.js
@@ -19,7 +19,11 @@ export default function renderMermaid($els) {
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
mermaid.initialize({
- loadOnStart: false,
+ // mermaid core options
+ mermaid: {
+ startOnLoad: false,
+ },
+ // mermaidAPI options
theme: 'neutral',
});
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index 5351873e945..cd5ab53eace 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -13,12 +13,10 @@ Mousetrap.stopCallback = (e, element, combo) => {
};
export default class Shortcuts {
- constructor(skipResetBindings) {
+ constructor() {
this.onToggleHelp = this.onToggleHelp.bind(this);
this.enabledHelp = [];
- if (!skipResetBindings) {
- Mousetrap.reset();
- }
+
Mousetrap.bind('?', this.onToggleHelp);
Mousetrap.bind('s', Shortcuts.focusSearch);
Mousetrap.bind('f', this.focusFilter.bind(this));
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index 6aeae84cdc5..689befc742e 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -1,10 +1,10 @@
import Mousetrap from 'mousetrap';
import _ from 'underscore';
import Sidebar from './right_sidebar';
-import ShortcutsNavigation from './shortcuts_navigation';
+import Shortcuts from './shortcuts';
import { CopyAsGFM } from './behaviors/copy_as_gfm';
-export default class ShortcutsIssuable extends ShortcutsNavigation {
+export default class ShortcutsIssuable extends Shortcuts {
constructor(isMergeRequest) {
super();
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index 2075f8e4fec..98d33f9efaa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -1,4 +1,4 @@
-import Project from '~/project';
+import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import Flash from '../flash';
import {
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 1e91db5af9b..d835d49d8b2 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -124,6 +124,10 @@
&.wiki {
padding: $gl-padding;
+
+ @media (min-width: $screen-md-min) {
+ padding: $gl-padding * 2;
+ }
}
&.blob-no-preview {
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 924472f2d7e..51ae09777fd 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -12,6 +12,7 @@
min-height: $modal-body-height;
position: relative;
padding: #{3 * $grid-size} #{2 * $grid-size};
+ text-align: left;
.form-actions {
margin: #{2 * $grid-size} #{-2 * $grid-size} #{-2 * $grid-size};
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 3b35beb7695..cfef6476d4d 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -117,47 +117,6 @@
top: $gl-padding-top;
}
- .content-list {
- li {
- padding: 18px $gl-padding $gl-padding;
-
- .container-fluid {
- padding: 0;
- }
- }
-
- .title-col {
- p {
- margin: 0;
-
- &.title {
- line-height: 19px;
- font-size: 14px;
- font-weight: $gl-font-weight-bold;
- color: $gl-text-color;
- }
-
- &.text {
- color: $layout-link-gray;
-
- &.value-col {
- color: $gl-text-color;
- }
- }
- }
- }
-
- .value-col {
- text-align: right;
-
- span {
- position: relative;
- vertical-align: middle;
- top: 3px;
- }
- }
- }
-
.fa-spinner {
font-size: 28px;
position: relative;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 766e02b12ea..db88d4a16b7 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -228,7 +228,7 @@
.stage-cell {
&.table-section {
@media (min-width: $screen-md-min) {
- min-width: 148px;
+ min-width: 160px; /* Hack alert: Without this the mini graph pipeline won't work properly*/
margin-right: -4px;
}
}
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index 77e3c95d197..2b47819303e 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -59,11 +59,9 @@ class Admin::HooksController < Admin::ApplicationController
def hook_params
params.require(:hook).permit(
:enable_ssl_verification,
- :push_events,
- :tag_push_events,
- :repository_update_events,
:token,
- :url
+ :url,
+ *SystemHook.triggers.values
)
end
end
diff --git a/app/controllers/admin/jobs_controller.rb b/app/controllers/admin/jobs_controller.rb
index 5162273ef8a..ae7a7f6279c 100644
--- a/app/controllers/admin/jobs_controller.rb
+++ b/app/controllers/admin/jobs_controller.rb
@@ -20,6 +20,6 @@ class Admin::JobsController < Admin::ApplicationController
def cancel_all
Ci::Build.running_or_pending.each(&:cancel)
- redirect_to admin_jobs_path
+ redirect_to admin_jobs_path, status: 303
end
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 026708169f4..0a40c67368f 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -13,31 +13,37 @@ class Projects::CommitsController < Projects::ApplicationController
@merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened
.find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
- respond_to do |format|
- format.html
- format.atom { render layout: 'xml.atom' }
-
- format.json do
- pager_json(
- 'projects/commits/_commits',
- @commits.size,
- project: @project,
- ref: @ref)
+ # https://gitlab.com/gitlab-org/gitaly/issues/931
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ respond_to do |format|
+ format.html
+ format.atom { render layout: 'xml.atom' }
+
+ format.json do
+ pager_json(
+ 'projects/commits/_commits',
+ @commits.size,
+ project: @project,
+ ref: @ref)
+ end
end
end
end
def signatures
- respond_to do |format|
- format.json do
- render json: {
- signatures: @commits.select(&:has_signature?).map do |commit|
- {
- commit_sha: commit.sha,
- html: view_to_html_string('projects/commit/_signature', signature: commit.signature)
- }
- end
- }
+ # https://gitlab.com/gitlab-org/gitaly/issues/931
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ respond_to do |format|
+ format.json do
+ render json: {
+ signatures: @commits.select(&:has_signature?).map do |commit|
+ {
+ commit_sha: commit.sha,
+ html: view_to_html_string('projects/commit/_signature', signature: commit.signature)
+ }
+ end
+ }
+ end
end
end
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 6f51e7b9b40..dd7aa1a67b9 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -64,18 +64,10 @@ class Projects::HooksController < Projects::ApplicationController
def hook_params
params.require(:hook).permit(
- :job_events,
- :pipeline_events,
:enable_ssl_verification,
- :issues_events,
- :confidential_issues_events,
- :merge_requests_events,
- :note_events,
- :push_events,
- :tag_push_events,
:token,
:url,
- :wiki_page_events
+ *ProjectHook.triggers.values
)
end
end
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 6de9eb89468..1427cdaa382 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -71,7 +71,7 @@ class LabelsFinder < UnionFinder
end
def projects?
- params[:project_ids].present?
+ params[:project_ids]
end
def only_group_labels?
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 2668cf78afe..520a875238c 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -304,6 +304,12 @@ module IssuablesHelper
issuable.model_name.human.downcase
end
+ def selected_labels
+ Array(params[:label_name]).map do |label_name|
+ Label.new(title: label_name)
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/models/concerns/triggerable_hooks.rb b/app/models/concerns/triggerable_hooks.rb
new file mode 100644
index 00000000000..ec0ed3b795a
--- /dev/null
+++ b/app/models/concerns/triggerable_hooks.rb
@@ -0,0 +1,40 @@
+module TriggerableHooks
+ AVAILABLE_TRIGGERS = {
+ repository_update_hooks: :repository_update_events,
+ push_hooks: :push_events,
+ tag_push_hooks: :tag_push_events,
+ issue_hooks: :issues_events,
+ confidential_issue_hooks: :confidential_issues_events,
+ note_hooks: :note_events,
+ merge_request_hooks: :merge_requests_events,
+ job_hooks: :job_events,
+ pipeline_hooks: :pipeline_events,
+ wiki_page_hooks: :wiki_page_events
+ }.freeze
+
+ extend ActiveSupport::Concern
+
+ class_methods do
+ attr_reader :triggerable_hooks
+
+ attr_reader :triggers
+
+ def hooks_for(trigger)
+ callable_scopes = triggers.keys + [:all]
+ return none unless callable_scopes.include?(trigger)
+
+ public_send(trigger) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ private
+
+ def triggerable_hooks(hooks)
+ triggers = AVAILABLE_TRIGGERS.slice(*hooks)
+ @triggers = triggers
+
+ triggers.each do |trigger, event|
+ scope trigger, -> { where(event => true) }
+ end
+ end
+ end
+end
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index a8c424a6614..b6dd39b860b 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -1,19 +1,17 @@
class ProjectHook < WebHook
- TRIGGERS = {
- push_hooks: :push_events,
- tag_push_hooks: :tag_push_events,
- issue_hooks: :issues_events,
- confidential_issue_hooks: :confidential_issues_events,
- note_hooks: :note_events,
- merge_request_hooks: :merge_requests_events,
- job_hooks: :job_events,
- pipeline_hooks: :pipeline_events,
- wiki_page_hooks: :wiki_page_events
- }.freeze
+ include TriggerableHooks
- TRIGGERS.each do |trigger, event|
- scope trigger, -> { where(event => true) }
- end
+ triggerable_hooks [
+ :push_hooks,
+ :tag_push_hooks,
+ :issue_hooks,
+ :confidential_issue_hooks,
+ :note_hooks,
+ :merge_request_hooks,
+ :job_hooks,
+ :pipeline_hooks,
+ :wiki_page_hooks
+ ]
belongs_to :project
validates :project, presence: true
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 180c479c41b..0528266e5b3 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -1,14 +1,14 @@
class SystemHook < WebHook
- TRIGGERS = {
- repository_update_hooks: :repository_update_events,
- push_hooks: :push_events,
- tag_push_hooks: :tag_push_events
- }.freeze
+ include TriggerableHooks
- TRIGGERS.each do |trigger, event|
- scope trigger, -> { where(event => true) }
- end
+ triggerable_hooks [
+ :repository_update_hooks,
+ :push_hooks,
+ :tag_push_hooks,
+ :merge_request_hooks
+ ]
default_value_for :push_events, false
default_value_for :repository_update_events, true
+ default_value_for :merge_requests_events, false
end
diff --git a/app/models/project.rb b/app/models/project.rb
index d011b614c69..4017864f718 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -967,10 +967,12 @@ class Project < ActiveRecord::Base
def execute_hooks(data, hooks_scope = :push_hooks)
run_after_commit_or_now do
- hooks.public_send(hooks_scope).each do |hook| # rubocop:disable GitlabSecurity/PublicSend
+ hooks.hooks_for(hooks_scope).each do |hook|
hook.async_execute(data, hooks_scope.to_s)
end
end
+
+ SystemHooksService.new.execute_hooks(data, hooks_scope)
end
def execute_services(data, hooks_scope = :push_hooks)
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index af6d77ef5e8..a6b7a6e1416 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -8,7 +8,7 @@ class SystemHooksService
end
def execute_hooks(data, hooks_scope = :all)
- SystemHook.public_send(hooks_scope).find_each do |hook| # rubocop:disable GitlabSecurity/PublicSend
+ SystemHook.hooks_for(hooks_scope).find_each do |hook|
hook.async_execute(data, 'system_hooks')
end
end
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
index 20d90504bd2..e9aefb1fb75 100644
--- a/app/services/test_hooks/base_service.rb
+++ b/app/services/test_hooks/base_service.rb
@@ -9,7 +9,7 @@ module TestHooks
end
def execute
- trigger_key = hook.class::TRIGGERS.key(trigger.to_sym)
+ trigger_key = hook.class.triggers.key(trigger.to_sym)
trigger_data_method = "#{trigger}_data"
if trigger_key.nil? || !self.respond_to?(trigger_data_method, true)
diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb
index 67552edefc9..9016c77b7f0 100644
--- a/app/services/test_hooks/system_service.rb
+++ b/app/services/test_hooks/system_service.rb
@@ -13,5 +13,12 @@ module TestHooks
def repository_update_events_data
Gitlab::DataBuilder::Repository.sample_data
end
+
+ def merge_requests_events_data
+ merge_request = MergeRequest.of_projects(current_user.projects.select(:id)).first
+ throw(:validation_error, 'Ensure one of your projects has merge requests.') unless merge_request.present?
+
+ merge_request.to_hook_data(current_user)
+ end
end
end
diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml
index 645005c6deb..d8f96ed5b0d 100644
--- a/app/views/admin/hooks/_form.html.haml
+++ b/app/views/admin/hooks/_form.html.haml
@@ -38,6 +38,13 @@
%strong Tag push events
%p.light
This URL will be triggered when a new tag is pushed to the repository
+ %div
+ = form.check_box :merge_requests_events, class: 'pull-left'
+ .prepend-left-20
+ = form.label :merge_requests_events, class: 'list-label' do
+ %strong Merge request events
+ %p.light
+ This URL will be triggered when a merge request is created/updated/merged
.form-group
= form.label :enable_ssl_verification, 'SSL verification', class: 'control-label checkbox'
.col-sm-10
diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml
index efb15ccc8df..629b1a9940f 100644
--- a/app/views/admin/hooks/edit.html.haml
+++ b/app/views/admin/hooks/edit.html.haml
@@ -13,7 +13,7 @@
= render partial: 'form', locals: { form: f, hook: @hook }
.form-actions
= f.submit 'Save changes', class: 'btn btn-create'
- = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: @hook
+ = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: @hook
= link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index b6e1df5f3ac..bc02d9969d6 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -22,12 +22,12 @@
- @hooks.each do |hook|
%li
.controls
- = render 'shared/web_hooks/test_button', triggers: SystemHook::TRIGGERS, hook: hook, button_class: 'btn-sm'
+ = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: hook, button_class: 'btn-sm'
= link_to 'Edit', edit_admin_hook_path(hook), class: 'btn btn-sm'
= link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-remove btn-sm'
.monospace= hook.url
%div
- - SystemHook::TRIGGERS.each_value do |event|
+ - SystemHook.triggers.each_value do |event|
- if hook.public_send(event)
%span.label.label-gray= event.to_s.titleize
%span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index 7066ed12b95..a01676d82a8 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -9,7 +9,12 @@
.nav-controls
- if @all_builds.running_or_pending.any?
- = link_to 'Cancel all', cancel_all_admin_jobs_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
+ #stop-jobs-modal
+
+ %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal',
+ target: '#stop-jobs-modal',
+ url: cancel_all_admin_jobs_path } }
+ = s_('AdminArea|Stop all jobs')
.row-content-block.second-block
#{(@scope || 'all').capitalize} jobs
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index aeae7455a1c..66d1d1e8d44 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -3,23 +3,6 @@
= render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
- .col-lg-4
- %h4.prepend-top-0
- Web IDE (Beta)
- %p Enable the new web IDE on this device to make it possible to open and edit multiple files with a single commit
- .col-lg-8.multi-file-editor-options
- = label_tag do
- .preview.append-bottom-10= image_tag "multi-editor-off.png"
- = f.radio_button :multi_file, "off", checked: true
- Off
- = label_tag do
- .preview.append-bottom-10= image_tag "multi-editor-on.png"
- = f.radio_button :multi_file, "on", checked: false
- On
-
- .col-sm-12
- %hr
-
.col-lg-4.application-theme
%h4.prepend-top-0
GitLab navigation theme
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index a94d9c14722..dab94d10bb1 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -9,34 +9,27 @@
- can_create_snippet = can?(current_user, :create_snippet, @project)
- if can_create_issue
- %li
- = link_to _('New issue'), new_project_issue_path(@project)
+ %li= link_to _('New issue'), new_project_issue_path(@project)
+
- if merge_project
- %li
- = link_to _('New merge request'), project_new_merge_request_path(merge_project)
+ %li= link_to _('New merge request'), project_new_merge_request_path(merge_project)
+
- if can_create_snippet
- %li
- = link_to _('New snippet'), new_project_snippet_path(@project)
+ %li= link_to _('New snippet'), new_project_snippet_path(@project)
- if can_create_issue || merge_project || can_create_snippet
%li.divider
- if can?(current_user, :push_code, @project)
- %li
- = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
+ %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- unless @project.empty_repo?
- %li
- = link_to _('New branch'), new_project_branch_path(@project)
- %li
- = link_to _('New tag'), new_project_tag_path(@project)
+ %li= link_to _('New branch'), new_project_branch_path(@project)
+ %li= link_to _('New tag'), new_project_tag_path(@project)
- elsif current_user && current_user.already_forked?(@project)
- %li
- = link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
+ %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- elsif can?(current_user, :fork_project, @project)
- %li
- - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
- notice: edit_in_new_fork_notice,
- notice_now: edit_in_new_fork_notice_now }
- - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id,
- continue: continue_params)
- = link_to _('New file'), fork_path, method: :post
+ - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_params)
+ %li= link_to _('New file'), fork_path, method: :post
diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml
index b1219f019d7..dcc1f0e3fbe 100644
--- a/app/views/projects/hooks/edit.html.haml
+++ b/app/views/projects/hooks/edit.html.haml
@@ -12,7 +12,7 @@
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
= f.submit 'Save changes', class: 'btn btn-create'
- = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: @hook
+ = render 'shared/web_hooks/test_button', triggers: ProjectHook.triggers, hook: @hook
= link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' }
%hr
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 2f56630c22e..61ae0ebbce6 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -4,8 +4,6 @@
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
- visibility_level = params.dig(:project, :visibility_level) || default_project_visibility
-- content_for :page_specific_javascripts do
- = webpack_bundle_tag 'project_new'
.project-edit-container
.project-edit-errors
diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml
index 82516cb4bcf..cd003107d66 100644
--- a/app/views/projects/settings/integrations/_project_hook.html.haml
+++ b/app/views/projects/settings/integrations/_project_hook.html.haml
@@ -3,14 +3,14 @@
.col-md-8.col-lg-7
%strong.light-header= hook.url
%div
- - ProjectHook::TRIGGERS.each_value do |event|
+ - ProjectHook.triggers.each_value do |event|
- if hook.public_send(event)
%span.label.label-gray.deploy-project-label= event.to_s.titleize
.col-md-4.col-lg-5.text-right-lg.prepend-top-5
%span.append-right-10.inline
SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'}
= link_to 'Edit', edit_project_hook_path(@project, hook), class: 'btn btn-sm'
- = render 'shared/web_hooks/test_button', triggers: ProjectHook::TRIGGERS, hook: hook, button_class: 'btn-sm'
+ = render 'shared/web_hooks/test_button', triggers: ProjectHook.triggers, hook: hook, button_class: 'btn-sm'
= link_to project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-transparent' do
%span.sr-only Remove
= icon('trash')
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 8442d7ff4a2..7704c88905b 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -22,7 +22,7 @@
= render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true, show_started: true
.filter-item.inline.labels-filter
- = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
+ = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
- if issuable_filter_present?
.filter-item.inline.reset-filters
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 1f0e7629fb4..ad4d39b4aa1 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -50,7 +50,7 @@
= form.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20
= form.label :merge_requests_events, class: 'list-label' do
- %strong Merge Request events
+ %strong Merge request events
%p.light
This URL will be triggered when a merge request is created/updated/merged
%li
diff --git a/changelogs/unreleased/34252-trailing-plus.yml b/changelogs/unreleased/34252-trailing-plus.yml
new file mode 100644
index 00000000000..fce17cb6ab9
--- /dev/null
+++ b/changelogs/unreleased/34252-trailing-plus.yml
@@ -0,0 +1,5 @@
+---
+title: Allow trailing + on labels in board filters
+merge_request: 16490
+author:
+type: fixed
diff --git a/changelogs/unreleased/41743-unused-selectors-for-cycle-analytics.yml b/changelogs/unreleased/41743-unused-selectors-for-cycle-analytics.yml
new file mode 100644
index 00000000000..03060c357fe
--- /dev/null
+++ b/changelogs/unreleased/41743-unused-selectors-for-cycle-analytics.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unused CSS selectors for Cycle Analytics
+merge_request: 16270
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/feature-merge-request-system-hook.yml b/changelogs/unreleased/feature-merge-request-system-hook.yml
new file mode 100644
index 00000000000..cfc4c4235d6
--- /dev/null
+++ b/changelogs/unreleased/feature-merge-request-system-hook.yml
@@ -0,0 +1,5 @@
+---
+title: System hooks for Merge Requests
+merge_request: 14387
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/file-content-large-screen-padding.yml b/changelogs/unreleased/file-content-large-screen-padding.yml
new file mode 100644
index 00000000000..5691cd09b1f
--- /dev/null
+++ b/changelogs/unreleased/file-content-large-screen-padding.yml
@@ -0,0 +1,5 @@
+---
+title: Double padding for file-content wiki class on larger screens
+merge_request:
+author:
+type: changed
diff --git a/changelogs/unreleased/issue_37143_2.yml b/changelogs/unreleased/issue_37143_2.yml
new file mode 100644
index 00000000000..38125f666b2
--- /dev/null
+++ b/changelogs/unreleased/issue_37143_2.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unecessary query from labels filter
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-fix-mermaid-start-on-load-typo.yml b/changelogs/unreleased/sh-fix-mermaid-start-on-load-typo.yml
new file mode 100644
index 00000000000..a2d4ade8e54
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-mermaid-start-on-load-typo.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Mermaid drawings not loading on some browsers
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/winh-search-page-filters.yml b/changelogs/unreleased/winh-search-page-filters.yml
new file mode 100644
index 00000000000..90c5cd8d818
--- /dev/null
+++ b/changelogs/unreleased/winh-search-page-filters.yml
@@ -0,0 +1,5 @@
+---
+title: Filter groups and projects dropdowns of search page on backend
+merge_request: 16336
+author:
+type: fixed
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 95fa79990e2..23784dd2a29 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -66,7 +66,6 @@ var config = {
pipelines_times: './pipelines/pipelines_times.js',
profile: './profile/profile_bundle.js',
project_import_gl: './projects/project_import_gitlab_project.js',
- project_new: './projects/project_new.js',
prometheus_metrics: './prometheus_metrics',
protected_branches: './protected_branches',
protected_tags: './protected_tags',
diff --git a/db/migrate/20170425112128_create_pipeline_schedules_table.rb b/db/migrate/20170425112128_create_pipeline_schedules_table.rb
index 57df47f5f42..4f9c56a1ad8 100644
--- a/db/migrate/20170425112128_create_pipeline_schedules_table.rb
+++ b/db/migrate/20170425112128_create_pipeline_schedules_table.rb
@@ -17,7 +17,7 @@ class CreatePipelineSchedulesTable < ActiveRecord::Migration
t.boolean :active, default: true
t.datetime :deleted_at
- t.timestamps
+ t.timestamps null: true
end
add_index(:ci_pipeline_schedules, :project_id)
diff --git a/db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb b/db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb
index ec0427a8e5a..680855af945 100644
--- a/db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb
+++ b/db/migrate/20171215113714_populate_can_push_from_deploy_keys_projects.rb
@@ -6,6 +6,8 @@ class PopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
+ DATABASE_NAME = Gitlab::Database.database_name
+
disable_ddl_transaction!
class DeploysKeyProject < ActiveRecord::Base
@@ -18,13 +20,22 @@ class PopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
DeploysKeyProject.each_batch(of: 10_000) do |batch|
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
- execute <<-EOF
- UPDATE deploy_keys_projects
- SET can_push = keys.can_push
- FROM keys
- WHERE deploy_key_id = keys.id
- AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
- EOF
+ if Gitlab::Database.mysql?
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects, #{DATABASE_NAME}.keys
+ SET deploy_keys_projects.can_push = #{DATABASE_NAME}.keys.can_push
+ WHERE deploy_keys_projects.deploy_key_id = #{DATABASE_NAME}.keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ else
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects
+ SET can_push = keys.can_push
+ FROM keys
+ WHERE deploy_key_id = keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ end
end
end
@@ -32,13 +43,22 @@ class PopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
DeploysKeyProject.each_batch(of: 10_000) do |batch|
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
- execute <<-EOF
- UPDATE keys
- SET can_push = deploy_keys_projects.can_push
- FROM deploy_keys_projects
- WHERE deploy_keys_projects.deploy_key_id = keys.id
- AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
- EOF
+ if Gitlab::Database.mysql?
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects, #{DATABASE_NAME}.keys
+ SET #{DATABASE_NAME}.keys.can_push = deploy_keys_projects.can_push
+ WHERE deploy_keys_projects.deploy_key_id = #{DATABASE_NAME}.keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ else
+ execute <<-EOF.strip_heredoc
+ UPDATE keys
+ SET can_push = deploy_keys_projects.can_push
+ FROM deploy_keys_projects
+ WHERE deploy_keys_projects.deploy_key_id = keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ end
end
end
end
diff --git a/db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb b/db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb
index 05d236f8e96..3a5850df3db 100644
--- a/db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb
+++ b/db/post_migrate/20171215121205_post_populate_can_push_from_deploy_keys_projects.rb
@@ -5,6 +5,8 @@ class PostPopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
+ DATABASE_NAME = Gitlab::Database.database_name
+
disable_ddl_transaction!
class DeploysKeyProject < ActiveRecord::Base
@@ -17,13 +19,22 @@ class PostPopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
DeploysKeyProject.each_batch(of: 10_000) do |batch|
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
- execute <<-EOF
- UPDATE deploy_keys_projects
- SET can_push = keys.can_push
- FROM keys
- WHERE deploy_key_id = keys.id
- AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
- EOF
+ if Gitlab::Database.mysql?
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects, #{DATABASE_NAME}.keys
+ SET deploy_keys_projects.can_push = #{DATABASE_NAME}.keys.can_push
+ WHERE deploy_keys_projects.deploy_key_id = #{DATABASE_NAME}.keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ else
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects
+ SET can_push = keys.can_push
+ FROM keys
+ WHERE deploy_key_id = keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ end
end
end
@@ -31,13 +42,22 @@ class PostPopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
DeploysKeyProject.each_batch(of: 10_000) do |batch|
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
- execute <<-EOF
- UPDATE keys
- SET can_push = deploy_keys_projects.can_push
- FROM deploy_keys_projects
- WHERE deploy_keys_projects.deploy_key_id = keys.id
- AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
- EOF
+ if Gitlab::Database.mysql?
+ execute <<-EOF.strip_heredoc
+ UPDATE deploy_keys_projects, #{DATABASE_NAME}.keys
+ SET #{DATABASE_NAME}.keys.can_push = deploy_keys_projects.can_push
+ WHERE deploy_keys_projects.deploy_key_id = #{DATABASE_NAME}.keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ else
+ execute <<-EOF.strip_heredoc
+ UPDATE keys
+ SET can_push = deploy_keys_projects.can_push
+ FROM deploy_keys_projects
+ WHERE deploy_keys_projects.deploy_key_id = keys.id
+ AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
+ EOF
+ end
end
end
end
diff --git a/doc/README.md b/doc/README.md
index 11d52001440..330670587f5 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -10,9 +10,9 @@ platform for software development!
GitLab offers the most scalable Git-based fully integrated platform for software development, with flexible products and subscription plans:
-- **GitLab Community Edition (CE)** is an [opensource product](https://gitlab.com/gitlab-org/gitlab-ce/),
+- **GitLab Community Edition (CE)** is an [open source product](https://gitlab.com/gitlab-org/gitlab-ce/),
self-hosted, free to use. Every feature available in GitLab CE is also available on GitLab Enterprise Edition (Starter and Premium) and GitLab.com.
-- **GitLab Enterprise Edition (EE)** is an [opencore product](https://gitlab.com/gitlab-org/gitlab-ee/),
+- **GitLab Enterprise Edition (EE)** is an [open-core product](https://gitlab.com/gitlab-org/gitlab-ee/),
self-hosted, fully featured solution of GitLab, available under distinct [subscriptions](https://about.gitlab.com/products/): **GitLab Enterprise Edition Starter (EES)**, **GitLab Enterprise Edition Premium (EEP)**, and **GitLab Enterprise Edition Ultimate (EEU)**.
- **GitLab.com**: SaaS GitLab solution, with [free and paid subscriptions](https://about.gitlab.com/gitlab-com/). GitLab.com is hosted by GitLab, Inc., and administrated by GitLab (users don't have access to admin settings).
@@ -148,8 +148,8 @@ Regular users don't have access to GitLab administration tools and settings.
## Contributor documentation
-GitLab Community Edition is [opensource](https://gitlab.com/gitlab-org/gitlab-ce/)
-and Enterprise Editions are [opencore](https://gitlab.com/gitlab-org/gitlab-ee/).
+GitLab Community Edition is [open source](https://gitlab.com/gitlab-org/gitlab-ce/)
+and Enterprise Editions are [open-core](https://gitlab.com/gitlab-org/gitlab-ee/).
Learn how to contribute to GitLab:
- [Development](development/README.md): All styleguides and explanations how to contribute.
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index ad2521230e6..cc495c5d091 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -131,12 +131,13 @@ Available only for admins.
GET /projects/:id/snippets/:snippet_id/user_agent_detail
```
-| Attribute | Type | Required | Description |
-|-------------|---------|----------|--------------------------------------|
-| `id` | Integer | yes | The ID of a snippet |
+| Attribute | Type | Required | Description |
+|---------------|---------|----------|--------------------------------------|
+| `id` | Integer | yes | The ID of a project |
+| `snippet_id` | Integer | yes | The ID of a snippet |
```bash
-curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/snippets/1/user_agent_detail
+curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/snippets/2/user_agent_detail
```
Example response:
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index 9750475f0a6..dd424470b67 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -33,6 +33,7 @@ Example response:
"created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
+ "merge_requests_events": true,
"enable_ssl_verification":true
}
]
@@ -54,6 +55,7 @@ POST /hooks
| `token` | string | no | Secret token to validate received payloads; this will not be returned in the response |
| `push_events` | boolean | no | When true, the hook will fire on push events |
| `tag_push_events` | boolean | no | When true, the hook will fire on new tags being pushed |
+| `merge_requests_events` | boolean | no | Trigger hook on merge requests events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
Example request:
@@ -72,6 +74,7 @@ Example response:
"created_at":"2016-10-31T12:32:15.192Z",
"push_events":true,
"tag_push_events":false,
+ "merge_requests_events": true,
"enable_ssl_verification":true
}
]
diff --git a/doc/articles/index.md b/doc/articles/index.md
index c1c3ff67328..9f85533ea94 100644
--- a/doc/articles/index.md
+++ b/doc/articles/index.md
@@ -1,3 +1,7 @@
+---
+comments: false
+---
+
# Technical articles list (deprecated)
[Technical articles](../development/writing_documentation.md#technical-articles) are
@@ -10,4 +14,5 @@ The list of technical articles was [deprecated](https://gitlab.com/gitlab-org/gi
- [GitLab administrator](../administration/index.md)
- [GitLab CI/CD](../ci/README.md)
- [GitLab Pages](../user/project/pages/index.md)
+- [GitLab user](../user/index.md)
- [Install GitLab](../install/README.md)
diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md
index f6c81b076bc..7e49721daf1 100644
--- a/doc/ci/examples/test-phoenix-application.md
+++ b/doc/ci/examples/test-phoenix-application.md
@@ -53,4 +53,3 @@ If you do not have any migrations yet, you will need to create an empty
## Sources
- https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142
-- https://davejlong.com/ci-with-phoenix-and-gitlab/
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index df66810a838..03aa6ff8e7c 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -144,6 +144,28 @@ To protect/unprotect Runners:
![specific Runners edit icon](img/protected_runners_check_box.png)
+## Manually clearing the Runners cache
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41249) in GitLab 10.4.
+
+GitLab Runners use [cache](../yaml/README.md#cache) to speed up the execution
+of your jobs by reusing existing data. This however, can sometimes lead to an
+inconsistent behavior.
+
+To start with a fresh copy of the cache, you can easily do it via GitLab's UI:
+
+1. Navigate to your project's **CI/CD > Pipelines** page.
+1. Click on the **Clear Runner caches** to clean up the cache.
+1. On the next push, your CI/CD job will use a new cache.
+
+That way, you don't have to change the [cache key](../yaml/README.md#cache-key)
+in your `.gitlab-ci.yml`.
+
+Behind the scenes, this works by increasing a counter in the database, and the
+value of that counter is used to create the key for the cache. After a push, a
+new key is generated and the old cache is not valid anymore. Eventually, the
+Runner's garbage collector will remove it form the filesystem.
+
## How shared Runners pick jobs
Shared Runners abide to a process queue we call fair usage. The fair usage
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 272148033b5..4324b4ca0b8 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -7,6 +7,7 @@
- Ubuntu
- Debian
- CentOS
+- openSUSE
- Red Hat Enterprise Linux (please use the CentOS packages and instructions)
- Scientific Linux (please use the CentOS packages and instructions)
- Oracle Linux (please use the CentOS packages and instructions)
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 8c8501bcc23..9ba05c7815b 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -465,6 +465,135 @@ X-Gitlab-Event: System Hook
"total_commits_count": 0
}
```
+
+### Merge request events
+
+Triggered when a new merge request is created, an existing merge request was
+updated/merged/closed or a commit is added in the source branch.
+
+**Request header**:
+
+```
+X-Gitlab-Event: System Hook
+```
+
+```json
+{
+ "object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon"
+ },
+ "project": {
+ "name": "Example",
+ "description": "",
+ "web_url": "http://example.com/jsmith/example",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:jsmith/example.git",
+ "git_http_url": "http://example.com/jsmith/example.git",
+ "namespace": "Jsmith",
+ "visibility_level": 0,
+ "path_with_namespace": "jsmith/example",
+ "default_branch": "master",
+ "ci_config_path": "",
+ "homepage": "http://example.com/jsmith/example",
+ "url": "git@example.com:jsmith/example.git",
+ "ssh_url": "git@example.com:jsmith/example.git",
+ "http_url": "http://example.com/jsmith/example.git"
+ },
+ "object_attributes": {
+ "id": 90,
+ "target_branch": "master",
+ "source_branch": "ms-viewport",
+ "source_project_id": 14,
+ "author_id": 51,
+ "assignee_id": 6,
+ "title": "MS-Viewport",
+ "created_at": "2017-09-20T08:31:45.944Z",
+ "updated_at": "2017-09-28T12:23:42.365Z",
+ "milestone_id": null,
+ "state": "opened",
+ "merge_status": "unchecked",
+ "target_project_id": 14,
+ "iid": 1,
+ "description": "",
+ "updated_by_id": 1,
+ "merge_error": null,
+ "merge_params": {
+ "force_remove_source_branch": "0"
+ },
+ "merge_when_pipeline_succeeds": false,
+ "merge_user_id": null,
+ "merge_commit_sha": null,
+ "deleted_at": null,
+ "in_progress_merge_commit_sha": null,
+ "lock_version": 5,
+ "time_estimate": 0,
+ "last_edited_at": "2017-09-27T12:43:37.558Z",
+ "last_edited_by_id": 1,
+ "head_pipeline_id": 61,
+ "ref_fetched": true,
+ "merge_jid": null,
+ "source": {
+ "name": "Awesome Project",
+ "description": "",
+ "web_url": "http://example.com/awesome_space/awesome_project",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:awesome_space/awesome_project.git",
+ "git_http_url": "http://example.com/awesome_space/awesome_project.git",
+ "namespace": "root",
+ "visibility_level": 0,
+ "path_with_namespace": "awesome_space/awesome_project",
+ "default_branch": "master",
+ "ci_config_path": "",
+ "homepage": "http://example.com/awesome_space/awesome_project",
+ "url": "http://example.com/awesome_space/awesome_project.git",
+ "ssh_url": "git@example.com:awesome_space/awesome_project.git",
+ "http_url": "http://example.com/awesome_space/awesome_project.git"
+ },
+ "target": {
+ "name": "Awesome Project",
+ "description": "Aut reprehenderit ut est.",
+ "web_url": "http://example.com/awesome_space/awesome_project",
+ "avatar_url": null,
+ "git_ssh_url": "git@example.com:awesome_space/awesome_project.git",
+ "git_http_url": "http://example.com/awesome_space/awesome_project.git",
+ "namespace": "Awesome Space",
+ "visibility_level": 0,
+ "path_with_namespace": "awesome_space/awesome_project",
+ "default_branch": "master",
+ "ci_config_path": "",
+ "homepage": "http://example.com/awesome_space/awesome_project",
+ "url": "http://example.com/awesome_space/awesome_project.git",
+ "ssh_url": "git@example.com:awesome_space/awesome_project.git",
+ "http_url": "http://example.com/awesome_space/awesome_project.git"
+ },
+ "last_commit": {
+ "id": "ba3e0d8ff79c80d5b0bbb4f3e2e343e0aaa662b7",
+ "message": "fixed readme",
+ "timestamp": "2017-09-26T16:12:57Z",
+ "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+ "author": {
+ "name": "GitLab dev user",
+ "email": "gitlabdev@dv6700.(none)"
+ }
+ },
+ "work_in_progress": false,
+ "total_time_spent": 0,
+ "human_total_time_spent": null,
+ "human_time_estimate": null
+ },
+ "labels": null,
+ "repository": {
+ "name": "git-gpg-test",
+ "url": "git@example.com:awesome_space/awesome_project.git",
+ "description": "",
+ "homepage": "http://example.com/awesome_space/awesome_project"
+ }
+}
+```
+
## Repository Update events
Triggered only once when you push to the repository (including tags).
diff --git a/doc/user/admin_area/monitoring/img/convdev_index.png b/doc/user/admin_area/monitoring/img/convdev_index.png
index ffe18d76c96..1bf1d6a83c9 100644
--- a/doc/user/admin_area/monitoring/img/convdev_index.png
+++ b/doc/user/admin_area/monitoring/img/convdev_index.png
Binary files differ
diff --git a/doc/workflow/lfs/img/lfs-icon.png b/doc/workflow/lfs/img/lfs-icon.png
new file mode 100644
index 00000000000..eef9a14187a
--- /dev/null
+++ b/doc/workflow/lfs/img/lfs-icon.png
Binary files differ
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index f7b87dee8e1..ce7895780c3 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -4,6 +4,11 @@ Managing large files such as audio, video and graphics files has always been one
of the shortcomings of Git. The general recommendation is to not have Git repositories
larger than 1GB to preserve performance.
+![Git LFS tracking status](img/lfs-icon.png)
+
+An LFS icon is shown on files tracked by Git LFS to denote if a file is stored
+as a blob or as an LFS pointer.
+
## How it works
Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 3e2e7d0f7b6..0203757e0e1 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -65,7 +65,7 @@ Below is the table of events users can be notified of:
| Group access level changed | User | Sent when user group access level is changed |
| Project moved | Project members [1] | [1] not disabled |
-### Issue / Merge Request events
+### Issue / Merge request events
In all of the below cases, the notification will be sent to:
- Participants:
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 971cb83a2d9..3f4b62dc1b2 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -65,12 +65,12 @@ module API
end
class Hook < Grape::Entity
- expose :id, :url, :created_at, :push_events, :tag_push_events, :repository_update_events
+ expose :id, :url, :created_at, :push_events, :tag_push_events, :merge_requests_events, :repository_update_events
expose :enable_ssl_verification
end
class ProjectHook < Hook
- expose :project_id, :issues_events, :merge_requests_events
+ expose :project_id, :issues_events
expose :note_events, :pipeline_events, :wiki_page_events
expose :job_events
end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 5bed58c2d63..39c03c40bab 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -143,7 +143,7 @@ module API
get ":id/snippets/:snippet_id/user_agent_detail" do
authenticated_as_admin!
- snippet = Snippet.find_by!(id: params[:id])
+ snippet = Snippet.find_by!(id: params[:snippet_id], project_id: params[:id])
return not_found!('UserAgentDetail') unless snippet.user_agent_detail
diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb
index 6b6a03e3300..c7a460df46a 100644
--- a/lib/api/system_hooks.rb
+++ b/lib/api/system_hooks.rb
@@ -26,6 +26,7 @@ module API
optional :token, type: String, desc: 'The token used to validate payloads'
optional :push_events, type: Boolean, desc: "Trigger hook on push events"
optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
+ optional :merge_requests_events, type: Boolean, desc: "Trigger hook on tag push events"
optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
end
post do
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 46e0c0e82a2..768617e2cae 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -239,6 +239,24 @@ module Gitlab
end
end
end
+
+ def extract_signature(repository, commit_id)
+ repository.gitaly_migrate(:extract_commit_signature) do |is_enabled|
+ if is_enabled
+ repository.gitaly_commit_client.extract_signature(commit_id)
+ else
+ rugged_extract_signature(repository, commit_id)
+ end
+ end
+ end
+
+ def rugged_extract_signature(repository, commit_id)
+ begin
+ Rugged::Commit.extract_signature(repository.rugged, commit_id)
+ rescue Rugged::OdbError
+ nil
+ end
+ end
end
def initialize(repository, raw_commit, head = nil)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index c5d5c3a72c0..b89a38d187e 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -490,11 +490,7 @@ module Gitlab
return []
end
- if log_using_shell?(options)
- log_by_shell(sha, options)
- else
- log_by_walk(sha, options)
- end
+ log_by_shell(sha, options)
end
def count_commits(options)
@@ -1512,27 +1508,6 @@ module Gitlab
end
end
- def log_using_shell?(options)
- options[:path].present? ||
- options[:disable_walk] ||
- options[:skip_merges] ||
- options[:after] ||
- options[:before]
- end
-
- def log_by_walk(sha, options)
- walk_options = {
- show: sha,
- sort: Rugged::SORT_NONE,
- limit: options[:limit],
- offset: options[:offset]
- }
- Rugged::Walker.walk(rugged, walk_options).to_a
- end
-
- # Gitaly note: JV: although #log_by_shell shells out to Git I think the
- # complexity is such that we should migrate it as Ruby before trying to
- # do it in Go.
def log_by_shell(sha, options)
limit = options[:limit].to_i
offset = options[:offset].to_i
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 71b212023d6..2231371cfa7 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -282,6 +282,23 @@ module Gitlab
end
end
+ def extract_signature(commit_id)
+ request = Gitaly::ExtractCommitSignatureRequest.new(repository: @gitaly_repo, commit_id: commit_id)
+ response = GitalyClient.call(@repository.storage, :commit_service, :extract_commit_signature, request)
+
+ signature = ''.b
+ signed_text = ''.b
+
+ response.each do |message|
+ signature << message.signature
+ signed_text << message.signed_text
+ end
+
+ return if signature.blank? && signed_text.blank?
+
+ [signature, signed_text]
+ end
+
private
def call_commit_diff(request_params, options = {})
diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb
index 0f4ba6f83fc..672b5579dfd 100644
--- a/lib/gitlab/gpg/commit.rb
+++ b/lib/gitlab/gpg/commit.rb
@@ -4,12 +4,8 @@ module Gitlab
def initialize(commit)
@commit = commit
- @signature_text, @signed_text =
- begin
- Rugged::Commit.extract_signature(@commit.project.repository.rugged, @commit.sha)
- rescue Rugged::OdbError
- nil
- end
+ repo = commit.project.repository.raw_repository
+ @signature_text, @signed_text = Gitlab::Git::Commit.extract_signature(repo, commit.sha)
end
def has_signature?
diff --git a/spec/controllers/admin/hooks_controller_spec.rb b/spec/controllers/admin/hooks_controller_spec.rb
index e6ba596117a..d2c1e634930 100644
--- a/spec/controllers/admin/hooks_controller_spec.rb
+++ b/spec/controllers/admin/hooks_controller_spec.rb
@@ -11,11 +11,13 @@ describe Admin::HooksController do
it 'sets all parameters' do
hook_params = {
enable_ssl_verification: true,
+ token: "TEST TOKEN",
+ url: "http://example.com",
+
push_events: true,
tag_push_events: true,
repository_update_events: true,
- token: "TEST TOKEN",
- url: "http://example.com"
+ merge_requests_events: true
}
post :create, hook: hook_params
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
index aba70c6d4c1..2d473d5bf52 100644
--- a/spec/controllers/projects/hooks_controller_spec.rb
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -18,4 +18,30 @@ describe Projects::HooksController do
)
end
end
+
+ describe '#create' do
+ it 'sets all parameters' do
+ hook_params = {
+ enable_ssl_verification: true,
+ token: "TEST TOKEN",
+ url: "http://example.com",
+
+ push_events: true,
+ tag_push_events: true,
+ merge_requests_events: true,
+ issues_events: true,
+ confidential_issues_events: true,
+ note_events: true,
+ job_events: true,
+ pipeline_events: true,
+ wiki_page_events: true
+ }
+
+ post :create, namespace_id: project.namespace, project_id: project, hook: hook_params
+
+ expect(response).to have_http_status(302)
+ expect(ProjectHook.all.size).to eq(1)
+ expect(ProjectHook.first).to have_attributes(hook_params)
+ end
+ end
end
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index e020579f71e..51b42d1b43b 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -21,7 +21,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_selector('.row-content-block', text: 'All jobs')
expect(page.all('.build-link').size).to eq(4)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -31,7 +31,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -51,7 +51,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
expect(page.find('.build-link')).not_to have_content(build4.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -63,7 +63,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -83,7 +83,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
expect(page.find('.build-link')).not_to have_content(build4.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -95,7 +95,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No jobs to show'
- expect(page).not_to have_link 'Cancel all'
+ expect(page).not_to have_button 'Stop all jobs'
end
end
end
@@ -113,7 +113,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).not_to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).to have_content(build3.id)
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
@@ -125,7 +125,7 @@ describe 'Admin Builds' do
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No jobs to show'
- expect(page).to have_link 'Cancel all'
+ expect(page).to have_button 'Stop all jobs'
end
end
end
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index eec44549a03..f266f2ecc54 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -1,11 +1,10 @@
require 'spec_helper'
-describe 'Admin::Hooks', :js do
- before do
- @project = create(:project)
- sign_in(create(:admin))
+describe 'Admin::Hooks' do
+ let(:user) { create(:admin) }
- @system_hook = create(:system_hook)
+ before do
+ sign_in(user)
end
describe 'GET /admin/hooks' do
@@ -13,15 +12,17 @@ describe 'Admin::Hooks', :js do
visit admin_root_path
page.within '.nav-sidebar' do
- click_on 'Hooks'
+ click_on 'System Hooks', match: :first
end
expect(current_path).to eq(admin_hooks_path)
end
it 'has hooks list' do
+ system_hook = create(:system_hook)
+
visit admin_hooks_path
- expect(page).to have_content(@system_hook.url)
+ expect(page).to have_content(system_hook.url)
end
end
@@ -43,6 +44,10 @@ describe 'Admin::Hooks', :js do
describe 'Update existing hook' do
let(:new_url) { generate(:url) }
+ before do
+ create(:system_hook)
+ end
+
it 'updates existing hook' do
visit admin_hooks_path
@@ -57,7 +62,11 @@ describe 'Admin::Hooks', :js do
end
end
- describe 'Remove existing hook' do
+ describe 'Remove existing hook', :js do
+ before do
+ create(:system_hook)
+ end
+
context 'removes existing hook' do
it 'from hooks list page' do
visit admin_hooks_path
@@ -76,7 +85,8 @@ describe 'Admin::Hooks', :js do
describe 'Test', :js do
before do
- WebMock.stub_request(:post, @system_hook.url)
+ system_hook = create(:system_hook)
+ WebMock.stub_request(:post, system_hook.url)
visit admin_hooks_path
find('.hook-test-button.dropdown').click
@@ -85,4 +95,41 @@ describe 'Admin::Hooks', :js do
it { expect(current_path).to eq(admin_hooks_path) }
end
+
+ context 'Merge request hook' do
+ describe 'New Hook' do
+ let(:url) { generate(:url) }
+
+ it 'adds new hook' do
+ visit admin_hooks_path
+
+ fill_in 'hook_url', with: url
+ uncheck 'Repository update events'
+ check 'Merge request events'
+
+ expect { click_button 'Add system hook' }.to change(SystemHook, :count).by(1)
+ expect(current_path).to eq(admin_hooks_path)
+ expect(page).to have_content(url)
+ end
+ end
+
+ describe 'Test', :js do
+ before do
+ system_hook = create(:system_hook)
+ WebMock.stub_request(:post, system_hook.url)
+ end
+
+ it 'succeeds if the user has a repository with a merge request' do
+ project = create(:project, :repository)
+ create(:project_member, user: user, project: project)
+ create(:merge_request, source_project: project)
+
+ visit admin_hooks_path
+ find('.hook-test-button.dropdown').click
+ click_link 'Merge requests events'
+
+ expect(page).to have_content 'Hook executed successfully'
+ end
+ end
+ end
end
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 3876d1c76d7..3d13f806b11 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -69,6 +69,7 @@ describe 'Issue Boards', :js do
let!(:backlog) { create(:label, project: project, name: 'Backlog') }
let!(:closed) { create(:label, project: project, name: 'Closed') }
let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') }
+ let!(:a_plus) { create(:label, project: project, name: 'A+') }
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
let!(:list2) { create(:list, board: board, label: development, position: 1) }
@@ -83,6 +84,7 @@ describe 'Issue Boards', :js do
let!(:issue7) { create(:labeled_issue, project: project, title: 'ggg', description: '777', labels: [development], relative_position: 2) }
let!(:issue8) { create(:closed_issue, project: project, title: 'hhh', description: '888') }
let!(:issue9) { create(:labeled_issue, project: project, title: 'iii', description: '999', labels: [planning, testing, bug, accepting], relative_position: 1) }
+ let!(:issue10) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
before do
visit project_board_path(project, board)
@@ -400,6 +402,15 @@ describe 'Issue Boards', :js do
wait_for_empty_boards((3..4))
end
+ it 'filters by label with encoded character' do
+ set_filter("label", a_plus.title)
+ click_filter_link(a_plus.title)
+ submit_filter
+
+ wait_for_board_cards(1, 1)
+ wait_for_empty_boards((2..4))
+ end
+
it 'filters by label with space after reload' do
set_filter("label", "\"#{accepting.title}")
click_filter_link(accepting.title)
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 266af8f4e3d..90d6841af0e 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -32,18 +32,6 @@ describe 'User visits the profile preferences page' do
end
end
- describe 'User changes their multi file editor preferences', :js do
- it 'set the new_repo cookie when the option is ON' do
- choose 'user_multi_file_on'
- expect(get_cookie('new_repo')).not_to be_nil
- end
-
- it 'deletes the new_repo cookie when the option is OFF' do
- choose 'user_multi_file_off'
- expect(get_cookie('new_repo')).to be_nil
- end
- end
-
describe 'User changes their default dashboard', :js do
it 'creates a flash message' do
select 'Starred Projects', from: 'user_dashboard'
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index d601cbdb39b..c8d64d64cf4 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -192,4 +192,33 @@ describe IssuablesHelper do
expect(JSON.parse(helper.issuable_initial_data(issue))).to eq(expected_data)
end
end
+
+ describe '#selected_labels' do
+ context 'if label_name param is a string' do
+ it 'returns a new label with title' do
+ allow(helper).to receive(:params)
+ .and_return(ActionController::Parameters.new(label_name: 'test label'))
+
+ labels = helper.selected_labels
+
+ expect(labels).to be_an(Array)
+ expect(labels.size).to eq(1)
+ expect(labels.first.title).to eq('test label')
+ end
+ end
+
+ context 'if label_name param is an array' do
+ it 'returns a new label with title for each element' do
+ allow(helper).to receive(:params)
+ .and_return(ActionController::Parameters.new(label_name: ['test label 1', 'test label 2']))
+
+ labels = helper.selected_labels
+
+ expect(labels).to be_an(Array)
+ expect(labels.size).to eq(2)
+ expect(labels.first.title).to eq('test label 1')
+ expect(labels.second.title).to eq('test label 2')
+ end
+ end
+ end
end
diff --git a/spec/javascripts/boards/utils/query_data_spec.js b/spec/javascripts/boards/utils/query_data_spec.js
new file mode 100644
index 00000000000..922215ffc1d
--- /dev/null
+++ b/spec/javascripts/boards/utils/query_data_spec.js
@@ -0,0 +1,27 @@
+import queryData from '~/boards/utils/query_data';
+
+describe('queryData', () => {
+ it('parses path for label with trailing +', () => {
+ expect(
+ queryData('label_name[]=label%2B', {}),
+ ).toEqual({
+ label_name: ['label+'],
+ });
+ });
+
+ it('parses path for milestone with trailing +', () => {
+ expect(
+ queryData('milestone_title=A%2B', {}),
+ ).toEqual({
+ milestone_title: 'A+',
+ });
+ });
+
+ it('parses path for search terms with spaces', () => {
+ expect(
+ queryData('search=two+words', {}),
+ ).toEqual({
+ search: 'two words',
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb
new file mode 100644
index 00000000000..703cd3d49fa
--- /dev/null
+++ b/spec/javascripts/fixtures/search.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe SearchController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('search/')
+ end
+
+ it 'search/show.html.raw' do |example|
+ get :show
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js b/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
new file mode 100644
index 00000000000..440a6585d57
--- /dev/null
+++ b/spec/javascripts/pages/admin/jobs/index/components/stop_jobs_modal_spec.js
@@ -0,0 +1,63 @@
+import Vue from 'vue';
+
+import axios from '~/lib/utils/axios_utils';
+import stopJobsModal from '~/pages/admin/jobs/index/components/stop_jobs_modal.vue';
+import * as urlUtility from '~/lib/utils/url_utility';
+
+import mountComponent from '../../../../../helpers/vue_mount_component_helper';
+
+describe('stop_jobs_modal.vue', () => {
+ const props = {
+ url: `${gl.TEST_HOST}/stop_jobs_modal.vue/stopAll`,
+ };
+ let vm;
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ beforeEach(() => {
+ const Component = Vue.extend(stopJobsModal);
+ vm = mountComponent(Component, props);
+ });
+
+ describe('onSubmit', () => {
+ it('stops jobs and redirects to overview page', (done) => {
+ const responseURL = `${gl.TEST_HOST}/stop_jobs_modal.vue/jobs`;
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+ spyOn(axios, 'post').and.callFake((url) => {
+ expect(url).toBe(props.url);
+ return Promise.resolve({
+ request: {
+ responseURL,
+ },
+ });
+ });
+
+ vm.onSubmit()
+ .then(() => {
+ expect(redirectSpy).toHaveBeenCalledWith(responseURL);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays error if stopping jobs failed', (done) => {
+ const dummyError = new Error('stopping jobs failed');
+ const redirectSpy = spyOn(urlUtility, 'redirectTo');
+ spyOn(axios, 'post').and.callFake((url) => {
+ expect(url).toBe(props.url);
+ return Promise.reject(dummyError);
+ });
+
+ vm.onSubmit()
+ .then(done.fail)
+ .catch((error) => {
+ expect(error).toBe(dummyError);
+ expect(redirectSpy).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js
new file mode 100644
index 00000000000..38e94d45e55
--- /dev/null
+++ b/spec/javascripts/search_spec.js
@@ -0,0 +1,40 @@
+import Api from '~/api';
+import Search from '~/pages/search/show/search';
+
+describe('Search', () => {
+ const fixturePath = 'search/show.html.raw';
+ const searchTerm = 'some search';
+ const fillDropdownInput = (dropdownSelector) => {
+ const dropdownElement = document.querySelector(dropdownSelector).parentNode;
+ const inputElement = dropdownElement.querySelector('.dropdown-input-field');
+ inputElement.value = searchTerm;
+ return inputElement;
+ };
+
+ preloadFixtures(fixturePath);
+
+ beforeEach(() => {
+ loadFixtures(fixturePath);
+ new Search(); // eslint-disable-line no-new
+ });
+
+ it('requests groups from backend when filtering', (done) => {
+ spyOn(Api, 'groups').and.callFake((term) => {
+ expect(term).toBe(searchTerm);
+ done();
+ });
+ const inputElement = fillDropdownInput('.js-search-group-dropdown');
+
+ $(inputElement).trigger('input');
+ });
+
+ it('requests projects from backend when filtering', (done) => {
+ spyOn(Api, 'projects').and.callFake((term) => {
+ expect(term).toBe(searchTerm);
+ done();
+ });
+ const inputElement = fillDropdownInput('.js-search-project-dropdown');
+
+ $(inputElement).trigger('input');
+ });
+});
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 6a07a3ca8b8..85e6efd7ca2 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -388,6 +388,84 @@ describe Gitlab::Git::Commit, seed_helper: true do
end
end
end
+
+ describe '.extract_signature' do
+ subject { described_class.extract_signature(repository, commit_id) }
+
+ shared_examples '.extract_signature' do
+ context 'when the commit is signed' do
+ let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' }
+
+ it 'returns signature and signed text' do
+ signature, signed_text = subject
+
+ expected_signature = <<~SIGNATURE
+ -----BEGIN PGP SIGNATURE-----
+ Version: GnuPG/MacGPG2 v2.0.22 (Darwin)
+ Comment: GPGTools - https://gpgtools.org
+
+ iQEcBAABCgAGBQJTDvaZAAoJEGJ8X1ifRn8XfvYIAMuB0yrbTGo1BnOSoDfyrjb0
+ Kw2EyUzvXYL72B63HMdJ+/0tlSDC6zONF3fc+bBD8z+WjQMTbwFNMRbSSy2rKEh+
+ mdRybOP3xBIMGgEph0/kmWln39nmFQBsPRbZBWoU10VfI/ieJdEOgOphszgryRar
+ TyS73dLBGE9y9NIININVaNISet9D9QeXFqc761CGjh4YIghvPpi+YihMWapGka6v
+ hgKhX+hc5rj+7IEE0CXmlbYR8OYvAbAArc5vJD7UTxAY4Z7/l9d6Ydt9GQ25khfy
+ ANFgltYzlR6evLFmDjssiP/mx/ZMN91AL0ueJ9nNGv411Mu2CUW+tDCaQf35mdc=
+ =j51i
+ -----END PGP SIGNATURE-----
+ SIGNATURE
+
+ expect(signature).to eq(expected_signature.chomp)
+ expect(signature).to be_a_binary_string
+
+ expected_signed_text = <<~SIGNED_TEXT
+ tree 22bfa2fbd217df24731f43ff43a4a0f8db759dae
+ parent ae73cb07c9eeaf35924a10f713b364d32b2dd34f
+ author Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200
+ committer Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> 1393489561 +0200
+
+ Feature added
+
+ Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
+ SIGNED_TEXT
+
+ expect(signed_text).to eq(expected_signed_text)
+ expect(signed_text).to be_a_binary_string
+ end
+ end
+
+ context 'when the commit has no signature' do
+ let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e1eb03e2c6' }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when the commit cannot be found' do
+ let(:commit_id) { Gitlab::Git::BLANK_SHA }
+
+ it 'returns nil' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when the commit ID is invalid' do
+ let(:commit_id) { '4b4918a572fa86f9771e5ba40fbd48e' }
+
+ it 'raises ArgumentError' do
+ expect { subject }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context 'with gitaly' do
+ it_behaves_like '.extract_signature'
+ end
+
+ context 'without gitaly', :skip_gitaly_mock do
+ it_behaves_like '.extract_signature'
+ end
+ end
end
describe '#init_from_rugged' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 3cf165716bd..aec7cde6df8 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -899,44 +899,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
end
- context "compare results between log_by_walk and log_by_shell" do
- let(:options) { { ref: "master" } }
- let(:commits_by_walk) { repository.log(options).map(&:id) }
- let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:id) }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
-
- context "with limit" do
- let(:options) { { ref: "master", limit: 1 } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with offset" do
- let(:options) { { ref: "master", offset: 1 } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with skip_merges" do
- let(:options) { { ref: "master", skip_merges: true } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
-
- context "with path" do
- let(:options) { { ref: "master", path: "encoding" } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
-
- context "with follow" do
- let(:options) { { ref: "master", path: "encoding", follow: true } }
-
- it { expect(commits_by_walk).to eq(commits_by_shell) }
- end
- end
- end
-
context "where provides 'after' timestamp" do
options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb
index a6c99bc07d4..e3bf2801406 100644
--- a/spec/lib/gitlab/gpg/commit_spec.rb
+++ b/spec/lib/gitlab/gpg/commit_spec.rb
@@ -38,8 +38,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -77,8 +77,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User3.signed_commit_signature,
@@ -116,8 +116,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -151,8 +151,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -187,8 +187,8 @@ describe Gitlab::Gpg::Commit do
end
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
@@ -217,8 +217,8 @@ describe Gitlab::Gpg::Commit do
let!(:commit) { create :commit, project: project, sha: commit_sha }
before do
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(
[
GpgHelpers::User1.signed_commit_signature,
diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
index d6000af0ecd..c034eccf2a6 100644
--- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
+++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb
@@ -26,8 +26,8 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do
before do
allow_any_instance_of(Project).to receive(:commit).and_return(commit)
- allow(Rugged::Commit).to receive(:extract_signature)
- .with(Rugged::Repository, commit_sha)
+ allow(Gitlab::Git::Commit).to receive(:extract_signature)
+ .with(Gitlab::Git::Repository, commit_sha)
.and_return(signature)
end
diff --git a/spec/migrations/fix_wrongly_renamed_routes_spec.rb b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
index 5ef10b92a3a..78f8ab7512d 100644
--- a/spec/migrations/fix_wrongly_renamed_routes_spec.rb
+++ b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
@@ -1,29 +1,35 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb')
-describe FixWronglyRenamedRoutes, truncate: true do
+describe FixWronglyRenamedRoutes, :truncate, :migration do
let(:subject) { described_class.new }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:routes_table) { table(:routes) }
let(:broken_namespace) do
- namespace = create(:group, name: 'apiis')
- namespace.route.update_attribute(:path, 'api0is')
- namespace
+ namespaces_table.create!(name: 'apiis', path: 'apiis').tap do |namespace|
+ routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'api0is', path: 'api0is')
+ end
end
+ let(:broken_namespace_route) { routes_table.where(source_type: 'Namespace', source_id: broken_namespace.id).first }
describe '#wrongly_renamed' do
it "includes routes that have names that don't match their namespace" do
broken_namespace
- _other_namespace = create(:group, name: 'api0')
+ other_namespace = namespaces_table.create!(name: 'api0', path: 'api0')
+ routes_table.create!(source_type: 'Namespace', source_id: other_namespace.id, name: 'api0', path: 'api0')
expect(subject.wrongly_renamed.map(&:id))
- .to contain_exactly(broken_namespace.route.id)
+ .to contain_exactly(broken_namespace_route.id)
end
end
describe "#paths_and_corrections" do
it 'finds the wrong path and gets the correction from the namespace' do
broken_namespace
- namespace = create(:group, name: 'uploads-test')
- namespace.route.update_attribute(:path, 'uploads0-test')
+ namespaces_table.create!(name: 'uploads-test', path: 'uploads-test').tap do |namespace|
+ routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'uploads-test', path: 'uploads0-test')
+ end
expected_result = [
{ 'namespace_path' => 'apiis', 'path' => 'api0is' },
@@ -36,38 +42,45 @@ describe FixWronglyRenamedRoutes, truncate: true do
describe '#routes_in_namespace_query' do
it 'includes only the required routes' do
- namespace = create(:group, path: 'hello')
- project = create(:project, namespace: namespace)
- _other_namespace = create(:group, path: 'hello0')
-
- result = Route.where(subject.routes_in_namespace_query('hello'))
-
- expect(result).to contain_exactly(namespace.route, project.route)
+ namespace = namespaces_table.create!(name: 'hello', path: 'hello')
+ namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'hello')
+ project = projects_table.new(name: 'my-project', path: 'my-project', namespace_id: namespace.id).tap do |project|
+ project.save!(validate: false)
+ end
+ routes_table.create!(source_type: 'Project', source_id: project.id, name: 'my-project', path: 'hello/my-project')
+ _other_namespace = namespaces_table.create!(name: 'hello0', path: 'hello0')
+
+ result = routes_table.where(subject.routes_in_namespace_query('hello'))
+ project_route = routes_table.where(source_type: 'Project', source_id: project.id).first
+
+ expect(result).to contain_exactly(namespace_route, project_route)
end
end
describe '#up' do
- let(:broken_project) do
- project = create(:project, namespace: broken_namespace, path: 'broken-project')
- project.route.update_attribute(:path, 'api0is/broken-project')
- project
- end
-
it 'renames incorrectly named routes' do
- broken_project
+ broken_project =
+ projects_table.new(name: 'broken-project', path: 'broken-project', namespace_id: broken_namespace.id).tap do |project|
+ project.save!(validate: false)
+ routes_table.create!(source_type: 'Project', source_id: project.id, name: 'broken-project', path: 'api0is/broken-project')
+ end
subject.up
- expect(broken_project.route.reload.path).to eq('apiis/broken-project')
- expect(broken_namespace.route.reload.path).to eq('apiis')
+ broken_project_route = routes_table.where(source_type: 'Project', source_id: broken_project.id).first
+
+ expect(broken_project_route.path).to eq('apiis/broken-project')
+ expect(broken_namespace_route.reload.path).to eq('apiis')
end
it "doesn't touch namespaces that look like something that should be renamed" do
- namespace = create(:group, path: 'api0')
+ namespaces_table.create!(name: 'apiis', path: 'apiis')
+ namespace = namespaces_table.create!(name: 'hello', path: 'api0')
+ namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'api0')
subject.up
- expect(namespace.route.reload.path).to eq('api0')
+ expect(namespace_route.reload.path).to eq('api0')
end
end
end
diff --git a/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb b/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb
new file mode 100644
index 00000000000..0ff98933d5c
--- /dev/null
+++ b/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20171215113714_populate_can_push_from_deploy_keys_projects.rb')
+
+describe PopulateCanPushFromDeployKeysProjects, :migration do
+ let(:migration) { described_class.new }
+ let(:deploy_keys) { table(:keys) }
+ let(:deploy_keys_projects) { table(:deploy_keys_projects) }
+ let(:projects) { table(:projects) }
+
+ before do
+ deploy_keys.inheritance_column = nil
+
+ projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1')
+ (1..10).each do |index|
+ deploy_keys.create!(id: index, title: 'dummy', type: 'DeployKey', key: Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com')
+ deploy_keys_projects.create!(id: index, deploy_key_id: index, project_id: 1)
+ end
+ end
+
+ describe '#up' do
+ it 'migrates can_push from deploy_keys to deploy_keys_projects' do
+ deploy_keys.limit(5).update_all(can_push: true)
+
+ expected = deploy_keys.order(:id).pluck(:id, :can_push)
+
+ migration.up
+
+ expect(deploy_keys_projects.order(:id).pluck(:deploy_key_id, :can_push)).to eq expected
+ end
+ end
+
+ describe '#down' do
+ it 'migrates can_push from deploy_keys_projects to deploy_keys' do
+ deploy_keys_projects.limit(5).update_all(can_push: true)
+
+ expected = deploy_keys_projects.order(:id).pluck(:deploy_key_id, :can_push)
+
+ migration.down
+
+ expect(deploy_keys.order(:id).pluck(:id, :can_push)).to eq expected
+ end
+ end
+end
diff --git a/spec/migrations/update_upload_paths_to_system_spec.rb b/spec/migrations/update_upload_paths_to_system_spec.rb
index d4a1553fb0e..984b428a020 100644
--- a/spec/migrations/update_upload_paths_to_system_spec.rb
+++ b/spec/migrations/update_upload_paths_to_system_spec.rb
@@ -1,53 +1,59 @@
-require "spec_helper"
-require Rails.root.join("db", "post_migrate", "20170317162059_update_upload_paths_to_system.rb")
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170317162059_update_upload_paths_to_system.rb')
-describe UpdateUploadPathsToSystem do
+describe UpdateUploadPathsToSystem, :migration do
let(:migration) { described_class.new }
+ let(:uploads_table) { table(:uploads) }
+ let(:base_upload_attributes) { { size: 42, uploader: 'John Doe' } }
before do
allow(migration).to receive(:say)
end
- describe "#uploads_to_switch_to_new_path" do
- it "contains only uploads with the old path for the correct models" do
- _upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
- _upload_with_system_path = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
- _upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg")
- old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
- group_upload = create(:upload, model: create(:group), path: "uploads/group/avatar.jpg")
+ describe '#uploads_to_switch_to_new_path' do
+ it 'contains only uploads with the old path for the correct models' do
+ _upload_for_other_type = create_upload('Pipeline', 'uploads/ci_pipeline/avatar.jpg')
+ _upload_with_system_path = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
+ _upload_with_other_path = create_upload('Project', 'thelongsecretforafileupload/avatar.jpg')
+ old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
+ group_upload = create_upload('Namespace', 'uploads/group/avatar.jpg')
- expect(Upload.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload)
+ expect(uploads_table.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload)
end
end
- describe "#uploads_to_switch_to_old_path" do
- it "contains only uploads with the new path for the correct models" do
- _upload_for_other_type = create(:upload, model: create(:ci_pipeline), path: "uploads/ci_pipeline/avatar.jpg")
- upload_with_system_path = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
- _upload_with_other_path = create(:upload, model: create(:project), path: "thelongsecretforafileupload/avatar.jpg")
- _old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
+ describe '#uploads_to_switch_to_old_path' do
+ it 'contains only uploads with the new path for the correct models' do
+ _upload_for_other_type = create_upload('Pipeline', 'uploads/ci_pipeline/avatar.jpg')
+ upload_with_system_path = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
+ _upload_with_other_path = create_upload('Project', 'thelongsecretforafileupload/avatar.jpg')
+ _old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
- expect(Upload.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path)
+ expect(uploads_table.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path)
end
end
- describe "#up", :truncate do
- it "updates old upload records to the new path" do
- old_upload = create(:upload, model: create(:project), path: "uploads/project/avatar.jpg")
+ describe '#up' do
+ it 'updates old upload records to the new path' do
+ old_upload = create_upload('Project', 'uploads/project/avatar.jpg')
migration.up
- expect(old_upload.reload.path).to eq("uploads/-/system/project/avatar.jpg")
+ expect(old_upload.reload.path).to eq('uploads/-/system/project/avatar.jpg')
end
end
- describe "#down", :truncate do
- it "updates the new system patsh to the old paths" do
- new_upload = create(:upload, model: create(:project), path: "uploads/-/system/project/avatar.jpg")
+ describe '#down' do
+ it 'updates the new system patsh to the old paths' do
+ new_upload = create_upload('Project', 'uploads/-/system/project/avatar.jpg')
migration.down
- expect(new_upload.reload.path).to eq("uploads/project/avatar.jpg")
+ expect(new_upload.reload.path).to eq('uploads/project/avatar.jpg')
end
end
+
+ def create_upload(type, path)
+ uploads_table.create(base_upload_attributes.merge(model_type: type, path: path))
+ end
end
diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb
new file mode 100644
index 00000000000..621d2d38eae
--- /dev/null
+++ b/spec/models/concerns/triggerable_hooks_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+RSpec.describe TriggerableHooks do
+ before do
+ class TestableHook < WebHook
+ include TriggerableHooks
+ triggerable_hooks [:push_hooks]
+ end
+ end
+
+ describe 'scopes' do
+ it 'defines a scope for each of the requested triggers' do
+ expect(TestableHook).to respond_to :push_hooks
+ expect(TestableHook).not_to respond_to :tag_push_hooks
+ end
+ end
+
+ describe '.hooks_for' do
+ context 'the model has the required trigger scope' do
+ it 'returns the record' do
+ hook = TestableHook.create!(url: 'http://example.com', push_events: true)
+
+ expect(TestableHook.hooks_for(:push_hooks)).to eq [hook]
+ end
+ end
+
+ context 'the model does not have the required trigger scope' do
+ it 'returns an empty relation' do
+ TestableHook.create!(url: 'http://example.com')
+
+ expect(TestableHook.hooks_for(:tag_push_hooks)).to eq []
+ end
+ end
+
+ context 'the stock scope ".all" is accepted' do
+ it 'returns the record' do
+ hook = TestableHook.create!(url: 'http://example.com')
+
+ expect(TestableHook.hooks_for(:all)).to eq [hook]
+ end
+ end
+ end
+end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index 0e965f541d8..8bc45715dcd 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -7,7 +7,8 @@ describe SystemHook do
it 'sets defined default parameters' do
attrs = {
push_events: false,
- repository_update_events: true
+ repository_update_events: true,
+ merge_requests_events: false
}
expect(system_hook).to have_attributes(attrs)
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 78223c44999..987be8e8b46 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -3206,4 +3206,23 @@ describe Project do
expect { project.write_repository_config }.not_to raise_error
end
end
+
+ describe '#execute_hooks' do
+ it 'executes the projects hooks with the specified scope' do
+ hook1 = create(:project_hook, merge_requests_events: true, tag_push_events: false)
+ hook2 = create(:project_hook, merge_requests_events: false, tag_push_events: true)
+ project = create(:project, hooks: [hook1, hook2])
+
+ expect_any_instance_of(ProjectHook).to receive(:async_execute).once
+
+ project.execute_hooks({}, :tag_push_hooks)
+ end
+
+ it 'executes the system hooks with the specified scope' do
+ expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with({ data: 'data' }, :merge_request_hooks)
+
+ project = build(:project)
+ project.execute_hooks({ data: 'data' }, :merge_request_hooks)
+ end
+ end
end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index e741ac4b7bd..4a2289ca137 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -1,9 +1,9 @@
require 'rails_helper'
describe API::ProjectSnippets do
- let(:project) { create(:project, :public) }
- let(:user) { create(:user) }
- let(:admin) { create(:admin) }
+ set(:project) { create(:project, :public) }
+ set(:user) { create(:user) }
+ set(:admin) { create(:admin) }
describe "GET /projects/:project_id/snippets/:id/user_agent_detail" do
let(:snippet) { create(:project_snippet, :public, project: project) }
@@ -18,6 +18,13 @@ describe API::ProjectSnippets do
expect(json_response['akismet_submitted']).to eq(user_agent_detail.submitted)
end
+ it 'respects project scoping' do
+ other_project = create(:project)
+
+ get api("/projects/#{other_project.id}/snippets/#{snippet.id}/user_agent_detail", admin)
+ expect(response).to have_gitlab_http_status(404)
+ end
+
it "returns unautorized for non-admin users" do
get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/user_agent_detail", user)
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index c7a009e999e..6c57d443cbf 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -36,6 +36,7 @@ describe API::SystemHooks do
expect(json_response.first['url']).to eq(hook.url)
expect(json_response.first['push_events']).to be false
expect(json_response.first['tag_push_events']).to be false
+ expect(json_response.first['merge_requests_events']).to be false
expect(json_response.first['repository_update_events']).to be true
end
end
@@ -67,11 +68,28 @@ describe API::SystemHooks do
end
it 'sets default values for events' do
- post api('/hooks', admin), url: 'http://mep.mep', enable_ssl_verification: true
+ post api('/hooks', admin), url: 'http://mep.mep'
expect(response).to have_gitlab_http_status(201)
expect(json_response['enable_ssl_verification']).to be true
+ expect(json_response['push_events']).to be false
expect(json_response['tag_push_events']).to be false
+ expect(json_response['merge_requests_events']).to be false
+ end
+
+ it 'sets explicit values for events' do
+ post api('/hooks', admin),
+ url: 'http://mep.mep',
+ enable_ssl_verification: false,
+ push_events: true,
+ tag_push_events: true,
+ merge_requests_events: true
+
+ expect(response).to have_http_status(201)
+ expect(json_response['enable_ssl_verification']).to be false
+ expect(json_response['push_events']).to be true
+ expect(json_response['tag_push_events']).to be true
+ expect(json_response['merge_requests_events']).to be true
end
end
diff --git a/spec/services/test_hooks/system_service_spec.rb b/spec/services/test_hooks/system_service_spec.rb
index ff8b9595538..74d7715e50f 100644
--- a/spec/services/test_hooks/system_service_spec.rb
+++ b/spec/services/test_hooks/system_service_spec.rb
@@ -60,5 +60,25 @@ describe TestHooks::SystemService do
expect(service.execute).to include(success_result)
end
end
+
+ context 'merge_requests_events' do
+ let(:trigger) { 'merge_requests_events' }
+
+ it 'returns error message if the user does not have any repository with a merge request' do
+ expect(hook).not_to receive(:execute)
+ expect(service.execute).to include({ status: :error, message: 'Ensure one of your projects has merge requests.' })
+ end
+
+ it 'executes hook' do
+ trigger_key = :merge_request_hooks
+ sample_data = { data: 'sample' }
+ create(:project_member, user: current_user, project: project)
+ create(:merge_request, source_project: project)
+ allow_any_instance_of(MergeRequest).to receive(:to_hook_data).and_return(sample_data)
+
+ expect(hook).to receive(:execute).with(sample_data, trigger_key).and_return(success_result)
+ expect(service.execute).to include(success_result)
+ end
+ end
end
end