diff options
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 Binary files differdeleted file mode 100644 index ab100a95750..00000000000 --- a/doc/user/project/issues/img/import_csv_button.png +++ /dev/null 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" |