Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml46
-rw-r--r--.rubocop.yml1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions_dropdown.vue2
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js130
-rw-r--r--app/assets/javascripts/pages/groups/clusters/edit/index.js (renamed from app/assets/javascripts/pages/groups/clusters/update/index.js)0
-rw-r--r--app/assets/javascripts/pages/projects/clusters/edit/index.js (renamed from app/assets/javascripts/pages/projects/clusters/update/index.js)0
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue4
-rw-r--r--app/assets/stylesheets/components/related_items_list.scss381
-rw-r--r--app/assets/stylesheets/pages/issues.scss14
-rw-r--r--app/assets/stylesheets/pages/issues/issue_count_badge.scss6
-rw-r--r--app/controllers/concerns/uploads_actions.rb12
-rw-r--r--app/controllers/projects_controller.rb16
-rw-r--r--app/controllers/uploads_controller.rb4
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb3
-rw-r--r--app/models/label.rb1
-rw-r--r--app/models/storage/hashed_project.rb2
-rw-r--r--app/models/storage/legacy_project.rb11
-rw-r--r--app/services/projects/after_rename_service.rb29
-rw-r--r--app/services/projects/update_service.rb9
-rw-r--r--app/views/groups/labels/index.html.haml7
-rw-r--r--app/views/layouts/nav/sidebar/_profile.html.haml2
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml66
-rw-r--r--app/views/projects/issues/_merge_requests_status.html.haml22
-rw-r--r--app/views/projects/labels/index.html.haml7
-rw-r--r--app/views/shared/labels/_nav.html.haml4
-rw-r--r--app/workers/build_finished_worker.rb26
-rw-r--r--app/workers/expire_pipeline_cache_worker.rb28
-rw-r--r--changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml5
-rw-r--r--changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml5
-rw-r--r--changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml5
-rw-r--r--changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml5
-rw-r--r--changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml5
-rw-r--r--changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-propagation.yml5
-rw-r--r--changelogs/unreleased/fix-56558-move-primary-button.yml5
-rw-r--r--changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml5
-rw-r--r--changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml5
-rw-r--r--changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml5
-rw-r--r--config/initializers/tracing.rb20
-rw-r--r--config/routes/project.rb2
-rw-r--r--danger/documentation/Dangerfile2
-rw-r--r--db/migrate/20190114172110_add_domain_to_cluster.rb9
-rw-r--r--db/post_migrate/20161221153951_rename_reserved_project_names.rb10
-rw-r--r--db/post_migrate/20170313133418_rename_more_reserved_project_names.rb10
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/gitaly/index.md19
-rw-r--r--doc/development/automatic_ce_ee_merge.md4
-rw-r--r--doc/development/sidekiq_debugging.md9
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/update/10.0-to-10.1.md2
-rw-r--r--doc/update/10.1-to-10.2.md2
-rw-r--r--doc/update/10.2-to-10.3.md2
-rw-r--r--doc/update/10.3-to-10.4.md2
-rw-r--r--doc/update/10.4-to-10.5.md2
-rw-r--r--doc/update/10.5-to-10.6.md2
-rw-r--r--doc/update/10.6-to-10.7.md2
-rw-r--r--doc/update/10.7-to-10.8.md2
-rw-r--r--doc/update/10.8-to-11.0.md2
-rw-r--r--doc/update/11.0-to-11.1.md2
-rw-r--r--doc/update/11.1-to-11.2.md2
-rw-r--r--doc/update/11.2-to-11.3.md2
-rw-r--r--doc/update/11.3-to-11.4.md2
-rw-r--r--doc/update/11.4-to-11.5.md2
-rw-r--r--doc/update/11.5-to-11.6.md2
-rw-r--r--doc/update/11.6-to-11.7.md2
-rw-r--r--doc/update/11.7-to-11.8.md2
-rw-r--r--doc/update/6.9-to-7.0.md2
-rw-r--r--doc/update/6.x-or-7.x-to-7.14.md2
-rw-r--r--doc/update/7.0-to-7.1.md2
-rw-r--r--doc/update/8.10-to-8.11.md2
-rw-r--r--doc/update/8.11-to-8.12.md2
-rw-r--r--doc/update/8.12-to-8.13.md2
-rw-r--r--doc/update/8.13-to-8.14.md2
-rw-r--r--doc/update/8.14-to-8.15.md2
-rw-r--r--doc/update/8.15-to-8.16.md2
-rw-r--r--doc/update/8.16-to-8.17.md2
-rw-r--r--doc/update/8.17-to-9.0.md2
-rw-r--r--doc/update/9.0-to-9.1.md2
-rw-r--r--doc/update/9.1-to-9.2.md2
-rw-r--r--doc/update/9.2-to-9.3.md2
-rw-r--r--doc/update/9.3-to-9.4.md2
-rw-r--r--doc/update/9.4-to-9.5.md2
-rw-r--r--doc/update/9.5-to-10.0.md2
-rw-r--r--doc/user/project/clusters/serverless/index.md6
-rw-r--r--doc/user/project/index.md9
-rw-r--r--doc/user/project/issues/csv_import.md26
-rw-r--r--doc/user/project/issues/img/import_csv_button.pngbin4342 -> 0 bytes
-rw-r--r--doc/user/project/issues/index.md16
-rw-r--r--lib/gitlab/ci/pipeline/chain/build.rb1
-rw-r--r--lib/gitlab/ci/pipeline/chain/populate.rb4
-rw-r--r--lib/gitlab/gitaly_client.rb9
-rw-r--r--lib/gitlab/sidekiq_logging/structured_logger.rb17
-rw-r--r--lib/gitlab/tracing/common.rb59
-rw-r--r--lib/gitlab/tracing/grpc_interceptor.rb54
-rw-r--r--lib/gitlab/tracing/rack_middleware.rb46
-rw-r--r--lib/gitlab/tracing/sidekiq/client_middleware.rb26
-rw-r--r--lib/gitlab/tracing/sidekiq/server_middleware.rb26
-rw-r--r--lib/gitlab/tracing/sidekiq/sidekiq_common.rb22
-rw-r--r--package.json9
-rw-r--r--qa/qa/git/repository.rb114
-rw-r--r--qa/qa/page/label/index.rb2
-rw-r--r--qa/qa/resource/repository/push.rb29
-rw-r--r--spec/controllers/projects_controller_spec.rb53
-rw-r--r--spec/controllers/uploads_controller_spec.rb14
-rw-r--r--spec/factories/clusters/clusters.rb4
-rw-r--r--spec/features/issuables/markdown_references/internal_references_spec.rb4
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb2
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb2
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js40
-rw-r--r--spec/javascripts/matchers.js39
-rw-r--r--spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb65
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb25
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb31
-rw-r--r--spec/lib/gitlab/tracing/grpc_interceptor_spec.rb47
-rw-r--r--spec/lib/gitlab/tracing/rack_middleware_spec.rb62
-rw-r--r--spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb43
-rw-r--r--spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb43
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb5
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb5
-rw-r--r--spec/models/clusters/applications/runner_spec.rb6
-rw-r--r--spec/models/clusters/cluster_spec.rb27
-rw-r--r--spec/requests/api/submodules_spec.rb4
-rw-r--r--spec/routing/project_routing_spec.rb4
-rw-r--r--spec/services/projects/after_rename_service_spec.rb124
-rw-r--r--spec/support/migrations_helpers/cluster_helpers.rb71
-rw-r--r--yarn.lock124
130 files changed, 1931 insertions, 443 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 68dea56a67d..bca16f50245 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -427,15 +427,7 @@ setup-test-env:
- vendor/gitaly-ruby
# GitLab Review apps
-.review-base: &review-base
- <<: *dedicated-no-docs-no-db-pull-cache-job
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
- stage: test
- cache: {}
- dependencies: []
- environment: &review-environment
- name: review/${CI_COMMIT_REF_NAME}
- url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
+.review-only: &review-only
only:
refs:
- branches@gitlab-org/gitlab-ce
@@ -445,6 +437,17 @@ setup-test-env:
refs:
- master
- /(^docs[\/-].*|.*-docs$)/
+
+.review-base: &review-base
+ <<: *dedicated-no-docs-no-db-pull-cache-job
+ <<: *review-only
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
+ stage: test
+ cache: {}
+ dependencies: []
+ environment: &review-environment
+ name: review/${CI_COMMIT_REF_NAME}
+ url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
before_script: []
.review-docker: &review-docker
@@ -947,6 +950,21 @@ no_ee_check:
- //@gitlab-org/gitlab-ce
# GitLab Review apps
+review-build-cng:
+ <<: *single-script-job
+ <<: *review-only
+ variables:
+ <<: *single-script-job-variables
+ SCRIPT_NAME: trigger-build
+ script:
+ - gem install gitlab --no-document
+ - apk add --update openssl curl jq
+ - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/review_apps/review-apps.sh
+ - chmod 755 review-apps.sh
+ - source ./review-apps.sh
+ - wait_for_job_to_be_done "gitlab:assets:compile"
+ - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./$SCRIPT_NAME cng
+
review-deploy:
<<: *review-base
retry: 2
@@ -961,15 +979,14 @@ review-deploy:
<<: *review-environment
on_stop: review-stop
before_script:
- - apk update && apk add jq
- - gem install gitlab --no-document
- script:
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
+ - apk update && apk add jq
+ - gem install gitlab --no-document
- source ./scripts/review_apps/review-apps.sh
- - wait_for_job_to_be_done "gitlab:assets:compile"
- - BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
+ script:
+ - wait_for_job_to_be_done "review-build-cng"
- check_kube_domain
- download_gitlab_chart
- ensure_namespace
@@ -980,7 +997,6 @@ review-deploy:
.review-qa-base: &review-qa-base
<<: *review-docker
- retry: 2
allow_failure: true
variables:
<<: *review-docker-variables
diff --git a/.rubocop.yml b/.rubocop.yml
index e8e550fdbde..bcff67ded8c 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -143,6 +143,7 @@ Naming/FileName:
- XMPP
- XSRF
- XSS
+ - GRPC
# GitLab ###################################################################
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index f88cf52e6ef..cd99d386a8d 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.13.0 \ No newline at end of file
+1.14.0 \ No newline at end of file
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index ae9a76b9249..da156181014 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.0.0
+8.1.0 \ No newline at end of file
diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
index 561188c1e8f..80aec84f574 100644
--- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
@@ -141,7 +141,7 @@ export default {
<time-ago
v-if="version.created_at"
:time="version.created_at"
- class="js-timeago js-timeago-render"
+ class="js-timeago"
/>
</small>
</div>
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 01dbbb9dd16..d3fe8f77bd4 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -87,44 +87,67 @@ let timeagoInstance;
*/
export const getTimeago = () => {
if (!timeagoInstance) {
- const localeRemaining = (number, index) =>
- [
- [s__('Timeago|just now'), s__('Timeago|right now')],
- [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
- [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
- [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
- [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
- [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
- [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')],
- [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
- [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')],
- [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
- [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')],
- [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
- [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
- [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
- ][index];
-
- const locale = (number, index) =>
- [
- [s__('Timeago|just now'), s__('Timeago|right now')],
- [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
- [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
- [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
- [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
- [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')],
- [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')],
- [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
- [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')],
- [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
- [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')],
- [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
- [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
- [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
- ][index];
-
- timeago.register(timeagoLanguageCode, locale);
- timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining);
+ const memoizedLocaleRemaining = () => {
+ const cache = [];
+
+ const timeAgoLocaleRemaining = [
+ () => [s__('Timeago|just now'), s__('Timeago|right now')],
+ () => [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
+ () => [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
+ () => [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
+ () => [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
+ () => [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
+ () => [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')],
+ () => [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
+ () => [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')],
+ () => [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
+ () => [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')],
+ () => [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
+ () => [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
+ () => [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
+ ];
+
+ return (number, index) => {
+ if (cache[index]) {
+ return cache[index];
+ }
+ cache[index] = timeAgoLocaleRemaining[index] && timeAgoLocaleRemaining[index]();
+ return cache[index];
+ };
+ };
+
+ const memoizedLocale = () => {
+ const cache = [];
+
+ const timeAgoLocale = [
+ () => [s__('Timeago|just now'), s__('Timeago|right now')],
+ () => [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
+ () => [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
+ () => [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
+ () => [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
+ () => [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')],
+ () => [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')],
+ () => [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
+ () => [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')],
+ () => [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
+ () => [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')],
+ () => [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
+ () => [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
+ () => [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
+ ];
+
+ return (number, index) => {
+ if (cache[index]) {
+ return cache[index];
+ }
+ cache[index] = timeAgoLocale[index] && timeAgoLocale[index]();
+ return cache[index];
+ };
+ };
+
+ timeago.register(timeagoLanguageCode, memoizedLocale());
+ timeago.register(`${timeagoLanguageCode}-remaining`, memoizedLocaleRemaining());
+
timeagoInstance = timeago();
}
@@ -132,35 +155,28 @@ export const getTimeago = () => {
};
/**
- * For the given element, renders a timeago instance.
- * @param {jQuery} $els
- */
-export const renderTimeago = $els => {
- const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
-
- // timeago.js sets timeouts internally for each timeago value to be updated in real time
- getTimeago().render(timeagoEls, timeagoLanguageCode);
-};
-
-/**
* For the given elements, sets a tooltip with a formatted date.
- * @param {jQuery}
+ * @param {JQuery} $timeagoEls
* @param {Boolean} setTimeago
*/
export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
- $timeagoEls.each((i, el) => {
- if (setTimeago) {
+ getTimeago().render($timeagoEls, timeagoLanguageCode);
+
+ if (!setTimeago) {
+ return;
+ }
+
+ function addTimeAgoTooltip() {
+ $timeagoEls.each((i, el) => {
// Recreate with custom template
$(el).tooltip({
template:
'<div class="tooltip local-timeago" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>',
});
- }
-
- el.classList.add('js-timeago-render');
- });
+ });
+ }
- renderTimeago($timeagoEls);
+ requestIdleCallback(addTimeAgoTooltip);
};
/**
diff --git a/app/assets/javascripts/pages/groups/clusters/update/index.js b/app/assets/javascripts/pages/groups/clusters/edit/index.js
index 8001d2dd1da..8001d2dd1da 100644
--- a/app/assets/javascripts/pages/groups/clusters/update/index.js
+++ b/app/assets/javascripts/pages/groups/clusters/edit/index.js
diff --git a/app/assets/javascripts/pages/projects/clusters/update/index.js b/app/assets/javascripts/pages/projects/clusters/edit/index.js
index 8001d2dd1da..8001d2dd1da 100644
--- a/app/assets/javascripts/pages/projects/clusters/update/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/edit/index.js
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index faea64c9841..c5cfa92f3c8 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -104,9 +104,7 @@ export default {
</div>
<div class="title hide-collapsed">
- {{
- sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName })
- }}
+ {{ sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName }) }}
<button
v-if="isEditable"
class="float-right lock-edit"
diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss
new file mode 100644
index 00000000000..048a5c0300c
--- /dev/null
+++ b/app/assets/stylesheets/components/related_items_list.scss
@@ -0,0 +1,381 @@
+$item-path-max-width: 160px;
+$item-milestone-max-width: 120px;
+$item-weight-max-width: 48px;
+
+.related-items-list {
+ padding: $gl-padding-4;
+
+ &,
+ .list-item:last-child {
+ margin-bottom: 0;
+ }
+}
+
+.item-body {
+ display: flex;
+ position: relative;
+ align-items: center;
+ padding: $gl-padding-8;
+ line-height: $gl-line-height;
+
+ .item-contents {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ flex-grow: 1;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed,
+ .confidential-icon,
+ .item-milestone .icon,
+ .item-weight .board-card-info-icon {
+ min-width: $gl-padding;
+ cursor: help;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ margin-right: $gl-padding-4;
+ }
+
+ .confidential-icon {
+ align-self: baseline;
+ color: $orange-600;
+ margin-right: $gl-padding-4;
+ }
+
+ .item-title {
+ flex-basis: 100%;
+ margin-bottom: $gl-padding-8;
+ font-size: $gl-font-size-small;
+
+ &.mr-title {
+ font-weight: $gl-font-weight-bold;
+ }
+
+ .sortable-link {
+ max-width: 85%;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: none;
+ }
+ }
+
+ .item-meta {
+ display: flex;
+ flex-wrap: wrap;
+ flex-basis: 100%;
+ font-size: $gl-font-size-small;
+ color: $gl-text-color-secondary;
+
+ .item-meta-child {
+ order: 0;
+ display: flex;
+ flex-wrap: wrap;
+ flex-basis: 100%;
+
+ .item-due-date,
+ .item-weight {
+ margin-left: $gl-padding-8;
+ }
+
+ .item-milestone,
+ .item-weight {
+ cursor: help;
+ }
+
+ .item-milestone {
+ text-decoration: none;
+ max-width: $item-milestone-max-width;
+ }
+
+ .item-due-date {
+ margin-right: 0;
+ }
+
+ .item-weight {
+ margin-right: 0;
+ max-width: $item-weight-max-width;
+ }
+ }
+
+ .item-path-id .path-id-text,
+ .item-milestone .milestone-title,
+ .item-due-date,
+ .item-weight .board-card-info-text {
+ color: $gl-text-color-secondary;
+ display: inline-block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ }
+
+ .item-path-id {
+ margin-top: $gl-padding-4;
+ font-size: $gl-font-size-xs;
+ white-space: nowrap;
+
+ .path-id-text {
+ font-weight: $gl-font-weight-bold;
+ max-width: $item-path-max-width;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: block;
+ }
+
+ &:not(.mr-item-path) {
+ order: 1;
+ }
+ }
+
+ .item-milestone .ic-clock {
+ color: $gl-text-color-tertiary;
+ margin-right: $gl-padding-4;
+ }
+
+ .item-assignees {
+ order: 2;
+ align-self: flex-end;
+ align-items: center;
+ margin-left: auto;
+
+ .user-avatar-link {
+ margin-right: -$gl-padding-4;
+
+ &:nth-of-type(1) {
+ z-index: 2;
+ }
+
+ &:nth-of-type(2) {
+ z-index: 1;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
+ .avatar {
+ height: $gl-padding;
+ width: $gl-padding;
+ margin-right: 0;
+ vertical-align: bottom;
+ }
+
+ .avatar-counter {
+ height: $gl-padding;
+ border: 1px solid transparent;
+ background-color: $gl-text-color-tertiary;
+ font-weight: $gl-font-weight-bold;
+ padding: 0 $gl-padding-4;
+ line-height: $gl-padding;
+ }
+ }
+ }
+
+ .btn-item-remove {
+ position: absolute;
+ right: 0;
+ top: $gl-padding-4 / 2;
+ padding: $gl-padding-4;
+ margin-right: $gl-padding-4 / 2;
+ line-height: 0;
+ border-color: transparent;
+ color: $gl-text-color-secondary;
+
+ &:hover {
+ color: $gl-text-color;
+ }
+ }
+}
+
+.mr-status-wrapper,
+.mr-ci-status
+ {
+ line-height: 0;
+}
+
+@include media-breakpoint-up(sm) {
+ .item-body {
+ .item-contents .item-title {
+ .mr-title-link,
+ .sortable-link {
+ max-width: 90%;
+ }
+ }
+ }
+}
+
+/* Small devices (landscape phones, 768px and up) */
+@include media-breakpoint-up(md) {
+ .item-body {
+ .item-contents {
+ min-width: 0;
+
+ .item-title {
+ flex-basis: unset;
+ // 95% because we compensate
+ // for remove button which is
+ // positioned absolutely
+ width: 95%;
+ margin-bottom: $gl-padding-4;
+
+ .mr-title-link,
+ .sortable-link {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: 100%;
+ }
+ }
+
+ .item-meta {
+ .item-path-id {
+ order: 0;
+ margin-top: 0;
+ }
+
+ .item-meta-child {
+ flex-basis: unset;
+ margin-left: auto;
+ margin-right: $gl-padding-4;
+
+ ~ .item-assignees {
+ margin-left: $gl-padding-4;
+ }
+ }
+
+ .item-assignees {
+ margin-bottom: 0;
+ margin-left: 0;
+ order: 2;
+ }
+ }
+ }
+
+ .btn-item-remove {
+ order: 1;
+ }
+ }
+}
+
+/* Medium devices (desktops, 992px and up) */
+@include media-breakpoint-up(lg) {
+ .item-body {
+ padding: $gl-padding;
+
+ .item-title {
+ font-size: $gl-font-size;
+ }
+
+ .item-meta .item-path-id {
+ font-size: inherit; // Base size given to `item-meta` is `$gl-font-size-small`
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ margin-right: $gl-padding-4;
+ }
+ }
+}
+
+/* Large devices (large desktops, 1200px and up) */
+@include media-breakpoint-up(xl) {
+ .item-body {
+ padding: $gl-padding-8;
+ padding-left: $gl-padding;
+
+ .item-contents {
+ flex-wrap: nowrap;
+ overflow: hidden;
+
+ .item-title {
+ display: flex;
+ margin-bottom: 0;
+ min-width: 0;
+ width: auto;
+ flex-basis: unset;
+ font-weight: $gl-font-weight-normal;
+
+ .mr-title-link,
+ .sortable-link {
+ display: block;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: block;
+ margin-right: $gl-padding-8;
+ }
+
+ .confidential-icon {
+ align-self: auto;
+ margin-top: 0;
+ }
+ }
+
+ .item-meta {
+ margin-top: 0;
+ justify-content: flex-end;
+ flex: 1;
+ flex-wrap: nowrap;
+
+ .item-path-id {
+ order: 0;
+ margin-top: 0;
+ margin-left: $gl-padding-8;
+ margin-right: auto;
+
+ .issue-token-state-icon-open,
+ .issue-token-state-icon-closed {
+ display: none;
+ }
+ }
+
+ .item-meta-child {
+ margin-left: $gl-padding-8;
+ flex-wrap: nowrap;
+ }
+
+ .item-assignees {
+ flex-grow: 0;
+ margin-top: 0;
+ margin-right: $gl-padding-4;
+
+ .avatar {
+ height: $gl-padding-24;
+ width: $gl-padding-24;
+ }
+
+ .avatar-counter {
+ height: $gl-padding-24;
+ min-width: $gl-padding-24;
+ line-height: $gl-padding-24;
+ border-radius: $gl-padding-24;
+ }
+ }
+ }
+ }
+
+ .btn-item-remove {
+ position: relative;
+ align-self: center;
+ top: initial;
+ right: 0;
+ margin-right: 0;
+ padding: $btn-sm-side-margin;
+
+ &:hover {
+ border-color: $border-color;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 6c847fc0d53..0037364978c 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -80,7 +80,6 @@ ul.related-merge-requests > li {
}
}
-.merge-requests-title,
.related-branches-title {
font-size: 16px;
font-weight: $gl-font-weight-bold;
@@ -91,23 +90,16 @@ ul.related-merge-requests > li {
}
.merge-request-status {
- font-size: 13px;
- padding: 0 5px;
- color: $white-light;
- height: 20px;
- border-radius: 3px;
- line-height: 18px;
-
&.merged {
- background: $blue-500;
+ color: $blue-500;
}
&.closed {
- background: $red-500;
+ color: $red-500;
}
&.open {
- background: $green-500;
+ color: $green-500;
}
}
diff --git a/app/assets/stylesheets/pages/issues/issue_count_badge.scss b/app/assets/stylesheets/pages/issues/issue_count_badge.scss
index 4fba89e956b..64ca61f7094 100644
--- a/app/assets/stylesheets/pages/issues/issue_count_badge.scss
+++ b/app/assets/stylesheets/pages/issues/issue_count_badge.scss
@@ -1,11 +1,13 @@
-.issue-count-badge {
+.issue-count-badge,
+.mr-count-badge {
display: inline-flex;
border-radius: $border-radius-base;
border: 1px solid $border-color;
padding: 5px $gl-padding-8;
}
-.issue-count-badge-count {
+.issue-count-badge-count,
+.mr-count-badge-count {
display: inline-flex;
align-items: center;
}
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index c114e16edf8..4ec0e94df9a 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -29,7 +29,13 @@ module UploadsActions
def show
return render_404 unless uploader&.exists?
- expires_in 0.seconds, must_revalidate: true, private: true
+ if cache_publicly?
+ # We need to reset caching from the applications controller to get rid of the no-store value
+ headers['Cache-Control'] = ''
+ expires_in 5.minutes, public: true, must_revalidate: false
+ else
+ expires_in 0.seconds, must_revalidate: true, private: true
+ end
disposition = uploader.image_or_video? ? 'inline' : 'attachment'
@@ -114,6 +120,10 @@ module UploadsActions
nil
end
+ def cache_publicly?
+ false
+ end
+
def model
strong_memoize(:model) { find_model }
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 878816475b2..d3af35723ac 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -10,10 +10,10 @@ class ProjectsController < Projects::ApplicationController
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :whitelist_query_limiting, only: [:create]
- before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
+ before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :resolve]
before_action :redirect_git_extension, only: [:show]
- before_action :project, except: [:index, :new, :create]
- before_action :repository, except: [:index, :new, :create]
+ before_action :project, except: [:index, :new, :create, :resolve]
+ before_action :repository, except: [:index, :new, :create, :resolve]
before_action :assign_ref_vars, only: [:show], if: :repo_exists?
before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?]
@@ -442,4 +442,14 @@ class ProjectsController < Projects::ApplicationController
def present_project
@project = @project.present(current_user: current_user)
end
+
+ def resolve
+ @project = Project.find(params[:id])
+
+ if can?(current_user, :read_project, @project)
+ redirect_to @project
+ else
+ render_404
+ end
+ end
end
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index fa5d84633b5..519e7439205 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -70,6 +70,10 @@ class UploadsController < ApplicationController
end
end
+ def cache_publicly?
+ User === model || Appearance === model
+ end
+
def upload_model_class
MODEL_CLASSES[params[:model]] || raise(UnknownUploadModelError)
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 0c0247da1fb..f17da0bb7b1 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
- VERSION = '0.1.43'.freeze
+ VERSION = '0.1.45'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 6050955fbd8..a2c48973fa5 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -49,8 +49,9 @@ module Clusters
validates :name, cluster_name: true
validates :cluster_type, presence: true
- validate :restrict_modification, on: :update
+ validates :domain, allow_nil: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
+ validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
diff --git a/app/models/label.rb b/app/models/label.rb
index 5d2d1afd1d9..1c3db3eb35d 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -214,6 +214,7 @@ class Label < ActiveRecord::Base
super(options).tap do |json|
json[:type] = self.try(:type)
json[:priority] = priority(options[:project]) if options.key?(:project)
+ json[:textColor] = text_color
end
end
diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed_project.rb
index 911fb7e9ce9..f5d0d6fab3b 100644
--- a/app/models/storage/hashed_project.rb
+++ b/app/models/storage/hashed_project.rb
@@ -31,7 +31,7 @@ module Storage
gitlab_shell.add_namespace(repository_storage, base_dir)
end
- def rename_repo
+ def rename_repo(old_full_path: nil, new_full_path: nil)
true
end
diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb
index 9f6f19acb41..76ac5c13c18 100644
--- a/app/models/storage/legacy_project.rb
+++ b/app/models/storage/legacy_project.rb
@@ -29,18 +29,19 @@ module Storage
gitlab_shell.add_namespace(repository_storage, base_dir)
end
- def rename_repo
- new_full_path = project.build_full_path
+ def rename_repo(old_full_path: nil, new_full_path: nil)
+ old_full_path ||= project.full_path_was
+ new_full_path ||= project.build_full_path
- if gitlab_shell.mv_repository(repository_storage, project.full_path_was, new_full_path)
+ if gitlab_shell.mv_repository(repository_storage, old_full_path, new_full_path)
# If repository moved successfully we need to send update instructions to users.
# However we cannot allow rollback since we moved repository
# So we basically we mute exceptions in next actions
begin
- gitlab_shell.mv_repository(repository_storage, "#{project.full_path_was}.wiki", "#{new_full_path}.wiki")
+ gitlab_shell.mv_repository(repository_storage, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
return true
rescue => e
- Rails.logger.error "Exception renaming #{project.full_path_was} -> #{new_full_path}: #{e}"
+ Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}"
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
return false
diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb
index aa9b253eb20..c3cd9d1ea4a 100644
--- a/app/services/projects/after_rename_service.rb
+++ b/app/services/projects/after_rename_service.rb
@@ -12,22 +12,27 @@ module Projects
#
# Projects::AfterRenameService.new(project).execute
class AfterRenameService
- attr_reader :project, :full_path_before, :full_path_after, :path_before
+ # @return [String] The Project being renamed.
+ attr_reader :project
- RenameFailedError = Class.new(StandardError)
+ # @return [String] The path slug the project was using, before the rename took place.
+ attr_reader :path_before
- # @param [Project] project The Project of the repository to rename.
- def initialize(project)
- @project = project
+ # @return [String] The full path of the namespace + project, before the rename took place.
+ attr_reader :full_path_before
- # The full path of the namespace + project, before the rename took place.
- @full_path_before = project.full_path_was
+ # @return [String] The full path of the namespace + project, after the rename took place.
+ attr_reader :full_path_after
- # The full path of the namespace + project, after the rename took place.
- @full_path_after = project.build_full_path
+ RenameFailedError = Class.new(StandardError)
- # The path of just the project, before the rename took place.
- @path_before = project.path_was
+ # @param [Project] project The Project being renamed.
+ # @param [String] path_before The path slug the project was using, before the rename took place.
+ def initialize(project, path_before:, full_path_before:)
+ @project = project
+ @path_before = path_before
+ @full_path_before = full_path_before
+ @full_path_after = project.full_path
end
def execute
@@ -61,7 +66,7 @@ module Projects
.new(project, full_path_before)
.execute
else
- project.storage.rename_repo
+ project.storage.rename_repo(old_full_path: full_path_before, new_full_path: full_path_after)
end
rename_failed! unless success
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index dd1b9680ece..6856009b395 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -67,7 +67,7 @@ module Projects
end
if project.previous_changes.include?('path')
- AfterRenameService.new(project).execute
+ after_rename_service(project).execute
else
system_hook_service.execute_hooks_for(project, :update)
end
@@ -75,6 +75,13 @@ module Projects
update_pages_config if changing_pages_related_config?
end
+ def after_rename_service(project)
+ # The path slug the project was using, before the rename took place.
+ path_before = project.previous_changes['path'].first
+
+ AfterRenameService.new(project, path_before: path_before, full_path_before: project.full_path_was)
+ end
+
def changing_pages_related_config?
changing_pages_https_only? || changing_pages_access_level?
end
diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml
index 4df3d831942..5cf3193bc62 100644
--- a/app/views/groups/labels/index.html.haml
+++ b/app/views/groups/labels/index.html.haml
@@ -6,15 +6,10 @@
- subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || search.present? || subscribed.present?
-- if @labels.present? && can_admin_label
- - content_for(:header_content) do
- .nav-controls
- = link_to _('New label'), new_group_label_path(@group), class: "btn btn-success"
-
- if labels_or_filters
#promote-label-modal
%div{ class: container_class }
- = render 'shared/labels/nav'
+ = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.prepend-top-5
- if @labels.any?
diff --git a/app/views/layouts/nav/sidebar/_profile.html.haml b/app/views/layouts/nav/sidebar/_profile.html.haml
index 69167edb1df..1e3bb8f1224 100644
--- a/app/views/layouts/nav/sidebar/_profile.html.haml
+++ b/app/views/layouts/nav/sidebar/_profile.html.haml
@@ -3,7 +3,7 @@
.context-header
= link_to profile_path, title: _('Profile Settings') do
.avatar-container.s40.settings-avatar
- = sprite_icon('user', size: 24)
+ = image_tag avatar_icon_for_user(current_user, 40), class: "avatar s40 avatar-tile", alt: current_user.name
.sidebar-context-title User Settings
%ul.sidebar-top-level-items
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index 5c36d2202a6..eb46bf5c6a3 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -1,35 +1,35 @@
- if @merge_requests.any?
- %h2.merge-requests-title
- = pluralize(@merge_requests.count, 'Related Merge Request')
- %ul.unstyled-list.related-merge-requests
- - has_any_head_pipeline = @merge_requests.any?(&:head_pipeline_id)
- - @merge_requests.each do |merge_request|
- %li
- %span.merge-request-ci-status
- - if merge_request.head_pipeline
- = render_pipeline_status(merge_request.head_pipeline)
- - elsif has_any_head_pipeline
- = icon('blank fw')
- %span.merge-request-id
- = merge_request.to_reference
- %span.merge-request-info
- %strong
- = link_to merge_request.title, merge_request_path(merge_request), class: "row_title"
- - unless @issue.project.id == merge_request.target_project.id
- in
- - project = merge_request.target_project
- = link_to project.full_name, project_path(project)
+ .card-slim.mt-3
+ .card-header
+ %h2.card-title.mt-0.mb-0.h5.merge-requests-title
+ %span.mr-1.bold
+ = _('Related merge requests')
+ .d-inline-flex.lh-100.align-middle
+ .mr-count-badge
+ .mr-count-badge-count
+ = sprite_icon('merge-request', size: 16, css_class: 'mr-1 text-secondary')
+ = @merge_requests.count
+ %ul.content-list.related-items-list
+ - has_any_head_pipeline = @merge_requests.any?(&:head_pipeline_id)
+ - @merge_requests.each do |merge_request|
+ %li.list-item.py-0.px-0
+ .item-body.issuable-info-container.py-lg-3.px-lg-3.pl-md-3
+ .item-contents
+ .item-title.d-flex.align-items-center.mr-title
+ = render partial: 'projects/issues/merge_requests_status', locals: { merge_request: merge_request, css_class: 'd-none d-xl-block append-right-8' }
+ = link_to merge_request.title, merge_request_path(merge_request), { class: 'mr-title-link'}
+ .item-meta
+ = render partial: 'projects/issues/merge_requests_status', locals: { merge_request: merge_request, css_class: 'd-xl-none d-lg-block append-right-5' }
+ %span.d-flex.align-items-center.append-right-8.mr-item-path.item-path-id.mt-0
+ %span.path-id-text.bold.text-truncate{ data: { toggle: 'tooltip'}, title: merge_request.target_project.full_path }
+ = merge_request.target_project.full_path
+ = merge_request.to_reference
+ %span.mr-ci-status.flex-md-grow-1.justify-content-end.d-flex.ml-md-2
+ - if merge_request.head_pipeline
+ = render_pipeline_status(merge_request.head_pipeline, tooltip_placement: 'bottom')
+ - elsif has_any_head_pipeline
+ = icon('blank fw')
- - if merge_request.merged?
- %span.merge-request-status.prepend-left-10.merged
- Merged
- - elsif merge_request.closed?
- %span.merge-request-status.prepend-left-10.closed
- Closed
- - else
- %span.merge-request-status.prepend-left-10.open
- Open
-
- - if @closed_by_merge_requests.present?
- %li
- = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
+ - if @closed_by_merge_requests.present?
+ %p
+ = render partial: 'projects/issues/closed_by_box', locals: {merge_request_count: @merge_requests.count}
diff --git a/app/views/projects/issues/_merge_requests_status.html.haml b/app/views/projects/issues/_merge_requests_status.html.haml
new file mode 100644
index 00000000000..38126d6f0c6
--- /dev/null
+++ b/app/views/projects/issues/_merge_requests_status.html.haml
@@ -0,0 +1,22 @@
+- time_format = '%b %e, %Y %l:%M%P %Z%z'
+
+- if merge_request.merged?
+ - mr_status_date = merge_request.merged_at
+ - mr_status_title = _('Merged')
+ - mr_status_icon = 'merge'
+ - mr_status_class = 'merged'
+- elsif merge_request.closed?
+ - mr_status_date = merge_request.closed_event&.created_at
+ - mr_status_title = _('Closed')
+ - mr_status_icon = 'issue-close'
+ - mr_status_class = 'closed'
+- else
+ - mr_status_date = merge_request.created_at
+ - mr_status_title = _('Opened')
+ - mr_status_icon = 'issue-open-m'
+ - mr_status_class = 'open'
+
+- mr_status_tooltip = "<div><span class=\"bold\">#{mr_status_title}</span> #{time_ago_in_words(mr_status_date)} ago</div><span class=\"text-tertiary\">#{l(mr_status_date.to_time, format: time_format)}</span>"
+
+%span.mr-status-wrapper.suggestion-help-hover{ class: css_class, data: { toggle: 'tooltip', placement: 'bottom', html: 'true', title: mr_status_tooltip } }
+ = sprite_icon(mr_status_icon, size: 16, css_class: "merge-request-status #{mr_status_class}")
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 56b06374d6d..bb7c297ba1f 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -5,15 +5,10 @@
- subscribed = params[:subscribed]
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
-- if labels_or_filters && can_admin_label
- - content_for(:header_content) do
- .nav-controls
- = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
-
- if labels_or_filters
#promote-label-modal
%div{ class: container_class }
- = render 'shared/labels/nav'
+ = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label
.labels-container.prepend-top-10
- if can_admin_label
diff --git a/app/views/shared/labels/_nav.html.haml b/app/views/shared/labels/_nav.html.haml
index 98572db738b..e69246dd0eb 100644
--- a/app/views/shared/labels/_nav.html.haml
+++ b/app/views/shared/labels/_nav.html.haml
@@ -18,3 +18,7 @@
%button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') }
= icon("search")
= render 'shared/labels/sort_dropdown'
+ - if labels_or_filters && can_admin_label && @project
+ = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
+ - if labels_or_filters && can_admin_label && @group
+ = link_to _('New label'), new_group_label_path(@group), class: "btn btn-success qa-label-create-new"
diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb
index 61d866b1f02..ae853ec9316 100644
--- a/app/workers/build_finished_worker.rb
+++ b/app/workers/build_finished_worker.rb
@@ -9,14 +9,26 @@ class BuildFinishedWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
Ci::Build.find_by(id: build_id).try do |build|
- # We execute that in sync as this access the files in order to access local file, and reduce IO
- BuildTraceSectionsWorker.new.perform(build.id)
- BuildCoverageWorker.new.perform(build.id)
-
- # We execute that async as this are two independent operations that can be executed after TraceSections and Coverage
- BuildHooksWorker.perform_async(build.id)
- ArchiveTraceWorker.perform_async(build.id)
+ process_build(build)
end
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ # Processes a single CI build that has finished.
+ #
+ # This logic resides in a separate method so that EE can extend it more
+ # easily.
+ #
+ # @param [Ci::Build] build The build to process.
+ def process_build(build)
+ # We execute these in sync to reduce IO.
+ BuildTraceSectionsWorker.new.perform(build.id)
+ BuildCoverageWorker.new.perform(build.id)
+
+ # We execute these async as these are independent operations.
+ BuildHooksWorker.perform_async(build.id)
+ ArchiveTraceWorker.perform_async(build.id)
+ end
end
diff --git a/app/workers/expire_pipeline_cache_worker.rb b/app/workers/expire_pipeline_cache_worker.rb
index c96e8a0379b..148384600b6 100644
--- a/app/workers/expire_pipeline_cache_worker.rb
+++ b/app/workers/expire_pipeline_cache_worker.rb
@@ -11,16 +11,9 @@ class ExpirePipelineCacheWorker
pipeline = Ci::Pipeline.find_by(id: pipeline_id)
return unless pipeline
- project = pipeline.project
store = Gitlab::EtagCaching::Store.new
- store.touch(project_pipelines_path(project))
- store.touch(project_pipeline_path(project, pipeline))
- store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
- store.touch(new_merge_request_pipelines_path(project))
- each_pipelines_merge_request_path(project, pipeline) do |path|
- store.touch(path)
- end
+ update_etag_cache(pipeline, store)
Gitlab::Cache::Ci::ProjectPipelineStatus.update_for_pipeline(pipeline)
end
@@ -51,4 +44,23 @@ class ExpirePipelineCacheWorker
yield(path)
end
end
+
+ # Updates ETag caches of a pipeline.
+ #
+ # This logic resides in a separate method so that EE can more easily extend
+ # it.
+ #
+ # @param [Ci::Pipeline] pipeline
+ # @param [Gitlab::EtagCaching::Store] store
+ def update_etag_cache(pipeline, store)
+ project = pipeline.project
+
+ store.touch(project_pipelines_path(project))
+ store.touch(project_pipeline_path(project, pipeline))
+ store.touch(commit_pipelines_path(project, pipeline.commit)) unless pipeline.commit.nil?
+ store.touch(new_merge_request_pipelines_path(project))
+ each_pipelines_merge_request_path(project, pipeline) do |path|
+ store.touch(path)
+ end
+ end
end
diff --git a/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml b/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml
new file mode 100644
index 00000000000..28e2a4cc377
--- /dev/null
+++ b/changelogs/unreleased/47007-related-merge-requests-in-issue-design-restyle.yml
@@ -0,0 +1,5 @@
+---
+title: Redesigned related merge requests in issue page.
+merge_request: 24270
+author:
+type: changed
diff --git a/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml b/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml
new file mode 100644
index 00000000000..08c5ded05d5
--- /dev/null
+++ b/changelogs/unreleased/53671-redirect-projects-id-to-project-page.yml
@@ -0,0 +1,5 @@
+---
+title: Redirect GET projects/:id to project page
+merge_request: 24467
+author:
+type: added
diff --git a/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml b/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml
new file mode 100644
index 00000000000..d804e2df2cd
--- /dev/null
+++ b/changelogs/unreleased/53714-inconsistent-text-color-for-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Fix foreground color for labels to ensure consistency of label appearance
+merge_request: 23873
+author: Nathan Friend
+type: fixed
diff --git a/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml b/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml
new file mode 100644
index 00000000000..01a162944d3
--- /dev/null
+++ b/changelogs/unreleased/55966-when-ref-is-ambiguous-createpipelineservice-raises-an-error.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent checking protected_ref? for ambiguous refs.
+merge_request: 24437
+author:
+type: fixed
diff --git a/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml b/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml
new file mode 100644
index 00000000000..9ef274f3b49
--- /dev/null
+++ b/changelogs/unreleased/56547-limit-sidekiq-logging-based-on-argument-size.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent Sidekiq arguments over 10 KB in size from being logged to JSON
+merge_request: 24493
+author:
+type: changed
diff --git a/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml b/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml
new file mode 100644
index 00000000000..1f808850554
--- /dev/null
+++ b/changelogs/unreleased/56636-hashed-storage-afterrenameservice.yml
@@ -0,0 +1,5 @@
+---
+title: 'Hashed Storage: `AfterRenameService` was receiving the wrong `old_path` under some circunstances'
+merge_request: 24526
+author:
+type: fixed
diff --git a/changelogs/unreleased/an-opentracing-propagation.yml b/changelogs/unreleased/an-opentracing-propagation.yml
new file mode 100644
index 00000000000..d9aa7cd0048
--- /dev/null
+++ b/changelogs/unreleased/an-opentracing-propagation.yml
@@ -0,0 +1,5 @@
+---
+title: Adds inter-service OpenTracing propagation
+merge_request: 24239
+author:
+type: other
diff --git a/changelogs/unreleased/fix-56558-move-primary-button.yml b/changelogs/unreleased/fix-56558-move-primary-button.yml
new file mode 100644
index 00000000000..4dcc896b327
--- /dev/null
+++ b/changelogs/unreleased/fix-56558-move-primary-button.yml
@@ -0,0 +1,5 @@
+---
+title: Moved primary button for labels to follow the design patterns used on rest of the site
+merge_request:
+author: Martin Hobert
+type: fixed
diff --git a/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml b/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml
new file mode 100644
index 00000000000..1e0160c4d40
--- /dev/null
+++ b/changelogs/unreleased/gitlab-workhorse-update-8.1.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade gitlab-workhorse to 8.1.0
+merge_request: 24571
+author:
+type: other
diff --git a/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml b/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml
new file mode 100644
index 00000000000..932850cc825
--- /dev/null
+++ b/changelogs/unreleased/mg-fix-bad-cluster-update-entrypoint.yml
@@ -0,0 +1,5 @@
+---
+title: Fix cluster page non-interactive on form validation error
+merge_request: 24583
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml
new file mode 100644
index 00000000000..7d92929221f
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-45.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.1.45
+merge_request: 24564
+author:
+type: other
diff --git a/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml b/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml
new file mode 100644
index 00000000000..0ec76f9ce02
--- /dev/null
+++ b/changelogs/unreleased/yoginth-avatar-on-settings-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Added Avatar in the settings sidebar
+merge_request: 24515
+author: Yoginth
+type: changed
diff --git a/config/initializers/tracing.rb b/config/initializers/tracing.rb
index be95f30d075..46e8125daf8 100644
--- a/config/initializers/tracing.rb
+++ b/config/initializers/tracing.rb
@@ -3,6 +3,26 @@
if Gitlab::Tracing.enabled?
require 'opentracing'
+ Rails.application.configure do |config|
+ config.middleware.insert_after Gitlab::Middleware::CorrelationId, ::Gitlab::Tracing::RackMiddleware
+ end
+
+ # Instrument the Sidekiq client
+ Sidekiq.configure_client do |config|
+ config.client_middleware do |chain|
+ chain.add Gitlab::Tracing::Sidekiq::ClientMiddleware
+ end
+ end
+
+ # Instrument Sidekiq server calls when running Sidekiq server
+ if Sidekiq.server?
+ Sidekiq.configure_server do |config|
+ config.server_middleware do |chain|
+ chain.add Gitlab::Tracing::Sidekiq::ServerMiddleware
+ end
+ end
+ end
+
# In multi-processed clustered architectures (puma, unicorn) don't
# start tracing until the worker processes are spawned. This works
# around issues when the opentracing implementation spawns threads
diff --git a/config/routes/project.rb b/config/routes/project.rb
index d9afb4e7bc8..21793e7756a 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -2,6 +2,8 @@ resources :projects, only: [:index, :new, :create]
draw :git_http
+get '/projects/:id' => 'projects#resolve'
+
constraints(::Constraints::ProjectUrlConstrainer.new) do
# If the route has a wildcard segment, the segment has a regex constraint,
# the segment is potentially followed by _another_ wildcard segment, and
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 52af837c261..188331cc87c 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -32,7 +32,7 @@ to be reviewed.
| Tech writer | Stage(s) |
| ------------ | ------------------------------------------------------------ |
| `@marcia` | ~Create ~Release + ~"development guidelines" |
-| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitoring ~Package ~Secure |
+| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitor ~Package ~Secure |
| `@eread` | ~Manage ~Configure ~Geo ~Verify |
| `@mikelewis` | ~Plan |
diff --git a/db/migrate/20190114172110_add_domain_to_cluster.rb b/db/migrate/20190114172110_add_domain_to_cluster.rb
new file mode 100644
index 00000000000..58d7664b8c0
--- /dev/null
+++ b/db/migrate/20190114172110_add_domain_to_cluster.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddDomainToCluster < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :clusters, :domain, :string
+ end
+end
diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
index 50e1c8449ba..32579256299 100644
--- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb
+++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb
@@ -113,7 +113,7 @@ class RenameReservedProjectNames < ActiveRecord::Migration[4.2]
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
if rename_project_row(project, path)
- Projects::AfterRenameService.new(project).execute
+ after_rename_service(project, path_was, namespace_path).execute
end
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
@@ -126,4 +126,12 @@ class RenameReservedProjectNames < ActiveRecord::Migration[4.2]
project.update(path: path) &&
defined?(Projects::AfterRenameService)
end
+
+ def after_rename_service(project, path_was, namespace_path)
+ AfterRenameService.new(
+ project,
+ path_before: path_was,
+ full_path_before: "#{namespace_path}/#{path_was}"
+ ).execute
+ end
end
diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
index bef669b459d..85c97e3687e 100644
--- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
+++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb
@@ -55,7 +55,7 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration[4.2]
# Because project path update is quite complex operation we can't safely
# copy-paste all code from GitLab. As exception we use Rails code here
if rename_project_row(project, path)
- Projects::AfterRenameService.new(project).execute
+ after_rename_service(project, path_was, namespace_path).execute
end
rescue Exception => e # rubocop: disable Lint/RescueException
Rails.logger.error "Exception when renaming project #{id}: #{e.message}"
@@ -68,4 +68,12 @@ class RenameMoreReservedProjectNames < ActiveRecord::Migration[4.2]
project.update(path: path) &&
defined?(Projects::AfterRenameService)
end
+
+ def after_rename_service(project, path_was, namespace_path)
+ AfterRenameService.new(
+ project,
+ path_before: path_was,
+ full_path_before: "#{namespace_path}/#{path_was}"
+ ).execute
+ end
end
diff --git a/db/schema.rb b/db/schema.rb
index c6fef9b5d11..cd502d06bf4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -647,6 +647,7 @@ ActiveRecord::Schema.define(version: 20190115054216) do
t.string "name", null: false
t.string "environment_scope", default: "*", null: false
t.integer "cluster_type", limit: 2, default: 3, null: false
+ t.string "domain"
t.index ["enabled"], name: "index_clusters_on_enabled", using: :btree
t.index ["user_id"], name: "index_clusters_on_user_id", using: :btree
end
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 05c1923f0cb..abef7a6cd33 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -49,6 +49,25 @@ Starting with GitLab 11.4, Gitaly is a replacement for NFS except
when the [Elastic Search indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer)
is used.
+### Network architecture
+
+- gitlab-rails shards repositories into "repository storages"
+- gitlab-rails/config/gitlab.yml contains a map from storage names to
+ (Gitaly address, Gitaly token) pairs
+- the `storage name` -\> `(Gitaly address, Gitaly token)` map in
+ gitlab.yml is the single source of truth for the Gitaly network
+ topology
+- a (Gitaly address, Gitaly token) corresponds to a Gitaly server
+- a Gitaly server hosts one or more storages
+- Gitaly addresses must be specified in such a way that they resolve
+ correctly for ALL Gitaly clients
+- Gitaly clients are: unicorn, sidekiq, gitlab-workhorse,
+ gitlab-shell, and Gitaly itself
+- special case: a Gitaly server must be able to make RPC calls **to
+ itself** via its own (Gitaly address, Gitaly token) pair as
+ specified in gitlab-rails/config/gitlab.yml
+- Gitaly servers must not be exposed to the public internet
+
Gitaly network traffic is unencrypted so you should use a firewall to
restrict access to your Gitaly server.
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index 0cc083cefc0..fed772b9240 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -148,7 +148,7 @@ merge commit SHA is `138f5e2f20289bb376caffa0303adb0cac859ce1`:
- To cherry-pick multiple commits, such as B and D in a range [A > B > C > D], use:
```shell
- git cherry-pick commmit-B-SHA commit-D-SHA
+ git cherry-pick commit-B-SHA commit-D-SHA
```
For example, suppose commit B SHA = `4f5e4018c09ed797fdf446b3752f82e46f5af502`,
@@ -213,7 +213,7 @@ being able to deploy.
No, not if there is an EE merge request for every CE merge request that causes
conflicts _and_ that EE merge request is merged first. In the past we may have
been a bit more relaxed when it comes to enforcing EE merge requests, but to
-enable automatic merging have to start requiring such merge requests even for
+enable automatic merging we have to start requiring such merge requests even for
the smallest conflicts.
### Some files I work with often conflict, how can I best deal with this?
diff --git a/doc/development/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md
index 84b61bd7e61..2b3a9481b93 100644
--- a/doc/development/sidekiq_debugging.md
+++ b/doc/development/sidekiq_debugging.md
@@ -11,6 +11,11 @@ Example:
gitlab_rails['env'] = {"SIDEKIQ_LOG_ARGUMENTS" => "1"}
```
-Please note: It is not recommend to enable this setting in production because some
+Please note: It is not recommend to enable this setting in production because some
Sidekiq jobs (such as sending a password reset email) take secret arguments (for
-example the password reset token). \ No newline at end of file
+example the password reset token).
+
+When using [Sidekiq JSON logging](../administration/logs.md#sidekiqlog),
+arguments logs are limited to a maximum size of 10 kilobytes of text;
+any arguments after this limit will be discarded and replaced with a
+single argument containing the string `"..."`.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 2eba2cc4847..b3ad1c5a91c 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -170,7 +170,7 @@ sudo make install
Then install the Bundler Gem:
```sh
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
## 3. Go
diff --git a/doc/update/10.0-to-10.1.md b/doc/update/10.0-to-10.1.md
index 10cf02a984f..d4373ca3f23 100644
--- a/doc/update/10.0-to-10.1.md
+++ b/doc/update/10.0-to-10.1.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.1-to-10.2.md b/doc/update/10.1-to-10.2.md
index 20895a05567..0705b58ed7a 100644
--- a/doc/update/10.1-to-10.2.md
+++ b/doc/update/10.1-to-10.2.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.2-to-10.3.md b/doc/update/10.2-to-10.3.md
index 441a241d053..33a52d3e807 100644
--- a/doc/update/10.2-to-10.3.md
+++ b/doc/update/10.2-to-10.3.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.3-to-10.4.md b/doc/update/10.3-to-10.4.md
index 9f3efdd790e..3ba96535965 100644
--- a/doc/update/10.3-to-10.4.md
+++ b/doc/update/10.3-to-10.4.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.4-to-10.5.md b/doc/update/10.4-to-10.5.md
index 3766645a141..f00bbcaeaa6 100644
--- a/doc/update/10.4-to-10.5.md
+++ b/doc/update/10.4-to-10.5.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.5-to-10.6.md b/doc/update/10.5-to-10.6.md
index 986ecbf5ef0..6c3f8b663cc 100644
--- a/doc/update/10.5-to-10.6.md
+++ b/doc/update/10.5-to-10.6.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.6-to-10.7.md b/doc/update/10.6-to-10.7.md
index 10d29837bfb..9bd354a5bcd 100644
--- a/doc/update/10.6-to-10.7.md
+++ b/doc/update/10.6-to-10.7.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.7-to-10.8.md b/doc/update/10.7-to-10.8.md
index 0cc46fc5aa9..9aafd3f269f 100644
--- a/doc/update/10.7-to-10.8.md
+++ b/doc/update/10.7-to-10.8.md
@@ -52,7 +52,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/10.8-to-11.0.md b/doc/update/10.8-to-11.0.md
index ad3305d8ebd..f6fdc342e3d 100644
--- a/doc/update/10.8-to-11.0.md
+++ b/doc/update/10.8-to-11.0.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.0-to-11.1.md b/doc/update/11.0-to-11.1.md
index 5b2dd48a744..25a7c1cf929 100644
--- a/doc/update/11.0-to-11.1.md
+++ b/doc/update/11.0-to-11.1.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.1-to-11.2.md b/doc/update/11.1-to-11.2.md
index cb09d0a2505..ced59c245b8 100644
--- a/doc/update/11.1-to-11.2.md
+++ b/doc/update/11.1-to-11.2.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.2-to-11.3.md b/doc/update/11.2-to-11.3.md
index 228ff6cb70e..fa0c6872182 100644
--- a/doc/update/11.2-to-11.3.md
+++ b/doc/update/11.2-to-11.3.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.3-to-11.4.md b/doc/update/11.3-to-11.4.md
index 5f64bf81127..18bbfe4747e 100644
--- a/doc/update/11.3-to-11.4.md
+++ b/doc/update/11.3-to-11.4.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.4-to-11.5.md b/doc/update/11.4-to-11.5.md
index fd7a8e5c2ae..6ad58ae8781 100644
--- a/doc/update/11.4-to-11.5.md
+++ b/doc/update/11.4-to-11.5.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.5-to-11.6.md b/doc/update/11.5-to-11.6.md
index 33e09e1915b..c890f7b6147 100644
--- a/doc/update/11.5-to-11.6.md
+++ b/doc/update/11.5-to-11.6.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.6-to-11.7.md b/doc/update/11.6-to-11.7.md
index 9e61d978631..fd7c3697e80 100644
--- a/doc/update/11.6-to-11.7.md
+++ b/doc/update/11.6-to-11.7.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/11.7-to-11.8.md b/doc/update/11.7-to-11.8.md
index 7ab98d80eb9..82293ff40a8 100644
--- a/doc/update/11.7-to-11.8.md
+++ b/doc/update/11.7-to-11.8.md
@@ -51,7 +51,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md
index 7f3abf74675..b54884c4682 100644
--- a/doc/update/6.9-to-7.0.md
+++ b/doc/update/6.9-to-7.0.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 3. Get latest code
diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md
index c20a72ce162..745df56bbb0 100644
--- a/doc/update/6.x-or-7.x-to-7.14.md
+++ b/doc/update/6.x-or-7.x-to-7.14.md
@@ -67,7 +67,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
## 3. Get latest code
diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md
index fb4710faad5..8b69431dee1 100644
--- a/doc/update/7.0-to-7.1.md
+++ b/doc/update/7.0-to-7.1.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 3. Get latest code
diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md
index 12a465e1602..f8415b5159b 100644
--- a/doc/update/8.10-to-8.11.md
+++ b/doc/update/8.10-to-8.11.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md
index b9a7986d5ba..07ac8129b4f 100644
--- a/doc/update/8.11-to-8.12.md
+++ b/doc/update/8.11-to-8.12.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md
index 37e61794e7e..bf622deaba8 100644
--- a/doc/update/8.12-to-8.13.md
+++ b/doc/update/8.12-to-8.13.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md
index 927f453b9bf..43b636ea958 100644
--- a/doc/update/8.13-to-8.14.md
+++ b/doc/update/8.13-to-8.14.md
@@ -47,7 +47,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md
index d98a60d31c8..eadf1743597 100644
--- a/doc/update/8.14-to-8.15.md
+++ b/doc/update/8.14-to-8.15.md
@@ -50,7 +50,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.15-to-8.16.md b/doc/update/8.15-to-8.16.md
index 94b0102ed48..4e8d54d5010 100644
--- a/doc/update/8.15-to-8.16.md
+++ b/doc/update/8.15-to-8.16.md
@@ -50,7 +50,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Get latest code
diff --git a/doc/update/8.16-to-8.17.md b/doc/update/8.16-to-8.17.md
index 5a4f620a164..cab28a4d1c6 100644
--- a/doc/update/8.16-to-8.17.md
+++ b/doc/update/8.16-to-8.17.md
@@ -50,7 +50,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md
index 38f7d22437a..55cf0842df4 100644
--- a/doc/update/8.17-to-9.0.md
+++ b/doc/update/8.17-to-9.0.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.0-to-9.1.md b/doc/update/9.0-to-9.1.md
index a4d2e7be23c..10214fd8aca 100644
--- a/doc/update/9.0-to-9.1.md
+++ b/doc/update/9.0-to-9.1.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.1-to-9.2.md b/doc/update/9.1-to-9.2.md
index dd808c51985..79d92f05257 100644
--- a/doc/update/9.1-to-9.2.md
+++ b/doc/update/9.1-to-9.2.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.2-to-9.3.md b/doc/update/9.2-to-9.3.md
index d2bcf45a28e..98443b8bfa6 100644
--- a/doc/update/9.2-to-9.3.md
+++ b/doc/update/9.2-to-9.3.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.3-to-9.4.md b/doc/update/9.3-to-9.4.md
index dae2162a964..640b9c3997e 100644
--- a/doc/update/9.3-to-9.4.md
+++ b/doc/update/9.3-to-9.4.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.4-to-9.5.md b/doc/update/9.4-to-9.5.md
index f2811e9471f..e6cfa70975e 100644
--- a/doc/update/9.4-to-9.5.md
+++ b/doc/update/9.4-to-9.5.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/update/9.5-to-10.0.md b/doc/update/9.5-to-10.0.md
index 333a6e35714..8b565f67cb1 100644
--- a/doc/update/9.5-to-10.0.md
+++ b/doc/update/9.5-to-10.0.md
@@ -49,7 +49,7 @@ sudo make install
Install Bundler:
```bash
-sudo gem install bundler --no-document
+sudo gem install bundler --no-document --version '< 2'
```
### 4. Update Node
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 9ecb109fa89..bebccf97987 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -121,9 +121,9 @@ In order to deploy functions to your Knative instance, the following files must
runtime: https://gitlab.com/triggermesh/runtimes/raw/master/nodejs.yaml
description: "echo function using node.js runtime"
buildargs:
- - DIRECTORY=echo
- environment:
- FUNCTION: echo
+ - DIRECTORY=echo
+ environment:
+ FUNCTION: echo
```
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index b8989f5ae14..6a1aadf058e 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -154,7 +154,7 @@ When [renaming a user](../profile/index.md#changing-your-username),
Any project can be used as a Go package including private projects in subgroups. To use packages
hosted in private projects with the `go get` command, use a [`.netrc` file](https://ec.haxx.se/usingcurl-netrc.html)
-and a personal access token in the password field.
+and a [personal access token](../profile/personal_access_tokens.md) in the password field.
For example:
@@ -163,3 +163,10 @@ machine example.gitlab.com
login <gitlab_user_name>
password <personal_access_token>
```
+
+## Access project page with project ID
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53671) in GitLab 11.8.
+
+To quickly access a project from the GitLab UI using the project ID,
+visit the `/projects/:id` URL in your browser or other tool accessing the project.
diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md
index 001e0d303e9..032e3a73ad0 100644
--- a/doc/user/project/issues/csv_import.md
+++ b/doc/user/project/issues/csv_import.md
@@ -2,16 +2,30 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23532) in GitLab 11.7.
-Issues can be imported by uploading a CSV file. The file will be processed in the background and a notification email
-will be sent to you once the import is completed.
+Issues can be imported to a project by uploading a CSV file. Supported fields are
+`title` and `description`.
+
+The user uploading the CSV file will be set as the author of the imported issues.
> **Note:** A permission level of `Developer` or higher is required to import issues.
+To import issues:
+
+1. Ensure your CSV file meets the [file format](#csv-file-format) requirements.
+1. Navigate to a project's Issues list page.
+1. If existing issues are present, click the import icon at the top right, next to the **Edit issues** button.
+1. For a project without any issues, click the button labeled **Import CSV** in the middle of the page.
+1. Select the file and click the **Import issues** button.
+
+The file is processed in the background and a notification email is sent
+to you once the import is completed.
+
## CSV File Format
### Header row
-CSV files must contain a header row with at least two columns: `title` and `description`, in that order.
+CSV files must contain a header row beginning with at least two columns, `title` and `description`, in that order.
+If additional columns are present, they will be ignored.
### Column separator
@@ -33,7 +47,11 @@ a double-quote (`"`) within a quoted field, use two double-quote characters in s
After the header row, succeeding rows must follow the same column order. The issue title is required while the
description is optional.
-The user uploading the CSV file will be set as the author of the imported issues.
+### File size
+
+The limit depends on the configuration value of Max Attachment Size for the GitLab instance.
+
+For GitLab.com, it is set to 10 MB.
## Sample Data
diff --git a/doc/user/project/issues/img/import_csv_button.png b/doc/user/project/issues/img/import_csv_button.png
deleted file mode 100644
index ab100a95750..00000000000
--- a/doc/user/project/issues/img/import_csv_button.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 40a1f60c4ab..5a3ac9c175b 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -144,12 +144,12 @@ create various boards per project with [Multiple Issue Boards](https://docs.gitl
### Import Issues from CSV
-From the project-level issues list, you can find the import button near the "Edit issues" button in the upper-right
-side.
+You can import a CSV file containing issue titles and descriptions to create
+a batch of issues simultaneously.
-![Import CSV button](img/import_csv_button.png)
+When you navigate to the Issues list page, an import button is displayed.
-Learn more about [importing issues from CSV](csv_import.md)
+For further details, see [Importing issues from CSV](csv_import.md)
### External Issue Tracker
@@ -157,14 +157,14 @@ Alternatively to GitLab's built-in Issue Tracker, you can also use an [external
tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine,
or Bugzilla.
-### Issue's API
+### Issue API
-Read through the [API documentation](../../../api/issues.md).
+See the [API documentation](../../../api/issues.md).
### Bulk editing issues
-Find out about [bulk editing issues](../../project/bulk_editing.md).
+See the [bulk editing issues](../../project/bulk_editing.md) page.
### Similar issues
-Find out about [similar issues](similar_issues.md).
+See the [similar issues](similar_issues.md) page.
diff --git a/lib/gitlab/ci/pipeline/chain/build.rb b/lib/gitlab/ci/pipeline/chain/build.rb
index d33d1edfe35..41632211374 100644
--- a/lib/gitlab/ci/pipeline/chain/build.rb
+++ b/lib/gitlab/ci/pipeline/chain/build.rb
@@ -17,7 +17,6 @@ module Gitlab
user: @command.current_user,
pipeline_schedule: @command.schedule,
merge_request: @command.merge_request,
- protected: @command.protected_ref?,
variables_attributes: Array(@command.variables_attributes)
)
diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb
index 633d3cd4f6b..0405292a25b 100644
--- a/lib/gitlab/ci/pipeline/chain/populate.rb
+++ b/lib/gitlab/ci/pipeline/chain/populate.rb
@@ -13,6 +13,10 @@ module Gitlab
# Allocate next IID. This operation must be outside of transactions of pipeline creations.
pipeline.ensure_project_iid!
+ # Protect the pipeline. This is assigned in Populate instead of
+ # Build to prevent erroring out on ambiguous refs.
+ pipeline.protected = @command.protected_ref?
+
##
# Populate pipeline with block argument of CreatePipelineService#execute.
#
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 8bf8a3b53cd..85afbd85fe6 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -52,11 +52,18 @@ module Gitlab
klass = stub_class(name)
addr = stub_address(storage)
creds = stub_creds(storage)
- klass.new(addr, creds)
+ klass.new(addr, creds, interceptors: interceptors)
end
end
end
+ def self.interceptors
+ return [] unless Gitlab::Tracing.enabled?
+
+ [Gitlab::Tracing::GRPCInterceptor.instance]
+ end
+ private_class_method :interceptors
+
def self.stub_cert_paths
cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"]
cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE
diff --git a/lib/gitlab/sidekiq_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb
index e86db8db3a1..fdc0d518c59 100644
--- a/lib/gitlab/sidekiq_logging/structured_logger.rb
+++ b/lib/gitlab/sidekiq_logging/structured_logger.rb
@@ -5,6 +5,7 @@ module Gitlab
class StructuredLogger
START_TIMESTAMP_FIELDS = %w[created_at enqueued_at].freeze
DONE_TIMESTAMP_FIELDS = %w[started_at retried_at failed_at completed_at].freeze
+ MAXIMUM_JOB_ARGUMENTS_LENGTH = 10.kilobytes
def call(job, queue)
started_at = current_time
@@ -64,6 +65,7 @@ module Gitlab
job['pid'] = ::Process.pid
job.delete('args') unless ENV['SIDEKIQ_LOG_ARGUMENTS']
+ job['args'] = limited_job_args(job['args']) if job['args']
convert_to_iso8601(job, START_TIMESTAMP_FIELDS)
@@ -93,6 +95,21 @@ module Gitlab
Time.at(timestamp).utc.iso8601(3)
end
+
+ def limited_job_args(args)
+ return unless args.is_a?(Array)
+
+ total_length = 0
+ limited_args = args.take_while do |arg|
+ total_length += arg.to_json.length
+
+ total_length <= MAXIMUM_JOB_ARGUMENTS_LENGTH
+ end
+
+ limited_args.push('...') if total_length > MAXIMUM_JOB_ARGUMENTS_LENGTH
+
+ limited_args
+ end
end
end
end
diff --git a/lib/gitlab/tracing/common.rb b/lib/gitlab/tracing/common.rb
new file mode 100644
index 00000000000..5e2b12e3f90
--- /dev/null
+++ b/lib/gitlab/tracing/common.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Tracing
+ module Common
+ def tracer
+ OpenTracing.global_tracer
+ end
+
+ # Convience method for running a block with a span
+ def in_tracing_span(operation_name:, tags:, child_of: nil)
+ scope = tracer.start_active_span(
+ operation_name,
+ child_of: child_of,
+ tags: tags
+ )
+ span = scope.span
+
+ # Add correlation details to the span if we have them
+ correlation_id = Gitlab::CorrelationId.current_id
+ if correlation_id
+ span.set_tag('correlation_id', correlation_id)
+ end
+
+ begin
+ yield span
+ rescue => e
+ log_exception_on_span(span, e)
+ raise e
+ ensure
+ scope.close
+ end
+ end
+
+ def log_exception_on_span(span, exception)
+ span.set_tag('error', true)
+ span.log_kv(kv_tags_for_exception(exception))
+ end
+
+ def kv_tags_for_exception(exception)
+ case exception
+ when Exception
+ {
+ 'event': 'error',
+ 'error.kind': exception.class.to_s,
+ 'message': Gitlab::UrlSanitizer.sanitize(exception.message),
+ 'stack': exception.backtrace.join("\n")
+ }
+ else
+ {
+ 'event': 'error',
+ 'error.kind': exception.class.to_s,
+ 'error.object': Gitlab::UrlSanitizer.sanitize(exception.to_s)
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/grpc_interceptor.rb b/lib/gitlab/tracing/grpc_interceptor.rb
new file mode 100644
index 00000000000..6c2aab73125
--- /dev/null
+++ b/lib/gitlab/tracing/grpc_interceptor.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+require 'grpc'
+
+module Gitlab
+ module Tracing
+ class GRPCInterceptor < GRPC::ClientInterceptor
+ include Common
+ include Singleton
+
+ def request_response(request:, call:, method:, metadata:)
+ wrap_with_tracing(method, 'unary', metadata) do
+ yield
+ end
+ end
+
+ def client_streamer(requests:, call:, method:, metadata:)
+ wrap_with_tracing(method, 'client_stream', metadata) do
+ yield
+ end
+ end
+
+ def server_streamer(request:, call:, method:, metadata:)
+ wrap_with_tracing(method, 'server_stream', metadata) do
+ yield
+ end
+ end
+
+ def bidi_streamer(requests:, call:, method:, metadata:)
+ wrap_with_tracing(method, 'bidi_stream', metadata) do
+ yield
+ end
+ end
+
+ private
+
+ def wrap_with_tracing(method, grpc_type, metadata)
+ tags = {
+ 'component' => 'grpc',
+ 'span.kind' => 'client',
+ 'grpc.method' => method,
+ 'grpc.type' => grpc_type
+ }
+
+ in_tracing_span(operation_name: "grpc:#{method}", tags: tags) do |span|
+ OpenTracing.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, metadata)
+
+ yield
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/rack_middleware.rb b/lib/gitlab/tracing/rack_middleware.rb
new file mode 100644
index 00000000000..e6a31293f7b
--- /dev/null
+++ b/lib/gitlab/tracing/rack_middleware.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+
+module Gitlab
+ module Tracing
+ class RackMiddleware
+ include Common
+
+ REQUEST_METHOD = 'REQUEST_METHOD'
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ method = env[REQUEST_METHOD]
+
+ context = tracer.extract(OpenTracing::FORMAT_RACK, env)
+ tags = {
+ 'component' => 'rack',
+ 'span.kind' => 'server',
+ 'http.method' => method,
+ 'http.url' => self.class.build_sanitized_url_from_env(env)
+ }
+
+ in_tracing_span(operation_name: "http:#{method}", child_of: context, tags: tags) do |span|
+ @app.call(env).tap do |status_code, _headers, _body|
+ span.set_tag('http.status_code', status_code)
+ end
+ end
+ end
+
+ # Generate a sanitized (safe) request URL from the rack environment
+ def self.build_sanitized_url_from_env(env)
+ request = ActionDispatch::Request.new(env)
+
+ original_url = request.original_url
+ uri = URI.parse(original_url)
+ uri.query = request.filtered_parameters.to_query if uri.query.present?
+
+ uri.to_s
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/sidekiq/client_middleware.rb b/lib/gitlab/tracing/sidekiq/client_middleware.rb
new file mode 100644
index 00000000000..2b71c1ea21e
--- /dev/null
+++ b/lib/gitlab/tracing/sidekiq/client_middleware.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+
+module Gitlab
+ module Tracing
+ module Sidekiq
+ class ClientMiddleware
+ include SidekiqCommon
+
+ SPAN_KIND = 'client'
+
+ def call(worker_class, job, queue, redis_pool)
+ in_tracing_span(
+ operation_name: "sidekiq:#{job['class']}",
+ tags: tags_from_job(job, SPAN_KIND)) do |span|
+ # Inject the details directly into the job
+ tracer.inject(span.context, OpenTracing::FORMAT_TEXT_MAP, job)
+
+ yield
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/sidekiq/server_middleware.rb b/lib/gitlab/tracing/sidekiq/server_middleware.rb
new file mode 100644
index 00000000000..5b43c4310e6
--- /dev/null
+++ b/lib/gitlab/tracing/sidekiq/server_middleware.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'opentracing'
+
+module Gitlab
+ module Tracing
+ module Sidekiq
+ class ServerMiddleware
+ include SidekiqCommon
+
+ SPAN_KIND = 'server'
+
+ def call(worker, job, queue)
+ context = tracer.extract(OpenTracing::FORMAT_TEXT_MAP, job)
+
+ in_tracing_span(
+ operation_name: "sidekiq:#{job['class']}",
+ child_of: context,
+ tags: tags_from_job(job, SPAN_KIND)) do |span|
+ yield
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/sidekiq/sidekiq_common.rb b/lib/gitlab/tracing/sidekiq/sidekiq_common.rb
new file mode 100644
index 00000000000..a911a29d773
--- /dev/null
+++ b/lib/gitlab/tracing/sidekiq/sidekiq_common.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Tracing
+ module Sidekiq
+ module SidekiqCommon
+ include Gitlab::Tracing::Common
+
+ def tags_from_job(job, kind)
+ {
+ 'component' => 'sidekiq',
+ 'span.kind' => kind,
+ 'sidekiq.queue' => job['queue'],
+ 'sidekiq.jid' => job['jid'],
+ 'sidekiq.retry' => job['retry'].to_s,
+ 'sidekiq.args' => job['args']&.join(", ")
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/package.json b/package.json
index 1fd7e53d62c..6c771e377b8 100644
--- a/package.json
+++ b/package.json
@@ -21,12 +21,12 @@
},
"dependencies": {
"@babel/core": "^7.2.2",
- "@babel/plugin-proposal-class-properties": "^7.2.3",
+ "@babel/plugin-proposal-class-properties": "^7.3.0",
"@babel/plugin-proposal-json-strings": "^7.2.0",
- "@babel/plugin-proposal-private-methods": "^7.2.3",
+ "@babel/plugin-proposal-private-methods": "^7.3.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
- "@babel/preset-env": "^7.2.3",
+ "@babel/preset-env": "^7.3.1",
"@gitlab/csslab": "^1.8.0",
"@gitlab/svgs": "^1.47.0",
"@gitlab/ui": "^1.20.0",
@@ -161,7 +161,8 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.18.4",
- "prettier": "1.15.3",
+ "pixelmatch": "^4.0.2",
+ "prettier": "1.16.1",
"vue-jest": "^3.0.2",
"webpack-dev-server": "^3.1.14",
"yarn-deduplicate": "^1.0.5"
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 86e00cdbb9c..ac8dcbf0d83 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -11,14 +11,15 @@ module QA
class Repository
include Scenario::Actable
- attr_writer :password
+ attr_writer :password, :use_lfs
attr_accessor :env_vars
def initialize
# We set HOME to the current working directory (which is a
# temporary directory created in .perform()) so the temporarily dropped
# .netrc can be utilised
- self.env_vars = [%Q{HOME="#{File.dirname(netrc_file_path)}"}]
+ self.env_vars = [%Q{HOME="#{tmp_home_dir}"}]
+ @use_lfs = false
end
def self.perform(*args)
@@ -33,17 +34,22 @@ module QA
def username=(username)
@username = username
- @uri.user = username
+ # Only include the user in the URI if we're using HTTP as this breaks
+ # SSH authentication.
+ @uri.user = username unless ssh_key_set?
end
def use_default_credentials
self.username, self.password = default_credentials
-
- add_credentials_to_netrc unless ssh_key_set?
end
def clone(opts = '')
- run("git clone #{opts} #{uri} ./")
+ clone_result = run("git clone #{opts} #{uri} ./")
+ return clone_result.response unless clone_result.success
+
+ enable_lfs_result = enable_lfs if use_lfs?
+
+ clone_result.to_s + enable_lfs_result.to_s
end
def checkout(branch_name, new_branch: false)
@@ -58,8 +64,6 @@ module QA
def configure_identity(name, email)
run(%Q{git config user.name #{name}})
run(%Q{git config user.email #{email}})
-
- add_credentials_to_netrc
end
def commit_file(name, contents, message)
@@ -70,15 +74,22 @@ module QA
def add_file(name, contents)
::File.write(name, contents)
- run(%Q{git add #{name}})
+ if use_lfs?
+ git_lfs_track_result = run(%Q{git lfs track #{name} --lockable})
+ return git_lfs_track_result.response unless git_lfs_track_result.success
+ end
+
+ git_add_result = run(%Q{git add #{name}})
+
+ git_lfs_track_result.to_s + git_add_result.to_s
end
def commit(message)
- run(%Q{git commit -m "#{message}"})
+ run(%Q{git commit -m "#{message}"}).to_s
end
def push_changes(branch = 'master')
- run("git push #{uri} #{branch}")
+ run("git push #{uri} #{branch}").to_s
end
def merge(branch)
@@ -86,7 +97,7 @@ module QA
end
def commits
- run('git log --oneline').split("\n")
+ run('git log --oneline').to_s.split("\n")
end
def use_ssh_key(key)
@@ -98,7 +109,8 @@ module QA
keyscan_params = ['-H']
keyscan_params << "-p #{uri.port}" if uri.port
keyscan_params << uri.host
- run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
+ res = run("ssh-keyscan #{keyscan_params.join(' ')} >> #{known_hosts_file.path}")
+ return res.response unless res.success?
self.env_vars << %Q{GIT_SSH_COMMAND="ssh -i #{private_key_file.path} -o UserKnownHostsFile=#{known_hosts_file.path}"}
end
@@ -132,23 +144,66 @@ module QA
output[/git< version (\d+)/, 1] || 'unknown'
end
+ def try_add_credentials_to_netrc
+ return unless add_credentials?
+ return if netrc_already_contains_content?
+
+ # Despite libcurl supporting a custom .netrc location through the
+ # CURLOPT_NETRC_FILE environment variable, git does not support it :(
+ # Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
+ #
+ # This will create a .netrc in the correct working directory, which is
+ # a temporary directory created in .perform()
+ #
+ FileUtils.mkdir_p(tmp_home_dir)
+ File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
+ File.chmod(0600, netrc_file_path)
+ end
+
private
- attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file
+ attr_reader :uri, :username, :password, :known_hosts_file,
+ :private_key_file, :use_lfs
+
+ alias_method :use_lfs?, :use_lfs
+
+ Result = Struct.new(:success, :response) do
+ alias_method :success?, :success
+ alias_method :to_s, :response
+ end
+
+ def add_credentials?
+ return false if !username || !password
+ return true unless ssh_key_set?
+ return true if ssh_key_set? && use_lfs?
+
+ false
+ end
def ssh_key_set?
!private_key_file.nil?
end
+ def enable_lfs
+ # git lfs install *needs* a .gitconfig defined at ${HOME}/.gitconfig
+ FileUtils.mkdir_p(tmp_home_dir)
+ touch_gitconfig_result = run("touch #{tmp_home_dir}/.gitconfig")
+ return touch_gitconfig_result.response unless touch_gitconfig_result.success?
+
+ git_lfs_install_result = run('git lfs install')
+
+ touch_gitconfig_result.to_s + git_lfs_install_result.to_s
+ end
+
def run(command_str, *extra_env)
command = [env_vars, *extra_env, command_str, '2>&1'].compact.join(' ')
- Runtime::Logger.debug "Git: command=[#{command}]"
+ Runtime::Logger.debug "Git: pwd=[#{Dir.pwd}], command=[#{command}]"
- output, _ = Open3.capture2(command)
- output = output.chomp.gsub(/\s+$/, '')
- Runtime::Logger.debug "Git: output=[#{output}]"
+ output, status = Open3.capture2e(command)
+ output.chomp!
+ Runtime::Logger.debug "Git: output=[#{output}], exitstatus=[#{status.exitstatus}]"
- output
+ Result.new(status.exitstatus == 0, output)
end
def default_credentials
@@ -159,12 +214,12 @@ module QA
end
end
- def tmp_netrc_directory
- @tmp_netrc_directory ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
+ def tmp_home_dir
+ @tmp_home_dir ||= File.join(Dir.tmpdir, "qa-netrc-credentials", $$.to_s)
end
def netrc_file_path
- @netrc_file_path ||= File.join(tmp_netrc_directory, '.netrc')
+ @netrc_file_path ||= File.join(tmp_home_dir, '.netrc')
end
def netrc_content
@@ -175,21 +230,6 @@ module QA
File.exist?(netrc_file_path) &&
File.readlines(netrc_file_path).grep(/^#{netrc_content}$/).any?
end
-
- def add_credentials_to_netrc
- # Despite libcurl supporting a custom .netrc location through the
- # CURLOPT_NETRC_FILE environment variable, git does not support it :(
- # Info: https://curl.haxx.se/libcurl/c/CURLOPT_NETRC_FILE.html
- #
- # This will create a .netrc in the correct working directory, which is
- # a temporary directory created in .perform()
- #
- return if netrc_already_contains_content?
-
- FileUtils.mkdir_p(tmp_netrc_directory)
- File.open(netrc_file_path, 'a') { |file| file.puts(netrc_content) }
- File.chmod(0600, netrc_file_path)
- end
end
end
end
diff --git a/qa/qa/page/label/index.rb b/qa/qa/page/label/index.rb
index 9344371a0b6..97ce8f0eba5 100644
--- a/qa/qa/page/label/index.rb
+++ b/qa/qa/page/label/index.rb
@@ -2,7 +2,7 @@ module QA
module Page
module Label
class Index < Page::Base
- view 'app/views/projects/labels/index.html.haml' do
+ view 'app/views/shared/labels/_nav.html.haml' do
element :label_create_new
end
diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb
index f33aa0acef0..32f15547da2 100644
--- a/qa/qa/resource/repository/push.rb
+++ b/qa/qa/resource/repository/push.rb
@@ -8,7 +8,7 @@ module QA
class Push < Base
attr_accessor :file_name, :file_content, :commit_message,
:branch_name, :new_branch, :output, :repository_http_uri,
- :repository_ssh_uri, :ssh_key, :user
+ :repository_ssh_uri, :ssh_key, :user, :use_lfs
attr_writer :remote_branch
@@ -20,6 +20,7 @@ module QA
@new_branch = true
@repository_http_uri = ""
@ssh_key = nil
+ @use_lfs = false
end
def remote_branch
@@ -33,7 +34,9 @@ module QA
end
def files=(files)
- if !files.is_a?(Array) || files.empty?
+ if !files.is_a?(Array) ||
+ files.empty? ||
+ files.any? { |file| !file.has_key?(:name) || !file.has_key?(:content) }
raise ArgumentError, "Please provide an array of hashes e.g.: [{name: 'file1', content: 'foo'}]"
end
@@ -42,6 +45,8 @@ module QA
def fabricate!
Git::Repository.perform do |repository|
+ @output = ''
+
if ssh_key
repository.uri = repository_ssh_uri
repository.use_ssh_key(ssh_key)
@@ -50,6 +55,8 @@ module QA
repository.use_default_credentials unless user
end
+ repository.use_lfs = use_lfs
+
username = 'GitLab QA'
email = 'root@gitlab.com'
@@ -60,29 +67,27 @@ module QA
email = user.email
end
- repository.clone
+ repository.try_add_credentials_to_netrc
+
+ @output += repository.clone
repository.configure_identity(username, email)
- if new_branch
- repository.checkout(branch_name, new_branch: true)
- else
- repository.checkout(branch_name)
- end
+ @output += repository.checkout(branch_name, new_branch: new_branch)
if @directory
@directory.each_child do |f|
- repository.add_file(f.basename, f.read) if f.file?
+ @output += repository.add_file(f.basename, f.read) if f.file?
end
elsif @files
@files.each do |f|
repository.add_file(f[:name], f[:content])
end
else
- repository.add_file(file_name, file_content)
+ @output += repository.add_file(file_name, file_content)
end
- repository.commit(commit_message)
- @output = repository.push_changes("#{branch_name}:#{remote_branch}")
+ @output += repository.commit(commit_message)
+ @output += repository.push_changes("#{branch_name}:#{remote_branch}")
repository.delete_ssh_key
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index f84f069f4db..9801ed19957 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -955,6 +955,59 @@ describe ProjectsController do
end
end
+ describe 'GET resolve' do
+ shared_examples 'resolvable endpoint' do
+ it 'redirects to the project page' do
+ get :resolve, params: { id: project.id }
+
+ expect(response).to have_gitlab_http_status(302)
+ expect(response).to redirect_to(project_path(project))
+ end
+ end
+
+ context 'with an authenticated user' do
+ before do
+ sign_in(user)
+ end
+
+ context 'when user has access to the project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'resolvable endpoint'
+ end
+
+ context 'when user has no access to the project' do
+ it 'gives 404 for existing project' do
+ get :resolve, params: { id: project.id }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ it 'gives 404 for non-existing project' do
+ get :resolve, params: { id: '0' }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'non authenticated user' do
+ context 'with a public project' do
+ let(:project) { public_project }
+
+ it_behaves_like 'resolvable endpoint'
+ end
+
+ it 'gives 404 for private project' do
+ get :resolve, params: { id: project.id }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
def project_moved_message(redirect_route, project)
"Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
end
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index 19142aa1272..5fbb71eca96 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -12,6 +12,12 @@ shared_examples 'content not cached without revalidation and no-store' do
end
end
+shared_examples 'content publicly cached' do
+ it 'ensures content is publicly cached' do
+ expect(subject['Cache-Control']).to eq('max-age=300, public')
+ end
+end
+
describe UploadsController do
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
@@ -184,7 +190,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation and no-store' do
+ it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' }
@@ -201,7 +207,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' }
@@ -537,7 +543,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'appearance', mounted_as: 'header_logo', id: appearance.id, filename: 'dk.png' }
@@ -557,7 +563,7 @@ describe UploadsController do
expect(response).to have_gitlab_http_status(200)
end
- it_behaves_like 'content not cached without revalidation' do
+ it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'appearance', mounted_as: 'logo', id: appearance.id, filename: 'dk.png' }
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index 3e2c0df8afb..a2e5f4862db 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -59,5 +59,9 @@ FactoryBot.define do
trait :with_installed_helm do
application_helm factory: %i(clusters_applications_helm installed)
end
+
+ trait :with_domain do
+ domain 'example.com'
+ end
end
end
diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb
index 9613e22bf24..23385ba65fc 100644
--- a/spec/features/issuables/markdown_references/internal_references_spec.rb
+++ b/spec/features/issuables/markdown_references/internal_references_spec.rb
@@ -64,11 +64,13 @@ describe "Internal references", :js do
it "shows references" do
page.within("#merge-requests .merge-requests-title") do
- expect(page).to have_content("1 Related Merge Request")
+ expect(page).to have_content("Related merge requests")
+ expect(page).to have_css(".mr-count-badge")
end
page.within("#merge-requests ul") do
expect(page).to have_content(private_project_merge_request.title)
+ expect(page).to have_css(".merge-request-status")
end
expect(page).to have_content("mentioned in merge request #{private_project_merge_request.to_reference(public_project)}")
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index 32bc851f00f..693ad89069c 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -141,7 +141,7 @@ describe 'User creates branch and merge request on issue page', :js do
it 'disables the create branch button' do
expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)')
expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false)
- expect(page).to have_content /1 Related Merge Request/
+ expect(page).to have_content /Related merge requests/
end
end
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 055a0c83a11..d36f043f880 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -125,7 +125,7 @@ describe 'Prioritize labels' do
wait_for_requests
end
- page.within('.breadcrumbs-container') do
+ page.within('.top-area') do
expect(page).to have_link('New label')
end
end
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 0dc7e93539a..121c4040212 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -3,6 +3,25 @@ import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data';
+const PIXEL_TOLERANCE = 0.2;
+
+/**
+ * Loads a data URL as the src of an
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image|Image}
+ * and resolves to that Image once loaded.
+ *
+ * @param url
+ * @returns {Promise}
+ */
+const urlToImage = url =>
+ new Promise(resolve => {
+ const img = new Image();
+ img.onload = function() {
+ resolve(img);
+ };
+ img.src = url;
+ });
+
describe('common_utils', () => {
describe('parseUrl', () => {
it('returns an anchor tag with url', () => {
@@ -513,8 +532,9 @@ describe('common_utils', () => {
it('should return the favicon with the overlay', done => {
commonUtils
.createOverlayIcon(faviconDataUrl, overlayDataUrl)
- .then(url => {
- expect(url).toEqual(faviconWithOverlayDataUrl);
+ .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
+ .then(([actual, expected]) => {
+ expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
done();
})
.catch(done.fail);
@@ -536,10 +556,10 @@ describe('common_utils', () => {
it('should set page favicon to provided favicon overlay', done => {
commonUtils
.setFaviconOverlay(overlayDataUrl)
- .then(() => {
- expect(document.getElementById('favicon').getAttribute('href')).toEqual(
- faviconWithOverlayDataUrl,
- );
+ .then(() => document.getElementById('favicon').getAttribute('href'))
+ .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
+ .then(([actual, expected]) => {
+ expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
done();
})
.catch(done.fail);
@@ -582,10 +602,10 @@ describe('common_utils', () => {
commonUtils
.setCiStatusFavicon(BUILD_URL)
- .then(() => {
- const favicon = document.getElementById('favicon');
-
- expect(favicon.getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
+ .then(() => document.getElementById('favicon').getAttribute('href'))
+ .then(url => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
+ .then(([actual, expected]) => {
+ expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
done();
})
.catch(done.fail);
diff --git a/spec/javascripts/matchers.js b/spec/javascripts/matchers.js
index 0d465510fd3..406527b08a3 100644
--- a/spec/javascripts/matchers.js
+++ b/spec/javascripts/matchers.js
@@ -1,3 +1,5 @@
+import pixelmatch from 'pixelmatch';
+
export default {
toContainText: () => ({
compare(vm, text) {
@@ -54,4 +56,41 @@ export default {
return result;
},
}),
+ toImageDiffEqual: () => {
+ const getImageData = img => {
+ const canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+ canvas.getContext('2d').drawImage(img, 0, 0);
+ return canvas.getContext('2d').getImageData(0, 0, img.width, img.height).data;
+ };
+
+ return {
+ compare(actual, expected, threshold = 0.1) {
+ if (actual.height !== expected.height || actual.width !== expected.width) {
+ return {
+ pass: false,
+ message: `Expected image dimensions (h x w) of ${expected.height}x${expected.width}.
+ Received an image with ${actual.height}x${actual.width}`,
+ };
+ }
+
+ const { width, height } = actual;
+ const differentPixels = pixelmatch(
+ getImageData(actual),
+ getImageData(expected),
+ null,
+ width,
+ height,
+ { threshold },
+ );
+
+ return {
+ pass: differentPixels < 20,
+ message: `${differentPixels} pixels differ more than ${threshold *
+ 100} percent between input and output.`,
+ };
+ },
+ };
+ },
};
diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
index 8e3cb36d313..812e0cc6947 100644
--- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
@@ -2,92 +2,88 @@
require 'spec_helper'
-# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :migration, schema: 20181022173835 do
+ include MigrationHelpers::ClusterHelpers
+
let(:migration) { described_class.new }
- let(:clusters) { create_list(:cluster, 10, :project, :provided_by_gcp) }
+ let(:clusters_table) { table(:clusters) }
+ let(:cluster_projects_table) { table(:cluster_projects) }
+ let(:cluster_kubernetes_namespaces_table) { table(:clusters_kubernetes_namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:provider_gcp_table) { table(:cluster_providers_gcp) }
+ let(:platform_kubernetes_table) { table(:cluster_platforms_kubernetes) }
before do
- clusters
+ create_cluster_project_list(10)
end
shared_examples 'consistent kubernetes namespace attributes' do
it 'should populate namespace and service account information' do
- subject
+ migration.perform
clusters_with_namespace.each do |cluster|
- project = cluster.project
- cluster_project = cluster.cluster_projects.first
+ cluster_project = cluster_projects_table.find_by(cluster_id: cluster.id)
+ project = projects_table.find(cluster_project.project_id)
+ kubernetes_namespace = cluster_kubernetes_namespaces_table.find_by(cluster_id: cluster.id)
namespace = "#{project.path}-#{project.id}"
- kubernetes_namespace = cluster.reload.kubernetes_namespace
expect(kubernetes_namespace).to be_present
- expect(kubernetes_namespace.cluster_project).to eq(cluster_project)
- expect(kubernetes_namespace.project).to eq(cluster_project.project)
- expect(kubernetes_namespace.cluster).to eq(cluster_project.cluster)
+ expect(kubernetes_namespace.cluster_project_id).to eq(cluster_project.id)
+ expect(kubernetes_namespace.project_id).to eq(cluster_project.project_id)
+ expect(kubernetes_namespace.cluster_id).to eq(cluster_project.cluster_id)
expect(kubernetes_namespace.namespace).to eq(namespace)
expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
end
end
end
- subject { migration.perform }
-
context 'when no Clusters::Project has a Clusters::KubernetesNamespace' do
- let(:cluster_projects) { Clusters::Project.all }
+ let(:cluster_projects) { cluster_projects_table.all }
it 'should create a Clusters::KubernetesNamespace per Clusters::Project' do
expect do
- subject
- end.to change(Clusters::KubernetesNamespace, :count).by(cluster_projects.count)
+ migration.perform
+ end.to change(Clusters::KubernetesNamespace, :count).by(cluster_projects_table.count)
end
it_behaves_like 'consistent kubernetes namespace attributes' do
- let(:clusters_with_namespace) { clusters }
+ let(:clusters_with_namespace) { clusters_table.all }
end
end
context 'when every Clusters::Project has Clusters::KubernetesNamespace' do
before do
- clusters.each do |cluster|
- create(:cluster_kubernetes_namespace,
- cluster_project: cluster.cluster_projects.first,
- cluster: cluster,
- project: cluster.project)
- end
+ create_kubernetes_namespace(clusters_table.all)
end
it 'should not create any Clusters::KubernetesNamespace' do
expect do
- subject
+ migration.perform
end.not_to change(Clusters::KubernetesNamespace, :count)
end
end
context 'when only some Clusters::Project have Clusters::KubernetesNamespace related' do
- let(:with_kubernetes_namespace) { clusters.first(6) }
- let(:with_no_kubernetes_namespace) { clusters.last(4) }
+ let(:with_kubernetes_namespace) { clusters_table.first(6) }
+ let(:with_no_kubernetes_namespace) { clusters_table.last(4) }
before do
- with_kubernetes_namespace.each do |cluster|
- create(:cluster_kubernetes_namespace,
- cluster_project: cluster.cluster_projects.first,
- cluster: cluster,
- project: cluster.project)
- end
+ create_kubernetes_namespace(with_kubernetes_namespace)
end
it 'creates limited number of Clusters::KubernetesNamespace' do
expect do
- subject
+ migration.perform
end.to change(Clusters::KubernetesNamespace, :count).by(with_no_kubernetes_namespace.count)
end
it 'should not modify clusters with Clusters::KubernetesNamespace' do
- subject
+ migration.perform
with_kubernetes_namespace.each do |cluster|
- expect(cluster.kubernetes_namespaces.count).to eq(1)
+ kubernetes_namespace = cluster_kubernetes_namespaces_table.where(cluster_id: cluster.id)
+ expect(kubernetes_namespace.count).to eq(1)
end
end
@@ -96,4 +92,3 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :
end
end
end
-# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 1b014ecfaa4..3459939267a 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -79,6 +79,31 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
end
end
+ describe 'pipeline protect' do
+ subject { step.perform! }
+
+ context 'when ref is protected' do
+ before do
+ allow(project).to receive(:protected_for?).with('master').and_return(true)
+ allow(project).to receive(:protected_for?).with('refs/heads/master').and_return(true)
+ end
+
+ it 'does not protect the pipeline' do
+ subject
+
+ expect(pipeline.protected).to eq(true)
+ end
+ end
+
+ context 'when ref is not protected' do
+ it 'does not protect the pipeline' do
+ subject
+
+ expect(pipeline.protected).to eq(false)
+ end
+ end
+ end
+
context 'when pipeline has validation errors' do
let(:pipeline) do
build(:ci_pipeline, project: project, ref: nil)
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index f773f370ee2..a9d15f1d522 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -82,15 +82,36 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end.to raise_error(ArgumentError)
end
end
+
+ context 'when the job args are bigger than the maximum allowed' do
+ it 'keeps args from the front until they exceed the limit' do
+ Timecop.freeze(timestamp) do
+ job['args'] = [
+ 1,
+ 2,
+ 'a' * (described_class::MAXIMUM_JOB_ARGUMENTS_LENGTH / 2),
+ 'b' * (described_class::MAXIMUM_JOB_ARGUMENTS_LENGTH / 2),
+ 3
+ ]
+
+ expected_args = job['args'].take(3) + ['...']
+
+ expect(logger).to receive(:info).with(start_payload.merge('args' => expected_args)).ordered
+ expect(logger).to receive(:info).with(end_payload.merge('args' => expected_args)).ordered
+ expect(subject).to receive(:log_job_start).and_call_original
+ expect(subject).to receive(:log_job_done).and_call_original
+
+ subject.call(job, 'test_queue') { }
+ end
+ end
+ end
end
context 'with SIDEKIQ_LOG_ARGUMENTS disabled' do
- it 'logs start and end of job' do
+ it 'logs start and end of job without args' do
Timecop.freeze(timestamp) do
- start_payload.delete('args')
-
- expect(logger).to receive(:info).with(start_payload).ordered
- expect(logger).to receive(:info).with(end_payload).ordered
+ expect(logger).to receive(:info).with(start_payload.except('args')).ordered
+ expect(logger).to receive(:info).with(end_payload.except('args')).ordered
expect(subject).to receive(:log_job_start).and_call_original
expect(subject).to receive(:log_job_done).and_call_original
diff --git a/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb b/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb
new file mode 100644
index 00000000000..7f5aecb7baa
--- /dev/null
+++ b/spec/lib/gitlab/tracing/grpc_interceptor_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::GRPCInterceptor do
+ subject { described_class.instance }
+
+ shared_examples_for "a grpc interceptor method" do
+ let(:custom_error) { Class.new(StandardError) }
+
+ it 'yields' do
+ expect { |b| method.call(kwargs, &b) }.to yield_control
+ end
+
+ it 'propagates exceptions' do
+ expect { method.call(kwargs) { raise custom_error } }.to raise_error(custom_error)
+ end
+ end
+
+ describe '#request_response' do
+ let(:method) { subject.method(:request_response) }
+ let(:kwargs) { { request: {}, call: {}, method: 'grc_method', metadata: {} } }
+
+ it_behaves_like 'a grpc interceptor method'
+ end
+
+ describe '#client_streamer' do
+ let(:method) { subject.method(:client_streamer) }
+ let(:kwargs) { { requests: [], call: {}, method: 'grc_method', metadata: {} } }
+
+ it_behaves_like 'a grpc interceptor method'
+ end
+
+ describe '#server_streamer' do
+ let(:method) { subject.method(:server_streamer) }
+ let(:kwargs) { { request: {}, call: {}, method: 'grc_method', metadata: {} } }
+
+ it_behaves_like 'a grpc interceptor method'
+ end
+
+ describe '#bidi_streamer' do
+ let(:method) { subject.method(:bidi_streamer) }
+ let(:kwargs) { { requests: [], call: {}, method: 'grc_method', metadata: {} } }
+
+ it_behaves_like 'a grpc interceptor method'
+ end
+end
diff --git a/spec/lib/gitlab/tracing/rack_middleware_spec.rb b/spec/lib/gitlab/tracing/rack_middleware_spec.rb
new file mode 100644
index 00000000000..13d4d8a89f7
--- /dev/null
+++ b/spec/lib/gitlab/tracing/rack_middleware_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Tracing::RackMiddleware do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#call' do
+ context 'for normal middleware flow' do
+ let(:fake_app) { -> (env) { fake_app_response } }
+ subject { described_class.new(fake_app) }
+ let(:request) { }
+
+ context 'for 200 responses' do
+ let(:fake_app_response) { [200, { 'Content-Type': 'text/plain' }, ['OK']] }
+
+ it 'delegates correctly' do
+ expect(subject.call(Rack::MockRequest.env_for("/"))).to eq(fake_app_response)
+ end
+ end
+
+ context 'for 500 responses' do
+ let(:fake_app_response) { [500, { 'Content-Type': 'text/plain' }, ['Error']] }
+
+ it 'delegates correctly' do
+ expect(subject.call(Rack::MockRequest.env_for("/"))).to eq(fake_app_response)
+ end
+ end
+ end
+
+ context 'when an application is raising an exception' do
+ let(:custom_error) { Class.new(StandardError) }
+ let(:fake_app) { ->(env) { raise custom_error } }
+
+ subject { described_class.new(fake_app) }
+
+ it 'delegates propagates exceptions correctly' do
+ expect { subject.call(Rack::MockRequest.env_for("/")) }.to raise_error(custom_error)
+ end
+ end
+ end
+
+ describe '.build_sanitized_url_from_env' do
+ def env_for_url(url)
+ env = Rack::MockRequest.env_for(input_url)
+ env['action_dispatch.parameter_filter'] = [/token/]
+
+ env
+ end
+
+ where(:input_url, :output_url) do
+ '/gitlab-org/gitlab-ce' | 'http://example.org/gitlab-org/gitlab-ce'
+ '/gitlab-org/gitlab-ce?safe=1' | 'http://example.org/gitlab-org/gitlab-ce?safe=1'
+ '/gitlab-org/gitlab-ce?private_token=secret' | 'http://example.org/gitlab-org/gitlab-ce?private_token=%5BFILTERED%5D'
+ '/gitlab-org/gitlab-ce?mixed=1&private_token=secret' | 'http://example.org/gitlab-org/gitlab-ce?mixed=1&private_token=%5BFILTERED%5D'
+ end
+
+ with_them do
+ it { expect(described_class.build_sanitized_url_from_env(env_for_url(input_url))).to eq(output_url) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb b/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb
new file mode 100644
index 00000000000..3755860b5ba
--- /dev/null
+++ b/spec/lib/gitlab/tracing/sidekiq/client_middleware_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::Sidekiq::ClientMiddleware do
+ describe '#call' do
+ let(:worker_class) { 'test_worker_class' }
+ let(:job) do
+ {
+ 'class' => "jobclass",
+ 'queue' => "jobqueue",
+ 'retry' => 0,
+ 'args' => %w{1 2 3}
+ }
+ end
+ let(:queue) { 'test_queue' }
+ let(:redis_pool) { double("redis_pool") }
+ let(:custom_error) { Class.new(StandardError) }
+ let(:span) { OpenTracing.start_span('test', ignore_active_scope: true) }
+
+ subject { described_class.new }
+
+ it 'yields' do
+ expect(subject).to receive(:in_tracing_span).with(
+ operation_name: "sidekiq:jobclass",
+ tags: {
+ "component" => "sidekiq",
+ "span.kind" => "client",
+ "sidekiq.queue" => "jobqueue",
+ "sidekiq.jid" => nil,
+ "sidekiq.retry" => "0",
+ "sidekiq.args" => "1, 2, 3"
+ }
+ ).and_yield(span)
+
+ expect { |b| subject.call(worker_class, job, queue, redis_pool, &b) }.to yield_control
+ end
+
+ it 'propagates exceptions' do
+ expect { subject.call(worker_class, job, queue, redis_pool) { raise custom_error } }.to raise_error(custom_error)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb b/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb
new file mode 100644
index 00000000000..c3087de785a
--- /dev/null
+++ b/spec/lib/gitlab/tracing/sidekiq/server_middleware_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::Sidekiq::ServerMiddleware do
+ describe '#call' do
+ let(:worker_class) { 'test_worker_class' }
+ let(:job) do
+ {
+ 'class' => "jobclass",
+ 'queue' => "jobqueue",
+ 'retry' => 0,
+ 'args' => %w{1 2 3}
+ }
+ end
+ let(:queue) { 'test_queue' }
+ let(:custom_error) { Class.new(StandardError) }
+ let(:span) { OpenTracing.start_span('test', ignore_active_scope: true) }
+ subject { described_class.new }
+
+ it 'yields' do
+ expect(subject).to receive(:in_tracing_span).with(
+ hash_including(
+ operation_name: "sidekiq:jobclass",
+ tags: {
+ "component" => "sidekiq",
+ "span.kind" => "server",
+ "sidekiq.queue" => "jobqueue",
+ "sidekiq.jid" => nil,
+ "sidekiq.retry" => "0",
+ "sidekiq.args" => "1, 2, 3"
+ }
+ )
+ ).and_yield(span)
+
+ expect { |b| subject.call(worker_class, job, queue, &b) }.to yield_control
+ end
+
+ it 'propagates exceptions' do
+ expect { subject.call(worker_class, job, queue) { raise custom_error } }.to raise_error(custom_error)
+ end
+ end
+end
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
index baf16c2ce53..80ae209e9d1 100644
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -37,9 +37,8 @@ describe RenameMoreReservedProjectNames, :delete do
.to receive(:execute)
.and_raise(Projects::AfterRenameService::RenameFailedError)
- allow(Projects::AfterRenameService)
- .to receive(:new)
- .with(project)
+ expect(migration)
+ .to receive(:after_rename_service)
.and_return(service)
end
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
index 7818aa0d560..93e5c032287 100644
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -41,9 +41,8 @@ describe RenameReservedProjectNames, :migration, schema: :latest do
.to receive(:execute)
.and_raise(Projects::AfterRenameService::RenameFailedError)
- allow(Projects::AfterRenameService)
- .to receive(:new)
- .with(project)
+ expect(migration)
+ .to receive(:after_rename_service)
.and_return(service)
end
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 3d0735c6d0b..8ad41e997c2 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do
let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') }
it 'updates the application version' do
- expect(application.reload.version).to eq('0.1.43')
+ expect(application.reload.version).to eq('0.1.45')
end
end
end
@@ -46,7 +46,7 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
- expect(subject.version).to eq('0.1.43')
+ expect(subject.version).to eq('0.1.45')
expect(subject).to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
@@ -64,7 +64,7 @@ describe Clusters::Applications::Runner do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.1.43')
+ expect(subject.version).to eq('0.1.45')
end
end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index f447e64b029..0161db740ee 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -113,7 +113,7 @@ describe Clusters::Cluster do
end
end
- describe 'validation' do
+ describe 'validations' do
subject { cluster.valid? }
context 'when validates name' do
@@ -252,6 +252,31 @@ describe Clusters::Cluster do
end
end
end
+
+ describe 'domain validation' do
+ let(:cluster) { build(:cluster) }
+
+ subject { cluster }
+
+ context 'when cluster has domain' do
+ let(:cluster) { build(:cluster, :with_domain) }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when cluster has an invalid domain' do
+ let(:cluster) { build(:cluster, domain: 'not-valid-domain') }
+
+ it 'should add an error on domain' do
+ expect(subject).not_to be_valid
+ expect(subject.errors[:domain].first).to eq('is not a fully qualified domain name')
+ end
+ end
+
+ context 'when cluster does not have a domain' do
+ it { is_expected.to be_valid }
+ end
+ end
end
describe '.ancestor_clusters_for_clusterable' do
diff --git a/spec/requests/api/submodules_spec.rb b/spec/requests/api/submodules_spec.rb
index c482a85c68f..064392fb185 100644
--- a/spec/requests/api/submodules_spec.rb
+++ b/spec/requests/api/submodules_spec.rb
@@ -64,7 +64,7 @@ describe API::Submodules do
expect(response).to have_gitlab_http_status(400)
end
- it 'returns the commmit' do
+ it 'returns the commit' do
head_commit = project.repository.commit.id
put api(route(submodule), user), params: params
@@ -81,7 +81,7 @@ describe API::Submodules do
let(:branch) { 'submodule_inside_folder' }
let(:encoded_submodule) { CGI.escape(submodule) }
- it 'returns the commmit' do
+ it 'returns the commit' do
expect(Submodules::UpdateService)
.to receive(:new)
.with(any_args, hash_including(submodule: submodule))
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 5c3b37ef11c..a0d01fc8263 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -122,6 +122,10 @@ describe 'project routing' do
route_to('projects#preview_markdown', namespace_id: 'gitlab', id: 'gitlabhq')
)
end
+
+ it 'to #resolve' do
+ expect(get('/projects/1')).to route_to('projects#resolve', id: '1')
+ end
end
# members_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/members(.:format) projects/autocomplete_sources#members
diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb
index 59c08b30f9f..bc5366a3339 100644
--- a/spec/services/projects/after_rename_service_spec.rb
+++ b/spec/services/projects/after_rename_service_spec.rb
@@ -4,53 +4,45 @@ require 'spec_helper'
describe Projects::AfterRenameService do
let(:rugged_config) { rugged_repo(project.repository).config }
+ let(:legacy_storage) { Storage::LegacyProject.new(project) }
+ let(:hashed_storage) { Storage::HashedProject.new(project) }
+ let!(:path_before_rename) { project.path }
+ let!(:full_path_before_rename) { project.full_path }
+ let!(:path_after_rename) { "#{project.path}-renamed" }
+ let!(:full_path_after_rename) { "#{project.full_path}-renamed" }
describe '#execute' do
context 'using legacy storage' do
- let(:project) { create(:project, :repository, :legacy_storage) }
- let(:gitlab_shell) { Gitlab::Shell.new }
+ let(:project) { create(:project, :repository, :wiki_repo, :legacy_storage) }
let(:project_storage) { project.send(:storage) }
+ let(:gitlab_shell) { Gitlab::Shell.new }
before do
# Project#gitlab_shell returns a new instance of Gitlab::Shell on every
# call. This makes testing a bit easier.
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project)
- .to receive(:previous_changes)
- .and_return('path' => ['foo'])
-
- allow(project)
- .to receive(:path_was)
- .and_return('foo')
-
stub_feature_flags(skip_hashed_storage_upgrade: false)
end
it 'renames a repository' do
stub_container_registry_config(enabled: false)
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage, "#{project.namespace.full_path}/foo", "#{project.full_path}")
- .and_return(true)
-
- expect(gitlab_shell).to receive(:mv_repository)
- .ordered
- .with(project.repository_storage, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
- .and_return(true)
-
expect_any_instance_of(SystemHooksService)
.to receive(:execute_hooks_for)
.with(project, :rename)
expect_any_instance_of(Gitlab::UploadsTransfer)
.to receive(:rename_project)
- .with('foo', project.path, project.namespace.full_path)
+ .with(path_before_rename, path_after_rename, project.namespace.full_path)
- expect(project).to receive(:expire_caches_before_rename)
+ expect_repository_exist("#{full_path_before_rename}.git")
+ expect_repository_exist("#{full_path_before_rename}.wiki.git")
- described_class.new(project).execute
+ service_execute
+
+ expect_repository_exist("#{full_path_after_rename}.git")
+ expect_repository_exist("#{full_path_after_rename}.wiki.git")
end
context 'container registry with images' do
@@ -63,8 +55,7 @@ describe Projects::AfterRenameService do
end
it 'raises a RenameFailedError' do
- expect { described_class.new(project).execute }
- .to raise_error(described_class::RenameFailedError)
+ expect { service_execute }.to raise_error(described_class::RenameFailedError)
end
end
@@ -76,7 +67,7 @@ describe Projects::AfterRenameService do
it 'moves pages folder to new location' do
expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
- described_class.new(project).execute
+ service_execute
end
end
@@ -88,14 +79,12 @@ describe Projects::AfterRenameService do
it 'moves uploads folder to new location' do
expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project)
- described_class.new(project).execute
+ service_execute
end
end
it 'updates project full path in .git/config' do
- allow(project_storage).to receive(:rename_repo).and_return(true)
-
- described_class.new(project).execute
+ service_execute
expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
end
@@ -103,13 +92,25 @@ describe Projects::AfterRenameService do
it 'updates storage location' do
allow(project_storage).to receive(:rename_repo).and_return(true)
- described_class.new(project).execute
+ service_execute
expect(project.project_repository).to have_attributes(
disk_path: project.disk_path,
shard_name: project.repository_storage
)
end
+
+ context 'with hashed storage upgrade when renaming enabled' do
+ it 'calls HashedStorageMigrationService with correct options' do
+ stub_application_setting(hashed_storage_enabled: true)
+
+ expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service|
+ expect(service).to receive(:execute).and_return(true)
+ end
+
+ service_execute
+ end
+ end
end
context 'using hashed storage' do
@@ -123,25 +124,11 @@ describe Projects::AfterRenameService do
# Project#gitlab_shell returns a new instance of Gitlab::Shell on every
# call. This makes testing a bit easier.
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
stub_feature_flags(skip_hashed_storage_upgrade: false)
stub_application_setting(hashed_storage_enabled: true)
end
- context 'migration to hashed storage' do
- it 'calls HashedStorageMigrationService with correct options' do
- project = create(:project, :repository, :legacy_storage)
- allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
-
- expect_next_instance_of(::Projects::HashedStorageMigrationService) do |service|
- expect(service).to receive(:execute).and_return(true)
- end
-
- described_class.new(project).execute
- end
- end
-
it 'renames a repository' do
stub_container_registry_config(enabled: false)
@@ -153,7 +140,7 @@ describe Projects::AfterRenameService do
expect(project).to receive(:expire_caches_before_rename)
- described_class.new(project).execute
+ service_execute
end
context 'container registry with images' do
@@ -166,7 +153,7 @@ describe Projects::AfterRenameService do
end
it 'raises a RenameFailedError' do
- expect { described_class.new(project).execute }
+ expect { service_execute }
.to raise_error(described_class::RenameFailedError)
end
end
@@ -175,38 +162,46 @@ describe Projects::AfterRenameService do
it 'moves pages folder to new location' do
expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project)
- described_class.new(project).execute
+ service_execute
end
end
context 'attachments' do
+ let(:uploader) { create(:upload, :issuable_upload, :with_file, model: project) }
+ let(:file_uploader) { build(:file_uploader, project: project) }
+ let(:legacy_storage_path) { File.join(file_uploader.root, legacy_storage.disk_path) }
+ let(:hashed_storage_path) { File.join(file_uploader.root, hashed_storage.disk_path) }
+
it 'keeps uploads folder location unchanged' do
expect_any_instance_of(Gitlab::UploadsTransfer).not_to receive(:rename_project)
- described_class.new(project).execute
+ service_execute
end
context 'when not rolled out' do
let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) }
- it 'moves pages folder to hashed storage' do
- expect_next_instance_of(Projects::HashedStorage::MigrateAttachmentsService) do |service|
- expect(service).to receive(:execute)
- end
+ it 'moves attachments folder to hashed storage' do
+ expect(File.directory?(legacy_storage_path)).to be_truthy
+ expect(File.directory?(hashed_storage_path)).to be_falsey
- described_class.new(project).execute
+ service_execute
+ expect(project.reload.hashed_storage?(:attachments)).to be_truthy
+
+ expect(File.directory?(legacy_storage_path)).to be_falsey
+ expect(File.directory?(hashed_storage_path)).to be_truthy
end
end
end
it 'updates project full path in .git/config' do
- described_class.new(project).execute
+ service_execute
expect(rugged_config['gitlab.fullpath']).to eq(project.full_path)
end
it 'updates storage location' do
- described_class.new(project).execute
+ service_execute
expect(project.project_repository).to have_attributes(
disk_path: project.disk_path,
@@ -215,4 +210,21 @@ describe Projects::AfterRenameService do
end
end
end
+
+ def service_execute
+ # AfterRenameService is called by UpdateService after a successful model.update
+ # the initialization will include before and after paths values
+ project.update(path: path_after_rename)
+
+ described_class.new(project, path_before: path_before_rename, full_path_before: full_path_before_rename).execute
+ end
+
+ def expect_repository_exist(full_path_with_extension)
+ expect(
+ gitlab_shell.exists?(
+ project.repository_storage,
+ full_path_with_extension
+ )
+ ).to be_truthy
+ end
end
diff --git a/spec/support/migrations_helpers/cluster_helpers.rb b/spec/support/migrations_helpers/cluster_helpers.rb
new file mode 100644
index 00000000000..b54af15c29e
--- /dev/null
+++ b/spec/support/migrations_helpers/cluster_helpers.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module MigrationHelpers
+ module ClusterHelpers
+ # Creates a list of cluster projects.
+ def create_cluster_project_list(quantity)
+ group = namespaces_table.create(name: 'gitlab-org', path: 'gitlab-org')
+
+ quantity.times do |id|
+ create_cluster_project(group, id)
+ end
+ end
+
+ # Creates dependencies for a cluster project:
+ # - Group
+ # - Project
+ # - Cluster
+ # - Project - cluster relationship
+ # - GCP provider
+ # - Platform Kubernetes
+ def create_cluster_project(group, id)
+ project = projects_table.create!(
+ name: "project-#{id}",
+ path: "project-#{id}",
+ namespace_id: group.id
+ )
+
+ cluster = clusters_table.create(
+ name: 'test-cluster',
+ cluster_type: 3,
+ provider_type: :gcp,
+ platform_type: :kubernetes
+ )
+
+ cluster_projects_table.create(project_id: project.id, cluster_id: cluster.id)
+
+ provider_gcp_table.create!(
+ gcp_project_id: "test-gcp-project-#{id}",
+ endpoint: '111.111.111.111',
+ cluster_id: cluster.id,
+ status: 3,
+ num_nodes: 1,
+ zone: 'us-central1-a'
+ )
+
+ platform_kubernetes_table.create(
+ cluster_id: cluster.id,
+ api_url: 'https://kubernetes.example.com',
+ encrypted_token: 'a' * 40,
+ encrypted_token_iv: 'a' * 40
+ )
+ end
+
+ # Creates a Kubernetes namespace for a list of clusters
+ def create_kubernetes_namespace(clusters)
+ clusters.each do |cluster|
+ cluster_project = cluster_projects_table.find_by(cluster_id: cluster.id)
+ project = projects_table.find(cluster_project.project_id)
+ namespace = "#{project.path}-#{project.id}"
+
+ cluster_kubernetes_namespaces_table.create(
+ cluster_project_id: cluster_project.id,
+ cluster_id: cluster.id,
+ project_id: cluster_project.project_id,
+ namespace: namespace,
+ service_account_name: "#{namespace}-service-account"
+ )
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 7b3144fca16..bb948ad703c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -64,10 +64,10 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-create-class-features-plugin@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.2.3.tgz#f6e719abb90cb7f4a69591e35fd5eb89047c4a7c"
- integrity sha512-xO/3Gn+2C7/eOUeb0VRnSP1+yvWHNxlpAot1eMhtoKDCN7POsyQP5excuT5UsV5daHxMWBeIIOeI5cmB8vMRgQ==
+"@babel/helper-create-class-features-plugin@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.0.tgz#2b01a81b3adc2b1287f9ee193688ef8dc71e718f"
+ integrity sha512-DUsQNS2CGLZZ7I3W3fvh0YpPDd6BuWJlDl+qmZZpABZHza2ErE3LxtEzLJFHFC1ZwtlAXvHhbFYbtM5o5B0WBw==
dependencies:
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-member-expression-to-functions" "^7.0.0"
@@ -238,12 +238,12 @@
"@babel/helper-remap-async-to-generator" "^7.1.0"
"@babel/plugin-syntax-async-generators" "^7.2.0"
-"@babel/plugin-proposal-class-properties@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.2.3.tgz#c9e1294363b346cff333007a92080f3203698461"
- integrity sha512-FVuQngLoN2iDrpW7LmhPZ2sO4DJxf35FOcwidwB9Ru9tMvI5URthnkVHuG14IStV+TzkMTyLMoOUlSTtrdVwqw==
+"@babel/plugin-proposal-class-properties@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd"
+ integrity sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.2.3"
+ "@babel/helper-create-class-features-plugin" "^7.3.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-json-strings@^7.2.0":
@@ -254,10 +254,10 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-json-strings" "^7.2.0"
-"@babel/plugin-proposal-object-rest-spread@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.2.0.tgz#88f5fec3e7ad019014c97f7ee3c992f0adbf7fb8"
- integrity sha512-1L5mWLSvR76XYUQJXkd/EEQgjq8HHRP6lQuZTTg0VA4tTGPpGemmCdAfQIz1rzEuWAm+ecP8PyyEm30jC1eQCg==
+"@babel/plugin-proposal-object-rest-spread@^7.3.1":
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.1.tgz#f69fb6a1ea6a4e1c503994a91d9cf76f3c4b36e8"
+ integrity sha512-Nmmv1+3LqxJu/V5jU9vJmxR/KIRWFk2qLHmbB56yRRRFhlaSuOVXscX3gUmhaKgUhzA3otOHVubbIEVYsZ0eZg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
@@ -270,12 +270,12 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
-"@babel/plugin-proposal-private-methods@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.2.3.tgz#aff0f5436df2c4365938c0309d551984e42c290c"
- integrity sha512-jehrt1/TuLdLeBAVEv1VmTCNJcvSj+5Ozp7l21DN19Ylo0ATxpZ5bDk8i4WS9Ngvdgk/YTcqJCTp3uY2lwQoxw==
+"@babel/plugin-proposal-private-methods@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.3.0.tgz#da373257a66525cb76544c37ab2ce4c611568841"
+ integrity sha512-j6luy/F0MX6kd71e9hz97my2tBXTa+czAz+sscJVCRmjB9e9g2D4JN+tyfcwMCXUM2afj/tYCjzNaxwWJ4SdYg==
dependencies:
- "@babel/helper-create-class-features-plugin" "^7.2.3"
+ "@babel/helper-create-class-features-plugin" "^7.3.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-unicode-property-regex@^7.2.0":
@@ -467,6 +467,13 @@
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-transform-named-capturing-groups-regex@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz#140b52985b2d6ef0cb092ef3b29502b990f9cd50"
+ integrity sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw==
+ dependencies:
+ regexp-tree "^0.1.0"
+
"@babel/plugin-transform-new-target@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a"
@@ -544,19 +551,20 @@
"@babel/helper-regex" "^7.0.0"
regexpu-core "^4.1.3"
-"@babel/preset-env@^7.2.3":
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.2.3.tgz#948c8df4d4609c99c7e0130169f052ea6a7a8933"
- integrity sha512-AuHzW7a9rbv5WXmvGaPX7wADxFkZIqKlbBh1dmZUQp4iwiPpkE/Qnrji6SC4UQCQzvWY/cpHET29eUhXS9cLPw==
+"@babel/preset-env@^7.3.1":
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.1.tgz#389e8ca6b17ae67aaf9a2111665030be923515db"
+ integrity sha512-FHKrD6Dxf30e8xgHQO0zJZpUPfVZg+Xwgz5/RdSWCbza9QLNk4Qbp40ctRoqDxml3O8RMzB1DU55SXeDG6PqHQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-async-generator-functions" "^7.2.0"
"@babel/plugin-proposal-json-strings" "^7.2.0"
- "@babel/plugin-proposal-object-rest-spread" "^7.2.0"
+ "@babel/plugin-proposal-object-rest-spread" "^7.3.1"
"@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
"@babel/plugin-proposal-unicode-property-regex" "^7.2.0"
"@babel/plugin-syntax-async-generators" "^7.2.0"
+ "@babel/plugin-syntax-json-strings" "^7.2.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
"@babel/plugin-transform-arrow-functions" "^7.2.0"
@@ -576,6 +584,7 @@
"@babel/plugin-transform-modules-commonjs" "^7.2.0"
"@babel/plugin-transform-modules-systemjs" "^7.2.0"
"@babel/plugin-transform-modules-umd" "^7.2.0"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.3.0"
"@babel/plugin-transform-new-target" "^7.0.0"
"@babel/plugin-transform-object-super" "^7.2.0"
"@babel/plugin-transform-parameters" "^7.2.0"
@@ -2179,6 +2188,16 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
+cli-table3@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
+ integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
+ dependencies:
+ object-assign "^4.1.0"
+ string-width "^2.1.1"
+ optionalDependencies:
+ colors "^1.1.2"
+
cli-width@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
@@ -2268,6 +2287,11 @@ colors@^1.1.0:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
+colors@^1.1.2:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
+ integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==
+
combine-lists@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/combine-lists/-/combine-lists-1.0.1.tgz#458c07e09e0d900fc28b70a3fec2dacd1d2cb7f6"
@@ -7735,6 +7759,13 @@ pinkie@^2.0.0:
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
+pixelmatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854"
+ integrity sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=
+ dependencies:
+ pngjs "^3.0.0"
+
pkg-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
@@ -7766,6 +7797,11 @@ pn@^1.1.0:
resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
+pngjs@^3.0.0:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
+ integrity sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==
+
pofile@^1:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
@@ -7869,10 +7905,10 @@ prettier@1.13.7:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
-prettier@1.15.3:
- version "1.15.3"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
- integrity sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==
+prettier@1.16.1:
+ version "1.16.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.1.tgz#534c2c9d7853f8845e5e078384e71973bd74089f"
+ integrity sha512-XXUITwIkGb3CPJ2hforHah/zTINRyie5006Jd2HKy2qz7snEJXl0KLfsJZW/wst9g6R2rFvqba3VpNYdu1hDcA==
pretty-format@^23.6.0:
version "23.6.0"
@@ -8274,6 +8310,15 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
+regexp-tree@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.0.tgz#a56ad7746097888ea16457479029ec9345b96ab0"
+ integrity sha512-rHQv+tzu+0l3KS/ERabas1yK49ahNVxuH40WcPg53CzP5p8TgmmyBgHELLyJcvjhTD0e5ahSY6C76LbEVtr7cg==
+ dependencies:
+ cli-table3 "^0.5.0"
+ colors "^1.1.2"
+ yargs "^10.0.3"
+
regexpp@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
@@ -10437,6 +10482,13 @@ yargs-parser@^11.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
+yargs-parser@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
+ integrity sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==
+ dependencies:
+ camelcase "^4.1.0"
+
yargs-parser@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
@@ -10462,6 +10514,24 @@ yargs@12.0.2:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^10.1.0"
+yargs@^10.0.3:
+ version "10.1.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5"
+ integrity sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==
+ dependencies:
+ cliui "^4.0.0"
+ decamelize "^1.1.1"
+ find-up "^2.1.0"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^8.1.0"
+
yargs@^11.0.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"