summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/CODEOWNERS.disabled4
-rw-r--r--.rubocop_todo.yml1
-rw-r--r--CHANGELOG.md16
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/boards/models/issue.js16
-rw-r--r--app/assets/javascripts/boards/models/list.js19
-rw-r--r--app/assets/javascripts/boards/models/milestone.js11
-rw-r--r--app/assets/javascripts/diffs/constants.js2
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_form.js2
-rw-r--r--app/assets/javascripts/ide/stores/mutations/merge_request.js3
-rw-r--r--app/assets/javascripts/import_projects/components/provider_repo_table_row.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue2
-rw-r--r--app/assets/javascripts/jobs/components/job_container_item.vue8
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue6
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue2
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js17
-rw-r--r--app/assets/stylesheets/framework/common.scss6
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/forms.scss4
-rw-r--r--app/assets/stylesheets/pages/builds.scss47
-rw-r--r--app/assets/stylesheets/pages/import.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss11
-rw-r--r--app/assets/stylesheets/pages/settings.scss2
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss8
-rw-r--r--app/controllers/projects/repositories_controller.rb2
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/models/application_setting.rb12
-rw-r--r--app/models/badge.rb2
-rw-r--r--app/models/ci/build_runner_session.rb2
-rw-r--r--app/models/environment.rb2
-rw-r--r--app/models/error_tracking/project_error_tracking_setting.rb2
-rw-r--r--app/models/generic_commit_status.rb2
-rw-r--r--app/models/project.rb3
-rw-r--r--app/models/project_services/hipchat_service.rb311
-rw-r--r--app/models/releases/link.rb2
-rw-r--r--app/models/remote_mirror.rb2
-rw-r--r--app/models/repository.rb5
-rw-r--r--app/models/service.rb1
-rw-r--r--app/services/git/base_hooks_service.rb2
-rw-r--r--app/services/groups/create_service.rb6
-rw-r--r--app/services/projects/create_service.rb4
-rw-r--r--app/validators/addressable_url_validator.rb112
-rw-r--r--app/validators/public_url_validator.rb19
-rw-r--r--app/validators/url_validator.rb104
-rw-r--r--app/views/projects/_classification_policy_settings.html.haml6
-rw-r--r--app/views/projects/_export.html.haml67
-rw-r--r--app/views/projects/buttons/_download.html.haml36
-rw-r--r--app/views/projects/buttons/_download_links.html.haml9
-rw-r--r--app/views/projects/edit.html.haml329
-rw-r--r--app/views/projects/graphs/show.html.haml4
-rw-r--r--app/views/projects/settings/_general.html.haml42
-rw-r--r--app/views/shared/_confirm_modal.html.haml8
-rw-r--r--app/workers/post_receive.rb27
-rw-r--r--changelogs/unreleased/24704-download-repository-path.yml5
-rw-r--r--changelogs/unreleased/24985-align-urlvalidator-to-validate_url-gem-implementation.yml5
-rw-r--r--changelogs/unreleased/60304-long-file-names-in-mr-diffs-cause-horizontal-scrolling.yml5
-rw-r--r--changelogs/unreleased/allow-to-use-untrusted-ruby-syntax.yml5
-rw-r--r--changelogs/unreleased/fix-api-group-visibility.yml5
-rw-r--r--changelogs/unreleased/fix-pull-request-importer.yml5
-rw-r--r--changelogs/unreleased/fixed-web-ide-merge-request-review.yml5
-rw-r--r--changelogs/unreleased/fj-bump-workhorse-version-8-6-0.yml5
-rw-r--r--changelogs/unreleased/jc-guard-against-empty-dereferenced_target.yml5
-rw-r--r--changelogs/unreleased/limit-amount-of-created-pipelines.yml5
-rw-r--r--changelogs/unreleased/sh-disable-diff-instrumentation.yml5
-rw-r--r--config/initializers/hipchat_client_patch.rb15
-rw-r--r--db/fixtures/development/02_application_settings.rb2
-rw-r--r--db/migrate/20190107151029_remove_hipchat_services.rb16
-rw-r--r--doc/administration/high_availability/nfs.md11
-rw-r--r--doc/api/services.md39
-rw-r--r--doc/ci/yaml/README.md22
-rw-r--r--doc/development/contributing/style_guides.md2
-rw-r--r--doc/install/google_cloud_platform/index.md6
-rw-r--r--doc/integration/README.md4
-rw-r--r--doc/project_services/hipchat.md1
-rw-r--r--doc/university/glossary/README.md2
-rw-r--r--doc/user/group/subgroups/index.md2
-rw-r--r--doc/user/index.md4
-rw-r--r--doc/user/project/integrations/hipchat.md53
-rw-r--r--doc/user/project/integrations/project_services.md1
-rw-r--r--doc/user/project/pages/getting_started_part_one.md89
-rw-r--r--doc/user/project/pages/getting_started_part_three.md3
-rw-r--r--doc/user/project/pages/getting_started_part_two.md4
-rw-r--r--doc/user/project/pages/img/pages_remove.pngbin3777 -> 0 bytes
-rw-r--r--doc/user/project/pages/img/remove_pages.pngbin0 -> 58035 bytes
-rw-r--r--doc/user/project/pages/index.md9
-rw-r--r--doc/user/project/pages/introduction.md352
-rw-r--r--doc/user/project/repository/img/download_source_code.pngbin61467 -> 0 bytes
-rw-r--r--doc/user/project/repository/index.md20
-rw-r--r--doc/workflow/repository_mirroring.md17
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--lib/api/helpers/services_helpers.rb40
-rw-r--r--lib/api/import_github.rb4
-rw-r--r--lib/gitlab/git/repository.rb9
-rw-r--r--lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb2
-rw-r--r--lib/gitlab/url_blocker.rb10
-rw-r--r--lib/gitlab/visibility_level.rb2
-rw-r--r--lib/gitlab/workhorse.rb38
-rw-r--r--locale/gitlab.pot176
-rw-r--r--qa/qa/page/project/settings/advanced.rb20
-rw-r--r--qa/qa/page/project/settings/common.rb8
-rw-r--r--qa/qa/page/project/settings/main.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb3
-rw-r--r--spec/controllers/projects/mirrors_controller_spec.rb2
-rw-r--r--spec/factories/services.rb6
-rw-r--r--spec/features/ide/user_opens_merge_request_spec.rb21
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/services/disable_triggers_spec.rb5
-rw-r--r--spec/features/projects/services/user_activates_hipchat_spec.rb40
-rw-r--r--spec/features/projects/services/user_views_services_spec.rb3
-rw-r--r--spec/features/projects/settings/user_renames_a_project_spec.rb37
-rw-r--r--spec/features/projects/show/download_buttons_spec.rb3
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb2
-rw-r--r--spec/features/projects_spec.rb15
-rw-r--r--spec/frontend/.eslintrc.yml7
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js2
-rw-r--r--spec/frontend/ide/stores/mutations/merge_request_spec.js18
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js14
-rw-r--r--spec/javascripts/fixtures/environments/table.html.haml11
-rw-r--r--spec/javascripts/fixtures/static/environments/table.html15
-rw-r--r--spec/javascripts/fixtures/static_fixtures.rb19
-rw-r--r--spec/javascripts/notes_spec.js2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb18
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/project.json22
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb6
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb8
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb31
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb410
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/requests/api/commit_statuses_spec.rb17
-rw-r--r--spec/services/groups/create_service_spec.rb11
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb4
-rw-r--r--spec/services/merge_requests/update_service_spec.rb2
-rw-r--r--spec/support/shared_examples/dirty_submit_form_shared_examples.rb14
-rw-r--r--spec/support/shared_examples/url_validator_examples.rb24
-rw-r--r--spec/validators/addressable_url_validator_spec.rb (renamed from spec/validators/url_validator_spec.rb)131
-rw-r--r--spec/validators/public_url_validator_spec.rb8
-rw-r--r--spec/workers/post_receive_spec.rb42
-rw-r--r--vendor/licenses.csv1
142 files changed, 2191 insertions, 1180 deletions
diff --git a/.gitlab/CODEOWNERS.disabled b/.gitlab/CODEOWNERS.disabled
index 89a9696d3e8..f7e2c06dae5 100644
--- a/.gitlab/CODEOWNERS.disabled
+++ b/.gitlab/CODEOWNERS.disabled
@@ -1,6 +1,6 @@
# Backend Maintainers are the default for all ruby files
-*.rb @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @nick.thomas @rspeicher @rymai @smcgivern
-*.rake @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @nick.thomas @rspeicher @rymai @smcgivern
+*.rb @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @nick.thomas @rspeicher @rymai @smcgivern
+*.rake @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @nick.thomas @rspeicher @rymai @smcgivern
# Technical writing team are the default reviewers for everything in `doc/`
/doc/ @axil @marcia
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 97e39ce99cb..77ad4753c84 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -268,6 +268,7 @@ Rails/Presence:
- 'app/models/clusters/platforms/kubernetes.rb'
- 'app/models/concerns/mentionable.rb'
- 'app/models/concerns/token_authenticatable.rb'
+ - 'app/models/project_services/hipchat_service.rb'
- 'app/models/project_services/irker_service.rb'
- 'app/models/project_services/jira_service.rb'
- 'app/models/project_services/kubernetes_service.rb'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e91b9f29ff..41506746c98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,22 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.9.8 (2019-04-11)
+
+### Deprecated (1 change)
+
+- Allow to use untrusted Regexp via feature flag. !26905
+
+### Performance (2 changes)
+
+- Improve performance of PR import. !27121
+- Disable method instrumentation for diffs. !27235
+
+### Other (1 change)
+
+- Restore HipChat project service. !27172
+
+
## 11.9.7 (2019-04-09)
- No changes.
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index f9c71a52e2f..acd405b1d62 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.5.1
+8.6.0
diff --git a/Gemfile b/Gemfile
index 6052018754a..1c7ad5abcb5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -201,6 +201,9 @@ gem 'connection_pool', '~> 2.0'
# Discord integration
gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
+# HipChat integration
+gem 'hipchat', '~> 1.5.0'
+
# JIRA integration
gem 'jira-ruby', '~> 1.4'
diff --git a/Gemfile.lock b/Gemfile.lock
index b522aa85b39..3314a769949 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -366,6 +366,9 @@ GEM
hashie (>= 3.0)
health_check (2.6.0)
rails (>= 4.0)
+ hipchat (1.5.2)
+ httparty
+ mimemagic
html-pipeline (2.8.4)
activesupport (>= 2)
nokogiri (>= 1.4)
@@ -1040,6 +1043,7 @@ DEPENDENCIES
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
health_check (~> 2.6.0)
+ hipchat (~> 1.5.0)
html-pipeline (~> 2.8)
html2text
httparty (~> 0.16.4)
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 2edb6723ada..b4d913f5d69 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -5,6 +5,7 @@
import Vue from 'vue';
import '~/vue_shared/models/label';
+import { isEE } from '~/lib/utils/common_utils';
import IssueProject from './project';
import boardsStore from '../stores/boards_store';
@@ -28,7 +29,6 @@ class ListIssue {
this.referencePath = obj.reference_path;
this.path = obj.real_path;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
- this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
this.timeEstimate = obj.time_estimate;
this.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
@@ -39,6 +39,7 @@ class ListIssue {
if (obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
+ this.milestone_id = obj.milestone.id;
}
obj.labels.forEach(label => {
@@ -88,6 +89,19 @@ class ListIssue {
this.assignees = [];
}
+ addMilestone(milestone) {
+ const miletoneId = this.milestone ? this.milestone.id : null;
+ if (isEE && milestone.id !== miletoneId) {
+ this.milestone = new ListMilestone(milestone);
+ }
+ }
+
+ removeMilestone(removeMilestone) {
+ if (isEE && removeMilestone && removeMilestone.id === this.milestone.id) {
+ this.milestone = {};
+ }
+ }
+
getLists() {
return boardsStore.state.lists.filter(list => list.findIssue(this.id));
}
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 9f6d9a853da..6cf77705847 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -4,8 +4,9 @@
import { __ } from '~/locale';
import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
-import { urlParamsToObject } from '~/lib/utils/common_utils';
+import { isEE, urlParamsToObject } from '~/lib/utils/common_utils';
import boardsStore from '../stores/boards_store';
+import ListMilestone from './milestone';
const PER_PAGE = 20;
@@ -51,6 +52,9 @@ class List {
} else if (obj.user) {
this.assignee = new ListAssignee(obj.user);
this.title = this.assignee.name;
+ } else if (isEE && obj.milestone) {
+ this.milestone = new ListMilestone(obj.milestone);
+ this.title = this.milestone.title;
}
if (!typeInfo.isBlank && this.id) {
@@ -69,12 +73,14 @@ class List {
}
save() {
- const entity = this.label || this.assignee;
+ const entity = this.label || this.assignee || this.milestone;
let entityType = '';
if (this.label) {
entityType = 'label_id';
- } else {
+ } else if (this.assignee) {
entityType = 'assignee_id';
+ } else if (isEE && this.milestone) {
+ entityType = 'milestone_id';
}
return gl.boardService
@@ -192,6 +198,13 @@ class List {
issue.addAssignee(this.assignee);
}
+ if (isEE && this.milestone) {
+ if (listFrom && listFrom.type === 'milestone') {
+ issue.removeMilestone(listFrom.milestone);
+ }
+ issue.addMilestone(this.milestone);
+ }
+
if (listFrom) {
this.issuesSize += 1;
diff --git a/app/assets/javascripts/boards/models/milestone.js b/app/assets/javascripts/boards/models/milestone.js
index 17d15278a74..6f81d6bc6f8 100644
--- a/app/assets/javascripts/boards/models/milestone.js
+++ b/app/assets/javascripts/boards/models/milestone.js
@@ -1,7 +1,16 @@
-class ListMilestone {
+import { isEE } from '~/lib/utils/common_utils';
+
+export default class ListMilestone {
constructor(obj) {
this.id = obj.id;
this.title = obj.title;
+
+ if (isEE) {
+ this.path = obj.path;
+ this.state = obj.state;
+ this.webUrl = obj.web_url || obj.webUrl;
+ this.description = obj.description;
+ }
}
}
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 5dabe224baa..4feb73cfef2 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -49,4 +49,4 @@ export const TYPE_KEY = 'type';
export const LEFT_LINE_KEY = 'left';
export const CENTERED_LIMITED_CONTAINER_CLASSES =
- 'container-limited limit-container-width mx-auto px-3';
+ 'container-limited limit-container-width mx-lg-auto px-3';
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_form.js b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
index 00e41dd0301..765969daa32 100644
--- a/app/assets/javascripts/dirty_submit/dirty_submit_form.js
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
@@ -1,4 +1,5 @@
import _ from 'underscore';
+import $ from 'jquery';
class DirtySubmitForm {
constructor(form) {
@@ -26,6 +27,7 @@ class DirtySubmitForm {
);
this.form.addEventListener('input', throttledUpdateDirtyInput);
this.form.addEventListener('change', throttledUpdateDirtyInput);
+ $(this.form).on('change.select2', throttledUpdateDirtyInput);
this.form.addEventListener('submit', event => this.formSubmit(event));
}
diff --git a/app/assets/javascripts/ide/stores/mutations/merge_request.js b/app/assets/javascripts/ide/stores/mutations/merge_request.js
index 334819fe702..e5b5107bc93 100644
--- a/app/assets/javascripts/ide/stores/mutations/merge_request.js
+++ b/app/assets/javascripts/ide/stores/mutations/merge_request.js
@@ -7,6 +7,8 @@ export default {
});
},
[types.SET_MERGE_REQUEST](state, { projectPath, mergeRequestId, mergeRequest }) {
+ const existingMergeRequest = state.projects[projectPath].mergeRequests[mergeRequestId] || {};
+
Object.assign(state.projects[projectPath], {
mergeRequests: {
[mergeRequestId]: {
@@ -15,6 +17,7 @@ export default {
changes: [],
versions: [],
baseCommitSha: null,
+ ...existingMergeRequest,
},
},
});
diff --git a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
index 7cc29fa1b91..3c6c9c71b8c 100644
--- a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
+++ b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue
@@ -41,7 +41,7 @@ export default {
return {
data: this.namespaceSelectOptions,
containerCssClass:
- 'import-namespace-select js-namespace-select qa-project-namespace-select',
+ 'import-namespace-select js-namespace-select qa-project-namespace-select w-auto',
};
},
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 0670e2b06b9..7594edfac27 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -275,7 +275,7 @@ export default {
<!-- job log -->
<div
v-if="hasTrace"
- class="build-trace-container"
+ class="build-trace-container position-relative"
:class="{ 'prepend-top-default': !job.archived }"
>
<log-top-bar
diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue
index 845699a90b5..a55dffbe488 100644
--- a/app/assets/javascripts/jobs/components/job_container_item.vue
+++ b/app/assets/javascripts/jobs/components/job_container_item.vue
@@ -43,7 +43,7 @@ export default {
<template>
<div
- class="build-job"
+ class="build-job position-relative"
:class="{
retried: job.retried,
active: isActive,
@@ -56,7 +56,11 @@ export default {
data-boundary="viewport"
class="js-job-link"
>
- <icon v-if="isActive" name="arrow-right" class="js-arrow-right icon-arrow-right" />
+ <icon
+ v-if="isActive"
+ name="arrow-right"
+ class="js-arrow-right icon-arrow-right position-absolute d-block"
+ />
<ci-icon :status="job.status" />
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 52e14f954ee..607b2bd1c74 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -75,7 +75,11 @@ export default {
<template v-if="isTraceSizeVisible">
{{ jobLogSize }}
- <gl-link v-if="rawPath" :href="rawPath" class="js-raw-link raw-link">
+ <gl-link
+ v-if="rawPath"
+ :href="rawPath"
+ class="js-raw-link text-plain text-underline prepend-left-5"
+ >
{{ s__('Job|Complete Raw') }}
</gl-link>
</template>
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index 997737b3e23..922f64d93fe 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -52,7 +52,7 @@ export default {
</p>
<template v-if="hasVariables">
- <p class="trigger-variables-btn-container">
+ <p class="trigger-variables-btn-container d-flex">
<span class="font-weight-bold">{{ __('Trigger variables:') }}</span>
<gl-button
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 278c35d3846..92ed6a652d7 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -3,17 +3,24 @@ import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
+import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initAvatarPicker from '~/avatar_picker';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectPermissionsSettings from '../shared/permissions';
document.addEventListener('DOMContentLoaded', () => {
- initProjectLoadingSpinner();
- setupProjectEdit();
- // Initialize expandable settings panels
- initSettingsPanels();
initAvatarPicker();
- initProjectPermissionsSettings();
initConfirmDangerModal();
+ initSettingsPanels();
mountBadgeSettings(PROJECT_BADGE);
+
+ initProjectLoadingSpinner();
+ initProjectPermissionsSettings();
+ setupProjectEdit();
+
+ dirtySubmitFactory(
+ document.querySelectorAll(
+ '.js-general-settings-form, .js-mr-settings-form, .js-mr-approvals-form',
+ ),
+ );
});
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index db6c107210e..c3031c3375e 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -436,6 +436,12 @@ img.emoji {
.h-3 { width: #{3 * $grid-size}; }
+.svg-w-100 {
+ svg {
+ width: 100%;
+ }
+}
+
/** COMMON SPACING CLASSES **/
.gl-pl-0 { padding-left: 0; }
.gl-pl-1 { padding-left: #{0.5 * $grid-size}; }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index efcd35a2e0e..b90db135b4a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -287,7 +287,7 @@
list-style: none;
padding: 0 1px;
- a:not(.btn),
+ a,
button,
.menu-item {
@include dropdown-link;
@@ -351,10 +351,6 @@
// Expects up to 3 digits on the badge
margin-right: 40px;
}
-
- .dropdown-menu-content {
- padding: $dropdown-item-padding-y $dropdown-item-padding-x;
- }
}
.droplab-dropdown {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 1c23c14c2de..be544c0a814 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -157,6 +157,10 @@ label {
padding-left: 10px;
padding-right: 10px;
appearance: none;
+ /* stylelint-disable property-no-vendor-prefix */
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ /* stylelint-enable property-no-vendor-prefix */
&::-ms-expand {
display: none;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 916f6cd3137..6fc742871e7 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -46,10 +46,6 @@
}
.build-page {
- .build-trace-container {
- position: relative;
- }
-
.build-trace {
@include build-trace();
}
@@ -104,18 +100,6 @@
top: 0;
}
- .truncated-info {
- .truncated-info-size {
- margin: 0 5px;
- }
-
- .raw-link {
- color: $gl-text-color;
- margin-left: 5px;
- text-decoration: underline;
- }
- }
-
.controllers {
@include build-controllers(15px, center, false, 0, inline, 0);
}
@@ -142,12 +126,6 @@
}
}
-.with-performance-bar .build-page {
- .top-bar.affix {
- top: $header-height + $performance-bar-height;
- }
-}
-
.build-header {
.ci-header-container,
.header-action-buttons {
@@ -233,7 +211,6 @@
}
.trigger-variables-btn-container {
- @extend .d-flex;
justify-content: space-between;
align-items: center;
@@ -277,12 +254,6 @@
.retry-link {
display: block;
- .btn {
- i {
- margin-left: 5px;
- }
- }
-
.btn-inverted-secondary {
color: $blue-500;
@@ -329,16 +300,12 @@
}
}
- .build-job {
- position: relative;
-
- .icon-arrow-right {
- position: absolute;
- left: 15px;
- top: 20px;
- display: block;
- }
+ .icon-arrow-right {
+ left: 15px;
+ top: 20px;
+ }
+ .build-job {
&.active {
font-weight: $gl-font-weight-bold;
}
@@ -350,10 +317,6 @@
&:hover {
background-color: $gray-darker;
}
-
- .icon-retry {
- margin-left: 3px;
- }
}
}
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
index 20240835fda..74f80a11471 100644
--- a/app/assets/stylesheets/pages/import.scss
+++ b/app/assets/stylesheets/pages/import.scss
@@ -18,8 +18,6 @@
}
.import-namespace-select {
- width: auto !important;
-
> .select2-choice {
border-radius: $border-radius-default 0 0 $border-radius-default;
position: relative;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9be3f8138a0..fbd291f095a 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -1,8 +1,3 @@
-// Limit MR description for side-by-side diff view
-.fixed-width-container {
- @include fixed-width-container;
-}
-
.issuable-warning-icon {
background-color: $orange-100;
border-radius: $border-radius-default;
@@ -27,7 +22,7 @@
.files-changed-inner,
.limited-header-width,
.limited-width-notes {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
.issuable-details {
@@ -35,13 +30,13 @@
.mr-source-target,
.mr-state-widget,
.merge-manually {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
}
.merge-request-details {
.emoji-list-container {
- @extend .fixed-width-container;
+ @include fixed-width-container;
}
}
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 7b0538dca20..0a9c56f5625 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -39,7 +39,7 @@
.settings-header {
position: relative;
- padding: 20px 110px 10px 0;
+ padding: 20px 110px 0 0;
h4 {
margin-top: 0;
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index 79186480605..31ccdacbc02 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -18,10 +18,6 @@
@include make-col-ready();
@include make-col(12);
}
-
- svg {
- width: 100%;
- }
}
#contributors {
@@ -31,10 +27,6 @@
margin: 0 0 10px;
list-style: none;
padding: 0;
-
- svg {
- width: 100%;
- }
}
.person {
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 3b4215b766e..4eeaeb860ee 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -23,7 +23,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
append_sha = false if @filename == shortname
end
- send_git_archive @repository, ref: @ref, path: params[:path], format: params[:format], append_sha: append_sha
+ send_git_archive @repository, ref: @ref, format: params[:format], append_sha: append_sha
rescue => ex
logger.error("#{self.class.name}: #{ex}")
git_not_found!
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 2ac90eb8d9f..7da51da8473 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -299,10 +299,6 @@ module ProjectsHelper
}.to_json
end
- def directory?
- @path.present?
- end
-
def external_classification_label_help_message
default_label = ::Gitlab::CurrentSettings.current_application_settings
.external_authorization_service_default_label
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index d28a12413bf..eb6ddaac871 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -48,17 +48,17 @@ class ApplicationSetting < ApplicationRecord
validates :home_page_url,
allow_blank: true,
- url: true,
+ addressable_url: true,
if: :home_page_url_column_exists?
validates :help_page_support_url,
allow_blank: true,
- url: true,
+ addressable_url: true,
if: :help_page_support_url_column_exists?
validates :after_sign_out_path,
allow_blank: true,
- url: true
+ addressable_url: true
validates :admin_notification_email,
devise_email: true,
@@ -218,7 +218,7 @@ class ApplicationSetting < ApplicationRecord
if: :external_authorization_service_enabled
validates :external_authorization_service_url,
- url: true, allow_blank: true,
+ addressable_url: true, allow_blank: true,
if: :external_authorization_service_enabled
validates :external_authorization_service_timeout,
@@ -259,7 +259,9 @@ class ApplicationSetting < ApplicationRecord
after_commit :expire_performance_bar_allowed_user_ids_cache, if: -> { previous_changes.key?('performance_bar_allowed_group_id') }
def self.create_from_defaults
- super
+ transaction(requires_new: true) do
+ super
+ end
rescue ActiveRecord::RecordNotUnique
# We already have an ApplicationSetting record, so just return it.
current_without_cache
diff --git a/app/models/badge.rb b/app/models/badge.rb
index a244ed473de..50299cd6652 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -22,7 +22,7 @@ class Badge < ApplicationRecord
scope :order_created_at_asc, -> { reorder(created_at: :asc) }
- validates :link_url, :image_url, url: { protocols: %w(http https) }
+ validates :link_url, :image_url, addressable_url: true
validates :type, presence: true
def rendered_link_url(project = nil)
diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb
index 80dbb150085..997bf298025 100644
--- a/app/models/ci/build_runner_session.rb
+++ b/app/models/ci/build_runner_session.rb
@@ -13,7 +13,7 @@ module Ci
belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session
validates :build, presence: true
- validates :url, url: { protocols: %w(https) }
+ validates :url, addressable_url: { schemes: %w(https) }
def terminal_specification
wss_url = Gitlab::UrlHelpers.as_wss(self.url)
diff --git a/app/models/environment.rb b/app/models/environment.rb
index fa29a83e517..69224635e34 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -35,7 +35,7 @@ class Environment < ApplicationRecord
validates :external_url,
length: { maximum: 255 },
allow_nil: true,
- url: true
+ addressable_url: true
delegate :stop_action, :manual_actions, to: :last_deployment, allow_nil: true
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
index 70954bf8b05..72270ee8b4f 100644
--- a/app/models/error_tracking/project_error_tracking_setting.rb
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -22,7 +22,7 @@ module ErrorTracking
belongs_to :project
- validates :api_url, length: { maximum: 255 }, public_url: true, url: { enforce_sanitization: true, ascii_only: true }, allow_nil: true
+ validates :api_url, length: { maximum: 255 }, public_url: { enforce_sanitization: true, ascii_only: true }, allow_nil: true
validates :api_url, presence: { message: 'is a required field' }, if: :enabled
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
index 3028bf21301..8a768b3a2c0 100644
--- a/app/models/generic_commit_status.rb
+++ b/app/models/generic_commit_status.rb
@@ -3,7 +3,7 @@
class GenericCommitStatus < CommitStatus
before_validation :set_default_values
- validates :target_url, url: true,
+ validates :target_url, addressable_url: true,
length: { maximum: 255 },
allow_nil: true
diff --git a/app/models/project.rb b/app/models/project.rb
index 66fc83113ea..c838df03c0d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -146,6 +146,7 @@ class Project < ApplicationRecord
has_one :pipelines_email_service
has_one :irker_service
has_one :pivotaltracker_service
+ has_one :hipchat_service
has_one :flowdock_service
has_one :assembla_service
has_one :asana_service
@@ -327,7 +328,7 @@ class Project < ApplicationRecord
validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id }
- validates :import_url, public_url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
+ validates :import_url, public_url: { schemes: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
new file mode 100644
index 00000000000..a69b7b4c4b6
--- /dev/null
+++ b/app/models/project_services/hipchat_service.rb
@@ -0,0 +1,311 @@
+# frozen_string_literal: true
+
+class HipchatService < Service
+ include ActionView::Helpers::SanitizeHelper
+
+ MAX_COMMITS = 3
+ HIPCHAT_ALLOWED_TAGS = %w[
+ a b i strong em br img pre code
+ table th tr td caption colgroup col thead tbody tfoot
+ ul ol li dl dt dd
+ ].freeze
+
+ prop_accessor :token, :room, :server, :color, :api_version
+ boolean_accessor :notify_only_broken_pipelines, :notify
+ validates :token, presence: true, if: :activated?
+
+ def initialize_properties
+ if properties.nil?
+ self.properties = {}
+ self.notify_only_broken_pipelines = true
+ end
+ end
+
+ def title
+ 'HipChat'
+ end
+
+ def description
+ 'Private group chat and IM'
+ end
+
+ def self.to_param
+ 'hipchat'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'token', placeholder: 'Room token', required: true },
+ { type: 'text', name: 'room', placeholder: 'Room name or ID' },
+ { type: 'checkbox', name: 'notify' },
+ { type: 'select', name: 'color', choices: %w(yellow red green purple gray random) },
+ { type: 'text', name: 'api_version',
+ placeholder: 'Leave blank for default (v2)' },
+ { type: 'text', name: 'server',
+ placeholder: 'Leave blank for default. https://hipchat.example.com' },
+ { type: 'checkbox', name: 'notify_only_broken_pipelines' }
+ ]
+ end
+
+ def self.supported_events
+ %w(push issue confidential_issue merge_request note confidential_note tag_push pipeline)
+ end
+
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ message = create_message(data)
+ return unless message.present?
+
+ gate[room].send('GitLab', message, message_options(data)) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def test(data)
+ begin
+ result = execute(data)
+ rescue StandardError => error
+ return { success: false, result: error }
+ end
+
+ { success: true, result: result }
+ end
+
+ private
+
+ def gate
+ options = { api_version: api_version.present? ? api_version : 'v2' }
+ options[:server_url] = server unless server.blank?
+ @gate ||= HipChat::Client.new(token, options)
+ end
+
+ def message_options(data = nil)
+ { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) }
+ end
+
+ def create_message(data)
+ object_kind = data[:object_kind]
+
+ case object_kind
+ when "push", "tag_push"
+ create_push_message(data)
+ when "issue"
+ create_issue_message(data) unless update?(data)
+ when "merge_request"
+ create_merge_request_message(data) unless update?(data)
+ when "note"
+ create_note_message(data)
+ when "pipeline"
+ create_pipeline_message(data) if should_pipeline_be_notified?(data)
+ end
+ end
+
+ def render_line(text)
+ markdown(text.lines.first.chomp, pipeline: :single_line) if text
+ end
+
+ def create_push_message(push)
+ ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch'
+ ref = Gitlab::Git.ref_name(push[:ref])
+
+ before = push[:before]
+ after = push[:after]
+
+ message = []
+ message << "#{push[:user_name]} "
+
+ if Gitlab::Git.blank_ref?(before)
+ message << "pushed new #{ref_type} <a href=\""\
+ "#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"\
+ " to #{project_link}\n"
+ elsif Gitlab::Git.blank_ref?(after)
+ message << "removed #{ref_type} <b>#{ref}</b> from <a href=\"#{project.web_url}\">#{project_name}</a> \n"
+ else
+ message << "pushed to #{ref_type} <a href=\""\
+ "#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
+ message << "of <a href=\"#{project.web_url}\">#{project.full_name.gsub!(/\s/, '')}</a> "
+ message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
+
+ push[:commits].take(MAX_COMMITS).each do |commit|
+ message << "<br /> - #{render_line(commit[:message])} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
+ end
+
+ if push[:commits].count > MAX_COMMITS
+ message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits"
+ end
+ end
+
+ message.join
+ end
+
+ def markdown(text, options = {})
+ return "" unless text
+
+ context = {
+ project: project,
+ pipeline: :email
+ }
+
+ Banzai.render(text, context)
+
+ context.merge!(options)
+
+ html = Banzai.render_and_post_process(text, context)
+ sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt])
+
+ sanitized_html.truncate(200, separator: ' ', omission: '...')
+ end
+
+ def create_issue_message(data)
+ user_name = data[:user][:name]
+
+ obj_attr = data[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ title = render_line(obj_attr[:title])
+ state = obj_attr[:state]
+ issue_iid = obj_attr[:iid]
+ issue_url = obj_attr[:url]
+ description = obj_attr[:description]
+
+ issue_link = "<a href=\"#{issue_url}\">issue ##{issue_iid}</a>"
+
+ message = ["#{user_name} #{state} #{issue_link} in #{project_link}: <b>#{title}</b>"]
+ message << "<pre>#{markdown(description)}</pre>"
+
+ message.join
+ end
+
+ def create_merge_request_message(data)
+ user_name = data[:user][:name]
+
+ obj_attr = data[:object_attributes]
+ obj_attr = HashWithIndifferentAccess.new(obj_attr)
+ merge_request_id = obj_attr[:iid]
+ state = obj_attr[:state]
+ description = obj_attr[:description]
+ title = render_line(obj_attr[:title])
+
+ merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}"
+ merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>"
+ message = ["#{user_name} #{state} #{merge_request_link} in " \
+ "#{project_link}: <b>#{title}</b>"]
+
+ message << "<pre>#{markdown(description)}</pre>"
+ message.join
+ end
+
+ def format_title(title)
+ "<b>#{render_line(title)}</b>"
+ end
+
+ def create_note_message(data)
+ data = HashWithIndifferentAccess.new(data)
+ user_name = data[:user][:name]
+
+ obj_attr = HashWithIndifferentAccess.new(data[:object_attributes])
+ note = obj_attr[:note]
+ note_url = obj_attr[:url]
+ noteable_type = obj_attr[:noteable_type]
+ commit_id = nil
+
+ case noteable_type
+ when "Commit"
+ commit_attr = HashWithIndifferentAccess.new(data[:commit])
+ commit_id = commit_attr[:id]
+ subject_desc = commit_id
+ subject_desc = Commit.truncate_sha(subject_desc)
+ subject_type = "commit"
+ title = format_title(commit_attr[:message])
+ when "Issue"
+ subj_attr = HashWithIndifferentAccess.new(data[:issue])
+ subject_id = subj_attr[:iid]
+ subject_desc = "##{subject_id}"
+ subject_type = "issue"
+ title = format_title(subj_attr[:title])
+ when "MergeRequest"
+ subj_attr = HashWithIndifferentAccess.new(data[:merge_request])
+ subject_id = subj_attr[:iid]
+ subject_desc = "!#{subject_id}"
+ subject_type = "merge request"
+ title = format_title(subj_attr[:title])
+ when "Snippet"
+ subj_attr = HashWithIndifferentAccess.new(data[:snippet])
+ subject_id = subj_attr[:id]
+ subject_desc = "##{subject_id}"
+ subject_type = "snippet"
+ title = format_title(subj_attr[:title])
+ end
+
+ subject_html = "<a href=\"#{note_url}\">#{subject_type} #{subject_desc}</a>"
+ message = ["#{user_name} commented on #{subject_html} in #{project_link}: "]
+ message << title
+
+ message << "<pre>#{markdown(note, ref: commit_id)}</pre>"
+ message.join
+ end
+
+ def create_pipeline_message(data)
+ pipeline_attributes = data[:object_attributes]
+ pipeline_id = pipeline_attributes[:id]
+ ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
+ ref = pipeline_attributes[:ref]
+ user_name = (data[:user] && data[:user][:name]) || 'API'
+ status = pipeline_attributes[:status]
+ duration = pipeline_attributes[:duration]
+
+ branch_link = "<a href=\"#{project_url}/commits/#{CGI.escape(ref)}\">#{ref}</a>"
+ pipeline_url = "<a href=\"#{project_url}/pipelines/#{pipeline_id}\">##{pipeline_id}</a>"
+
+ "#{project_link}: Pipeline #{pipeline_url} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)"
+ end
+
+ def message_color(data)
+ pipeline_status_color(data) || color || 'yellow'
+ end
+
+ def pipeline_status_color(data)
+ return unless data && data[:object_kind] == 'pipeline'
+
+ case data[:object_attributes][:status]
+ when 'success'
+ 'green'
+ else
+ 'red'
+ end
+ end
+
+ def project_name
+ project.full_name.gsub(/\s/, '')
+ end
+
+ def project_url
+ project.web_url
+ end
+
+ def project_link
+ "<a href=\"#{project_url}\">#{project_name}</a>"
+ end
+
+ def update?(data)
+ data[:object_attributes][:action] == 'update'
+ end
+
+ def humanized_status(status)
+ case status
+ when 'success'
+ 'passed'
+ else
+ status
+ end
+ end
+
+ def should_pipeline_be_notified?(data)
+ case data[:object_attributes][:status]
+ when 'success'
+ !notify_only_broken_pipelines?
+ when 'failed'
+ true
+ else
+ false
+ end
+ end
+end
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
index 36ec33d3e3e..58c2b98e524 100644
--- a/app/models/releases/link.rb
+++ b/app/models/releases/link.rb
@@ -6,7 +6,7 @@ module Releases
belongs_to :release
- validates :url, presence: true, url: { protocols: %w(http https ftp) }, uniqueness: { scope: :release }
+ validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release }
scope :sorted, -> { order(created_at: :desc) }
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index 5610cfe0f24..b2fd5394a03 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -17,7 +17,7 @@ class RemoteMirror < ApplicationRecord
belongs_to :project, inverse_of: :remote_mirrors
- validates :url, presence: true, public_url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true }
+ validates :url, presence: true, public_url: { schemes: %w(ssh git http https), allow_blank: true, enforce_user: true }
before_save :set_new_remote_name, if: :mirror_url_changed?
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 51ab2247a03..574ce12b309 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -299,14 +299,13 @@ class Repository
end
end
- def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:, path: nil)
+ def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
raw_repository.archive_metadata(
ref,
storage_path,
project.path,
format,
- append_sha: append_sha,
- path: path
+ append_sha: append_sha
)
end
diff --git a/app/models/service.rb b/app/models/service.rb
index c6d5eb353dc..de549becf71 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -255,6 +255,7 @@ class Service < ApplicationRecord
external_wiki
flowdock
hangouts_chat
+ hipchat
irker
jira
kubernetes
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index fce4040e390..a8478e3a904 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -51,6 +51,8 @@ module Git
end
def create_pipelines
+ return unless params.fetch(:create_pipelines, true)
+
Ci::CreatePipelineService
.new(project, current_user, push_data)
.execute(:push, pipeline_options)
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index 74aad3b1c94..8f1f25a7307 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -62,12 +62,16 @@ module Groups
end
def can_use_visibility_level?
- unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)
deny_visibility_level(@group)
return false
end
true
end
+
+ def visibility_level
+ params[:visibility].present? ? Gitlab::VisibilityLevel.level_value(params[:visibility]) : params[:visibility_level]
+ end
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 3723c5ef7d7..4ea40e3c8ce 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -159,8 +159,8 @@ module Projects
log_message << " Project ID: #{@project.id}" if @project&.id
Rails.logger.error(log_message)
- if @project
- @project.import_state.mark_as_failed(message) if @project.persisted? && @project.import?
+ if @project && @project.persisted? && @project.import_state
+ @project.import_state.mark_as_failed(message)
end
@project
diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb
new file mode 100644
index 00000000000..273e15ef925
--- /dev/null
+++ b/app/validators/addressable_url_validator.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+# AddressableUrlValidator
+#
+# Custom validator for URLs. This is a stricter version of UrlValidator - it also checks
+# for using the right protocol, but it actually parses the URL checking for any syntax errors.
+# The regex is also different from `URI` as we use `Addressable::URI` here.
+#
+# By default, only URLs for the HTTP(S) schemes will be considered valid.
+# Provide a `:schemes` option to configure accepted schemes.
+#
+# Example:
+#
+# class User < ActiveRecord::Base
+# validates :personal_url, addressable_url: true
+#
+# validates :ftp_url, addressable_url: { schemes: %w(ftp) }
+#
+# validates :git_url, addressable_url: { schemes: %w(http https ssh git) }
+# end
+#
+# This validator can also block urls pointing to localhost or the local network to
+# protect against Server-side Request Forgery (SSRF), or check for the right port.
+#
+# Configuration options:
+# * <tt>message</tt> - A custom error message (default is: "must be a valid URL").
+# * <tt>schemes</tt> - Array of URI schemes. Default: +['http', 'https']+
+# * <tt>allow_localhost</tt> - Allow urls pointing to +localhost+. Default: +true+
+# * <tt>allow_local_network</tt> - Allow urls pointing to private network addresses. Default: +true+
+# * <tt>allow_blank</tt> - Allow urls to be +blank+. Default: +false+
+# * <tt>allow_nil</tt> - Allow urls to be +nil+. Default: +false+
+# * <tt>ports</tt> - Allowed ports. Default: +all+.
+# * <tt>enforce_user</tt> - Validate user format. Default: +false+
+# * <tt>enforce_sanitization</tt> - Validate that there are no html/css/js tags. Default: +false+
+#
+# Example:
+# class User < ActiveRecord::Base
+# validates :personal_url, addressable_url: { allow_localhost: false, allow_local_network: false}
+#
+# validates :web_url, addressable_url: { ports: [80, 443] }
+# end
+class AddressableUrlValidator < ActiveModel::EachValidator
+ attr_reader :record
+
+ BLOCKER_VALIDATE_OPTIONS = {
+ schemes: %w(http https),
+ ports: [],
+ allow_localhost: true,
+ allow_local_network: true,
+ ascii_only: false,
+ enforce_user: false,
+ enforce_sanitization: false
+ }.freeze
+
+ DEFAULT_OPTIONS = BLOCKER_VALIDATE_OPTIONS.merge({
+ message: 'must be a valid URL'
+ }).freeze
+
+ def initialize(options)
+ options.reverse_merge!(DEFAULT_OPTIONS)
+
+ super(options)
+ end
+
+ def validate_each(record, attribute, value)
+ @record = record
+
+ unless value.present?
+ record.errors.add(attribute, options.fetch(:message))
+ return
+ end
+
+ value = strip_value!(record, attribute, value)
+
+ Gitlab::UrlBlocker.validate!(value, blocker_args)
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ record.errors.add(attribute, "is blocked: #{e.message}")
+ end
+
+ private
+
+ def strip_value!(record, attribute, value)
+ new_value = value.strip
+ return value if new_value == value
+
+ record.public_send("#{attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def current_options
+ options.map do |option, value|
+ [option, value.is_a?(Proc) ? value.call(record) : value]
+ end.to_h
+ end
+
+ def blocker_args
+ current_options.slice(*BLOCKER_VALIDATE_OPTIONS.keys).tap do |args|
+ if self.class.allow_setting_local_requests?
+ args[:allow_localhost] = args[:allow_local_network] = true
+ end
+ end
+ end
+
+ def self.allow_setting_local_requests?
+ # We cannot use Gitlab::CurrentSettings as ApplicationSetting itself
+ # uses UrlValidator to validate urls. This ends up in a cycle
+ # when Gitlab::CurrentSettings creates an ApplicationSetting which then
+ # calls this validator.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab-ee/issues/9833
+ ApplicationSetting.current&.allow_local_requests_from_hooks_and_services?
+ end
+end
diff --git a/app/validators/public_url_validator.rb b/app/validators/public_url_validator.rb
index 3ff880deedd..91847c5d866 100644
--- a/app/validators/public_url_validator.rb
+++ b/app/validators/public_url_validator.rb
@@ -2,7 +2,7 @@
# PublicUrlValidator
#
-# Custom validator for URLs. This validator works like UrlValidator but
+# Custom validator for URLs. This validator works like AddressableUrlValidator but
# it blocks by default urls pointing to localhost or the local network.
#
# This validator accepts the same params UrlValidator does.
@@ -12,17 +12,20 @@
# class User < ActiveRecord::Base
# validates :personal_url, public_url: true
#
-# validates :ftp_url, public_url: { protocols: %w(ftp) }
+# validates :ftp_url, public_url: { schemes: %w(ftp) }
#
# validates :git_url, public_url: { allow_localhost: true, allow_local_network: true}
# end
#
-class PublicUrlValidator < UrlValidator
- private
+class PublicUrlValidator < AddressableUrlValidator
+ DEFAULT_OPTIONS = {
+ allow_localhost: false,
+ allow_local_network: false
+ }.freeze
- def default_options
- # By default block all urls pointing to localhost or the local network
- super.merge(allow_localhost: false,
- allow_local_network: false)
+ def initialize(options)
+ options.reverse_merge!(DEFAULT_OPTIONS)
+
+ super(options)
end
end
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
deleted file mode 100644
index 3fd015c3cf5..00000000000
--- a/app/validators/url_validator.rb
+++ /dev/null
@@ -1,104 +0,0 @@
-# frozen_string_literal: true
-
-# UrlValidator
-#
-# Custom validator for URLs.
-#
-# By default, only URLs for the HTTP(S) protocols will be considered valid.
-# Provide a `:protocols` option to configure accepted protocols.
-#
-# Example:
-#
-# class User < ActiveRecord::Base
-# validates :personal_url, url: true
-#
-# validates :ftp_url, url: { protocols: %w(ftp) }
-#
-# validates :git_url, url: { protocols: %w(http https ssh git) }
-# end
-#
-# This validator can also block urls pointing to localhost or the local network to
-# protect against Server-side Request Forgery (SSRF), or check for the right port.
-#
-# The available options are:
-# - protocols: Allowed protocols. Default: http and https
-# - allow_localhost: Allow urls pointing to localhost. Default: true
-# - allow_local_network: Allow urls pointing to private network addresses. Default: true
-# - ports: Allowed ports. Default: all.
-# - enforce_user: Validate user format. Default: false
-# - enforce_sanitization: Validate that there are no html/css/js tags. Default: false
-#
-# Example:
-# class User < ActiveRecord::Base
-# validates :personal_url, url: { allow_localhost: false, allow_local_network: false}
-#
-# validates :web_url, url: { ports: [80, 443] }
-# end
-class UrlValidator < ActiveModel::EachValidator
- DEFAULT_PROTOCOLS = %w(http https).freeze
-
- attr_reader :record
-
- def validate_each(record, attribute, value)
- @record = record
-
- unless value.present?
- record.errors.add(attribute, 'must be a valid URL')
- return
- end
-
- value = strip_value!(record, attribute, value)
-
- Gitlab::UrlBlocker.validate!(value, blocker_args)
- rescue Gitlab::UrlBlocker::BlockedUrlError => e
- record.errors.add(attribute, "is blocked: #{e.message}")
- end
-
- private
-
- def strip_value!(record, attribute, value)
- new_value = value.strip
- return value if new_value == value
-
- record.public_send("#{attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend
- end
-
- def default_options
- # By default the validator doesn't block any url based on the ip address
- {
- protocols: DEFAULT_PROTOCOLS,
- ports: [],
- allow_localhost: true,
- allow_local_network: true,
- ascii_only: false,
- enforce_user: false,
- enforce_sanitization: false
- }
- end
-
- def current_options
- options = self.options.map do |option, value|
- [option, value.is_a?(Proc) ? value.call(record) : value]
- end.to_h
-
- default_options.merge(options)
- end
-
- def blocker_args
- current_options.slice(*default_options.keys).tap do |args|
- if allow_setting_local_requests?
- args[:allow_localhost] = args[:allow_local_network] = true
- end
- end
- end
-
- def allow_setting_local_requests?
- # We cannot use Gitlab::CurrentSettings as ApplicationSetting itself
- # uses UrlValidator to validate urls. This ends up in a cycle
- # when Gitlab::CurrentSettings creates an ApplicationSetting which then
- # calls this validator.
- #
- # See https://gitlab.com/gitlab-org/gitlab-ee/issues/9833
- ApplicationSetting.current&.allow_local_requests_from_hooks_and_services?
- end
-end
diff --git a/app/views/projects/_classification_policy_settings.html.haml b/app/views/projects/_classification_policy_settings.html.haml
index 57c7a718d53..5a766ab024f 100644
--- a/app/views/projects/_classification_policy_settings.html.haml
+++ b/app/views/projects/_classification_policy_settings.html.haml
@@ -1,8 +1,6 @@
- if ::Gitlab::ExternalAuthorization.enabled?
- .form-group
- = f.label :external_authorization_classification_label, class: 'label-bold' do
- = s_('ExternalAuthorizationService|Classification Label')
- %span.light (optional)
+ .form-group.col-md-9
+ = f.label :external_authorization_classification_label, _('Classification Label (optional)'), class: 'label-bold'
= f.text_field :external_authorization_classification_label, class: "form-control"
%span.form-text.text-muted
= external_classification_label_help_message
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index 409b6dba9ca..1056977886a 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -1,42 +1,33 @@
- return unless Gitlab::CurrentSettings.project_export_enabled?
- project = local_assigns.fetch(:project)
-- expanded = Rails.env.test?
-%section.settings.no-animate#js-export-project{ class: ('expanded' if expanded) }
- .settings-header
- %h4
- Export project
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.
- .settings-content
- .bs-callout.bs-callout-info
- %p.append-bottom-0
- %p
- The following items will be exported:
- %ul
- %li Project and wiki repositories
- %li Project uploads
- %li Project configuration, including services
- %li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
- %li LFS objects
- %p
- The following items will NOT be exported:
- %ul
- %li Job traces and artifacts
- %li Container registry images
- %li CI variables
- %li Webhooks
- %li Any encrypted tokens
- %p
- Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
- - if project.export_status == :finished
- = link_to 'Download export', download_export_project_path(project),
- rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
- = link_to 'Generate new export', generate_new_export_project_path(project),
- method: :post, class: "btn btn-default"
- - else
- = link_to 'Export project', export_project_path(project),
- method: :post, class: "btn btn-default"
+.sub-section
+ %h4= _('Export project')
+ %p= _('Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the "New Project" page.')
+
+ .bs-callout.bs-callout-info
+ %p.append-bottom-0
+ %p= _('The following items will be exported:')
+ %ul
+ %li= _('Project and wiki repositories')
+ %li= _('Project uploads')
+ %li= _('Project configuration, including services')
+ %li= _('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities')
+ %li= _('LFS objects')
+ %p= _('The following items will NOT be exported:')
+ %ul
+ %li= _('Job traces and artifacts')
+ %li= _('Container registry images')
+ %li= _('CI variables')
+ %li= _('Webhooks')
+ %li= _('Any encrypted tokens')
+ %p= _('Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.')
+ - if project.export_status == :finished
+ = link_to _('Download export'), download_export_project_path(project),
+ rel: 'nofollow', download: '', method: :get, class: "btn btn-default"
+ = link_to _('Generate new export'), generate_new_export_project_path(project),
+ method: :post, class: "btn btn-default"
+ - else
+ = link_to _('Export project'), export_project_path(project),
+ method: :post, class: "btn btn-default"
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index acd63de2277..4eb53faa6ff 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -8,20 +8,30 @@
%span.sr-only= _('Select Archive Format')
= sprite_icon("arrow-down")
%ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
- %li.dropdown-bold-header= _('Download source code')
- %li.dropdown-menu-content
- = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: nil
- - if directory?
- %li.separator
- %li.dropdown-bold-header= _('Download this directory')
- %li.dropdown-menu-content
- = render 'projects/buttons/download_links', project: project, ref: ref, archive_prefix: archive_prefix, path: @path
+ %li.dropdown-header
+ #{ _('Source code') }
+ %li
+ = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'zip'), rel: 'nofollow', download: '' do
+ %span= _('Download zip')
+ %li
+ = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.gz'), rel: 'nofollow', download: '' do
+ %span= _('Download tar.gz')
+ %li
+ = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.bz2'), rel: 'nofollow', download: '' do
+ %span= _('Download tar.bz2')
+ %li
+ = link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar'), rel: 'nofollow', download: '' do
+ %span= _('Download tar')
+
- if pipeline && pipeline.latest_builds_with_artifacts.any?
- %li.separator
- %li.dropdown-bold-header= _('Download artifacts')
+ %li.dropdown-header Artifacts
- unless pipeline.latest?
- %span.unclickable= ci_status_for_statuseable(project.pipeline_for(ref))
- %li.dropdown-header= _('Previous Artifacts')
+ - latest_pipeline = project.pipeline_for(ref)
+ %li
+ .unclickable= ci_status_for_statuseable(latest_pipeline)
+ %li.dropdown-header Previous Artifacts
- pipeline.latest_builds_with_artifacts.each do |job|
%li
- = link_to job.name, latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: ''
+ = link_to latest_succeeded_project_artifacts_path(project, "#{ref}/download", job: job.name), rel: 'nofollow', download: '' do
+ %span
+ #{s_('DownloadArtifacts|Download')} '#{job.name}'
diff --git a/app/views/projects/buttons/_download_links.html.haml b/app/views/projects/buttons/_download_links.html.haml
deleted file mode 100644
index 47a1704f946..00000000000
--- a/app/views/projects/buttons/_download_links.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-%ul
- %li.d-inline-block.m-0.p-0
- = link_to 'zip', project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: 'zip'), rel: 'nofollow', download: '', class: 'btn btn-primary btn-xs'
- %li.d-inline-block.m-0.p-0
- = link_to 'tar.gz', project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: 'tar.gz'), rel: 'nofollow', download: '', class: 'btn btn-xs'
- %li.d-inline-block.m-0.p-0
- = link_to 'tar.bz2', project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: 'tar.bz2'), rel: 'nofollow', download: '', class: 'btn btn-xs'
- %li.d-inline-block.m-0.p-0
- = link_to 'tar', project_archive_path(project, id: tree_join(ref, archive_prefix), path: path, format: 'tar'), rel: 'nofollow', download: '', class: 'btn btn-xs'
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index abf2fb7dc57..1a3e4a5d608 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -3,210 +3,155 @@
- @content_class = "limit-container-width" unless fluid_layout
- expanded = Rails.env.test?
-.project-edit-container
- %section.settings.general-settings.no-animate#js-general-project-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, tags, avatar')
- %button.btn.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Update your project name, tags, description and avatar.')
-
- .settings-content
- .project-edit-errors
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-general-project-settings' }
- %fieldset
- .row
- .form-group.col-md-9
- = f.label :name, class: 'label-bold', for: 'project_name_edit' do
- Project name
- = f.text_field :name, class: "form-control", id: "project_name_edit"
-
- .form-group.col-md-3
- = f.label :id, class: 'label-bold' do
- Project ID
- = f.text_field :id, class: 'form-control', readonly: true
-
- .form-group
- = f.label :description, class: 'label-bold' do
- Project description
- %span.light (optional)
- = f.text_area :description, class: "form-control", rows: 3, maxlength: 250
-
- = render 'projects/classification_policy_settings', f: f
-
- = render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
-
- .form-group
- = f.label :tag_list, "Topics", class: 'label-bold'
- = f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control"
- %p.form-text.text-muted Separate topics with commas.
-
- .form-group.prepend-top-default.append-bottom-20
- .avatar-container.s90
- = project_icon(@project, alt: _('Project avatar'), class: 'avatar project-avatar s90')
- = f.label :avatar, _('Project avatar'), class: 'label-bold d-block'
- = render 'shared/choose_avatar_button', f: f
- - if @project.avatar?
- %hr
- = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-link'
-
- = f.submit 'Save changes', class: "btn btn-success js-btn-success-general-project-settings"
-
- %section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions')
- %button.btn.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions.')
-
- .settings-content
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
- -# haml-lint:disable InlineJavaScript
- %script.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project)
- .js-project-permissions-form
- = f.submit 'Save changes', class: "btn btn-success"
-
- = render_if_exists 'projects/issues_settings'
-
- %section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
- %button.btn.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Choose your merge method, set up a default merge request description template.')
-
- .settings-content
- = render_if_exists 'shared/promotions/promote_mr_features'
-
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form" }, authenticity_token: true do |f|
- %input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
- = render 'projects/merge_request_settings', form: f
- = f.submit 'Save changes', class: "btn btn-success qa-save-merge-request-changes"
-
- = render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
-
-
- %section.settings.no-animate{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
- = s_('ProjectSettings|Badges')
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? 'Collapse' : 'Expand'
- %p
- = s_('ProjectSettings|Customize your project badges.')
- = link_to s_('ProjectSettings|Learn more about badges.'), help_page_path('user/project/badges')
- .settings-content
- = render 'shared/badges/badge_settings'
-
- = render_if_exists 'projects/service_desk_settings'
- = render 'export', project: @project
-
- %section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced')
- %button.btn.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Housekeeping, export, path, transfer, remove, archive.')
-
- .settings-content
+%section.settings.general-settings.no-animate.expanded#js-general-settings
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, topics, avatar')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= _('Collapse')
+ %p= _('Update your project name, topics, description and avatar.')
+ .settings-content= render 'projects/settings/general'
+
+%section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions.')
+
+ .settings-content
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
+ %template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project)
+ .js-project-permissions-form
+ = f.submit _('Save changes'), class: "btn btn-success"
+
+%section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Choose your merge method, set up a default merge request description template.')
+
+ .settings-content
+ = render_if_exists 'shared/promotions/promote_mr_features'
+
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form js-mr-settings-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
+ = render 'projects/merge_request_settings', form: f
+ = f.submit _('Save changes'), class: "btn btn-success qa-save-merge-request-changes"
+
+= render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
+
+
+%section.settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
+ = s_('ProjectSettings|Badges')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = s_('ProjectSettings|Customize your project badges.')
+ = link_to s_('ProjectSettings|Learn more about badges.'), help_page_path('user/project/badges')
+ .settings-content
+ = render 'shared/badges/badge_settings'
+
+= render_if_exists 'projects/settings/default_issue_template'
+
+= render_if_exists 'projects/service_desk_settings'
+
+%section.qa-advanced-settings.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
+ %p= _('Housekeeping, export, path, transfer, remove, archive.')
+
+ .settings-content
+ .sub-section
+ %h4= _('Housekeeping')
+ %p= _('Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.')
+ = link_to _('Run housekeeping'), housekeeping_project_path(@project),
+ method: :post, class: "btn btn-default"
+
+ = render 'export', project: @project
+
+ - if can? current_user, :archive_project, @project
.sub-section
- %h4 Housekeeping
- %p
- Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.
- = link_to 'Run housekeeping', housekeeping_project_path(@project),
- method: :post, class: "btn btn-default"
- - if can? current_user, :archive_project, @project
- .sub-section
- %h4.warning-title
- - if @project.archived?
- Unarchive project
- - else
- Archive project
+ %h4.warning-title
- if @project.archived?
- %p
- Unarchiving the project will restore people's ability to make changes to it.
- The repository can be committed to, and issues, comments and other entities can be created.
- %strong Once active this project shows up in the search and on the dashboard.
- = link_to 'Unarchive project', unarchive_project_path(@project),
- data: { confirm: "Are you sure that you want to unarchive this project?" },
- method: :post, class: "btn btn-success"
+ = _('Unarchive project')
- else
- %p
- Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
- %strong The repository cannot be committed to, and no issues, comments or other entities can be created.
- = link_to 'Archive project', archive_project_path(@project),
- data: { confirm: "Are you sure that you want to archive this project?" },
- method: :post, class: "btn btn-warning"
- .sub-section.rename-repository
- %h4.warning-title
- Rename repository
- = render 'projects/errors'
- = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
- .form-group.project_name_holder
- = f.label :name, class: 'label-bold' do
- Project name
- .form-group
- = f.text_field :name, class: "form-control"
+ = _('Archive project')
+ - if @project.archived?
+ %p= _("Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>").html_safe
+ = link_to _('Unarchive project'), unarchive_project_path(@project),
+ data: { confirm: _("Are you sure that you want to unarchive this project?") },
+ method: :post, class: "btn btn-success"
+ - else
+ %p= _("Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>").html_safe
+ = link_to _('Archive project'), archive_project_path(@project),
+ data: { confirm: _("Are you sure that you want to archive this project?") },
+ method: :post, class: "btn btn-warning"
+ .sub-section.rename-repository
+ %h4.warning-title= _('Change path')
+ = render 'projects/errors'
+ = form_for([@project.namespace.becomes(Namespace), @project]) do |f|
+ .form-group
+ = f.label :path, _('Path'), class: 'label-bold'
+ .form-group
+ .input-group
+ .input-group-prepend
+ .input-group-text
+ #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/
+ = f.text_field :path, class: 'form-control qa-project-path-field h-auto'
+ %ul
+ %li= _("Be careful. Renaming a project's repository can have unintended side effects.")
+ %li= _('You will need to update your local repositories to point to the new location.')
+ - if @project.deployment_platform.present?
+ %li= _('Your deployment services will be broken, you will need to manually fix the services after renaming.')
+ = f.submit _('Change path'), class: "btn btn-warning qa-change-path-button"
+
+ - if can?(current_user, :change_namespace, @project)
+ .sub-section
+ %h4.danger-title= _('Transfer project')
+ = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
.form-group
- = f.label :path, class: 'label-bold' do
- %span Path
+ = label_tag :new_namespace_id, nil, class: 'label-bold' do
+ %span= _('Select a new namespace')
.form-group
- .input-group
- .input-group-prepend
- .input-group-text
- #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/
- = f.text_field :path, class: 'form-control'
+ = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2'
%ul
- %li Be careful. Renaming a project's repository can have unintended side effects.
- %li You will need to update your local repositories to point to the new location.
- - if @project.deployment_platform.present?
- %li Your deployment services will be broken, you will need to manually fix the services after renaming.
- = f.submit 'Rename project', class: "btn btn-warning"
- - if can?(current_user, :change_namespace, @project)
- .sub-section
- %h4.danger-title
- Transfer project
- = form_for([@project.namespace.becomes(Namespace), @project], url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'js-project-transfer-form' } ) do |f|
- .form-group
- = label_tag :new_namespace_id, nil, class: 'label-bold' do
- %span Select a new namespace
- .form-group
- = select_tag :new_namespace_id, namespaces_options(nil), include_blank: true, class: 'select2'
- %ul
- %li Be careful. Changing the project's namespace can have unintended side effects.
- %li You can only transfer the project to namespaces you manage.
- %li You will need to update your local repositories to point to the new location.
- %li Project visibility level will be changed to match namespace rules when transferring to a group.
- = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- - if @project.forked? && can?(current_user, :remove_fork_project, @project)
- .sub-section
- %h4.danger-title
- Remove fork relationship
+ %li= _("Be careful. Changing the project's namespace can have unintended side effects.")
+ %li= _('You can only transfer the project to namespaces you manage.')
+ %li= _('You will need to update your local repositories to point to the new location.')
+ %li= _('Project visibility level will be changed to match namespace rules when transferring to a group.')
+ = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+
+ - if @project.forked? && can?(current_user, :remove_fork_project, @project)
+ .sub-section
+ %h4.danger-title= _('Remove fork relationship')
+ %p
+ = _('This will remove the fork relationship to source project')
+ = succeed "." do
+ - if @project.fork_source
+ = link_to(fork_source_name(@project), project_path(@project.fork_source))
+ - else
+ = fork_source_name(@project)
+ = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
%p
- This will remove the fork relationship to source project
- = succeed "." do
- - if @project.fork_source
- = link_to(fork_source_name(@project), project_path(@project.fork_source))
- - else
- = fork_source_name(@project)
- = form_for([@project.namespace.becomes(Namespace), @project], url: remove_fork_project_path(@project), method: :delete, remote: true, html: { class: 'transfer-project' }) do |f|
- %p
- %strong Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.
- = button_to 'Remove fork relationship', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
- - if can?(current_user, :remove_project, @project)
- .sub-section
- %h4.danger-title
- Remove project
+ %strong= _('Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source.')
+ = button_to _('Remove fork relationship'), '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_fork_project_message(@project) }
+
+ - if can?(current_user, :remove_project, @project)
+ .sub-section
+ %h4.danger-title= _('Remove project')
+ %p= _('Removing the project will delete its repository and all related resources including issues, merge requests etc.')
+ = form_tag(project_path(@project), method: :delete) do
%p
- Removing the project will delete its repository and all related resources including issues, merge requests etc.
- = form_tag(project_path(@project), method: :delete) do
- %p
- %strong Removed projects cannot be restored!
- = button_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
+ %strong= _('Removed projects cannot be restored!')
+ = button_to _('Remove project'), '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) }
.save-project-loader.hide
.center
%h2
%i.fa.fa-spinner.fa-spin
- Saving project.
- %p Please wait a moment, this page will automatically refresh when ready.
+ = _('Saving project.')
+ %p= _('Please wait a moment, this page will automatically refresh when ready.')
= render 'shared/confirm_modal', phrase: @project.path
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index f1b14d4c4d1..4b2417ff43b 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -22,6 +22,6 @@
= s_('ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits.') % { branch_name: @ref }
%input#brush_change{ :type => "hidden" }
.graphs.row
- #contributors-master
+ #contributors-master.svg-w-100
#contributors.clearfix
- %ol.contributors-list.row
+ %ol.contributors-list.svg-w-100.row
diff --git a/app/views/projects/settings/_general.html.haml b/app/views/projects/settings/_general.html.haml
new file mode 100644
index 00000000000..380430ff52b
--- /dev/null
+++ b/app/views/projects/settings/_general.html.haml
@@ -0,0 +1,42 @@
+= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project js-general-settings-form" }, authenticity_token: true do |f|
+ %input{ name: 'update_section', type: 'hidden', value: 'js-general-settings' }
+ = form_errors(@project)
+
+ %fieldset
+ .row
+ .form-group.col-md-5
+ = f.label :name, class: 'label-bold', for: 'project_name_edit' do
+ = _('Project name')
+ = f.text_field :name, class: 'form-control qa-project-name-field', id: "project_name_edit"
+
+ .form-group.col-md-7
+ = f.label :id, class: 'label-bold' do
+ = _('Project ID')
+ = f.text_field :id, class: 'form-control w-auto', readonly: true
+
+ .row
+ .form-group.col-md-9
+ = f.label :tag_list, _('Topics'), class: 'label-bold'
+ = f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control"
+ %p.form-text.text-muted= _('Separate topics with commas.')
+
+ .row
+ .form-group.col-md-9
+ = f.label :description, _('Project description (optional)'), class: 'label-bold'
+ = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
+
+ .row= render_if_exists 'projects/classification_policy_settings', f: f
+
+ .row= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :project
+
+ .form-group.prepend-top-default.append-bottom-20
+ .avatar-container.s90
+ = project_icon(@project, alt: _('Project avatar'), class: 'avatar project-avatar s90')
+ = f.label :avatar, _('Project avatar'), class: 'label-bold d-block'
+ = render 'shared/choose_avatar_button', f: f
+ - if @project.avatar?
+ %hr
+ = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-link'
+
+
+ = f.submit _('Save changes'), class: "btn btn-success mt-4 qa-save-naming-topics-avatar-button"
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 1dcf4369253..3967c8148d2 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -2,8 +2,7 @@
.modal-dialog
.modal-content
.modal-header
- %h3.page-title
- Confirmation required
+ %h3.page-title= _('Confirmation required')
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
@@ -11,8 +10,7 @@
%p.text-danger.js-confirm-text
%p
- This action can lead to data loss.
- To prevent accidental actions we ask you to confirm your intention.
+ %span.js-warning-text= _('This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.')
%br
Please type
%code.js-confirm-danger-match= phrase
@@ -21,4 +19,4 @@
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
.form-actions
- = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit"
+ = submit_tag _('Confirm'), class: "btn btn-danger js-confirm-danger-submit"
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index a5554f07699..337efa7919b 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -3,6 +3,8 @@
class PostReceive
include ApplicationWorker
+ PIPELINE_PROCESS_LIMIT = 4
+
def perform(gl_repository, identifier, changes, push_options = {})
project, repo_type = Gitlab::GlRepository.parse(gl_repository)
@@ -36,23 +38,24 @@ class PostReceive
return false
end
- post_received.changes_refs do |oldrev, newrev, ref|
- if Gitlab::Git.tag_ref?(ref)
- Git::TagPushService.new(
- post_received.project,
- @user,
- oldrev: oldrev,
- newrev: newrev,
- ref: ref,
- push_options: post_received.push_options).execute
- elsif Gitlab::Git.branch_ref?(ref)
- Git::BranchPushService.new(
+ post_received.enum_for(:changes_refs).with_index do |(oldrev, newrev, ref), index|
+ service_klass =
+ if Gitlab::Git.tag_ref?(ref)
+ Git::TagPushService
+ elsif Gitlab::Git.branch_ref?(ref)
+ Git::BranchPushService
+ end
+
+ if service_klass
+ service_klass.new(
post_received.project,
@user,
oldrev: oldrev,
newrev: newrev,
ref: ref,
- push_options: post_received.push_options).execute
+ push_options: post_received.push_options,
+ create_pipelines: index < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, post_received.project)
+ ).execute
end
changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref)
diff --git a/changelogs/unreleased/24704-download-repository-path.yml b/changelogs/unreleased/24704-download-repository-path.yml
deleted file mode 100644
index ff3082bec45..00000000000
--- a/changelogs/unreleased/24704-download-repository-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Download a folder from repository
-merge_request: 26532
-author: kiameisomabes
-type: added
diff --git a/changelogs/unreleased/24985-align-urlvalidator-to-validate_url-gem-implementation.yml b/changelogs/unreleased/24985-align-urlvalidator-to-validate_url-gem-implementation.yml
new file mode 100644
index 00000000000..1143e4effea
--- /dev/null
+++ b/changelogs/unreleased/24985-align-urlvalidator-to-validate_url-gem-implementation.yml
@@ -0,0 +1,5 @@
+---
+title: "Align UrlValidator to validate_url gem implementation"
+merge_request: 27194
+author: Horatiu Eugen Vlad
+type: fixed
diff --git a/changelogs/unreleased/60304-long-file-names-in-mr-diffs-cause-horizontal-scrolling.yml b/changelogs/unreleased/60304-long-file-names-in-mr-diffs-cause-horizontal-scrolling.yml
new file mode 100644
index 00000000000..ec5e9e4703b
--- /dev/null
+++ b/changelogs/unreleased/60304-long-file-names-in-mr-diffs-cause-horizontal-scrolling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix long file header names bug in diffs
+merge_request: 27233
+author:
+type: fixed
diff --git a/changelogs/unreleased/allow-to-use-untrusted-ruby-syntax.yml b/changelogs/unreleased/allow-to-use-untrusted-ruby-syntax.yml
deleted file mode 100644
index 731c9c10b00..00000000000
--- a/changelogs/unreleased/allow-to-use-untrusted-ruby-syntax.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow to use untrusted Regexp via feature flag
-merge_request: 26905
-author:
-type: deprecated
diff --git a/changelogs/unreleased/fix-api-group-visibility.yml b/changelogs/unreleased/fix-api-group-visibility.yml
new file mode 100644
index 00000000000..7fbdcd729c6
--- /dev/null
+++ b/changelogs/unreleased/fix-api-group-visibility.yml
@@ -0,0 +1,5 @@
+---
+title: Fix api group visibility
+merge_request: 26896
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-pull-request-importer.yml b/changelogs/unreleased/fix-pull-request-importer.yml
deleted file mode 100644
index 5f642a0710b..00000000000
--- a/changelogs/unreleased/fix-pull-request-importer.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve performance of PR import
-merge_request: 27121
-author:
-type: performance
diff --git a/changelogs/unreleased/fixed-web-ide-merge-request-review.yml b/changelogs/unreleased/fixed-web-ide-merge-request-review.yml
new file mode 100644
index 00000000000..2799f5ee38a
--- /dev/null
+++ b/changelogs/unreleased/fixed-web-ide-merge-request-review.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed Web IDE not loading merge request files
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-bump-workhorse-version-8-6-0.yml b/changelogs/unreleased/fj-bump-workhorse-version-8-6-0.yml
new file mode 100644
index 00000000000..e53499e21ba
--- /dev/null
+++ b/changelogs/unreleased/fj-bump-workhorse-version-8-6-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Workhorse to v8.6.0
+merge_request: 27260
+author:
+type: fixed
diff --git a/changelogs/unreleased/jc-guard-against-empty-dereferenced_target.yml b/changelogs/unreleased/jc-guard-against-empty-dereferenced_target.yml
new file mode 100644
index 00000000000..94e5b4a81b7
--- /dev/null
+++ b/changelogs/unreleased/jc-guard-against-empty-dereferenced_target.yml
@@ -0,0 +1,5 @@
+---
+title: Guard against nil dereferenced_target
+merge_request: 27192
+author:
+type: fixed
diff --git a/changelogs/unreleased/limit-amount-of-created-pipelines.yml b/changelogs/unreleased/limit-amount-of-created-pipelines.yml
new file mode 100644
index 00000000000..51fdbb4d7ff
--- /dev/null
+++ b/changelogs/unreleased/limit-amount-of-created-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Process at most 4 pipelines during push
+merge_request: 27205
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-disable-diff-instrumentation.yml b/changelogs/unreleased/sh-disable-diff-instrumentation.yml
deleted file mode 100644
index 55f4c2a8510..00000000000
--- a/changelogs/unreleased/sh-disable-diff-instrumentation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disable method instrumentation for diffs
-merge_request: 27235
-author:
-type: performance
diff --git a/config/initializers/hipchat_client_patch.rb b/config/initializers/hipchat_client_patch.rb
new file mode 100644
index 00000000000..1879ecb15fb
--- /dev/null
+++ b/config/initializers/hipchat_client_patch.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+# This monkey patches the HTTParty used in https://github.com/hipchat/hipchat-rb.
+module HipChat
+ class Client
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+
+ class Room
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+
+ class User
+ connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
+ end
+end
diff --git a/db/fixtures/development/02_application_settings.rb b/db/fixtures/development/02_application_settings.rb
index d604f0be3cd..7cdc8e40b69 100644
--- a/db/fixtures/development/02_application_settings.rb
+++ b/db/fixtures/development/02_application_settings.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
puts "Creating the default ApplicationSetting record.".color(:green)
-Gitlab::CurrentSettings.current_application_settings
+ApplicationSetting.create_from_defaults
# Details https://gitlab.com/gitlab-org/gitlab-ce/issues/46241
puts "Enable hashed storage for every new projects.".color(:green)
diff --git a/db/migrate/20190107151029_remove_hipchat_services.rb b/db/migrate/20190107151029_remove_hipchat_services.rb
deleted file mode 100644
index 4741ec88907..00000000000
--- a/db/migrate/20190107151029_remove_hipchat_services.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-# See http://doc.gitlab.com/ce/development/migration_style_guide.html
-# for more information on how to write migrations for GitLab.
-
-class RemoveHipchatServices < ActiveRecord::Migration[5.0]
- DOWNTIME = false
-
- def up
- execute "DELETE FROM services WHERE type = 'HipchatService'"
- end
-
- def down
- # no-op
- end
-end
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index f406163aea0..72341a5c777 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -145,7 +145,6 @@ mountpoint
└── gitlab-data
├── builds
├── git-data
- ├── home-git
├── shared
└── uploads
```
@@ -158,16 +157,11 @@ configuration to move each data location to a subdirectory:
```ruby
git_data_dirs({"default" => { "path" => "/gitlab-nfs/gitlab-data/git-data"} })
-user['home'] = '/gitlab-nfs/gitlab-data/home'
gitlab_rails['uploads_directory'] = '/gitlab-nfs/gitlab-data/uploads'
gitlab_rails['shared_path'] = '/gitlab-nfs/gitlab-data/shared'
gitlab_ci['builds_directory'] = '/gitlab-nfs/gitlab-data/builds'
```
-To move the `git` home directory, all GitLab services must be stopped. Run
-`gitlab-ctl stop && initctl stop gitlab-runsvdir`. Then continue with the
-reconfigure.
-
Run `sudo gitlab-ctl reconfigure` to start using the central location. Please
be aware that if you had existing data you will need to manually copy/rsync it
to these new locations and then restart GitLab.
@@ -197,14 +191,13 @@ are empty before attempting a restore. Read more about the
## Multiple NFS mounts
-When using default Omnibus configuration you will need to share 5 data locations
+When using default Omnibus configuration you will need to share 4 data locations
between all GitLab cluster nodes. No other locations should be shared. The
-following are the 5 locations need to be shared:
+following are the 4 locations need to be shared:
| Location | Description | Default configuration |
| -------- | ----------- | --------------------- |
| `/var/opt/gitlab/git-data` | Git repository data. This will account for a large portion of your data | `git_data_dirs({"default" => { "path" => "/var/opt/gitlab/git-data"} })`
-| `/var/opt/gitlab/.ssh` | SSH `authorized_keys` file and keys used to import repositories from some other Git services | `user['home'] = '/var/opt/gitlab/'`
| `/var/opt/gitlab/gitlab-rails/uploads` | User uploaded attachments | `gitlab_rails['uploads_directory'] = '/var/opt/gitlab/gitlab-rails/uploads'`
| `/var/opt/gitlab/gitlab-rails/shared` | Build artifacts, GitLab Pages, LFS objects, temp files, etc. If you're using LFS this may also account for a large portion of your data | `gitlab_rails['shared_path'] = '/var/opt/gitlab/gitlab-rails/shared'`
| `/var/opt/gitlab/gitlab-ci/builds` | GitLab CI build traces | `gitlab_ci['builds_directory'] = '/var/opt/gitlab/gitlab-ci/builds'`
diff --git a/doc/api/services.md b/doc/api/services.md
index 1f84e2de7de..e8ae7ff78f4 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -449,6 +449,45 @@ Get Hangouts Chat service settings for a project.
GET /projects/:id/services/hangouts-chat
```
+## HipChat
+
+Private group chat and IM
+
+### Create/Edit HipChat service
+
+Set HipChat service for a project.
+
+```
+PUT /projects/:id/services/hipchat
+```
+
+Parameters:
+
+| Parameter | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `token` | string | true | Room token |
+| `color` | string | false | The room color |
+| `notify` | boolean | false | Enable notifications |
+| `room` | string | false |Room name or ID |
+| `api_version` | string | false | Leave blank for default (v2) |
+| `server` | string | false | Leave blank for default. For example, `https://hipchat.example.com`. |
+
+### Delete HipChat service
+
+Delete HipChat service for a project.
+
+```
+DELETE /projects/:id/services/hipchat
+```
+
+### Get HipChat service settings
+
+Get HipChat service settings for a project.
+
+```
+GET /projects/:id/services/hipchat
+```
+
## Irker (IRC gateway)
Send IRC messages, on update, to a list of recipients through an Irker gateway.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 5e44de13b51..36a0bf10416 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -437,10 +437,6 @@ Feature.enable(:allow_unsafe_ruby_regexp)
### `only`/`except` (advanced)
-> - `refs` and `kubernetes` policies introduced in GitLab 10.0.
-> - `variables` policy introduced in GitLab 10.7.
-> - `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in GitLab 11.4.
-
CAUTION: **Warning:**
This an _alpha_ feature, and it is subject to change at any time without
prior notice!
@@ -461,6 +457,8 @@ If you use multiple keys under `only` or `except`, they act as an AND. The logic
#### `only:refs`/`except:refs`
+> `refs` policy introduced in GitLab 10.0.
+
The `refs` strategy can take the same values as the
[simplified only/except configuration](#onlyexcept-basic).
@@ -477,6 +475,8 @@ deploy:
#### `only:kubernetes`/`except:kubernetes`
+> `kubernetes` policy introduced in GitLab 10.0.
+
The `kubernetes` strategy accepts only the `active` keyword.
In the example below, the `deploy` job is going to be created only when the
@@ -490,6 +490,8 @@ deploy:
#### `only:variables`/`except:variables`
+> `variables` policy introduced in GitLab 10.7.
+
The `variables` keyword is used to define variables expressions. In other words,
you can use predefined variables / project / group or
environment-scoped variables to define an expression GitLab is going to
@@ -522,6 +524,8 @@ Learn more about [variables expressions](../variables/README.md#environment-vari
#### `only:changes`/`except:changes`
+> `changes` policy [introduced][ce-19232] in GitLab 11.4.
+
Using the `changes` keyword with `only` or `except` makes it possible to define if
a job should be created based on files modified by a git push event.
@@ -2697,6 +2701,15 @@ Not to be confused with [`trigger`](#trigger-premium).
[Read more in the triggers documentation.](../triggers/README.md)
+## Processing Git pushes
+
+GitLab will create at most 4 branch and tags pipelines when
+doing pushing multiple changes in single `git push` invocation.
+
+This limitation does not affect any of the updated Merge Request pipelines,
+all updated Merge Requests will have a pipeline created when using
+[pipelines for merge requests](../merge_request_pipelines/index.md).
+
## Skipping jobs
If your commit message contains `[ci skip]` or `[skip ci]`, using any
@@ -2714,6 +2727,7 @@ git push -o ci.skip
[ce-7983]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7983
[ce-7447]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7447
[ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909
+[ce-19232]: https://gitlab.com/gitlab-org/gitlab-ce/issues/19232
[environment]: ../environments.md "CI/CD environments"
[schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules"
[variables]: ../variables/README.md "CI/CD variables"
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index 45104a1f91d..f319d00d7fe 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -10,7 +10,7 @@
1. [Testing][testing]
1. [JavaScript styleguide][js-styleguide]
1. [SCSS styleguide][scss-styleguide]
-1. [Shell commands](../shell_commands.md) created by GitLab
+1. [Shell commands (Ruby)](../shell_commands.md) created by GitLab
contributors to enhance security
1. [Database Migrations](../migration_style_guide.md)
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index aa4b3dccf7d..bd60ee25bff 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -8,6 +8,12 @@ description: 'Learn how to install a GitLab instance on Google Cloud Platform.'
Getting started with GitLab on a [Google Cloud Platform (GCP)][gcp] instance is quick and easy.
+NOTE: **Note:**
+Google provides a whitepaper for [deploying production-ready GitLab on
+Google Kubernetes Engine](https://cloud.google.com/solutions/deploying-production-ready-gitlab-on-gke),
+including all steps and external resource configuration. These are an alternative to using a GCP VM, and use
+the [Cloud native GitLab Helm chart](https://docs.gitlab.com/charts).
+
## Prerequisites
There are only two prerequisites in order to install GitLab on GCP:
diff --git a/doc/integration/README.md b/doc/integration/README.md
index f5bc0693b84..a539933f223 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -29,8 +29,8 @@ See the documentation below for details on how to configure these services.
## Project services
-Integration with services such as Campfire, Flowdock, Pivotal Tracker, and Slack
-are available in the form of a [Project Service][].
+Integration with services such as Campfire, Flowdock, HipChat,
+Pivotal Tracker, and Slack are available in the form of a [Project Service][].
[Project Service]: ../user/project/integrations/project_services.md
diff --git a/doc/project_services/hipchat.md b/doc/project_services/hipchat.md
new file mode 100644
index 00000000000..4ae9f6c6b2e
--- /dev/null
+++ b/doc/project_services/hipchat.md
@@ -0,0 +1 @@
+This document was moved to [user/project/integrations/hipchat.md](../user/project/integrations/hipchat.md).
diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md
index 254e234a22c..0af2f8d2f54 100644
--- a/doc/university/glossary/README.md
+++ b/doc/university/glossary/README.md
@@ -41,7 +41,7 @@ Objects (usually binary and large) created by a build process. These can include
### Atlassian
-A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Confluence, Bamboo.
+A [company](https://www.atlassian.com) that develops software products for developers and project managers including Bitbucket, Jira, Hipchat, Confluence, Bamboo.
### Audit Log
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 3cecefe11f5..4e81e28a45a 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -167,7 +167,7 @@ Here's a list of what you can't do with subgroups:
- [GitLab Pages](../../project/pages/index.md) supports projects hosted under
a subgroup, but not subgroup websites.
That means that only the highest-level group supports
- [group websites](../../project/pages/introduction.html#user-or-group-pages),
+ [group websites](../../project/pages/getting_started_part_one.md#gitlab-pages-domain-names),
although you can have project websites under a subgroup.
- It is not possible to share a project with a group that's an ancestor of
the group the project is in. That means you can only share as you walk down
diff --git a/doc/user/index.md b/doc/user/index.md
index 626246447f3..8164b31c37e 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -65,9 +65,7 @@ With GitLab Enterprise Edition, you can also:
- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html).
-You can also [integrate](project/integrations/project_services.md) GitLab with
-numerous third-party applications, such as Mattermost, Microsoft Teams, Trello,
-Slack, Bamboo CI, JIRA, and a lot more.
+You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
## Projects
diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md
new file mode 100644
index 00000000000..0fd847d415f
--- /dev/null
+++ b/doc/user/project/integrations/hipchat.md
@@ -0,0 +1,53 @@
+# Atlassian HipChat
+
+GitLab provides a way to send HipChat notifications upon a number of events,
+such as when a user pushes code, creates a branch or tag, adds a comment, and
+creates a merge request.
+
+## Setup
+
+GitLab requires the use of a HipChat v2 API token to work. v1 tokens are
+not supported at this time. Note the differences between v1 and v2 tokens:
+
+HipChat v1 API (legacy) supports "API Auth Tokens" in the Group API menu. A v1
+token is allowed to send messages to *any* room.
+
+HipChat v2 API has tokens that are can be created using the Integrations tab
+in the Group or Room admin page. By design, these are lightweight tokens that
+allow GitLab to send messages only to *one* room.
+
+### Complete these steps in HipChat
+
+1. Go to: <https://admin.hipchat.com/admin>
+1. Click on "Group Admin" -> "Integrations".
+1. Find "Build Your Own!" and click "Create".
+1. Select the desired room, name the integration "GitLab", and click "Create".
+1. In the "Send messages to this room by posting this URL" column, you should
+see a URL in the format:
+
+```
+https://api.hipchat.com/v2/room/<room>/notification?auth_token=<token>
+```
+
+HipChat is now ready to accept messages from GitLab. Next, set up the HipChat
+service in GitLab.
+
+### Complete these steps in GitLab
+
+1. Navigate to the project you want to configure for notifications.
+1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
+1. Click "HipChat".
+1. Select the "Active" checkbox.
+1. Insert the `token` field from the URL into the `Token` field on the Web page.
+1. Insert the `room` field from the URL into the `Room` field on the Web page.
+1. Save or optionally click "Test Settings".
+
+## Troubleshooting
+
+If you do not see notifications, make sure you are using a HipChat v2 API
+token, not a v1 token.
+
+Note that the v2 token is tied to a specific room. If you want to be able to
+specify arbitrary rooms, you can create an API token for a specific user in
+HipChat under "Account settings" and "API access". Use the `XXX` value under
+`auth_token=XXX`.
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index e2f23827360..42c7824a125 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -36,6 +36,7 @@ Click on the service links to see further configuration instructions and details
| External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
| Flowdock | Flowdock is a collaboration web app for technical teams |
| [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat |
+| [HipChat](hipchat.md) | Private group chat and IM |
| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
| [JIRA](jira.md) | JIRA issue tracker |
| JetBrains TeamCity CI | A continuous integration and build server |
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index f1e2771dcb9..7dbf58b5715 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -1,42 +1,11 @@
---
last_updated: 2018-02-16
-author: Marcia Ramos
-author_gitlab: marcia
-level: beginner
-article_type: user guide
-date: 2017-02-22
---
# Static sites and GitLab Pages domains
-This document is the beginning of a comprehensive guide, made for those who want to
-publish a website with GitLab Pages but aren't familiar with
-the entire process involved.
-
-This [first document](#what-you-need-to-know-before-getting-started) of this series will present you to the concepts of
-static sites, and go over how the default Pages domains work.
-
-The [second document](getting_started_part_two.md) covers how to get started with GitLab Pages: deploy
-a website from a forked project or create a new one from scratch.
-
-The [third document](getting_started_part_three.md) will show you how to set up a custom domain or subdomain
-to your site already deployed.
-
-The [fourth document](getting_started_part_four.md) will show you how to create and tweak GitLab CI for
-GitLab Pages.
-
-To **enable** GitLab Pages for GitLab CE (Community Edition)
-and GitLab EE (Enterprise Edition), please read the
-[admin documentation](https://docs.gitlab.com/ce/administration/pages/index.html),
-and/or watch this [video tutorial](https://youtu.be/dD8c7WNcc6s).
-
->**Note:**
-For this guide, we assume you already have GitLab Pages
-server up and running for your GitLab instance.
-
-## What you need to know before getting started
-
-Before we begin, let's understand a few concepts first.
+On this docucument, learn how to name your project for GitLab Pages
+according to your intended website's URL.
## Static sites
@@ -48,20 +17,10 @@ CSS, and JS, or use a [Static Site Generator (SSG)](https://www.staticgen.com/)
to simplify your code and build the static site for you,
which is highly recommendable and much faster than hardcoding.
-### Further reading
-
-- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
-- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
-- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
-- Fork an [example project](https://gitlab.com/pages) to build your website based upon
-
-## GitLab Pages domain
+See the [further reading](#further-reading) section below for
+references on static site concepts.
-If you set up a GitLab Pages project on GitLab.com,
-it will automatically be accessible under a
-[subdomain of `namespace.gitlab.io`](introduction.md#gitlab-pages-on-gitlabcom).
-The `namespace` is defined by your username on GitLab.com,
-or the group name you created this project under.
+## GitLab Pages domain names
>**Note:**
If you use your own GitLab instance to deploy your
@@ -70,11 +29,32 @@ Pages wildcard domain. This guide is valid for any GitLab instance,
you just need to replace Pages wildcard domain on GitLab.com
(`*.gitlab.io`) with your own.
-Learn more about [namespaces](../../group/index.md#namespaces).
+If you set up a GitLab Pages project on GitLab,
+it will automatically be accessible under a
+subdomain of `namespace.example.io`.
+The [`namespace`](../../group/index.md#namespaces)
+is defined by your username on GitLab.com,
+or the group name you created this project under.
+For GitLab self-managed instances, replace `example.io`
+with your instance's Pages domain. For GitLab.com,
+Pages domains are `*.gitlab.io`.
+
+| Type of GitLab Pages | The name of the project created in GitLab | Website URL |
+| -------------------- | ------------ | ----------- |
+| User pages | `username.example.io` | `http(s)://username.example.io` |
+| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` |
+| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` |
+| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`|
+| Project pages owned by a subgroup | `subgroup/projectname` | `http(s)://groupname.example.io/subgroup/projectname`|
+
+CAUTION: **Warning:**
+There are some known [limitations](introduction.md#limitations)
+regarding namespaces served under the general domain name and HTTPS.
+Make sure to read that section.
-### Practical examples
+To understand Pages domains clearly, read the examples below.
-#### Project Websites
+### Project website examples
- You created a project called `blog` under your username `john`,
therefore your project URL is `https://gitlab.com/john/blog/`.
@@ -92,7 +72,7 @@ Learn more about [namespaces](../../group/index.md#namespaces).
GitLab Pages for this project, the site will live under
`https://engineering.gitlab.io/docs/workflows`.
-#### User and Group Websites
+### User and Group website examples
- Under your username, `john`, you created a project called
`john.gitlab.io`. Your project URL will be `https://gitlab.com/john/john.gitlab.io`.
@@ -103,8 +83,6 @@ Learn more about [namespaces](../../group/index.md#namespaces).
Once you enable GitLab Pages for your project,
your website will be published under `https://websites.gitlab.io`.
-> Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
-
**General example:**
- On GitLab.com, a project site will always be available under
@@ -115,3 +93,10 @@ Learn more about [namespaces](../../group/index.md#namespaces).
Pages server domain. Ask your sysadmin for this information.
_Read on about [Projects for GitLab Pages and URL structure](getting_started_part_two.md)._
+
+### Further reading
+
+- Read through this technical overview on [Static versus Dynamic Websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/)
+- Understand [how modern Static Site Generators work](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) and what you can add to your static site
+- You can use [any SSG with GitLab Pages](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/)
+- Fork an [example project](https://gitlab.com/pages) to build your website based upon \ No newline at end of file
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index 2839f04ae59..9f2bc281f85 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -177,9 +177,6 @@ Note that [DNS propagation may take some time (up to 24h)](http://www.inmotionho
although it's usually a matter of minutes to complete. Until it does, verification
will fail and attempts to visit your domain will respond with a 404.
-Read through the [general documentation on GitLab Pages](introduction.md#add-a-custom-domain-to-your-pages-website) to learn more about adding
-custom domains to GitLab Pages sites.
-
### Redirecting `www.domain.com` to `domain.com` with Cloudflare
If you use Cloudflare, you can redirect `www` to `domain.com` without the need of adding both
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index 901fb226cda..1034ba1733d 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -104,8 +104,8 @@ from the Pages group into a **user/group** website, you'll need to:
### Create a project from scratch
1. From your **Project**'s **[Dashboard](https://gitlab.com/dashboard/projects)**,
- click **New project**, and name it considering the
- [practical examples](getting_started_part_one.md#practical-examples).
+ click **New project**, and name it according to the
+ [Pages domain names](getting_started_part_one.md#gitlab-pages-domain-names).
1. Clone it to your local computer, add your website
files to your project, add, commit and push to GitLab.
1. From the your **Project**'s page, click **Set up CI/CD**:
diff --git a/doc/user/project/pages/img/pages_remove.png b/doc/user/project/pages/img/pages_remove.png
deleted file mode 100644
index 10299880247..00000000000
--- a/doc/user/project/pages/img/pages_remove.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/img/remove_pages.png b/doc/user/project/pages/img/remove_pages.png
new file mode 100644
index 00000000000..60f76f15f93
--- /dev/null
+++ b/doc/user/project/pages/img/remove_pages.png
Binary files differ
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 885df9f0850..0cd47c65d76 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -5,6 +5,11 @@ last_updated: 2019-03-05
# GitLab Pages
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80) in GitLab Enterprise Edition 8.3.
+> - Custom CNAMEs with TLS support were [introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173) in GitLab Enterprise Edition 8.5.
+> - [Ported](https://gitlab.com/gitlab-org/gitlab-ce/issues/14605) to GitLab Community Edition in GitLab 8.17.
+> Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
+
**GitLab Pages is a feature that allows you to publish static websites
directly from a repository in GitLab.**
@@ -83,7 +88,7 @@ that will build your site and publish it to the GitLab Pages server. The sequenc
scripts that GitLab CI/CD runs to accomplish this task is created from a file named
`.gitlab-ci.yml`, which you can [create and modify](getting_started_part_four.md) at will.
-You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-domain),
+You can either use GitLab's [default domain for GitLab Pages websites](getting_started_part_one.md#gitlab-pages-domain-names),
`*.gitlab.io`, or your own domain (`example.com`). In that case, you'll
need admin access to your domain's registrar (or control panel) to set it up with Pages.
@@ -128,7 +133,7 @@ To learn more about GitLab Pages, read the following tutorials:
- [Projects for GitLab Pages and URL structure](getting_started_part_two.md): Forking projects and creating new ones from scratch, understanding URLs structure and baseurls
- [GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md): How to add custom domains and subdomains to your website, configure DNS records and SSL/TLS certificates
- [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md): Understand how to create your own `.gitlab-ci.yml` for your site
-- [Technical aspects, custom 404 pages, limitations](introduction.md)
+- [Exploring GitLab Pages](introduction.md): Technical aspects, specific configuration options, custom 404 pages, limitations
### GitLab Pages with Static Site Generators (SSGs)
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 39f14a1126f..a14a446aead 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -1,178 +1,44 @@
# Exploring GitLab Pages
-> **Notes:**
->
-> - This feature was [introduced][ee-80] in GitLab EE 8.3.
-> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
-> - GitLab Pages [was ported][ce-14605] to Community Edition in GitLab 8.17.
-> - This document is about the user guide. To learn how to enable GitLab Pages
-> across your GitLab instance, visit the [administrator documentation](../../../administration/pages/index.md).
+This document is a user guide to explore the options and settings
+GitLab Pages offers.
-With GitLab Pages you can host for free your static websites on GitLab.
-Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can
-deploy static pages for your individual projects, your user or your group.
+To familiarize yourself with GitLab Pages first:
-Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) for specific
-information, if you are using GitLab.com to host your website.
+- Read an [introduction to GitLab Pages](index.md#overview).
+- Learn [how to get started with Pages](index.md#getting-started).
+- Learn how to enable GitLab Pages
+across your GitLab instance on the [administrator documentation](../../../administration/pages/index.md).
-## Getting started with GitLab Pages domains
-
-> **Note:**
-> In the rest of this document we will assume that the general domain name that
-> is used for GitLab Pages is `example.io`.
-
-In general there are two types of pages one might create:
-
-- Pages per user (`username.example.io`) or per group (`groupname.example.io`)
-- Pages per project (`username.example.io/projectname` or `groupname.example.io/projectname`)
-
-In GitLab, usernames and groupnames are unique and we often refer to them
-as [namespaces](../../group/index.md#namespaces). There can be only one namespace
-in a GitLab instance. Below you
-can see the connection between the type of GitLab Pages, what the project name
-that is created on GitLab looks like and the website URL it will be ultimately
-be served on.
-
-| Type of GitLab Pages | The name of the project created in GitLab | Website URL |
-| -------------------- | ------------ | ----------- |
-| User pages | `username.example.io` | `http(s)://username.example.io` |
-| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` |
-| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` |
-| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`|
-| Project pages owned by a subgroup | `subgroup/projectname` | `http(s)://groupname.example.io/subgroup/projectname`|
-
-> **Warning:**
-> There are some known [limitations](#limitations) regarding namespaces served
-> under the general domain name and HTTPS. Make sure to read that section.
-
-### GitLab Pages requirements
+## Pages requirements
In brief, this is what you need to upload your website in GitLab Pages:
-1. Find out the general domain name that is used for GitLab Pages
- (ask your administrator). This is very important, so you should first make
- sure you get that right.
-1. Create a project
-1. Push a [`.gitlab-ci.yml` file][yaml] in the root directory
- of your repository with a specific job named [`pages`][pages]
-1. Set up a GitLab Runner to build your website
-
-> **Note:**
-If [shared runners](../../../ci/runners/README.md) are enabled by your GitLab
-administrator, you should be able to use them instead of bringing your own.
-
-### User or group Pages
-
-For user and group pages, the name of the project should be specific to the
-username or groupname and the general domain name that is used for GitLab Pages.
-Head over your GitLab instance that supports GitLab Pages and create a
-repository named `username.example.io`, where `username` is your username on
-GitLab. If the first part of the project name doesn't match exactly your
-username, it won’t work, so make sure to get it right.
-
-To create a group page, the steps are the same like when creating a website for
-users. Just make sure that you are creating the project within the group's
-namespace.
-
-![Create a user-based pages project](img/pages_create_user_page.png)
-
----
-
-After you push some static content to your repository and GitLab Runner uploads
-the artifacts to GitLab CI, you will be able to access your website under
-`http(s)://username.example.io`. Keep reading to find out how.
-
->**Note:**
-If your username/groupname contains a dot, for example `foo.bar`, you will not
-be able to use the wildcard domain HTTPS, read more at [limitations](#limitations).
+1. Domain of the instance: domain name that is used for GitLab Pages
+(ask your administrator).
+1. GitLab CI/CD: a `.gitlab-ci.yml` file with a specific job named [`pages`][pages] in the root directory of your repository.
+1. A directory called `public` in your site's repo containing the content
+to be published.
+1. GitLab Runner enabled for the project.
-### Project Pages
-
-GitLab Pages for projects can be created by both user and group accounts.
-The steps to create a project page for a user or a group are identical:
-
-1. Create a new project
-1. Push a [`.gitlab-ci.yml` file][yaml] in the root directory
- of your repository with a specific job named [`pages`][pages].
-1. Set up a GitLab Runner to build your website
-
-A user's project will be served under `http(s)://username.example.io/projectname`
-whereas a group's project under `http(s)://groupname.example.io/projectname`.
-
-For practical examples for group and project Pages, read through the guide
-[GitLab Pages from A to Z: Part 1 - Static sites and GitLab Pages domains](getting_started_part_one.md#practical-examples).
-
-## Quick Start
-
-Read through [GitLab Pages Quick Start Guide][pages-quick] or watch the video tutorial on
-[how to publish a website with GitLab Pages on GitLab.com from a forked project][video-pages-fork].
-
-See also [All you Need to Know About GitLab Pages][pages-index-guide] for a list with all the resources we have for GitLab Pages.
-
-### Explore the contents of `.gitlab-ci.yml`
-
-The key thing about GitLab Pages is the `.gitlab-ci.yml` file, something that
-gives you absolute control over the build process. You can actually watch your
-website being built live by following the CI job traces.
-
-For a simplified user guide on setting up GitLab CI/CD for Pages, read through
-the article [GitLab Pages from A to Z: Part 4 - Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md)
-
-> **Note:**
-> Before reading this section, make sure you familiarize yourself with GitLab CI
-> and the specific syntax of[`.gitlab-ci.yml`][yaml] by
-> following our [quick start guide].
-
-To make use of GitLab Pages, the contents of `.gitlab-ci.yml` must follow the
-rules below:
-
-1. A special job named [`pages`][pages] must be defined
-1. Any static content which will be served by GitLab Pages must be placed under
- a `public/` directory
-1. `artifacts` with a path to the `public/` directory must be defined
+## GitLab Pages on GitLab.com
-In its simplest form, `.gitlab-ci.yml` looks like:
+If you are using [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) to host your website, then:
-```yaml
-pages:
- script:
- - my_commands
- artifacts:
- paths:
- - public
-```
+- The domain name for GitLab Pages on GitLab.com is `gitlab.io`.
+- Custom domains and TLS support are enabled.
+- Shared runners are enabled by default, provided for free and can be used to
+ build your website. If you want you can still bring your own Runner.
-When the Runner reaches to build the `pages` job, it executes whatever is
-defined in the `script` parameter and if the job completes with a non-zero
-exit status, it then uploads the `public/` directory to GitLab Pages.
+## Example projects
-The `public/` directory should contain all the static content of your website.
-Depending on how you plan to publish your website, the steps defined in the
-[`script` parameter](../../../ci/yaml/README.md#script) may differ.
+Visit the [GitLab Pages group](https://gitlab.com/groups/pages) for a complete list of example projects. Contributions are very welcome.
-Be aware that Pages are by default branch/tag agnostic and their deployment
-relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the
-`pages` job with the [`only` parameter](../../../ci/yaml/README.md#onlyexcept-basic),
-whenever a new commit is pushed to whatever branch or tag, the Pages will be
-overwritten. In the example below, we limit the Pages to be deployed whenever
-a commit is pushed only on the `master` branch:
+## Specific configuration options for Pages
-```yaml
-pages:
- script:
- - my_commands
- artifacts:
- paths:
- - public
- only:
- - master
-```
-
-We then tell the Runner to treat the `public/` directory as `artifacts` and
-upload it to GitLab. And since all these parameters were all under a `pages`
-job, the contents of the `public` directory will be served by GitLab Pages.
+Learn how to set up GitLab CI/CD for specific use cases.
-#### How `.gitlab-ci.yml` looks like when the static content is in your repository
+### `.gitlab-ci.yml` for plain HTML websites
Supposed your repository contained the following files:
@@ -201,55 +67,11 @@ pages:
- master
```
-#### How `.gitlab-ci.yml` looks like when using a static generator
-
-In general, GitLab Pages support any kind of [static site generator][staticgen],
-since `.gitlab-ci.yml` can be configured to run any possible command.
-
-In the root directory of your Git repository, place the source files of your
-favorite static generator. Then provide a `.gitlab-ci.yml` file which is
-specific to your static generator.
+### `.gitlab-ci.yml` for a static site generator
-The example below, uses [Jekyll] to build the static site:
+See this document for a [step-by-step guide](getting_started_part_four.md).
-```yaml
-image: ruby:2.1 # the script will run in Ruby 2.1 using the Docker image ruby:2.1
-
-pages: # the build job must be named pages
- script:
- - gem install jekyll # we install jekyll
- - jekyll build -d public/ # we tell jekyll to build the site for us
- artifacts:
- paths:
- - public # this is where the site will live and the Runner uploads it in GitLab
- only:
- - master # this script is only affecting the master branch
-```
-
-Here, we used the Docker executor and in the first line we specified the base
-image against which our jobs will run.
-
-You have to make sure that the generated static files are ultimately placed
-under the `public` directory, that's why in the `script` section we run the
-`jekyll` command that jobs the website and puts all content in the `public/`
-directory. Depending on the static generator of your choice, this command will
-differ. Search in the documentation of the static generator you will use if
-there is an option to explicitly set the output directory. If there is not
-such an option, you can always add one more line under `script` to rename the
-resulting directory in `public/`.
-
-We then tell the Runner to treat the `public/` directory as `artifacts` and
-upload it to GitLab.
-
----
-
-See the [jekyll example project][pages-jekyll] to better understand how this
-works.
-
-For a list of Pages projects, see the [example projects](#example-projects) to
-get you started.
-
-#### How to set up GitLab Pages in a repository where there's also actual code
+### `.gitlab-ci.yml` for a repository where there's also actual code
Remember that GitLab Pages are by default branch/tag agnostic and their
deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit
@@ -294,28 +116,6 @@ also includes `.gitlab-ci.yml`.
[jekyll-master]: https://gitlab.com/pages/jekyll-branched/tree/master
[jekyll-pages]: https://gitlab.com/pages/jekyll-branched/tree/pages
-## Next steps
-
-So you have successfully deployed your website, congratulations! Let's check
-what more you can do with GitLab Pages.
-
-### Example projects
-
-Below is a list of example projects for GitLab Pages with a plain HTML website
-or various static site generators. Contributions are very welcome.
-
-- [Plain HTML](https://gitlab.com/pages/plain-html)
-- [Jekyll](https://gitlab.com/pages/jekyll)
-- [Hugo](https://gitlab.com/pages/hugo)
-- [Middleman](https://gitlab.com/pages/middleman)
-- [Hexo](https://gitlab.com/pages/hexo)
-- [Brunch](https://gitlab.com/pages/brunch)
-- [Metalsmith](https://gitlab.com/pages/metalsmith)
-- [Harp](https://gitlab.com/pages/harp)
-
-Visit the GitLab Pages group for a full list of example projects:
-<https://gitlab.com/groups/pages>.
-
### Serving compressed assets
Most modern browsers support downloading files in a compressed format. This
@@ -408,52 +208,6 @@ NOTE: **Note:**
When `public/data/index.html` exists, it takes priority over the `public/data.html`
file for both the `/data` and `/data/` URL paths.
-### Add a custom domain to your Pages website
-
-For a complete guide on Pages domains, read through the article
-[GitLab Pages from A to Z: Part 3 - GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md)
-
-If this setting is enabled by your GitLab administrator, you should be able to
-see the **New Domain** button when visiting your project's settings through the
-gear icon in the top right and then navigating to **Pages**.
-
-![New domain button](img/pages_new_domain_button.png)
-
----
-
-You can add multiple domains pointing to your website hosted under GitLab.
-Once the domain is added, you can see it listed under the **Domains** section.
-
-![Pages multiple domains](img/pages_multiple_domains.png)
-
----
-
-As a last step, you need to configure your DNS and add a CNAME pointing to your
-user/group page. Click on the **Details** button of a domain for further
-instructions.
-
-![Pages DNS details](img/pages_dns_details.png)
-
----
-
->**Note:**
-Currently there is support only for custom domains on per-project basis. That
-means that if you add a custom domain (`example.com`) for your user website
-(`username.example.io`), a project that is served under `username.example.io/foo`,
-will not be accessible under `example.com/foo`.
-
-### Secure your custom domain website with TLS
-
-When you add a new custom domain, you also have the chance to add a TLS
-certificate. If this setting is enabled by your GitLab administrator, you
-should be able to see the option to upload the public certificate and the
-private key when adding a new domain.
-
-![Pages upload cert](img/pages_upload_cert.png)
-
-For a complete guide on Pages domains, read through the article
-[GitLab Pages from A to Z: Part 3 - GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md)
-
### Custom error codes pages
You can provide your own 403 and 404 error pages by creating the `403.html` and
@@ -472,29 +226,17 @@ If the case of `404.html`, there are different scenarios. For example:
- If you use a custom domain and try to access `/non/existing_file`, GitLab
Pages will try to serve only `/404.html`.
-### Remove the contents of your pages
-
-If you ever feel the need to purge your Pages content, you can do so by going
-to your project's settings through the gear icon in the top right, and then
-navigating to **Pages**. Hit the **Remove pages** button and your Pages website
-will be deleted. Simple as that.
-
-![Remove pages](img/pages_remove.png)
-
-## GitLab Pages on GitLab.com
-
-If you are using GitLab.com to host your website, then:
-
-- The general domain name for GitLab Pages on GitLab.com is `gitlab.io`.
-- Custom domains and TLS support are enabled.
-- Shared runners are enabled by default, provided for free and can be used to
- build your website. If you want you can still bring your own Runner.
+### Redirects in GitLab Pages
-The rest of the guide still applies.
+Since you cannot use any custom server configuration files, like `.htaccess` or
+any `.conf` file, if you want to redirect a page to another
+location, you can use the [HTTP meta refresh tag][metarefresh].
-See also: [GitLab Pages from A to Z: Part 1 - Static sites and GitLab Pages domains](getting_started_part_one.md#gitlab-pages-domain).
+Some static site generators provide plugins for that functionality so that you
+don't have to create and edit HTML files manually. For example, Jekyll has the
+[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from).
-## GitLab Pages access control **[CORE ONLY]**
+### GitLab Pages Access Control **[CORE ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) in GitLab 11.5.
@@ -536,6 +278,15 @@ The next time someone tries to access your website and the access control is
enabled, they will be presented with a page to sign into GitLab and verify they
can access the website.
+## Unpublishing your Pages
+
+If you ever feel the need to purge your Pages content, you can do so by going
+to your project's settings through the gear icon in the top right, and then
+navigating to **Pages**. Hit the **Remove pages** button and your Pages website
+will be deleted.
+
+![Remove pages](img/remove_pages.png)
+
## Limitations
When using Pages under the general domain of a GitLab instance (`*.example.io`),
@@ -550,16 +301,6 @@ don't redirect HTTP to HTTPS.
GitLab Pages [does **not** support group websites for subgroups](../../group/subgroups/index.md#limitations).
You can only create the highest-level group website.
-## Redirects in GitLab Pages
-
-Since you cannot use any custom server configuration files, like `.htaccess` or
-any `.conf` file, if you want to redirect a page to another
-location, you can use the [HTTP meta refresh tag][metarefresh].
-
-Some static site generators provide plugins for that functionality so that you
-don't have to create and edit HTML files manually. For example, Jekyll has the
-[redirect-from plugin](https://github.com/jekyll/jekyll-redirect-from).
-
## Frequently Asked Questions
### Can I download my generated pages?
@@ -581,8 +322,6 @@ No, you don't. You can create your project first and it will be accessed under
For a list of known issues, visit GitLab's [public issue tracker].
[jekyll]: http://jekyllrb.com/
-[ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80
-[ee-173]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/173
[pages-daemon]: https://gitlab.com/gitlab-org/gitlab-pages
[gitlab ci]: https://about.gitlab.com/gitlab-ci
[gitlab runner]: https://docs.gitlab.com/runner/
@@ -592,7 +331,6 @@ For a list of known issues, visit GitLab's [public issue tracker].
[pages-jekyll]: https://gitlab.com/pages/jekyll
[metarefresh]: https://en.wikipedia.org/wiki/Meta_refresh
[public issue tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=pages
-[ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605
[quick start guide]: ../../../ci/quick_start/README.md
[pages-index-guide]: index.md
[pages-quick]: getting_started_part_one.md
diff --git a/doc/user/project/repository/img/download_source_code.png b/doc/user/project/repository/img/download_source_code.png
deleted file mode 100644
index 17f2cb4b3e8..00000000000
--- a/doc/user/project/repository/img/download_source_code.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 718566a539f..22d912cd9d1 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -241,24 +241,4 @@ Projects that contain a `.xcodeproj` or `.xcworkspace` directory can now be clon
in Xcode using the new **Open in Xcode** button, located next to the Git URL
used for cloning your project. The button is only shown on macOS.
-## Download Source Code
-
-Source code stored in the repository can be downloaded.
-
-By clicking the download icon, a dropdown will open with links to download the following:
-
-![Download source code](img/download_source_code.png)
-
-- **Source Code:**
- This allows users to download the source code on branch they're currently
- viewing. Available zip, tar, tar.gz and tar.bz2.
-- **Directory:**
- > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24704) in GitLab 11.10
-
- Only shows up when viewing a sub-directory. This allows users to download
- the specific directory they're currently viewing. Also available in zip, tar,
- tar.gz and tar.bz2.
-- **Artifacts:**
- This allows users to download the artifacts of the latest CI build.
-
[jupyter]: https://jupyter.org
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index ae1624b7dc0..9fcadbf3bee 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -84,7 +84,7 @@ To set up a mirror from GitLab to GitHub, you need to follow these steps:
1. Fill in **Password** field with your GitHub personal access token.
1. Click the **Mirror repository** button.
-The mirrored repository will be listed. For example, `https://*****:*****@github.com/<your_github_group>/<your_github_project>.git`.
+The mirrored repository will be listed. For example, `https://*****:*****@github.com/<your_github_group>/<your_github_project>.git`.
The repository will push soon. To force a push, click the appropriate button.
@@ -138,13 +138,18 @@ upstream and GitLab will no longer automatically update this branch to prevent a
### How it works
-Once you activate the pull mirroring feature, the mirror will be inserted into a queue. A scheduler
-will start every minute and schedule a fixed number of mirrors for update, based on the configured maximum capacity.
+Once the pull mirroring feature has been enabled for a repository, the repository is added to a queue.
-If the mirror updates successfully, it will be enqueued once again with a small backoff period.
+Once per minute, a Sidekiq cron job schedules repository mirrors to update, based on:
-If the mirror fails (for example, a branch diverged from upstream), the project's backoff period is
-increased each time it fails, up to a maximum amount of time.
+- The capacity available. This is determined by Sidekiq settings. For GitLab.com, see [GitLab.com Sidekiq settings](../user/gitlab_com/index.md#sidekiq).
+- The number of repository mirrors already in the queue that are due to be updated. Being due depends on when the repository mirror was last updated and how many times it's been retried.
+
+Repository mirrors are updated as Sidekiq becomes available to process them. If the process of updating the repository mirror:
+
+- Succeeds, an update will be enqueued again with at least a 30 minute wait.
+- Fails (for example, a branch diverged from upstream), it will be attempted again later. Mirrors can fail
+ up to 14 times before they will not be enqueued for update again.
### SSH authentication
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index e60b6819bf1..c03dffa967d 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -75,6 +75,6 @@ Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
Other interesting links:
-- [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/features/time-tracking)
+- [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/solutions/time-tracking/)
[quick actions]: ../user/project/quick_actions.md
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index 8582c45798f..953be7f3798 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -1,3 +1,4 @@
+# coding: utf-8
# frozen_string_literal: true
module API
@@ -386,6 +387,44 @@ module API
},
chat_notification_events
].flatten,
+ 'hipchat' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The room token'
+ },
+ {
+ required: false,
+ name: :room,
+ type: String,
+ desc: 'The room name or ID'
+ },
+ {
+ required: false,
+ name: :color,
+ type: String,
+ desc: 'The room color'
+ },
+ {
+ required: false,
+ name: :notify,
+ type: Boolean,
+ desc: 'Enable notifications'
+ },
+ {
+ required: false,
+ name: :api_version,
+ type: String,
+ desc: 'Leave blank for default (v2)'
+ },
+ {
+ required: false,
+ name: :server,
+ type: String,
+ desc: 'Leave blank for default. https://hipchat.example.com'
+ }
+ ],
'irker' => [
{
required: true,
@@ -690,6 +729,7 @@ module API
::ExternalWikiService,
::FlowdockService,
::HangoutsChatService,
+ ::HipchatService,
::IrkerService,
::JiraService,
::KubernetesService,
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index bb4e536cf57..e7504051808 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -20,6 +20,10 @@ module API
def provider
:github
end
+
+ def provider_unauthorized
+ error!("Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account.", 401)
+ end
end
desc 'Import a GitHub project' do
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index c33d243330d..be9e926728c 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -231,12 +231,12 @@ module Gitlab
end
end
- def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:, path: nil)
+ def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:)
ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref)
return {} if commit.nil?
- prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha, path: path)
+ prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha)
{
'ArchivePrefix' => prefix,
@@ -248,14 +248,13 @@ module Gitlab
# This is both the filename of the archive (missing the extension) and the
# name of the top-level member of the archive under which all files go
- def archive_prefix(ref, sha, project_path, append_sha:, path:)
+ def archive_prefix(ref, sha, project_path, append_sha:)
append_sha = (ref != sha) if append_sha.nil?
formatted_ref = ref.tr('/', '-')
prefix_segments = [project_path, formatted_ref]
prefix_segments << sha if append_sha
- prefix_segments << path.tr('/', '-').gsub(%r{^/|/$}, '') if path
prefix_segments.join('-')
end
@@ -466,7 +465,7 @@ module Gitlab
@refs_hash = Hash.new { |h, k| h[k] = [] }
(tags + branches).each do |ref|
- next unless ref.target && ref.name
+ next unless ref.target && ref.name && ref.dereferenced_target&.id
@refs_hash[ref.dereferenced_target.id] << ref.name
end
diff --git a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
index b30900f7c61..fcf6a25ab00 100644
--- a/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
+++ b/lib/gitlab/import_export/after_export_strategies/web_upload_strategy.rb
@@ -8,7 +8,7 @@ module Gitlab
POST_METHOD = 'POST'.freeze
INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.'.freeze
- validates :url, url: true
+ validates :url, addressable_url: true
validate do
unless [PUT_METHOD, POST_METHOD].include?(http_method.upcase)
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 9b7b0db9525..641ba70ef83 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -8,7 +8,7 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
class << self
- def validate!(url, ports: [], protocols: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false, enforce_sanitization: false)
+ def validate!(url, ports: [], schemes: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false, enforce_sanitization: false)
return true if url.nil?
# Param url can be a string, URI or Addressable::URI
@@ -20,7 +20,7 @@ module Gitlab
return true if internal?(uri)
port = get_port(uri)
- validate_protocol!(uri.scheme, protocols)
+ validate_scheme!(uri.scheme, schemes)
validate_port!(port, ports) if ports.any?
validate_user!(uri.user) if enforce_user
validate_hostname!(uri.hostname)
@@ -85,9 +85,9 @@ module Gitlab
raise BlockedUrlError, "Only allowed ports are #{ports.join(', ')}, and any over 1024"
end
- def validate_protocol!(protocol, protocols)
- if protocol.blank? || (protocols.any? && !protocols.include?(protocol))
- raise BlockedUrlError, "Only allowed protocols are #{protocols.join(', ')}"
+ def validate_scheme!(scheme, schemes)
+ if scheme.blank? || (schemes.any? && !schemes.include?(scheme))
+ raise BlockedUrlError, "Only allowed schemes are #{schemes.join(', ')}"
end
end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index a3c7de87765..8f9d5cf1e63 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -75,8 +75,8 @@ module Gitlab
user.admin? || allowed_level?(level.to_i)
end
+ # Level should be a numeric value, e.g. `20`
# Return true if the specified level is allowed for the current user.
- # Level should be a numeric value, e.g. `20`.
def allowed_level?(level)
valid_level?(level) && non_restricted_level?(level)
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 533757d2237..0c2acac3d1e 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -63,26 +63,13 @@ module Gitlab
]
end
- def send_git_archive(repository, ref:, format:, append_sha:, path: nil)
+ def send_git_archive(repository, ref:, format:, append_sha:)
format ||= 'tar.gz'
format = format.downcase
- metadata = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha, path: path)
+ params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha)
+ raise "Repository or ref not found" if params.empty?
- raise "Repository or ref not found" if metadata.empty?
-
- params = {
- 'GitalyServer' => gitaly_server_hash(repository),
- 'ArchivePath' => metadata['ArchivePath'],
- 'GetArchiveRequest' => encode_binary(
- Gitaly::GetArchiveRequest.new(
- repository: repository.gitaly_repository,
- commit_id: metadata['CommitId'],
- prefix: metadata['ArchivePrefix'],
- format: archive_format(format),
- path: path.presence || ""
- ).to_proto
- )
- }
+ params['GitalyServer'] = gitaly_server_hash(repository)
# If present DisableCache must be a Boolean. Otherwise workhorse ignores it.
params['DisableCache'] = true if git_archive_cache_disabled?
@@ -233,10 +220,6 @@ module Gitlab
Base64.urlsafe_encode64(JSON.dump(hash))
end
- def encode_binary(binary)
- Base64.urlsafe_encode64(binary)
- end
-
def gitaly_server_hash(repository)
{
address: Gitlab::GitalyClient.address(repository.project.repository_storage),
@@ -255,19 +238,6 @@ module Gitlab
def git_archive_cache_disabled?
ENV['WORKHORSE_ARCHIVE_CACHE_DISABLED'].present? || Feature.enabled?(:workhorse_archive_cache_disabled)
end
-
- def archive_format(format)
- case format
- when "tar.bz2", "tbz", "tbz2", "tb2", "bz2"
- Gitaly::GetArchiveRequest::Format::TAR_BZ2
- when "tar"
- Gitaly::GetArchiveRequest::Format::TAR
- when "zip"
- Gitaly::GetArchiveRequest::Format::ZIP
- else
- Gitaly::GetArchiveRequest::Format::TAR_GZ
- end
- end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 567e6c9f7bb..cfd630ae8e2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -867,6 +867,9 @@ msgstr ""
msgid "Any"
msgstr ""
+msgid "Any encrypted tokens"
+msgstr ""
+
msgid "Appearance"
msgstr ""
@@ -912,15 +915,27 @@ msgstr ""
msgid "Archive jobs"
msgstr ""
+msgid "Archive project"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
msgid "Archived projects"
msgstr ""
+msgid "Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches. <strong>The repository cannot be committed to, and no issues, comments or other entities can be created.</strong>"
+msgstr ""
+
msgid "Are you sure"
msgstr ""
+msgid "Are you sure that you want to archive this project?"
+msgstr ""
+
+msgid "Are you sure that you want to unarchive this project?"
+msgstr ""
+
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr ""
@@ -1206,6 +1221,12 @@ msgstr ""
msgid "Badges|e.g. %{exampleUrl}"
msgstr ""
+msgid "Be careful. Changing the project's namespace can have unintended side effects."
+msgstr ""
+
+msgid "Be careful. Renaming a project's repository can have unintended side effects."
+msgstr ""
+
msgid "Begin with the selected commit"
msgstr ""
@@ -1413,6 +1434,9 @@ msgstr ""
msgid "CI Lint"
msgstr ""
+msgid "CI variables"
+msgstr ""
+
msgid "CI/CD"
msgstr ""
@@ -1503,6 +1527,9 @@ msgstr ""
msgid "Certificate (PEM)"
msgstr ""
+msgid "Change path"
+msgstr ""
+
msgid "Change permissions"
msgstr ""
@@ -1707,6 +1734,9 @@ msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
+msgid "Classification Label (optional)"
+msgstr ""
+
msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr ""
@@ -2408,6 +2438,12 @@ msgstr ""
msgid "Configure the way a user creates a new account."
msgstr ""
+msgid "Confirm"
+msgstr ""
+
+msgid "Confirmation required"
+msgstr ""
+
msgid "Connect"
msgstr ""
@@ -2417,6 +2453,9 @@ msgstr ""
msgid "Container Registry"
msgstr ""
+msgid "Container registry images"
+msgstr ""
+
msgid "ContainerRegistry|Created"
msgstr ""
@@ -3109,10 +3148,22 @@ msgstr ""
msgid "Download asset"
msgstr ""
-msgid "Download source code"
+msgid "Download export"
+msgstr ""
+
+msgid "Download tar"
msgstr ""
-msgid "Download this directory"
+msgid "Download tar.bz2"
+msgstr ""
+
+msgid "Download tar.gz"
+msgstr ""
+
+msgid "Download zip"
+msgstr ""
+
+msgid "DownloadArtifacts|Download"
msgstr ""
msgid "DownloadCommit|Email Patches"
@@ -3670,6 +3721,12 @@ msgstr ""
msgid "Explore public groups"
msgstr ""
+msgid "Export project"
+msgstr ""
+
+msgid "Export this project with all its related data in order to move your project to a new GitLab instance. Once the export is finished, you can import the file from the \"New Project\" page."
+msgstr ""
+
msgid "External Classification Policy Authorization"
msgstr ""
@@ -3688,9 +3745,6 @@ msgstr ""
msgid "External authorization request timeout"
msgstr ""
-msgid "ExternalAuthorizationService|Classification Label"
-msgstr ""
-
msgid "ExternalAuthorizationService|Classification label"
msgstr ""
@@ -3972,6 +4026,9 @@ msgstr ""
msgid "Generate a default set of labels"
msgstr ""
+msgid "Generate new export"
+msgstr ""
+
msgid "Geo"
msgstr ""
@@ -4337,6 +4394,9 @@ msgstr ""
msgid "Hook was successfully updated."
msgstr ""
+msgid "Housekeeping"
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
@@ -4673,6 +4733,9 @@ msgstr ""
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
msgstr ""
+msgid "Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities"
+msgstr ""
+
msgid "Issues, merge requests, pushes, and comments."
msgstr ""
@@ -4712,6 +4775,9 @@ msgstr ""
msgid "Job is stuck. Check runners."
msgstr ""
+msgid "Job traces and artifacts"
+msgstr ""
+
msgid "Job was retried"
msgstr ""
@@ -4820,6 +4886,9 @@ msgstr ""
msgid "LFS"
msgstr ""
+msgid "LFS objects"
+msgstr ""
+
msgid "LFSStatus|Disabled"
msgstr ""
@@ -5418,7 +5487,7 @@ msgstr ""
msgid "Name:"
msgstr ""
-msgid "Naming, tags, avatar"
+msgid "Naming, topics, avatar"
msgstr ""
msgid "Naming, visibility"
@@ -5786,6 +5855,12 @@ msgstr ""
msgid "OfSearchInADropdown|Filter"
msgstr ""
+msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
+msgstr ""
+
+msgid "Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page."
+msgstr ""
+
msgid "One more item"
msgid_plural "%d more items"
msgstr[0] ""
@@ -5938,6 +6013,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path"
+msgstr ""
+
msgid "Path, transfer, remove"
msgstr ""
@@ -6235,6 +6313,9 @@ msgstr ""
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr ""
+msgid "Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
msgid "Please wait while we import the repository for you. Refresh at will."
msgstr ""
@@ -6262,9 +6343,6 @@ msgstr ""
msgid "Preview payload"
msgstr ""
-msgid "Previous Artifacts"
-msgstr ""
-
msgid "Prioritize"
msgstr ""
@@ -6574,15 +6652,27 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project ID"
+msgstr ""
+
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project and wiki repositories"
+msgstr ""
+
msgid "Project avatar"
msgstr ""
+msgid "Project configuration, including services"
+msgstr ""
+
+msgid "Project description (optional)"
+msgstr ""
+
msgid "Project details"
msgstr ""
@@ -6613,6 +6703,12 @@ msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project uploads"
+msgstr ""
+
+msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
+msgstr ""
+
msgid "Project:"
msgstr ""
@@ -6927,6 +7023,9 @@ msgstr ""
msgid "Remove avatar"
msgstr ""
+msgid "Remove fork relationship"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -6945,9 +7044,15 @@ msgstr ""
msgid "Removed group can not be restored!"
msgstr ""
+msgid "Removed projects cannot be restored!"
+msgstr ""
+
msgid "Removing group will cause all child projects and resources to be removed."
msgstr ""
+msgid "Removing the project will delete its repository and all related resources including issues, merge requests etc."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -7136,6 +7241,9 @@ msgstr ""
msgid "Revoked personal access token %{personal_access_token_name}!"
msgstr ""
+msgid "Run housekeeping"
+msgstr ""
+
msgid "Run untagged jobs"
msgstr ""
@@ -7190,6 +7298,9 @@ msgstr ""
msgid "Running…"
msgstr ""
+msgid "Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects."
+msgstr ""
+
msgid "SSH Keys"
msgstr ""
@@ -7229,6 +7340,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving project."
+msgstr ""
+
msgid "Schedule a new pipeline"
msgstr ""
@@ -7352,6 +7466,9 @@ msgstr ""
msgid "Select a namespace to fork the project"
msgstr ""
+msgid "Select a new namespace"
+msgstr ""
+
msgid "Select a timezone"
msgstr ""
@@ -7406,6 +7523,9 @@ msgstr ""
msgid "Sep"
msgstr ""
+msgid "Separate topics with commas."
+msgstr ""
+
msgid "September"
msgstr ""
@@ -8247,6 +8367,12 @@ msgstr ""
msgid "The file has been successfully deleted."
msgstr ""
+msgid "The following items will NOT be exported:"
+msgstr ""
+
+msgid "The following items will be exported:"
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
@@ -8478,6 +8604,9 @@ msgstr ""
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr ""
+msgid "This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention."
+msgstr ""
+
msgid "This application was created by %{link_to_owner}."
msgstr ""
@@ -8670,6 +8799,9 @@ msgstr ""
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
+msgid "This will remove the fork relationship to source project"
+msgstr ""
+
msgid "Time before an issue gets scheduled"
msgstr ""
@@ -8971,6 +9103,9 @@ msgstr ""
msgid "Too many changes to show."
msgstr ""
+msgid "Topics"
+msgstr ""
+
msgid "Total Time"
msgstr ""
@@ -8983,6 +9118,9 @@ msgstr ""
msgid "Track time with quick actions"
msgstr ""
+msgid "Transfer project"
+msgstr ""
+
msgid "Tree view"
msgstr ""
@@ -9043,6 +9181,12 @@ msgstr ""
msgid "Unable to schedule a pipeline to run immediately"
msgstr ""
+msgid "Unarchive project"
+msgstr ""
+
+msgid "Unarchiving the project will restore people's ability to make changes to it. The repository can be committed to, and issues, comments and other entities can be created. <strong>Once active this project shows up in the search and on the dashboard.</strong>"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -9124,7 +9268,7 @@ msgstr ""
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
-msgid "Update your project name, tags, description and avatar."
+msgid "Update your project name, topics, description and avatar."
msgstr ""
msgid "Updating"
@@ -9448,6 +9592,9 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "Webhooks"
+msgstr ""
+
msgid "Webhooks Help"
msgstr ""
@@ -9729,6 +9876,9 @@ msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
+msgid "You can only transfer the project to namespaces you manage."
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -9819,6 +9969,9 @@ msgstr ""
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
msgstr ""
+msgid "You will need to update your local repositories to point to the new location."
+msgstr ""
+
msgid "You will not get any notifications via email"
msgstr ""
@@ -9921,6 +10074,9 @@ msgstr ""
msgid "Your comment will not be visible to the public."
msgstr ""
+msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
+msgstr ""
+
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
msgstr ""
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index 578f097e2dc..6dffbac5694 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -4,27 +4,21 @@ module QA
module Settings
class Advanced < Page::Base
view 'app/views/projects/edit.html.haml' do
- element :project_path_field, 'text_field :path' # rubocop:disable QA/ElementWithPattern
- element :project_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
- element :rename_project_button, "submit 'Rename project'" # rubocop:disable QA/ElementWithPattern
+ element :project_path_field
+ element :change_path_button
end
- def rename_to(path)
- fill_project_name(path)
+ def update_project_path_to(path)
fill_project_path(path)
- rename_project!
+ click_change_path_button
end
def fill_project_path(path)
- fill_in :project_path, with: path
+ fill_element :project_path_field, path
end
- def fill_project_name(name)
- fill_in :project_name, with: name
- end
-
- def rename_project!
- click_on 'Rename project'
+ def click_change_path_button
+ click_element :change_path_button
end
end
end
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
index f3b217677f2..233e681e0df 100644
--- a/qa/qa/page/project/settings/common.rb
+++ b/qa/qa/page/project/settings/common.rb
@@ -4,14 +4,6 @@ module QA
module Settings
module Common
include QA::Page::Settings::Common
-
- def self.included(base)
- base.class_eval do
- view 'app/views/projects/edit.html.haml' do
- element :advanced_settings_expand, "= expanded ? 'Collapse' : 'Expand'" # rubocop:disable QA/ElementWithPattern
- end
- end
- end
end
end
end
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index d8cf1d49dd2..cf464e25ca5 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -9,6 +9,24 @@ module QA
element :advanced_settings
end
+ view 'app/views/projects/settings/_general.html.haml' do
+ element :project_name_field
+ element :save_naming_topics_avatar_button
+ end
+
+ def rename_project_to(name)
+ fill_project_name(name)
+ click_save_changes
+ end
+
+ def fill_project_name(name)
+ fill_element :project_name_field, name
+ end
+
+ def click_save_changes
+ click_element :save_naming_topics_avatar_button
+ end
+
def expand_advanced_settings(&block)
expand_section(:advanced_settings) do
Advanced.perform(&block)
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
index 7e8b42e286f..f97b0e56ca2 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Plan' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/53
+ context 'Plan', :quarantine do
describe 'issue suggestions' do
let(:issue_title) { 'Issue Lists are awesome' }
diff --git a/spec/controllers/projects/mirrors_controller_spec.rb b/spec/controllers/projects/mirrors_controller_spec.rb
index f2b73956e8d..3ababe18055 100644
--- a/spec/controllers/projects/mirrors_controller_spec.rb
+++ b/spec/controllers/projects/mirrors_controller_spec.rb
@@ -79,7 +79,7 @@ describe Projects::MirrorsController do
do_put(project, remote_mirrors_attributes: remote_mirror_attributes)
expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-push-remote-settings'))
- expect(flash[:alert]).to match(/Only allowed protocols are/)
+ expect(flash[:alert]).to match(/Only allowed schemes are/)
end
it 'does not create a RemoteMirror object' do
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 70c34f8640b..0d8c26a2ee9 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -62,4 +62,10 @@ FactoryBot.define do
project_key: 'jira-key'
)
end
+
+ factory :hipchat_service do
+ project
+ type 'HipchatService'
+ token 'test_token'
+ end
end
diff --git a/spec/features/ide/user_opens_merge_request_spec.rb b/spec/features/ide/user_opens_merge_request_spec.rb
new file mode 100644
index 00000000000..185349219a7
--- /dev/null
+++ b/spec/features/ide/user_opens_merge_request_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe 'IDE merge request', :js do
+ let(:merge_request) { create(:merge_request, :with_diffs, :simple, source_project: project) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.owner }
+
+ before do
+ sign_in(user)
+
+ visit(merge_request_path(merge_request))
+ end
+
+ it 'user opens merge request' do
+ click_link 'Open in Web IDE'
+
+ wait_for_requests
+
+ expect(page).to have_selector('.monaco-diff-editor')
+ end
+end
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index 3e75890725e..c8dc72a34ec 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -35,7 +35,7 @@ describe 'Download buttons in branches page' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, 'binary-encoding/download', job: 'build')
- expect(page).to have_link build.name, href: href
+ expect(page).to have_link "Download '#{build.name}'", href: href
end
end
end
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 111972a6b00..03cb3530e2b 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -30,7 +30,7 @@ describe 'Projects > Files > Download buttons in files tree' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
- expect(page).to have_link build.name, href: href
+ expect(page).to have_link "Download '#{build.name}'", href: href
end
end
end
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
index 65b597da269..1a13fe03a67 100644
--- a/spec/features/projects/services/disable_triggers_spec.rb
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -14,11 +14,10 @@ describe 'Disable individual triggers' do
end
context 'service has multiple supported events' do
- let(:service_name) { 'JIRA' }
+ let(:service_name) { 'HipChat' }
it 'shows trigger checkboxes' do
- event_count = JiraService.supported_events.count
- expect(event_count).to be > 1
+ event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger"
expect(page).to have_css(checkbox_selector, count: event_count)
diff --git a/spec/features/projects/services/user_activates_hipchat_spec.rb b/spec/features/projects/services/user_activates_hipchat_spec.rb
new file mode 100644
index 00000000000..d6b69a5bd68
--- /dev/null
+++ b/spec/features/projects/services/user_activates_hipchat_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User activates HipChat' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit(project_settings_integrations_path(project))
+
+ click_link('HipChat')
+ end
+
+ context 'with standart settings' do
+ it 'activates service' do
+ check('Active')
+ fill_in('Room', with: 'gitlab')
+ fill_in('Token', with: 'verySecret')
+ click_button('Save')
+
+ expect(page).to have_content('HipChat activated.')
+ end
+ end
+
+ context 'with custom settings' do
+ it 'activates service' do
+ check('Active')
+ fill_in('Room', with: 'gitlab_custom')
+ fill_in('Token', with: 'secretCustom')
+ fill_in('Server', with: 'https://chat.example.com')
+ click_button('Save')
+
+ expect(page).to have_content('HipChat activated.')
+ end
+ end
+end
diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb
index b0a838a7d2b..e9c8cf0fe34 100644
--- a/spec/features/projects/services/user_views_services_spec.rb
+++ b/spec/features/projects/services/user_views_services_spec.rb
@@ -14,6 +14,7 @@ describe 'User views services' do
it 'shows the list of available services' do
expect(page).to have_content('Project services')
expect(page).to have_content('Campfire')
+ expect(page).to have_content('HipChat')
expect(page).to have_content('Assembla')
expect(page).to have_content('Pushover')
expect(page).to have_content('Atlassian Bamboo')
@@ -21,7 +22,5 @@ describe 'User views services' do
expect(page).to have_content('Asana')
expect(page).to have_content('Irker (IRC gateway)')
expect(page).to have_content('Packagist')
- expect(page).to have_content('Mattermost')
- expect(page).to have_content('Slack')
end
end
diff --git a/spec/features/projects/settings/user_renames_a_project_spec.rb b/spec/features/projects/settings/user_renames_a_project_spec.rb
index 64c9af4b706..d3979b79910 100644
--- a/spec/features/projects/settings/user_renames_a_project_spec.rb
+++ b/spec/features/projects/settings/user_renames_a_project_spec.rb
@@ -9,24 +9,33 @@ describe 'Projects > Settings > User renames a project' do
visit edit_project_path(project)
end
- def rename_project(project, name: nil, path: nil)
- fill_in('project_name', with: name) if name
- fill_in('Path', with: path) if path
- click_button('Rename project')
+ def change_path(project, path)
+ within('.advanced-settings') do
+ fill_in('Path', with: path)
+ click_button('Change path')
+ end
+ project.reload
wait_for_edit_project_page_reload
+ end
+
+ def change_name(project, name)
+ within('.general-settings') do
+ fill_in('Project name', with: name)
+ click_button('Save changes')
+ end
project.reload
+ wait_for_edit_project_page_reload
end
def wait_for_edit_project_page_reload
- expect(find('.project-edit-container')).to have_content('Rename repository')
+ expect(find('.advanced-settings')).to have_content('Change path')
end
context 'with invalid characters' do
- it 'shows errors for invalid project path/name' do
- rename_project(project, name: 'foo&bar', path: 'foo&bar')
- expect(page).to have_field 'Project name', with: 'foo&bar'
+ it 'shows errors for invalid project path' do
+ change_path(project, 'foo&bar')
+
expect(page).to have_field 'Path', with: 'foo&bar'
- expect(page).to have_content "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'"
end
end
@@ -42,13 +51,13 @@ describe 'Projects > Settings > User renames a project' do
context 'when changing project name' do
it 'renames the repository' do
- rename_project(project, name: 'bar')
+ change_name(project, 'bar')
expect(find('.breadcrumbs')).to have_content(project.name)
end
context 'with emojis' do
it 'shows error for invalid project name' do
- rename_project(project, name: '🚀 foo bar ☁️')
+ change_name(project, '🚀 foo bar ☁️')
expect(page).to have_field 'Project name', with: '🚀 foo bar ☁️'
expect(page).not_to have_content "Name can contain only letters, digits, emojis '_', '.', dash and space. It must start with letter, digit, emoji or '_'."
end
@@ -67,7 +76,7 @@ describe 'Projects > Settings > User renames a project' do
end
it 'the project is accessible via the new path' do
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit new_path
@@ -77,7 +86,7 @@ describe 'Projects > Settings > User renames a project' do
it 'the project is accessible via a redirect from the old path' do
old_path = project_path(project)
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_path = namespace_project_path(project.namespace, 'bar')
visit old_path
@@ -88,7 +97,7 @@ describe 'Projects > Settings > User renames a project' do
context 'and a new project is added with the same path' do
it 'overrides the redirect' do
old_path = project_path(project)
- rename_project(project, path: 'bar')
+ change_path(project, 'bar')
new_project = create(:project, namespace: user.namespace, path: 'gitlabhq', name: 'quz')
visit old_path
diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb
index fee5f8001b0..3a2dcc5aa55 100644
--- a/spec/features/projects/show/download_buttons_spec.rb
+++ b/spec/features/projects/show/download_buttons_spec.rb
@@ -35,10 +35,11 @@ describe 'Projects > Show > Download buttons' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{project.default_branch}/download", job: 'build')
- expect(page).to have_link build.name, href: href
+ expect(page).to have_link "Download '#{build.name}'", href: href
end
it 'download links have download attribute' do
+ expect(page).to have_selector('a', text: 'Download')
page.all('a', text: 'Download').each do |link|
expect(link[:download]).to eq ''
end
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index 4c8ec53836a..fbfd8cee7aa 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -36,7 +36,7 @@ describe 'Download buttons in tags page' do
it 'shows download artifacts button' do
href = latest_succeeded_project_artifacts_path(project, "#{tag}/download", job: 'build')
- expect(page).to have_link build.name, href: href
+ expect(page).to have_link "Download '#{build.name}'", href: href
end
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index dbf0d427976..ff4e6197746 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -373,6 +373,21 @@ describe 'Project' do
end
end
+ describe 'edit' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:path) { edit_project_path(project) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ visit path
+ end
+
+ it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="project[name]"]' },
+ { form: '.qa-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
+ end
+
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml
index ff18f0e4a2d..e78a38d31f5 100644
--- a/spec/frontend/.eslintrc.yml
+++ b/spec/frontend/.eslintrc.yml
@@ -3,6 +3,8 @@ env:
jest/globals: true
plugins:
- jest
+extends:
+ - 'plugin:jest/recommended'
settings:
import/resolver:
jest:
@@ -12,8 +14,3 @@ globals:
loadFixtures: false
preloadFixtures: false
setFixtures: false
-rules:
- jest/no-identical-title: error
- jest/no-focused-tests: error
- jest/valid-describe: error
- jest/no-jasmine-globals: error
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index eea7bd87257..33a35069004 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -12,7 +12,7 @@ import { setTestTimeout } from 'helpers/timeout';
import $ from 'jquery';
describe('Clusters', () => {
- setTestTimeout(500);
+ setTestTimeout(1000);
let cluster;
let mock;
diff --git a/spec/frontend/ide/stores/mutations/merge_request_spec.js b/spec/frontend/ide/stores/mutations/merge_request_spec.js
index e30ca22022f..afbe6770c0d 100644
--- a/spec/frontend/ide/stores/mutations/merge_request_spec.js
+++ b/spec/frontend/ide/stores/mutations/merge_request_spec.js
@@ -32,6 +32,24 @@ describe('IDE store merge request mutations', () => {
expect(newMr.title).toBe('mr');
expect(newMr.active).toBeTruthy();
});
+
+ it('keeps original data', () => {
+ const versions = ['change'];
+ const mergeRequest = localState.projects.abcproject.mergeRequests[1];
+
+ mergeRequest.versions = versions;
+
+ mutations.SET_MERGE_REQUEST(localState, {
+ projectPath: 'abcproject',
+ mergeRequestId: 1,
+ mergeRequest: {
+ title: ['change'],
+ },
+ });
+
+ expect(mergeRequest.title).toBe('mr');
+ expect(mergeRequest.versions).toEqual(versions);
+ });
});
describe('SET_MERGE_REQUEST_CHANGES', () => {
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 3f331055a32..17fdbf606b2 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -23,14 +23,6 @@ describe('text_utility', () => {
});
});
- describe('capitalizeFirstCharacter', () => {
- it('returns string with first letter capitalized', () => {
- expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab');
- expect(textUtils.highCountTrim(105)).toBe('99+');
- expect(textUtils.highCountTrim(100)).toBe('99+');
- });
- });
-
describe('humanize', () => {
it('should remove underscores and uppercase the first letter', () => {
expect(textUtils.humanize('foo_bar')).toEqual('Foo bar');
@@ -63,6 +55,12 @@ describe('text_utility', () => {
});
});
+ describe('capitalizeFirstCharacter', () => {
+ it('returns string with first letter capitalized', () => {
+ expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab');
+ });
+ });
+
describe('slugifyWithHyphens', () => {
it('should replaces whitespaces with hyphens and convert to lower case', () => {
expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string');
diff --git a/spec/javascripts/fixtures/environments/table.html.haml b/spec/javascripts/fixtures/environments/table.html.haml
deleted file mode 100644
index 59edc0396d2..00000000000
--- a/spec/javascripts/fixtures/environments/table.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-%table
- %thead
- %tr
- %th Environment
- %th Last deployment
- %th Job
- %th Commit
- %th
- %th
- %tbody
- %tr#environment-row
diff --git a/spec/javascripts/fixtures/static/environments/table.html b/spec/javascripts/fixtures/static/environments/table.html
new file mode 100644
index 00000000000..417af564ff1
--- /dev/null
+++ b/spec/javascripts/fixtures/static/environments/table.html
@@ -0,0 +1,15 @@
+<table>
+<thead>
+<tr>
+<th>Environment</th>
+<th>Last deployment</th>
+<th>Job</th>
+<th>Commit</th>
+<th></th>
+<th></th>
+</tr>
+</thead>
+<tbody>
+<tr id="environment-row"></tr>
+</tbody>
+</table>
diff --git a/spec/javascripts/fixtures/static_fixtures.rb b/spec/javascripts/fixtures/static_fixtures.rb
deleted file mode 100644
index cb4b90cdca5..00000000000
--- a/spec/javascripts/fixtures/static_fixtures.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe ApplicationController, '(Static JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- Dir.glob('{,ee/}spec/javascripts/fixtures/**/*.haml').map do |file_path|
- it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '')}" do |example|
- store_frontend_fixture(render_template(file_path), example.description)
- end
- end
-
- private
-
- def render_template(template_file_name)
- controller = ApplicationController.new
- controller.prepend_view_path(File.dirname(template_file_name))
- controller.render_to_string(template: File.basename(template_file_name), layout: false)
- end
-end
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 3d2c617e479..394e3343be6 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -670,7 +670,7 @@ describe('Notes', function() {
done();
})
.catch(done.fail);
- });
+ }, 2000);
});
describe('postComment with Slash commands', () => {
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index fdb43d1221a..088f8acf554 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -152,14 +152,13 @@ describe Gitlab::Git::Repository, :seed_helper do
let(:append_sha) { true }
let(:ref) { 'master' }
let(:format) { nil }
- let(:path) { nil }
let(:expected_extension) { 'tar.gz' }
let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" }
let(:expected_path) { File.join(storage_path, cache_key, expected_filename) }
let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" }
- subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha, path: path) }
+ subject(:metadata) { repository.archive_metadata(ref, storage_path, 'gitlab-git-test', format, append_sha: append_sha) }
it 'sets CommitId to the commit SHA' do
expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID)
@@ -177,14 +176,6 @@ describe Gitlab::Git::Repository, :seed_helper do
expect(metadata['ArchivePath']).to eq(expected_path)
end
- context 'path is set' do
- let(:path) { 'foo/bar' }
-
- it 'appends the path to the prefix' do
- expect(metadata['ArchivePrefix']).to eq("#{expected_prefix}-foo-bar")
- end
- end
-
context 'append_sha varies archive path and filename' do
where(:append_sha, :ref, :expected_prefix) do
sha = SeedRepo::LastCommit::ID
@@ -531,6 +522,13 @@ describe Gitlab::Git::Repository, :seed_helper do
it 'has valid commit ids as keys' do
expect(subject.keys).to all( match(Commit::COMMIT_SHA_PATTERN) )
end
+
+ it 'does not error when dereferenced_target is nil' do
+ blob_id = repository.blob_at('master', 'README.md').id
+ repository_rugged.tags.create("refs/tags/blob-tag", blob_id)
+
+ expect { subject }.not_to raise_error
+ end
end
describe '#fetch_repository_as_mirror' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index ed557ffd4e3..54369ff75f4 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -223,6 +223,7 @@ project:
- packagist_service
- pivotaltracker_service
- prometheus_service
+- hipchat_service
- flowdock_service
- assembla_service
- asana_service
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 773651dd226..4a7accc4c52 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6795,6 +6795,28 @@
"wiki_page_events": true
},
{
+ "id": 93,
+ "title": "HipChat",
+ "project_id": 5,
+ "created_at": "2016-06-14T15:01:51.219Z",
+ "updated_at": "2016-06-14T15:01:51.219Z",
+ "active": false,
+ "properties": {
+ "notify_only_broken_pipelines": true
+ },
+ "template": false,
+ "push_events": true,
+ "issues_events": true,
+ "merge_requests_events": true,
+ "tag_push_events": true,
+ "note_events": true,
+ "pipeline_events": true,
+ "type": "HipchatService",
+ "category": "common",
+ "default": false,
+ "wiki_page_events": true
+ },
+ {
"id": 91,
"title": "Flowdock",
"project_id": 5,
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 62970bd8cb6..445a56ab0d8 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -23,10 +23,10 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git', ports: ports)).to be true
end
- it 'returns true for bad protocol' do
- expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['https'])).to be false
+ it 'returns true for bad scheme' do
+ expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['https'])).to be false
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
- expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true
+ expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['http'])).to be true
end
it 'returns true for bad protocol on configured web/SSH host and ports' do
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 2c1146ceff5..0a170a157fe 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -85,4 +85,12 @@ describe Gitlab::VisibilityLevel do
.to eq(described_class::PRIVATE)
end
end
+
+ describe '.valid_level?' do
+ it 'returns true when visibility is valid' do
+ expect(described_class.valid_level?(described_class::PRIVATE)).to be_truthy
+ expect(described_class.valid_level?(described_class::INTERNAL)).to be_truthy
+ expect(described_class.valid_level?(described_class::PUBLIC)).to be_truthy
+ end
+ end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index f8ce399287a..d02d9be5c5c 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -16,12 +16,20 @@ describe Gitlab::Workhorse do
let(:ref) { 'master' }
let(:format) { 'zip' }
let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path }
- let(:path) { 'some/path' }
- let(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: nil, path: path) }
+ let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) }
+ let(:gitaly_params) do
+ base_params.merge(
+ 'GitalyServer' => {
+ 'address' => Gitlab::GitalyClient.address(project.repository_storage),
+ 'token' => Gitlab::GitalyClient.token(project.repository_storage)
+ },
+ 'GitalyRepository' => repository.gitaly_repository.to_h.deep_stringify_keys
+ )
+ end
let(:cache_disabled) { false }
subject do
- described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil, path: path)
+ described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil)
end
before do
@@ -33,22 +41,7 @@ describe Gitlab::Workhorse do
expect(key).to eq('Gitlab-Workhorse-Send-Data')
expect(command).to eq('git-archive')
- expect(params).to eq({
- 'GitalyServer' => {
- address: Gitlab::GitalyClient.address(project.repository_storage),
- token: Gitlab::GitalyClient.token(project.repository_storage)
- },
- 'ArchivePath' => metadata['ArchivePath'],
- 'GetArchiveRequest' => Base64.urlsafe_encode64(
- Gitaly::GetArchiveRequest.new(
- repository: repository.gitaly_repository,
- commit_id: metadata['CommitId'],
- prefix: metadata['ArchivePrefix'],
- format: Gitaly::GetArchiveRequest::Format::ZIP,
- path: path
- ).to_proto
- )
- }.deep_stringify_keys)
+ expect(params).to include(gitaly_params)
end
context 'when archive caching is disabled' do
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
new file mode 100644
index 00000000000..fd9e33c1781
--- /dev/null
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -0,0 +1,410 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HipchatService do
+ describe "Associations" do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:token) }
+ end
+
+ context 'when service is inactive' do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ end
+ end
+
+ describe "Execute" do
+ let(:hipchat) { described_class.new }
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
+ let(:project_name) { project.full_name.gsub(/\s/, '') }
+ let(:token) { 'verySecret' }
+ let(:server_url) { 'https://hipchat.example.com'}
+ let(:push_sample_data) do
+ Gitlab::DataBuilder::Push.build_sample(project, user)
+ end
+
+ before do
+ allow(hipchat).to receive_messages(
+ project_id: project.id,
+ project: project,
+ room: 123456,
+ server: server_url,
+ token: token
+ )
+ WebMock.stub_request(:post, api_url)
+ end
+
+ it 'tests and return errors' do
+ allow(hipchat).to receive(:execute).and_raise(StandardError, 'no such room')
+ result = hipchat.test(push_sample_data)
+
+ expect(result[:success]).to be_falsey
+ expect(result[:result].to_s).to eq('no such room')
+ end
+
+ it 'uses v1 if version is provided' do
+ allow(hipchat).to receive(:api_version).and_return('v1')
+ expect(HipChat::Client).to receive(:new).with(
+ token,
+ api_version: 'v1',
+ server_url: server_url
+ ).and_return(double(:hipchat_service).as_null_object)
+ hipchat.execute(push_sample_data)
+ end
+
+ it 'uses v2 as the version when nothing is provided' do
+ allow(hipchat).to receive(:api_version).and_return('')
+ expect(HipChat::Client).to receive(:new).with(
+ token,
+ api_version: 'v2',
+ server_url: server_url
+ ).and_return(double(:hipchat_service).as_null_object)
+ hipchat.execute(push_sample_data)
+ end
+
+ context 'push events' do
+ it "calls Hipchat API for push events" do
+ hipchat.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a push message" do
+ message = hipchat.send(:create_push_message, push_sample_data)
+
+ push_sample_data[:object_attributes]
+ branch = push_sample_data[:ref].gsub('refs/heads/', '')
+ expect(message).to include("#{user.name} pushed to branch " \
+ "<a href=\"#{project.web_url}/commits/#{branch}\">#{branch}</a> of " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>")
+ end
+ end
+
+ context 'tag_push events' do
+ let(:push_sample_data) do
+ Gitlab::DataBuilder::Push.build(
+ project,
+ user,
+ Gitlab::Git::BLANK_SHA,
+ '1' * 40,
+ 'refs/tags/test',
+ [])
+ end
+
+ it "calls Hipchat API for tag push events" do
+ hipchat.execute(push_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a tag push message" do
+ message = hipchat.send(:create_push_message, push_sample_data)
+
+ push_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} pushed new tag " \
+ "<a href=\"#{project.web_url}/commits/test\">test</a> to " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>\n")
+ end
+ end
+
+ context 'issue events' do
+ let(:issue) { create(:issue, title: 'Awesome issue', description: '**please** fix') }
+ let(:issue_service) { Issues::CreateService.new(project, user) }
+ let(:issues_sample_data) { issue_service.hook_data(issue, 'open') }
+
+ it "calls Hipchat API for issue events" do
+ hipchat.execute(issues_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates an issue message" do
+ message = hipchat.send(:create_issue_message, issues_sample_data)
+
+ obj_attr = issues_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} opened " \
+ "<a href=\"#{obj_attr[:url]}\">issue ##{obj_attr["iid"]}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>Awesome issue</b>" \
+ "<pre><strong>please</strong> fix</pre>")
+ end
+ end
+
+ context 'merge request events' do
+ let(:merge_request) { create(:merge_request, description: '**please** fix', title: 'Awesome merge request', target_project: project, source_project: project) }
+ let(:merge_service) { MergeRequests::CreateService.new(project, user) }
+ let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') }
+
+ it "calls Hipchat API for merge requests events" do
+ hipchat.execute(merge_sample_data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a merge request message" do
+ message = hipchat.send(:create_merge_request_message,
+ merge_sample_data)
+
+ obj_attr = merge_sample_data[:object_attributes]
+ expect(message).to eq("#{user.name} opened " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{obj_attr["iid"]}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>Awesome merge request</b>" \
+ "<pre><strong>please</strong> fix</pre>")
+ end
+ end
+
+ context "Note events" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, creator: user) }
+
+ context 'when commit comment event triggered' do
+ let(:commit_note) do
+ create(:note_on_commit, author: user, project: project,
+ commit_id: project.repository.commit.id,
+ note: 'a comment on a commit')
+ end
+
+ it "calls Hipchat API for commit comment events" do
+ data = Gitlab::DataBuilder::Note.build(commit_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ commit_id = Commit.truncate_sha(data[:commit][:id])
+ title = hipchat.send(:format_title, data[:commit][:message])
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">commit #{commit_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "#{title}" \
+ "<pre>a comment on a commit</pre>")
+ end
+ end
+
+ context 'when merge request comment event triggered' do
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ let(:merge_request_note) do
+ create(:note_on_merge_request, noteable: merge_request,
+ project: project,
+ note: "merge request **note**")
+ end
+
+ it "calls Hipchat API for merge request comment events" do
+ data = Gitlab::DataBuilder::Note.build(merge_request_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ merge_id = data[:merge_request]['iid']
+ title = data[:merge_request]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">merge request !#{merge_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>merge request <strong>note</strong></pre>")
+ end
+ end
+
+ context 'when issue comment event triggered' do
+ let(:issue) { create(:issue, project: project) }
+ let(:issue_note) do
+ create(:note_on_issue, noteable: issue, project: project,
+ note: "issue **note**")
+ end
+
+ it "calls Hipchat API for issue comment events" do
+ data = Gitlab::DataBuilder::Note.build(issue_note, user)
+ hipchat.execute(data)
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ issue_id = data[:issue]['iid']
+ title = data[:issue]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">issue ##{issue_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>issue <strong>note</strong></pre>")
+ end
+
+ context 'with confidential issue' do
+ before do
+ issue.update!(confidential: true)
+ end
+
+ it 'calls Hipchat API with issue comment' do
+ data = Gitlab::DataBuilder::Note.build(issue_note, user)
+ hipchat.execute(data)
+
+ message = hipchat.send(:create_message, data)
+
+ expect(message).to include("<pre>issue <strong>note</strong></pre>")
+ end
+ end
+ end
+
+ context 'when snippet comment event triggered' do
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:snippet_note) do
+ create(:note_on_project_snippet, noteable: snippet,
+ project: project,
+ note: "snippet note")
+ end
+
+ it "calls Hipchat API for snippet comment events" do
+ data = Gitlab::DataBuilder::Note.build(snippet_note, user)
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+
+ message = hipchat.send(:create_message, data)
+
+ obj_attr = data[:object_attributes]
+ snippet_id = data[:snippet]['id']
+ title = data[:snippet]['title']
+
+ expect(message).to eq("#{user.name} commented on " \
+ "<a href=\"#{obj_attr[:url]}\">snippet ##{snippet_id}</a> in " \
+ "<a href=\"#{project.web_url}\">#{project_name}</a>: " \
+ "<b>#{title}</b>" \
+ "<pre>snippet note</pre>")
+ end
+ end
+ end
+
+ context 'pipeline events' do
+ let(:pipeline) { create(:ci_empty_pipeline, user: create(:user)) }
+ let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) }
+
+ context 'for failed' do
+ before do
+ pipeline.drop
+ end
+
+ it "calls Hipchat API" do
+ hipchat.execute(data)
+
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "creates a build message" do
+ message = hipchat.__send__(:create_pipeline_message, data)
+
+ project_url = project.web_url
+ project_name = project.full_name.gsub(/\s/, '')
+ pipeline_attributes = data[:object_attributes]
+ ref = pipeline_attributes[:ref]
+ ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
+ duration = pipeline_attributes[:duration]
+ user_name = data[:user][:name]
+
+ expect(message).to eq("<a href=\"#{project_url}\">#{project_name}</a>: " \
+ "Pipeline <a href=\"#{project_url}/pipelines/#{pipeline.id}\">##{pipeline.id}</a> " \
+ "of <a href=\"#{project_url}/commits/#{ref}\">#{ref}</a> #{ref_type} " \
+ "by #{user_name} failed in #{duration} second(s)")
+ end
+ end
+
+ context 'for succeeded' do
+ before do
+ pipeline.succeed
+ end
+
+ it "calls Hipchat API" do
+ hipchat.notify_only_broken_pipelines = false
+ hipchat.execute(data)
+ expect(WebMock).to have_requested(:post, api_url).once
+ end
+
+ it "notifies only broken" do
+ hipchat.notify_only_broken_pipelines = true
+ hipchat.execute(data)
+ expect(WebMock).not_to have_requested(:post, api_url).once
+ end
+ end
+ end
+
+ context "#message_options" do
+ it "is set to the defaults" do
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' })
+ end
+
+ it "sets notify to true" do
+ allow(hipchat).to receive(:notify).and_return('1')
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: true, color: 'yellow' })
+ end
+
+ it "sets the color" do
+ allow(hipchat).to receive(:color).and_return('red')
+
+ expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'red' })
+ end
+
+ context 'with a successful build' do
+ it 'uses the green color' do
+ data = { object_kind: 'pipeline',
+ object_attributes: { status: 'success' } }
+
+ expect(hipchat.__send__(:message_options, data)).to eq({ notify: false, color: 'green' })
+ end
+ end
+
+ context 'with a failed build' do
+ it 'uses the red color' do
+ data = { object_kind: 'pipeline',
+ object_attributes: { status: 'failed' } }
+
+ expect(hipchat.__send__(:message_options, data)).to eq({ notify: false, color: 'red' })
+ end
+ end
+ end
+ end
+
+ context 'with UrlBlocker' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository) }
+ let(:hipchat) { create(:hipchat_service, project: project, properties: { room: 'test' }) }
+ let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
+
+ describe '#execute' do
+ before do
+ hipchat.server = 'http://localhost:9123'
+ end
+
+ it 'raises UrlBlocker for localhost' do
+ expect(Gitlab::UrlBlocker).to receive(:validate!).and_call_original
+ expect { hipchat.execute(push_sample_data) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 7f8d2ff91fd..9f6a0b53281 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -44,6 +44,7 @@ describe Project do
it { is_expected.to have_one(:pipelines_email_service) }
it { is_expected.to have_one(:irker_service) }
it { is_expected.to have_one(:pivotaltracker_service) }
+ it { is_expected.to have_one(:hipchat_service) }
it { is_expected.to have_one(:flowdock_service) }
it { is_expected.to have_one(:assembla_service) }
it { is_expected.to have_one(:slack_slash_commands_service) }
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 9388343c392..b5e45f99109 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -306,7 +306,22 @@ describe API::CommitStatuses do
it 'responds with bad request status and validation errors' do
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['target_url'])
- .to include 'is blocked: Only allowed protocols are http, https'
+ .to include 'is blocked: Only allowed schemes are http, https'
+ end
+ end
+
+ context 'when target URL is an unsupported scheme' do
+ before do
+ post api(post_url, developer), params: {
+ state: 'pending',
+ target_url: 'git://example.com'
+ }
+ end
+
+ it 'responds with bad request status and validation errors' do
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['target_url'])
+ .to include 'is blocked: Only allowed schemes are http, https'
end
end
end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index fe6a8691ae0..cd061afbfd5 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -88,6 +88,17 @@ describe Groups::CreateService, '#execute' do
end
end
+ describe "when visibility level is passed as a string" do
+ let(:service) { described_class.new(user, group_params) }
+ let(:group_params) { { path: 'group_path', visibility: 'public' } }
+
+ it "assigns the correct visibility level" do
+ group = service.execute
+
+ expect(group.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
+ end
+ end
+
describe 'creating a mattermost team' do
let!(:params) { group_params.merge(create_chat_team: "true") }
let!(:service) { described_class.new(user, params) }
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 5ed06df7072..7063ca9ac14 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -269,13 +269,11 @@ describe MergeRequests::RefreshService do
end
end
- context 'push to origin repo source branch when an MR was reopened' do
+ context 'push to origin repo source branch' do
let(:refresh_service) { service.new(@project, @user) }
let(:notification_service) { spy('notification_service') }
before do
- @merge_request.update(state: :reopened)
-
allow(refresh_service).to receive(:execute_hooks)
allow(NotificationService).to receive(:new) { notification_service }
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 0525899ebfa..15aea97ff29 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -405,7 +405,7 @@ describe MergeRequests::UpdateService, :mailer do
end
end
- context 'when the issue is relabeled' do
+ context 'when the merge request is relabeled' do
let!(:non_subscriber) { create(:user) }
let!(:subscriber) { create(:user) { |u| label.toggle_subscription(u, project) } }
diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
index 52a2ee49495..4e45e2921e7 100644
--- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
+++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
@@ -1,18 +1,17 @@
shared_examples 'dirty submit form' do |selector_args|
selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
- def expect_disabled_state(form, submit, is_disabled = true)
+ def expect_disabled_state(form, submit_selector, is_disabled = true)
disabled_selector = is_disabled == true ? '[disabled]' : ':not([disabled])'
- form.find(".js-dirty-submit#{disabled_selector}", match: :first)
-
- expect(submit.disabled?).to be is_disabled
+ form.find("#{submit_selector}#{disabled_selector}")
end
selectors.each do |selector|
it "disables #{selector[:form]} submit until there are changes on #{selector[:input]}", :js do
form = find(selector[:form])
- submit = form.first('.js-dirty-submit')
+ submit_selector = selector[:submit] || 'input[type="submit"]'
+ submit = form.first(submit_selector)
input = form.first(selector[:input])
is_radio = input[:type] == 'radio'
is_checkbox = input[:type] == 'checkbox'
@@ -22,15 +21,14 @@ shared_examples 'dirty submit form' do |selector_args|
original_checkable = input if is_checkbox
expect(submit.disabled?).to be true
- expect(input.checked?).to be false
is_checkable ? input.click : input.set("#{original_value} changes")
- expect_disabled_state(form, submit, false)
+ expect_disabled_state(form, submit_selector, false)
is_checkable ? original_checkable.click : input.set(original_value)
- expect_disabled_state(form, submit)
+ expect_disabled_state(form, submit_selector)
end
end
end
diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb
index 1f7e2f7ff79..25277ccd9aa 100644
--- a/spec/support/shared_examples/url_validator_examples.rb
+++ b/spec/support/shared_examples/url_validator_examples.rb
@@ -1,15 +1,15 @@
-RSpec.shared_examples 'url validator examples' do |protocols|
+RSpec.shared_examples 'url validator examples' do |schemes|
let(:validator) { described_class.new(attributes: [:link_url], **options) }
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
- subject { validator.validate_each(badge, :link_url, badge.link_url) }
+ subject { validator.validate(badge) }
- describe '#validates_each' do
+ describe '#validate' do
context 'with no options' do
let(:options) { {} }
- it "allows #{protocols.join(',')} protocols by default" do
- expect(validator.send(:default_options)[:protocols]).to eq protocols
+ it "allows #{schemes.join(',')} schemes by default" do
+ expect(validator.options[:schemes]).to eq schemes
end
it 'checks that the url structure is valid' do
@@ -17,25 +17,25 @@ RSpec.shared_examples 'url validator examples' do |protocols|
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
- context 'with protocols' do
- let(:options) { { protocols: %w[http] } }
+ context 'with schemes' do
+ let(:options) { { schemes: %w(http) } }
- it 'allows urls with the defined protocols' do
+ it 'allows urls with the defined schemes' do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
end
- it 'add error if the url protocol does not match the selected ones' do
+ it 'add error if the url scheme does not match the selected ones' do
badge.link_url = 'https://www.example.com'
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb
index 1bb42382e8a..387e84b2d04 100644
--- a/spec/validators/url_validator_spec.rb
+++ b/spec/validators/addressable_url_validator_spec.rb
@@ -2,11 +2,11 @@
require 'spec_helper'
-describe UrlValidator do
+describe AddressableUrlValidator do
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
- subject { validator.validate_each(badge, :link_url, badge.link_url) }
+ subject { validator.validate(badge) }
- include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS
+ include_examples 'url validator examples', described_class::DEFAULT_OPTIONS[:schemes]
describe 'validations' do
include_context 'invalid urls'
@@ -14,13 +14,13 @@ describe UrlValidator do
let(:validator) { described_class.new(attributes: [:link_url]) }
it 'returns error when url is nil' do
- expect(validator.validate_each(badge, :link_url, nil)).to be_nil
- expect(badge.errors.first[1]).to eq 'must be a valid URL'
+ expect(validator.validate_each(badge, :link_url, nil)).to be_falsey
+ expect(badge.errors.first[1]).to eq validator.options.fetch(:message)
end
it 'returns error when url is empty' do
- expect(validator.validate_each(badge, :link_url, '')).to be_nil
- expect(badge.errors.first[1]).to eq 'must be a valid URL'
+ expect(validator.validate_each(badge, :link_url, '')).to be_falsey
+ expect(badge.errors.first[1]).to eq validator.options.fetch(:message)
end
it 'does not allow urls with CR or LF characters' do
@@ -30,6 +30,17 @@ describe UrlValidator do
end
end
end
+
+ it 'provides all arguments to UrlBlock validate' do
+ expect(Gitlab::UrlBlocker)
+ .to receive(:validate!)
+ .with(badge.link_url, described_class::BLOCKER_VALIDATE_OPTIONS)
+ .and_return(true)
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
end
context 'by default' do
@@ -40,7 +51,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
end
it 'does not block urls pointing to the local network' do
@@ -48,7 +59,23 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
+ end
+
+ it 'does block nil urls' do
+ badge.link_url = nil
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ it 'does block blank urls' do
+ badge.link_url = '\n\r \n'
+
+ subject
+
+ expect(badge.errors).to be_present
end
it 'strips urls' do
@@ -67,6 +94,40 @@ describe UrlValidator do
end
end
+ context 'when message is set' do
+ let(:message) { 'is blocked: test message' }
+ let(:validator) { described_class.new(attributes: [:link_url], allow_nil: false, message: message) }
+
+ it 'does block nil url with provided error message' do
+ expect(validator.validate_each(badge, :link_url, nil)).to be_falsey
+ expect(badge.errors.first[1]).to eq message
+ end
+ end
+
+ context 'when allow_nil is set to true' do
+ let(:validator) { described_class.new(attributes: [:link_url], allow_nil: true) }
+
+ it 'does not block nil urls' do
+ badge.link_url = nil
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+
+ context 'when allow_blank is set to true' do
+ let(:validator) { described_class.new(attributes: [:link_url], allow_blank: true) }
+
+ it 'does not block blank urls' do
+ badge.link_url = "\n\r \n"
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
+ end
+
context 'when allow_localhost is set to false' do
let(:validator) { described_class.new(attributes: [:link_url], allow_localhost: false) }
@@ -75,7 +136,21 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
+ end
+
+ context 'when allow_setting_local_requests is set to true' do
+ it 'does not block urls pointing to localhost' do
+ expect(described_class)
+ .to receive(:allow_setting_local_requests?)
+ .and_return(true)
+
+ badge.link_url = 'https://127.0.0.1'
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
end
end
@@ -87,7 +162,21 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
+ end
+
+ context 'when allow_setting_local_requests is set to true' do
+ it 'does not block urls pointing to local network' do
+ expect(described_class)
+ .to receive(:allow_setting_local_requests?)
+ .and_return(true)
+
+ badge.link_url = 'https://192.168.1.1'
+
+ subject
+
+ expect(badge.errors).to be_empty
+ end
end
end
@@ -100,7 +189,7 @@ describe UrlValidator do
it 'does not block any port' do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
end
end
@@ -110,7 +199,7 @@ describe UrlValidator do
it 'blocks urls with a different port' do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
end
@@ -127,7 +216,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
@@ -139,7 +228,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
end
end
end
@@ -156,7 +245,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
@@ -168,7 +257,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
end
end
end
@@ -191,7 +280,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
it 'prevents unsafe internal urls' do
@@ -199,7 +288,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
it 'allows safe urls' do
@@ -207,7 +296,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
end
end
@@ -219,7 +308,7 @@ describe UrlValidator do
subject
- expect(badge.errors.empty?).to be true
+ expect(badge.errors).to be_empty
end
end
end
diff --git a/spec/validators/public_url_validator_spec.rb b/spec/validators/public_url_validator_spec.rb
index 710dd3dc38e..f6364fb1dd5 100644
--- a/spec/validators/public_url_validator_spec.rb
+++ b/spec/validators/public_url_validator_spec.rb
@@ -1,20 +1,20 @@
require 'spec_helper'
describe PublicUrlValidator do
- include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS
+ include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
context 'by default' do
let(:validator) { described_class.new(attributes: [:link_url]) }
let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
- subject { validator.validate_each(badge, :link_url, badge.link_url) }
+ subject { validator.validate(badge) }
it 'blocks urls pointing to localhost' do
badge.link_url = 'https://127.0.0.1'
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
it 'blocks urls pointing to the local network' do
@@ -22,7 +22,7 @@ describe PublicUrlValidator do
subject
- expect(badge.errors.empty?).to be false
+ expect(badge.errors).to be_present
end
end
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index a3fe8fa4501..39f1beb4efa 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -99,11 +99,21 @@ describe PostReceive do
end
context "gitlab-ci.yml" do
- let(:changes) { "123456 789012 refs/heads/feature\n654321 210987 refs/tags/tag" }
+ let(:changes) do
+ <<-EOF.strip_heredoc
+ 123456 789012 refs/heads/feature
+ 654321 210987 refs/tags/tag
+ 123456 789012 refs/heads/feature2
+ 123458 789013 refs/heads/feature3
+ 123459 789015 refs/heads/feature4
+ EOF
+ end
+
+ let(:changes_count) { changes.lines.count }
subject { described_class.new.perform(gl_repository, key_id, base64_changes) }
- context "creates a Ci::Pipeline for every change" do
+ context "with valid .gitlab-ci.yml" do
before do
stub_ci_pipeline_to_return_yaml_file
@@ -116,7 +126,33 @@ describe PostReceive do
.and_return(true)
end
- it { expect { subject }.to change { Ci::Pipeline.count }.by(2) }
+ context 'when git_push_create_all_pipelines is disabled' do
+ before do
+ stub_feature_flags(git_push_create_all_pipelines: false)
+ end
+
+ it "creates pipeline for branches and tags" do
+ subject
+
+ expect(Ci::Pipeline.pluck(:ref)).to contain_exactly("feature", "tag", "feature2", "feature3")
+ end
+
+ it "creates exactly #{described_class::PIPELINE_PROCESS_LIMIT} pipelines" do
+ expect(changes_count).to be > described_class::PIPELINE_PROCESS_LIMIT
+
+ expect { subject }.to change { Ci::Pipeline.count }.by(described_class::PIPELINE_PROCESS_LIMIT)
+ end
+ end
+
+ context 'when git_push_create_all_pipelines is enabled' do
+ before do
+ stub_feature_flags(git_push_create_all_pipelines: true)
+ end
+
+ it "creates all pipelines" do
+ expect { subject }.to change { Ci::Pipeline.count }.by(changes_count)
+ end
+ end
end
context "does not create a Ci::Pipeline" do
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index de6e32cb998..0c52cb5a947 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -520,6 +520,7 @@ hashie-forbidden_attributes,0.1.1,MIT
he,1.1.1,MIT
health_check,2.6.0,MIT
highlight.js,9.13.1,New BSD
+hipchat,1.5.2,MIT
hmac-drbg,1.0.1,MIT
hoopy,0.1.4,MIT
html-pipeline,2.8.4,MIT