diff options
author | Sean McGivern <sean@gitlab.com> | 2019-02-07 14:46:17 +0000 |
---|---|---|
committer | Sean McGivern <sean@gitlab.com> | 2019-02-07 14:46:17 +0000 |
commit | c3da8849c364885841fddf592346974ff4148c8d (patch) | |
tree | 32ee05526f2b193dbd92db8d13e84cf909f1777d | |
parent | 1a0bab0ab2896f021a5d3a80e84b5e9d6678f6c0 (diff) | |
parent | c8fe0d6a8a76ee5865c71b5b6627608e5075797d (diff) | |
download | gitlab-ce-c3da8849c364885841fddf592346974ff4148c8d.tar.gz |
Merge branch 'master' into fabsrc/gitlab-ce-2105-add-setting-for-first-day-of-the-week
167 files changed, 2592 insertions, 674 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 092afa15df4..744068368fb 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.17.0 +1.18.0
\ No newline at end of file @@ -422,7 +422,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 1.5.0', require: 'gitaly' +gem 'gitaly-proto', '~> 1.10.0', require: 'gitaly' gem 'grpc', '~> 1.15.0' gem 'google-protobuf', '~> 3.6' diff --git a/Gemfile.lock b/Gemfile.lock index f661da41507..0b2bd2c96bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,7 +278,7 @@ GEM gettext_i18n_rails (>= 0.7.1) po_to_json (>= 1.0.0) rails (>= 3.2.0) - gitaly-proto (1.5.0) + gitaly-proto (1.10.0) grpc (~> 1.0) github-markup (1.7.0) gitlab-default_value_for (3.1.1) @@ -1020,7 +1020,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.3) - gitaly-proto (~> 1.5.0) + gitaly-proto (~> 1.10.0) github-markup (~> 1.7.0) gitlab-default_value_for (~> 3.1.1) gitlab-markup (~> 1.6.5) diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue index 3ef54752436..0bf2dde8b96 100644 --- a/app/assets/javascripts/diffs/components/compare_versions.vue +++ b/app/assets/javascripts/diffs/components/compare_versions.vue @@ -6,6 +6,7 @@ import { polyfillSticky } from '~/lib/utils/sticky'; import Icon from '~/vue_shared/components/icon.vue'; import CompareVersionsDropdown from './compare_versions_dropdown.vue'; import SettingsDropdown from './settings_dropdown.vue'; +import DiffStats from './diff_stats.vue'; export default { components: { @@ -14,6 +15,7 @@ export default { GlLink, GlButton, SettingsDropdown, + DiffStats, }, directives: { GlTooltip: GlTooltipDirective, @@ -35,8 +37,15 @@ export default { }, }, computed: { - ...mapState('diffs', ['commit', 'showTreeList', 'startVersion', 'latestVersionPath']), - ...mapGetters('diffs', ['hasCollapsedFile']), + ...mapGetters('diffs', ['hasCollapsedFile', 'diffFilesLength']), + ...mapState('diffs', [ + 'commit', + 'showTreeList', + 'startVersion', + 'latestVersionPath', + 'addedLines', + 'removedLines', + ]), comparableDiffs() { return this.mergeRequestDiffs.slice(1); }, @@ -104,6 +113,11 @@ export default { <gl-link :href="commit.commit_url" class="monospace">{{ commit.short_id }}</gl-link> </div> <div class="inline-parallel-buttons d-none d-md-flex ml-auto"> + <diff-stats + :diff-files-length="diffFilesLength" + :added-lines="addedLines" + :removed-lines="removedLines" + /> <gl-button v-if="commit || startVersion" :href="latestVersionPath" diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index b58f704bebb..60586d4a607 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -9,6 +9,7 @@ import { GlTooltipDirective } from '@gitlab/ui'; import { truncateSha } from '~/lib/utils/text_utility'; import { __, s__, sprintf } from '~/locale'; import EditButton from './edit_button.vue'; +import DiffStats from './diff_stats.vue'; export default { components: { @@ -16,6 +17,7 @@ export default { EditButton, Icon, FileIcon, + DiffStats, }, directives: { GlTooltip: GlTooltipDirective, @@ -202,6 +204,7 @@ export default { v-if="!diffFile.submodule && addMergeRequestButtons" class="file-actions d-none d-sm-block" > + <diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" /> <template v-if="diffFile.blob && diffFile.blob.readable_text"> <button :disabled="!diffHasDiscussions(diffFile)" diff --git a/app/assets/javascripts/diffs/components/diff_stats.vue b/app/assets/javascripts/diffs/components/diff_stats.vue new file mode 100644 index 00000000000..2e5855380af --- /dev/null +++ b/app/assets/javascripts/diffs/components/diff_stats.vue @@ -0,0 +1,52 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; +import { n__ } from '~/locale'; + +export default { + components: { Icon }, + props: { + addedLines: { + type: Number, + required: true, + }, + removedLines: { + type: Number, + required: true, + }, + diffFilesLength: { + type: Number, + required: false, + default: null, + }, + }, + computed: { + filesText() { + return n__('File', 'Files', this.diffFilesLength); + }, + isCompareVersionsHeader() { + return Boolean(this.diffFilesLength); + }, + }, +}; +</script> + +<template> + <div + class="diff-stats" + :class="{ + 'is-compare-versions-header d-none d-lg-inline-flex': isCompareVersionsHeader, + 'd-inline-flex': !isCompareVersionsHeader, + }" + > + <div v-if="diffFilesLength !== null" class="diff-stats-group"> + <icon name="doc-code" class="diff-stats-icon text-secondary" /> + <strong>{{ diffFilesLength }} {{ filesText }}</strong> + </div> + <div class="diff-stats-group cgreen"> + <icon name="file-addition" class="diff-stats-icon" /> <strong>{{ addedLines }}</strong> + </div> + <div class="diff-stats-group cred"> + <icon name="file-deletion" class="diff-stats-icon" /> <strong>{{ removedLines }}</strong> + </div> + </div> +</template> diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index a0f09932593..96ae197d8b8 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -14,8 +14,8 @@ export default { FileRow, }, computed: { - ...mapState('diffs', ['tree', 'addedLines', 'removedLines', 'renderTreeList']), - ...mapGetters('diffs', ['allBlobs', 'diffFilesLength']), + ...mapState('diffs', ['tree', 'renderTreeList']), + ...mapGetters('diffs', ['allBlobs']), filteredTreeList() { return this.renderTreeList ? this.tree : this.allBlobs; }, @@ -64,13 +64,6 @@ export default { {{ s__('MergeRequest|No files found') }} </p> </div> - <div v-once class="pt-3 pb-3 text-center"> - {{ n__('%d changed file', '%d changed files', diffFilesLength) }} - <div> - <span class="cgreen"> {{ n__('%d addition', '%d additions', addedLines) }} </span> - <span class="cred"> {{ n__('%d deleted', '%d deletions', removedLines) }} </span> - </div> - </div> </div> </template> diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js index 6ee33d9fc6d..47f78a5db54 100644 --- a/app/assets/javascripts/diffs/store/modules/diff_state.js +++ b/app/assets/javascripts/diffs/store/modules/diff_state.js @@ -11,6 +11,8 @@ const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY); export default () => ({ isLoading: true, + addedLines: null, + removedLines: null, endpoint: '', basePath: '', commit: null, diff --git a/app/assets/javascripts/lib/utils/file_upload.js b/app/assets/javascripts/lib/utils/file_upload.js index b41ffb44971..82ee83e4348 100644 --- a/app/assets/javascripts/lib/utils/file_upload.js +++ b/app/assets/javascripts/lib/utils/file_upload.js @@ -1,6 +1,9 @@ export default (buttonSelector, fileSelector) => { const btn = document.querySelector(buttonSelector); const fileInput = document.querySelector(fileSelector); + + if (!btn || !fileInput) return; + const form = btn.closest('form'); btn.addEventListener('click', () => { diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue index ec0e33a1927..14c02db7bcc 100644 --- a/app/assets/javascripts/monitoring/components/charts/area.vue +++ b/app/assets/javascripts/monitoring/components/charts/area.vue @@ -189,8 +189,8 @@ export default { <template> <div class="prometheus-graph col-12 col-lg-6"> <div class="prometheus-graph-header"> - <h5 class="prometheus-graph-title">{{ graphData.title }}</h5> - <div class="prometheus-graph-widgets"><slot></slot></div> + <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5> + <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div> </div> <gl-area-chart ref="areaChart" diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 9c5fd93f7d1..895a57785bc 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -160,7 +160,8 @@ export default { {{ s__('Metrics|Environment') }} <div class="dropdown prepend-left-10"> <button class="dropdown-menu-toggle" data-toggle="dropdown" type="button"> - <span> {{ currentEnvironmentName }} </span> <icon name="chevron-down" /> + <span>{{ currentEnvironmentName }}</span> + <icon name="chevron-down" /> </button> <div v-if="store.environmentsData.length > 0" @@ -172,9 +173,8 @@ export default { :href="environment.metrics_path" :class="{ 'is-active': environment.name == currentEnvironmentName }" class="dropdown-item" + >{{ environment.name }}</a > - {{ environment.name }} - </a> </li> </ul> </div> diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js index 8f98be79640..01001d4f3ff 100644 --- a/app/assets/javascripts/pages/dashboard/projects/index.js +++ b/app/assets/javascripts/pages/dashboard/projects/index.js @@ -1,7 +1,5 @@ import ProjectsList from '~/projects_list'; -import Star from '../../../star'; document.addEventListener('DOMContentLoaded', () => { new ProjectsList(); // eslint-disable-line no-new - new Star('.project-row'); // eslint-disable-line no-new }); diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js index 8f98be79640..01001d4f3ff 100644 --- a/app/assets/javascripts/pages/explore/projects/index.js +++ b/app/assets/javascripts/pages/explore/projects/index.js @@ -1,7 +1,5 @@ import ProjectsList from '~/projects_list'; -import Star from '../../../star'; document.addEventListener('DOMContentLoaded', () => { new ProjectsList(); // eslint-disable-line no-new - new Star('.project-row'); // eslint-disable-line no-new }); diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js index 0b644780ad4..0d69a689316 100644 --- a/app/assets/javascripts/pages/projects/environments/metrics/index.js +++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js @@ -1,3 +1,3 @@ -import monitoringBundle from '~/monitoring/monitoring_bundle'; +import monitoringBundle from 'ee_else_ce/monitoring/monitoring_bundle'; document.addEventListener('DOMContentLoaded', monitoringBundle); diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 037a5adfb7e..6108eaa1ad0 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -4,6 +4,7 @@ */ .file-holder { border: 1px solid $border-color; + border-top: 0; border-radius: $border-radius-default; &.file-holder-no-border { @@ -51,6 +52,7 @@ position: absolute; top: 5px; right: 15px; + margin-left: auto; .btn { padding: 0 10px; @@ -324,10 +326,12 @@ span.idiff { &, .file-holder & { display: flex; + flex-wrap: wrap; align-items: center; justify-content: space-between; background-color: $gray-light; border-bottom: 1px solid $border-color; + border-top: 1px solid $border-color; padding: 5px $gl-padding; margin: 0; border-radius: $border-radius-default $border-radius-default 0 0; @@ -365,16 +369,12 @@ span.idiff { margin: 0 10px 0 0; } - .file-actions { - white-space: nowrap; - - .btn { - padding: 0 10px; - font-size: 13px; - line-height: 28px; - display: inline-block; - float: none; - } + .file-actions .btn { + padding: 0 10px; + font-size: 13px; + line-height: 28px; + display: inline-block; + float: none; } @include media-breakpoint-down(xs) { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 9eae9a831fa..7088a6f59dc 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -490,6 +490,7 @@ $builds-trace-bg: #111; */ $commit-max-width-marker-color: rgba(0, 0, 0, 0); $commit-message-text-area-bg: rgba(0, 0, 0, 0); +$commit-stat-summary-height: 36px; /* * Common @@ -664,8 +665,14 @@ $priority-label-empty-state-width: 114px; Issues Analytics */ $issues-analytics-popover-boarder-color: rgba(0, 0, 0, 0.15); + /* Merge Requests */ $mr-tabs-height: 51px; $mr-version-controls-height: 56px; + +/* +Compare Branches +*/ +$compare-branches-sticky-header-height: 68px; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 02aac58a475..e3b98b26a11 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -7,22 +7,13 @@ cursor: pointer; @media (min-width: map-get($grid-breakpoints, md)) { + $mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height; + position: -webkit-sticky; position: sticky; - top: $mr-version-controls-height + $header-height + $mr-tabs-height; - margin-left: -1px; - border-left: 1px solid $border-color; + top: $mr-file-header-top; z-index: 102; - &.is-commit { - top: $header-height + 36px; - - .with-performance-bar & { - top: $header-height + 36px + $performance-bar-height; - - } - } - &::before { content: ''; position: absolute; @@ -35,7 +26,23 @@ } .with-performance-bar & { - top: $header-height + $performance-bar-height + $mr-version-controls-height + $mr-tabs-height; + top: $mr-file-header-top + $performance-bar-height; + } + + &.is-commit { + top: $header-height + $commit-stat-summary-height; + + .with-performance-bar & { + top: $header-height + $commit-stat-summary-height + $performance-bar-height; + } + } + + &.is-compare { + top: $header-height + $compare-branches-sticky-header-height; + + .with-performance-bar & { + top: $performance-bar-height + $header-height + $compare-branches-sticky-header-height; + } } } @@ -501,6 +508,25 @@ } } +.diff-stats { + align-items: center; + padding: 0 .25rem; + + .diff-stats-group { + padding: 0 .25rem; + } + + svg.diff-stats-icon { + vertical-align: text-bottom; + } + + &.is-compare-versions-header { + .diff-stats-group { + padding: 0 .5rem; + } + } +} + .file-content .diff-file { margin: 0; border: 0; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 2342c284a5e..3eb02cd4358 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -946,6 +946,11 @@ pre.light-well { .flex-wrapper { min-width: 0; margin-top: -$gl-padding-8; // negative margin required for flex-wrap + flex: 1 1 100%; + + .project-title { + line-height: 20px; + } } p, @@ -984,14 +989,16 @@ pre.light-well { } .controls { - margin-top: $gl-padding-8; + @include media-breakpoint-down(xs) { + margin-top: $gl-padding-8; + } - @include media-breakpoint-down(md) { + @include media-breakpoint-up(sm) { margin-top: 0; } - @include media-breakpoint-down(xs) { - margin-top: $gl-padding-8; + @include media-breakpoint-up(lg) { + flex: 1 1 40%; } .icon-wrapper { @@ -1041,7 +1048,7 @@ pre.light-well { min-height: 40px; min-width: 40px; - .identicon.s64 { + .identicon.s48 { font-size: 16px; } } diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index b9717b97640..3bd91b71d92 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -127,6 +127,7 @@ class Clusters::ClustersController < Clusters::BaseController params.require(:cluster).permit( :enabled, :environment_scope, + :base_domain, platform_kubernetes_attributes: [ :namespace ] @@ -136,6 +137,7 @@ class Clusters::ClustersController < Clusters::BaseController :enabled, :name, :environment_scope, + :base_domain, platform_kubernetes_attributes: [ :api_url, :token, diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 97120273d6b..cc2bb99f55b 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -116,8 +116,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController session[:service_tickets][provider] = ticket end + def build_auth_user(auth_user_class) + auth_user_class.new(oauth) + end + def sign_in_user_flow(auth_user_class) - auth_user = auth_user_class.new(oauth) + auth_user = build_auth_user(auth_user_class) user = auth_user.find_and_update! if auth_user.valid_sign_in? diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 79685e8b675..e9cd475a199 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -11,11 +11,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController before_action :verify_api_request!, only: :terminal_websocket_authorize before_action :expire_etag_cache, only: [:index] - before_action do - push_frontend_feature_flag(:area_chart, project) - end - - # Returns all environments or all folders based on the :nested param def index @environments = project.environments .with_state(params[:scope] || :available) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 3e08c0ccd8f..23af2e0521c 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -305,7 +305,7 @@ class IssuableFinder def use_subquery_for_search? strong_memoize(:use_subquery_for_search) do attempt_group_search_optimizations? && - Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: false) + Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: true) end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 67d16306525..e635f608237 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -232,7 +232,8 @@ module ApplicationSettingsHelper :web_ide_clientside_preview_enabled, :diff_max_patch_bytes, :commit_email_hostname, - :protected_ci_variables + :protected_ci_variables, + :local_markdown_version ] end diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb index 516c8a353ea..67e7e475920 100644 --- a/app/helpers/auto_devops_helper.rb +++ b/app/helpers/auto_devops_helper.rb @@ -9,41 +9,4 @@ module AutoDevopsHelper !project.repository.gitlab_ci_yml && !project.ci_service end - - def auto_devops_warning_message(project) - if missing_auto_devops_service?(project) - params = { - kubernetes: link_to('Kubernetes cluster', project_clusters_path(project)) - } - - if missing_auto_devops_domain?(project) - _('Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly.') % params - else - _('Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly.') % params - end - elsif missing_auto_devops_domain?(project) - _('Auto Review Apps and Auto Deploy need a domain name to work correctly.') - end - end - - # rubocop: disable CodeReuse/ActiveRecord - def cluster_ingress_ip(project) - project - .cluster_ingresses - .where("external_ip is not null") - .limit(1) - .pluck(:external_ip) - .first - end - # rubocop: enable CodeReuse/ActiveRecord - - private - - def missing_auto_devops_domain?(project) - !(project.auto_devops || project.build_auto_devops)&.has_domain? - end - - def missing_auto_devops_service?(project) - !project.deployment_platform&.active? - end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 6d7c51c2315..daadf9427ba 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -193,6 +193,10 @@ class ApplicationSetting < ActiveRecord::Base allow_nil: true, numericality: { only_integer: true, greater_than_or_equal_to: 1.day.seconds } + validates :local_markdown_version, + allow_nil: true, + numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 } + SUPPORTED_KEY_TYPES.each do |type| validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } end @@ -304,7 +308,8 @@ class ApplicationSetting < ActiveRecord::Base usage_stats_set_by_user_id: nil, diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES, commit_email_hostname: default_commit_email_hostname, - protected_ci_variables: false + protected_ci_variables: false, + local_markdown_version: 0 } end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index a2c48973fa5..f2f5b89e3bb 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -18,6 +18,7 @@ module Clusters Applications::Knative.application_name => Applications::Knative }.freeze DEFAULT_ENVIRONMENT = '*'.freeze + KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze belongs_to :user @@ -49,7 +50,7 @@ module Clusters validates :name, cluster_name: true validates :cluster_type, presence: true - validates :domain, allow_nil: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true } + validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true } validate :restrict_modification, on: :update validate :no_groups, unless: :group_type? @@ -65,6 +66,9 @@ module Clusters delegate :available?, to: :application_ingress, prefix: true, allow_nil: true delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true delegate :available?, to: :application_knative, prefix: true, allow_nil: true + delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true + + alias_attribute :base_domain, :domain enum cluster_type: { instance_type: 1, @@ -193,8 +197,41 @@ module Clusters project_type? end + def kube_ingress_domain + @kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain + end + + def predefined_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break variables unless kube_ingress_domain + + variables.append(key: KUBE_INGRESS_BASE_DOMAIN, value: kube_ingress_domain) + end + end + private + def instance_domain + @instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain + end + + # To keep backward compatibility with AUTO_DEVOPS_DOMAIN + # environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN + # is set if AUTO_DEVOPS_DOMAIN is set on any of the following options: + # ProjectAutoDevops#Domain, project variables or group variables, + # as the AUTO_DEVOPS_DOMAIN is needed for CI_ENVIRONMENT_URL + # + # This method should be removed on 12.0 + def legacy_auto_devops_domain + if project_type? + project&.auto_devops&.domain.presence || + project.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence || + project.group&.variables&.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence + elsif group_type? + group.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence + end + end + def restrict_modification if provider&.on_creation? errors.add(:base, "cannot modify during creation") diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 8f3424db295..c8969351ed9 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -98,6 +98,8 @@ module Clusters .append(key: 'KUBE_NAMESPACE', value: actual_namespace) .append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) end + + variables.concat(cluster.predefined_variables) end end diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb index 5fa6f79bdaa..1a8570b80c3 100644 --- a/app/models/concerns/cache_markdown_field.rb +++ b/app/models/concerns/cache_markdown_field.rb @@ -115,7 +115,28 @@ module CacheMarkdownField end def latest_cached_markdown_version - CacheMarkdownField::CACHE_COMMONMARK_VERSION + @latest_cached_markdown_version ||= (CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) | local_version + end + + def local_version + # because local_markdown_version is stored in application_settings which + # uses cached_markdown_version too, we check explicitly to avoid + # endless loop + return local_markdown_version if has_attribute?(:local_markdown_version) + + settings = Gitlab::CurrentSettings.current_application_settings + + # Following migrations are not properly isolated and + # use real models (by calling .ghost method), in these migrations + # local_markdown_version attribute doesn't exist yet, so we + # use a default value: + # db/migrate/20170825104051_migrate_issues_to_ghost_user.rb + # db/migrate/20171114150259_merge_requests_author_id_foreign_key.rb + if settings.respond_to?(:local_markdown_version) + settings.local_markdown_version + else + 0 + end end included do diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb index 34220c1b450..4635fc72dc7 100644 --- a/app/models/pool_repository.rb +++ b/app/models/pool_repository.rb @@ -96,7 +96,9 @@ class PoolRepository < ActiveRecord::Base @object_pool ||= Gitlab::Git::ObjectPool.new( shard.name, disk_path + '.git', - source_project.repository.raw) + source_project.repository.raw, + source_project.full_path + ) end def inspect diff --git a/app/models/project.rb b/app/models/project.rb index d4e2ed883bc..8f746f6e094 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1288,7 +1288,7 @@ class Project < ActiveRecord::Base # Forked import is handled asynchronously return if forked? && !force - if gitlab_shell.create_repository(repository_storage, disk_path) + if gitlab_shell.create_project_repository(self) repository.after_create true else diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index 2253ad7b543..b6c5c7c4c87 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -24,6 +24,11 @@ class ProjectAutoDevops < ActiveRecord::Base domain.present? || instance_domain.present? end + # From 11.8, AUTO_DEVOPS_DOMAIN has been replaced by KUBE_INGRESS_BASE_DOMAIN. + # See Clusters::Cluster#predefined_variables and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580 + # for more info. + # Support for AUTO_DEVOPS_DOMAIN support will be dropped on 12.0 on + # https://gitlab.com/gitlab-org/gitlab-ce/issues/52363 def predefined_variables Gitlab::Ci::Variables::Collection.new.tap do |variables| if has_domain? diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 559e4f99294..c43bd45a62f 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -60,7 +60,7 @@ class ProjectWiki def wiki @wiki ||= begin gl_repository = Gitlab::GlRepository.gl_repository(project, true) - raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository) + raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository, full_path) create_repo!(raw_repository) unless raw_repository.exists? @@ -175,7 +175,7 @@ class ProjectWiki private def create_repo!(raw_repository) - gitlab_shell.create_repository(project.repository_storage, disk_path) + gitlab_shell.create_wiki_repository(project) raise CouldNotCreateWikiError unless raw_repository.exists? diff --git a/app/models/repository.rb b/app/models/repository.rb index bfd2608bed4..7c50b4488e5 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1104,6 +1104,9 @@ class Repository end def initialize_raw_repository - Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki)) + Gitlab::Git::Repository.new(project.repository_storage, + disk_path + '.git', + Gitlab::GlRepository.gl_repository(project, is_wiki), + project.full_path) end end diff --git a/app/services/labels/update_service.rb b/app/services/labels/update_service.rb index e563447c64c..be33947d0eb 100644 --- a/app/services/labels/update_service.rb +++ b/app/services/labels/update_service.rb @@ -8,6 +8,7 @@ module Labels # returns the updated label def execute(label) + params[:name] = params.delete(:new_name) if params.key?(:new_name) params[:color] = convert_color_name_to_hex if params[:color].present? label.update(params) diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 5861b803996..7214e9efaf6 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -73,7 +73,7 @@ module Projects project.ensure_repository project.repository.fetch_as_mirror(project.import_url, refmap: refmap) else - gitlab_shell.import_repository(project.repository_storage, project.disk_path, project.import_url) + gitlab_shell.import_project_repository(project) end rescue Gitlab::Shell::Error => e # Expire cache to prevent scenarios such as: diff --git a/app/views/clusters/clusters/_form.html.haml b/app/views/clusters/clusters/_form.html.haml index 4c47e11927e..7acd9ce0562 100644 --- a/app/views/clusters/clusters/_form.html.haml +++ b/app/views/clusters/clusters/_form.html.haml @@ -20,12 +20,27 @@ .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.") - else = text_field_tag :environment_scope, '*', class: 'col-md-6 form-control disabled', placeholder: s_('ClusterIntegration|Environment scope'), disabled: true - - environment_scope_url = 'https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope-premium' + - environment_scope_url = help_page_path('user/project/clusters/index', anchor: 'base-domain') - environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url } .form-text.text-muted %code * = s_("ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe } + .form-group + %h5= s_('ClusterIntegration|Base domain') + = field.text_field :base_domain, class: 'col-md-6 form-control js-select-on-focus' + .form-text.text-muted + - auto_devops_url = help_page_path('topics/autodevops/index') + - auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url } + = s_('ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe } + - if @cluster.application_ingress_external_ip.present? + = s_('ClusterIntegration|Alternatively') + %code #{@cluster.application_ingress_external_ip}.nip.io + = s_('ClusterIntegration| can be used instead of a custom domain.') + - custom_domain_url = help_page_path('user/project/clusters/index', anchor: 'pointing-your-dns-at-the-cluster-ip') + - custom_domain_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: custom_domain_url } + = s_('ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}.').html_safe % { custom_domain_start: custom_domain_start, custom_domain_end: '</a>'.html_safe } + - if can?(current_user, :update_cluster, @cluster) .form-group = field.submit _('Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 06f0cd9675e..fe9a8ac4182 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -10,7 +10,7 @@ .container-fluid{ class: [limited_container_width, container_class] } = render "commit_box" = render "ci_menu" - = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, is_commit: true + = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-commit" .limited-width-notes = render "shared/notes/notes_with_form", :autocomplete => true diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index b6bebbabed0..5774b48a054 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -8,7 +8,7 @@ - if @commits.present? = render "projects/commits/commit_list" - = render "projects/diffs/diffs", diffs: @diffs, environment: @environment + = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare" - else .card.bg-light .center diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index cc2d0d3b2d8..2dba3fcd664 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -2,7 +2,7 @@ - show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true) - can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project) - diff_files = diffs.diff_files -- is_commit = local_assigns.fetch(:is_commit, false) +- diff_page_context = local_assigns.fetch(:diff_page_context, nil) .content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed .files-changed-inner @@ -25,4 +25,4 @@ = render 'projects/diffs/warning', diff_files: diffs .files{ data: { can_create_note: can_create_note } } - = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, is_commit: is_commit } + = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, diff_page_context: diff_page_context } diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 5565ae1d98b..855b719dc45 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -1,11 +1,11 @@ - environment = local_assigns.fetch(:environment, nil) -- is_commit = local_assigns.fetch(:is_commit, false) +- diff_page_context = local_assigns.fetch(:diff_page_context, nil) - file_hash = hexdigest(diff_file.file_path) - image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image' - image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha .diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) } - .js-file-title.file-title-flex-parent{ class: is_commit ? "is-commit" : "" } + .js-file-title.file-title-flex-parent{ class: diff_page_context } .file-header-content = render "projects/diffs/file_header", diff_file: diff_file, url: "##{file_hash}" diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index 5ec5a06396e..8c4d1c32ebe 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -4,10 +4,6 @@ = form_errors(@project) %fieldset.builds-feature.js-auto-devops-settings .form-group - - message = auto_devops_warning_message(@project) - - if message - %p.auto-devops-warning-message.settings-message.text-center - = message.html_safe = f.fields_for :auto_devops_attributes, @auto_devops do |form| .card.auto-devops-card .card-body @@ -21,19 +17,12 @@ = s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.') = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank' .card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' } - = form.label :domain do - %strong= _('Domain') - = form.text_field :domain, class: 'form-control', placeholder: 'domain.com' - .form-text.text-muted - = s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.') - - if cluster_ingress_ip = cluster_ingress_ip(@project) - = s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe } - = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank' - + %p.settings-message.text-center + - kubernetes_cluster_link = help_page_path('user/project/clusters/index') + - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link } + = s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe } %label.prepend-top-10 %strong= s_('CICD|Deployment strategy') - %p.settings-message.text-center - = s_('CICD|Deployment strategy needs a domain name to work correctly.') .form-check = form.radio_button :deploy_strategy, 'continuous', class: 'form-check-input' = form.label :deploy_strategy_continuous, class: 'form-check-label' do diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index e1564d57426..df17ae95e2a 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -12,21 +12,20 @@ - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - cache_key = project_list_cache_key(project) - updated_tooltip = time_ago_with_tooltip(project.last_activity_date) -- css_details_class = compact_mode ? "d-flex flex-column flex-sm-row flex-md-row align-items-sm-center" : "align-items-center flex-md-fill flex-lg-column d-sm-flex d-lg-block" -- css_controls_class = compact_mode ? "" : "align-items-md-end align-items-lg-center flex-lg-row" +- css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between" %li.project-row.d-flex{ class: css_class } = cache(cache_key) do - if avatar - .avatar-container.s64.flex-grow-0.flex-shrink-0 + .avatar-container.s48.flex-grow-0.flex-shrink-0 = link_to project_path(project), class: dom_class(project) do - if project.creator && use_creator_avatar - = image_tag avatar_icon_for_user(project.creator, 64), class: "avatar s65", alt:'' + = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s65", alt:'' - else - = project_icon(project, alt: '', class: 'avatar project-avatar s64', width: 64, height: 64) - .project-details.flex-sm-fill{ class: css_details_class } - .flex-wrapper.flex-fill - .d-flex.align-items-center.flex-wrap + = project_icon(project, alt: '', class: 'avatar project-avatar s48', width: 48, height: 48) + .project-details.d-sm-flex.flex-sm-fill.align-items-center + .flex-wrapper + .d-flex.align-items-center.flex-wrap.project-title %h2.d-flex.prepend-top-8 = link_to project_path(project), class: 'text-plain' do %span.project-full-name.append-right-8>< @@ -52,13 +51,13 @@ %span.user-access-role.d-block= Gitlab::Access.human_access(access) - if show_last_commit_as_description - .description.d-none.d-sm-block.prepend-top-8.append-right-default + .description.d-none.d-sm-block.append-right-default = link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message") - elsif project.description.present? - .description.d-none.d-sm-block.prepend-top-8.append-right-default + .description.d-none.d-sm-block.append-right-default = markdown_field(project, :description) - .controls.d-flex.flex-row.flex-sm-column.flex-md-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class } + .controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class } .icon-container.d-flex.align-items-center - if project.archived %span.d-flex.icon-wrapper.badge.badge-warning archived @@ -74,13 +73,13 @@ = number_with_delimiter(project.forks_count) - if show_merge_request_count?(disabled: !merge_requests, compact_mode: compact_mode) = link_to project_merge_requests_path(project), - class: "d-none d-lg-flex align-items-center icon-wrapper merge-requests has-tooltip", + class: "d-none d-xl-flex align-items-center icon-wrapper merge-requests has-tooltip", title: _('Merge Requests'), data: { container: 'body', placement: 'top' } do = sprite_icon('git-merge', size: 14, css_class: 'append-right-4') = number_with_delimiter(project.open_merge_requests_count) - if show_issue_count?(disabled: !issues, compact_mode: compact_mode) = link_to project_issues_path(project), - class: "d-none d-lg-flex align-items-center icon-wrapper issues has-tooltip", + class: "d-none d-xl-flex align-items-center icon-wrapper issues has-tooltip", title: _('Issues'), data: { container: 'body', placement: 'top' } do = sprite_icon('issues', size: 14, css_class: 'append-right-4') = number_with_delimiter(project.open_issues_count) @@ -89,19 +88,3 @@ = render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top') .updated-note %span Updated #{updated_tooltip} - - .d-none.d-lg-flex.align-item-stretch - - unless compact_mode - - if current_user - %button.star-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(project, :json) } } - - if current_user.starred?(project) - = sprite_icon('star', { css_class: 'icon' }) - %span.starred= s_('ProjectOverview|Unstar') - - else - = sprite_icon('star-o', { css_class: 'icon' }) - %span= s_('ProjectOverview|Star') - - - else - = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do - = sprite_icon('star-o', { css_class: 'icon' }) - %span= s_('ProjectOverview|Star') diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index 7eae07d3f6b..a9b88a133be 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -15,19 +15,19 @@ class RepositoryForkWorker return target_project.import_state.mark_as_failed(_('Source project cannot be found.')) end - fork_repository(target_project, source_project.repository_storage, source_project.disk_path) + fork_repository(target_project, source_project) end private - def fork_repository(target_project, source_repository_storage_name, source_disk_path) + def fork_repository(target_project, source_project) return unless start_fork(target_project) Gitlab::Metrics.add_event(:fork_repository) - result = gitlab_shell.fork_repository(source_repository_storage_name, source_disk_path, - target_project.repository_storage, target_project.disk_path) - raise "Unable to fork project #{target_project.id} for repository #{source_disk_path} -> #{target_project.disk_path}" unless result + result = gitlab_shell.fork_repository(source_project, target_project) + + raise "Unable to fork project #{target_project.id} for repository #{source_project.disk_path} -> #{target_project.disk_path}" unless result target_project.after_import end diff --git a/changelogs/unreleased/44332-add-openid-profile-scopes.yml b/changelogs/unreleased/44332-add-openid-profile-scopes.yml new file mode 100644 index 00000000000..b554fab5139 --- /dev/null +++ b/changelogs/unreleased/44332-add-openid-profile-scopes.yml @@ -0,0 +1,5 @@ +--- +title: GitLab now supports the profile and email scopes from OpenID Connect +merge_request: 24335 +author: Goten Xiao +type: added diff --git a/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml b/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml new file mode 100644 index 00000000000..cf1c4378f18 --- /dev/null +++ b/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml @@ -0,0 +1,5 @@ +--- +title: Show MR statistics in diff comparisons +merge_request: !24569 +author: +type: changed diff --git a/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml b/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml new file mode 100644 index 00000000000..eb4851971fb --- /dev/null +++ b/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml @@ -0,0 +1,5 @@ +--- +title: Moves domain setting from Auto DevOps to Cluster's page +merge_request: 24580 +author: +type: added diff --git a/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml b/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml new file mode 100644 index 00000000000..e324baa94a3 --- /dev/null +++ b/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml @@ -0,0 +1,5 @@ +--- +title: API allows setting the squash commit message when squashing a merge request +merge_request: 24784 +author: +type: added diff --git a/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml b/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml new file mode 100644 index 00000000000..388ff1d062a --- /dev/null +++ b/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml @@ -0,0 +1,5 @@ +--- +title: Project list UI improvements +merge_request: 24855 +author: +type: other diff --git a/changelogs/unreleased/56788-unicorn-metric-labels.yml b/changelogs/unreleased/56788-unicorn-metric-labels.yml new file mode 100644 index 00000000000..824c981780c --- /dev/null +++ b/changelogs/unreleased/56788-unicorn-metric-labels.yml @@ -0,0 +1,5 @@ +--- +title: Clean up unicorn sampler metric labels +merge_request: 24626 +author: bjk-gitlab +type: fixed diff --git a/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml b/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml new file mode 100644 index 00000000000..f619a009a63 --- /dev/null +++ b/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml @@ -0,0 +1,5 @@ +--- +title: Correct spacing for comparison page +merge_request: !24783 +author: +type: fixed diff --git a/changelogs/unreleased/adriel-remove-feature-flag.yml b/changelogs/unreleased/adriel-remove-feature-flag.yml new file mode 100644 index 00000000000..d442e120d60 --- /dev/null +++ b/changelogs/unreleased/adriel-remove-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Update metrics dashboard graph design +merge_request: 24653 +author: +type: changed diff --git a/changelogs/unreleased/api-group-labels.yml b/changelogs/unreleased/api-group-labels.yml new file mode 100644 index 00000000000..0df6f15a9b6 --- /dev/null +++ b/changelogs/unreleased/api-group-labels.yml @@ -0,0 +1,5 @@ +--- +title: 'API: Add support for group labels' +merge_request: 21368 +author: Robert Schilling +type: added diff --git a/changelogs/unreleased/fix-repo-settings-file-upload-error.yml b/changelogs/unreleased/fix-repo-settings-file-upload-error.yml new file mode 100644 index 00000000000..b219fdfaa1e --- /dev/null +++ b/changelogs/unreleased/fix-repo-settings-file-upload-error.yml @@ -0,0 +1,5 @@ +--- +title: Fix bug causing repository mirror settings UI to break +merge_request: 23712 +author: +type: fixed diff --git a/changelogs/unreleased/gitaly-update-1.18.0.yml b/changelogs/unreleased/gitaly-update-1.18.0.yml new file mode 100644 index 00000000000..392527f5e5d --- /dev/null +++ b/changelogs/unreleased/gitaly-update-1.18.0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade gitaly to 1.18.0 +merge_request: 24981 +author: +type: other diff --git a/changelogs/unreleased/jlenny-NewAndroidTemplate.yml b/changelogs/unreleased/jlenny-NewAndroidTemplate.yml new file mode 100644 index 00000000000..ae8c58da859 --- /dev/null +++ b/changelogs/unreleased/jlenny-NewAndroidTemplate.yml @@ -0,0 +1,5 @@ +--- +title: Add template for Android with Fastlane +merge_request: 24722 +author: +type: changed diff --git a/changelogs/unreleased/local-markdown-version-bkp3.yml b/changelogs/unreleased/local-markdown-version-bkp3.yml new file mode 100644 index 00000000000..ce5bff6ae6b --- /dev/null +++ b/changelogs/unreleased/local-markdown-version-bkp3.yml @@ -0,0 +1,5 @@ +--- +title: Allow admins to invalidate markdown texts by setting local markdown version. +merge_request: +author: +type: added diff --git a/changelogs/unreleased/support-chunking-in-client.yml b/changelogs/unreleased/support-chunking-in-client.yml new file mode 100644 index 00000000000..e50648ea4b2 --- /dev/null +++ b/changelogs/unreleased/support-chunking-in-client.yml @@ -0,0 +1,5 @@ +--- +title: Fix code search when text is larger than max gRPC message size +merge_request: 24111 +author: +type: changed diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb index e97c0fcbd6b..fd5a62c39c6 100644 --- a/config/initializers/doorkeeper_openid_connect.rb +++ b/config/initializers/doorkeeper_openid_connect.rb @@ -31,8 +31,27 @@ Doorkeeper::OpenidConnect.configure do o.claim(:name) { |user| user.name } o.claim(:nickname) { |user| user.username } - o.claim(:email) { |user| user.public_email } - o.claim(:email_verified) { |user| true if user.public_email? } + + # Check whether the application has access to the email scope, and grant + # access to the user's primary email address if so, otherwise their + # public email address (if present) + # This allows existing solutions built for GitLab's old behavior to keep + # working without modification. + o.claim(:email) do |user, scopes| + scopes.exists?(:email) ? user.email : user.public_email + end + o.claim(:email_verified) do |user, scopes| + if scopes.exists?(:email) + user.primary_email_verified? + elsif user.public_email? + user.verified_email?(user.public_email) + else + # If there is no public email set, tell doorkicker-openid-connect to + # exclude the email_verified claim by returning nil. + nil + end + end + o.claim(:website) { |user| user.full_website_url if user.website_url? } o.claim(:profile) { |user| Gitlab::Routing.url_helpers.user_url user } o.claim(:picture) { |user| user.avatar_url(only_path: false) } diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index 9f451046462..a2dff92908e 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -64,6 +64,8 @@ en: read_registry: Grants permission to read container registry images openid: Authenticate using OpenID Connect sudo: Perform API actions as any user in the system + profile: Allows read-only access to the user's personal information using OpenID Connect + email: Allows read-only access to the user's primary email address using OpenID Connect scope_desc: api: Grants complete read/write access to the API, including all groups and projects. @@ -77,6 +79,10 @@ en: Grants permission to authenticate with GitLab using OpenID Connect. Also gives read-only access to the user's profile and group memberships. sudo: Grants permission to perform API actions as any user in the system, when authenticated as an admin user. + profile: + Grants read-only access to the user's profile data using OpenID Connect. + email: + Grants read-only access to the user's primary email address using OpenID Connect. flash: applications: create: diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb index a69b02cddc4..bff1f01c654 100644 --- a/db/migrate/20140502125220_migrate_repo_size.rb +++ b/db/migrate/20140502125220_migrate_repo_size.rb @@ -11,7 +11,7 @@ class MigrateRepoSize < ActiveRecord::Migration[4.2] path = File.join(namespace_path, project['project_path'] + '.git') begin - repo = Gitlab::Git::Repository.new('default', path, '') + repo = Gitlab::Git::Repository.new('default', path, '', '') if repo.empty? print '-' else diff --git a/db/migrate/20190130091630_add_local_cached_markdown_version.rb b/db/migrate/20190130091630_add_local_cached_markdown_version.rb new file mode 100644 index 00000000000..00570e6458c --- /dev/null +++ b/db/migrate/20190130091630_add_local_cached_markdown_version.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddLocalCachedMarkdownVersion < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :application_settings, :local_markdown_version, :integer, default: 0, null: false + end +end diff --git a/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb b/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb new file mode 100644 index 00000000000..392e64eeade --- /dev/null +++ b/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class MigrateAutoDevOpsDomainToClusterDomain < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + execute(update_clusters_domain_query) + end + + def down + # no-op + end + + private + + def update_clusters_domain_query + if Gitlab::Database.mysql? + mysql_query + else + postgresql_query + end + end + + def mysql_query + <<~HEREDOC + UPDATE clusters, project_auto_devops, cluster_projects + SET + clusters.domain = project_auto_devops.domain + WHERE + cluster_projects.cluster_id = clusters.id + AND project_auto_devops.project_id = cluster_projects.project_id + AND project_auto_devops.domain != '' + HEREDOC + end + + def postgresql_query + <<~HEREDOC + UPDATE clusters + SET domain = project_auto_devops.domain + FROM cluster_projects, project_auto_devops + WHERE + cluster_projects.cluster_id = clusters.id + AND project_auto_devops.project_id = cluster_projects.project_id + AND project_auto_devops.domain != '' + HEREDOC + end +end diff --git a/db/schema.rb b/db/schema.rb index ffdc937ab52..023eee5f33e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190131122559) do +ActiveRecord::Schema.define(version: 20190204115450) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -168,6 +168,7 @@ ActiveRecord::Schema.define(version: 20190131122559) do t.string "commit_email_hostname" t.boolean "protected_ci_variables", default: false, null: false t.string "runners_registration_token_encrypted" + t.integer "local_markdown_version", default: 0, null: false t.integer "first_day_of_week", default: 0, null: false t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree end diff --git a/doc/administration/index.md b/doc/administration/index.md index 184754cd467..12fec2753bf 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -65,6 +65,7 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Backup and restore](../raketasks/backup_restore.md): Backup and restore your GitLab instance. - [Operations](operations/index.md): Keeping GitLab up and running (clean up Redis sessions, moving repositories, Sidekiq MemoryKiller, Unicorn). - [Restart GitLab](restart_gitlab.md): Learn how to restart GitLab and its components. +- [Invalidate markdown cache](invalidate_markdown_cache.md): Invalidate any cached markdown. #### Updating GitLab diff --git a/doc/administration/invalidate_markdown_cache.md b/doc/administration/invalidate_markdown_cache.md new file mode 100644 index 00000000000..ad64cb077c1 --- /dev/null +++ b/doc/administration/invalidate_markdown_cache.md @@ -0,0 +1,16 @@ +# Invalidate Markdown Cache + +For performance reasons, GitLab caches the HTML version of markdown text +(e.g. issue and merge request descriptions, comments). It's possible +that these cached versions become outdated, for example +when the `external_url` configuration option is changed - causing links +in the cached text to refer to the old URL. + +To avoid this problem, the administrator can invalidate the existing cache by +increasing the `local_markdown_version` setting in application settings. This can +be done by [changing the application settings through +the API](../api/settings.md#change-application-settings): + +```bash +curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/application/settings?local_markdown_version=<increased_number> +``` diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index c9a2778b3a4..6ea0ac0d495 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -48,6 +48,8 @@ The following metrics are available: | upload_file_does_not_exist | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file | | failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login | | successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login | +| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) | +| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections | ### Ruby metrics diff --git a/doc/api/README.md b/doc/api/README.md index 692f63a400c..a060e0481bf 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -29,6 +29,7 @@ The following API resources are available: - [Group access requests](access_requests.md) - [Group badges](group_badges.md) - [Group issue boards](group_boards.md) + - [Group labels](group_labels.md) - [Group-level variables](group_level_variables.md) - [Group members](members.md) - [Group milestones](group_milestones.md) diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md new file mode 100644 index 00000000000..c36d34b4af1 --- /dev/null +++ b/doc/api/group_labels.md @@ -0,0 +1,201 @@ +# Group Label API + +>**Note:** This feature was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21368) in GitLab 11.8. + +This API supports managing of [group labels](../user/project/labels.md#project-labels-and-group-labels). It allows to list, create, update, and delete group labels. Furthermore, users can subscribe and unsubscribe to and from group labels. + +## List group labels + +Get all labels for a given group. + +``` +GET /groups/:id/labels +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | + +```bash +curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels +``` + +Example response: + +```json +[ + { + "id": 7, + "name": "bug", + "color": "#FF0000", + "description": null, + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false + }, + { + "id": 4, + "name": "feature", + "color": "#228B22", + "description": null, + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false + } +] +``` + +## Create a new group label + +Create a new group label for a given group. + +``` +POST /groups/:id/labels +``` + +| Attribute | Type | Required | Description | +| ------------- | ------- | -------- | ---------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `name` | string | yes | The name of the label | +| `color` | string | yes | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) | +| `description` | string | no | The description of the label | + +```bash +curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "Feature Proposal", "color": "#FFA500", "description": "Describes new ideas" }' https://gitlab.example.com/api/v4/groups/5/labels +``` + +Example response: + +```json +{ + "id": 9, + "name": "Feature Proposal", + "color": "#FFA500", + "description": "Describes new ideas", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false +} +``` + +## Update a group label + +Updates an existing group label. At least one parameter is required, to update the group label. + +``` +PUT /groups/:id/labels +``` + +| Attribute | Type | Required | Description | +| ------------- | ------- | -------- | ---------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `name` | string | yes | The name of the label | +| `new_name` | string | no | The new name of the label | +| `color` | string | no | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) | +| `description` | string | no | The description of the label | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "Feature Proposal", "new_name": "Feature Idea" }' https://gitlab.example.com/api/v4/groups/5/labels +``` + +Example response: + +```json +{ + "id": 9, + "name": "Feature Idea", + "color": "#FFA500", + "description": "Describes new ideas", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false +} +``` + +## Delete a group label + +Deletes a group label with a given name. + +``` +DELETE /groups/:id/labels +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `name` | string | yes | The name of the label | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?name=bug +``` + +## Subscribe to a group label + +Subscribes the authenticated user to a group label to receive notifications. If +the user is already subscribed to the label, the status code `304` is returned. + +``` +POST /groups/:id/labels/:label_id/subscribe +``` + +| Attribute | Type | Required | Description | +| ---------- | ----------------- | -------- | ------------------------------------ | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `label_id` | integer or string | yes | The ID or title of a group's label | + +```bash +curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels/9/subscribe +``` + +Example response: + +```json +{ + "id": 9, + "name": "Feature Idea", + "color": "#FFA500", + "description": "Describes new ideas", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": true +} +``` + +## Unsubscribe from a group label + +Unsubscribes the authenticated user from a group label to not receive +notifications from it. If the user is not subscribed to the label, the status +code `304` is returned. + +``` +POST /groups/:id/labels/:label_id/unsubscribe +``` + +| Attribute | Type | Required | Description | +| ---------- | ----------------- | -------- | ------------------------------------ | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `label_id` | integer or string | yes | The ID or title of a group's label | + +```bash +curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels/9/unsubscribe +``` + +Example response: + +```json +{ + "id": 9, + "name": "Feature Idea", + "color": "#FFA500", + "description": "Describes new ideas", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false +} +``` diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 802ff1d1df9..d58cd45538d 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -994,6 +994,8 @@ Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user - `merge_request_iid` (required) - Internal ID of MR - `merge_commit_message` (optional) - Custom merge commit message +- `squash_commit_message` (optional) - Custom squash commit message +- `squash` (optional) - if `true` the commits will be squashed into a single commit on merge - `should_remove_source_branch` (optional) - if `true` removes the source branch - `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds - `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail diff --git a/doc/api/settings.md b/doc/api/settings.md index 2ad6a46fa4b..2e0a2a09133 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -62,7 +62,8 @@ Example response: "terms": "Hello world!", "performance_bar_allowed_group_id": 42, "instance_statistics_visibility_private": false, - "user_show_add_ssh_key_message": true + "user_show_add_ssh_key_message": true, + "local_markdown_version": 0 } ``` @@ -119,7 +120,8 @@ Example response: "terms": "Hello world!", "performance_bar_allowed_group_id": 42, "instance_statistics_visibility_private": false, - "user_show_add_ssh_key_message": true + "user_show_add_ssh_key_message": true, + "local_markdown_version": 0 } ``` @@ -238,3 +240,4 @@ are listed in the descriptions of the relevant settings. | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider. | | `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. | | `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. | +| `local_markdown_version` | integer | no | Increase this value when any cached markdown should be invalidated. | diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 97e133a2e2f..32c73c4f398 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -98,7 +98,7 @@ future GitLab releases.** | **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` | | **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] | | **CI_PIPELINE_URL** | 11.1 | 0.5 | Pipeline details URL | -| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run | +| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. | | **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally | | **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built (actually it is project folder name) | | **CI_PROJECT_NAMESPACE** | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built | diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md index 9bef0635e3f..19b6181c9a2 100644 --- a/doc/development/contributing/merge_request_workflow.md +++ b/doc/development/contributing/merge_request_workflow.md @@ -86,6 +86,9 @@ request is as follows: guidelines](../merge_request_performance_guidelines.md). 1. For tests that use Capybara or PhantomJS, see this [article on how to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara). +1. If your merge request introduces changes that require additional steps when + installing GitLab from source, add them to `doc/install/installation.md` in + the same merge request. Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. diff --git a/doc/install/installation.md b/doc/install/installation.md index 45a76bd1d01..a8064ae046e 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -345,11 +345,15 @@ cd /home/git ```sh # Clone GitLab repository -sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-7-stable gitlab +sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b X-Y-stable gitlab ``` +Make sure to replace `X-Y-stable` with the stable branch that matches the +version you want to install. For example, if you want to install 11.8 you would +use the branch name `11-8-stable`. + CAUTION: **Caution:** -You can change `11-7-stable` to `master` if you want the *bleeding edge* version, but never install `master` on a production server! +You can change `X-Y-stable` to `master` if you want the *bleeding edge* version, but never install `master` on a production server! ### Configure It @@ -691,6 +695,11 @@ sudo nginx -t You should receive `syntax is okay` and `test is successful` messages. If you receive errors check your `gitlab` or `gitlab-ssl` Nginx config file for typos, etc. as indicated in the error message given. +NOTE: **Note:** +Verify that the installed version is greater than 1.12.1 by running `nginx -v`. If it's lower, you may receive the error below: +`nginx: [emerg] unknown "start$temp=[filtered]$rest" variable +nginx: configuration file /etc/nginx/nginx.conf test failed` + ### Restart ```sh diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 325de50cab0..5b6b857dd74 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -126,14 +126,22 @@ Auto Deploy, and Auto Monitoring will be silently skipped. ## Auto DevOps base domain +NOTE: **Note** +`AUTO_DEVOPS_DOMAIN` environment variable is deprecated and +[is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959) in GitLab 12.0. + The Auto DevOps base domain is required if you want to make use of [Auto Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It can be defined -in three places: +in any of the following places: -- either under the project's CI/CD settings while [enabling Auto DevOps](#enabling-auto-devops) +- either under the cluster's settings, whether for [projects](../../user/project/clusters/index.md#base-domain) or [groups](../../user/group/clusters/index.md#base-domain) - or in instance-wide settings in the **admin area > Settings** under the "Continuous Integration and Delivery" section -- or at the project as a variable: `AUTO_DEVOPS_DOMAIN` (required if you want to use [multiple clusters](#using-multiple-kubernetes-clusters)) -- or at the group level as a variable: `AUTO_DEVOPS_DOMAIN` +- or at the project level as a variable: `KUBE_INGRESS_BASE_DOMAIN` +- or at the group level as a variable: `KUBE_INGRESS_BASE_DOMAIN`. + +NOTE: **Note** +Auto DevOps base domain variable (`KUBE_INGRESS_BASE_DOMAIN`) follows the same order of precedence +as other environment [varibles](../../ci/variables/README.md#priority-of-variables). A wildcard DNS A record matching the base domain(s) is required, for example, given a base domain of `example.com`, you'd need a DNS entry like: @@ -170,13 +178,13 @@ In the [Auto DevOps template](https://gitlab.com/gitlab-org/gitlab-ce/blob/maste Those environments are tied to jobs that use [Auto Deploy](#auto-deploy), so except for the environment scope, they would also need to have a different domain they would be deployed to. This is why you need to define a separate -`AUTO_DEVOPS_DOMAIN` variable for all the above +`KUBE_INGRESS_BASE_DOMAIN` variable for all the above [based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-variables). The following table is an example of how the three different clusters would be configured. -| Cluster name | Cluster environment scope | `AUTO_DEVOPS_DOMAIN` variable value | Variable environment scope | Notes | +| Cluster name | Cluster environment scope | `KUBE_INGRESS_BASE_DOMAIN` variable value | Variable environment scope | Notes | | ------------ | -------------- | ----------------------------- | ------------- | ------ | | review | `review/*` | `review.example.com` | `review/*` | The review cluster which will run all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, which means it will be used by every environment name starting with `review/`. | | staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which will run the deployments of the staging environments. You need to [enable it first](#deploy-policy-for-staging-and-production-environments). | @@ -190,14 +198,11 @@ To add a different cluster for each environment: ![Auto DevOps multiple clusters](img/autodevops_multiple_clusters.png) 1. After the clusters are created, navigate to each one and install Helm Tiller - and Ingress. + and Ingress. Wait for the Ingress IP address to be assigned. 1. Make sure you have [configured your DNS](#auto-devops-base-domain) with the specified Auto DevOps domains. -1. Navigate to your project's **Settings > CI/CD > Environment variables** and add - the `AUTO_DEVOPS_DOMAIN` variables with their respective environment - scope. - - ![Auto DevOps domain variables](img/autodevops_domain_variables.png) +1. Navigate to each cluster's page, through **Operations > Kubernetes**, + and add the domain based on its Ingress IP address. Now that all is configured, you can test your setup by creating a merge request and verifying that your app is deployed as a review app in the Kubernetes @@ -205,10 +210,9 @@ cluster with the `review/*` environment scope. Similarly, you can check the other environments. NOTE: **Note:** -Auto DevOps is not supported for a group with multiple clusters, as it -is not possible to set `AUTO_DEVOPS_DOMAIN` per environment on the group -level. This will be resolved in the future with the [following issue]( -https://gitlab.com/gitlab-org/gitlab-ce/issues/52363). +From GitLab 11.8, `KUBE_INGRESS_BASE_DOMAIN` replaces `AUTO_DEVOPS_DOMAIN`. +`AUTO_DEVOPS_DOMAIN` [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959) +in GitLab 12.0. ## Enabling/Disabling Auto DevOps @@ -681,7 +685,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | **Variable** | **Description** | | ------------ | --------------- | -| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). | +| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain). By default, set automatically by the [Auto DevOps setting](#enabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959) in GitLab 12.0. | | `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/auto-deploy-app). | | `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. | | `REPLICAS` | The number of replicas to deploy; defaults to 1. | @@ -711,6 +715,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. | | `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. | | `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. | +| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. | TIP: **Tip:** Set up the replica variables using a diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index 9f9b2da23e1..52db51fd7bc 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -59,11 +59,17 @@ Add another cluster similar to the first one and make sure to [set an environment scope](#environment-scopes) that will differentiate the new cluster from the rest. +## Base domain + NOTE: **Note:** -Auto DevOps is not supported for a group with multiple clusters, as it -is not possible to set `AUTO_DEVOPS_DOMAIN` per environment on the group -level. This will be resolved in the future with the [following issue]( -https://gitlab.com/gitlab-org/gitlab-ce/issues/52363). +[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8. + +Domains at the cluster level permit support for multiple domains +per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain, +this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during +the [Auto DevOps](../../../topics/autodevops/index.md) stages. + +The domain should have a wildcard DNS configured to the Ingress IP address. ## Environment scopes **[PREMIUM]** diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index bb815695cb1..ff490478232 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -172,6 +172,18 @@ functionalities needed to successfully build and deploy a containerized application. Bear in mind that the same credentials are used for all the applications running on the cluster. +## Base domain + +NOTE: **Note:** +[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8. + +Domains at the cluster level permit support for multiple domains +per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain, +this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during +the [Auto DevOps](../../../topics/autodevops/index.md) stages. + +The domain should have a wildcard DNS configured to the Ingress IP address. + ## Access controls When creating a cluster in GitLab, you will be asked if you would like to create an @@ -254,6 +266,12 @@ install it manually. ## Installing applications +NOTE: **Note:** +Before starting the installation of applications, make sure that time is synchronized +between your GitLab server and your Kubernetes cluster. Otherwise, installation could fail +and you may get errors like `Error: remote error: tls: bad certificate` +in the `stdout` of pods created by GitLab in your Kubernetes cluster. + GitLab provides a one-click install for various applications which can be added directly to your configured cluster. Those applications are needed for [Review Apps](../../../ci/review_apps/index.md) and @@ -449,6 +467,7 @@ GitLab CI/CD build environment. | `KUBE_CA_PEM_FILE` | Path to a file containing PEM data. Only present if a custom CA bundle was specified. | | `KUBE_CA_PEM` | (**deprecated**) Raw PEM data. Only if a custom CA bundle was specified. | | `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. This config also embeds the same token defined in `KUBE_TOKEN` so you likely will only need this variable. This variable name is also automatically picked up by `kubectl` so you won't actually need to reference it explicitly if using `kubectl`. | +| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](#base-domain) for more information. | NOTE: **NOTE:** Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index 68dd3330d7a..b2da1c85c62 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -79,11 +79,14 @@ running on your instance). ![DNS A record pointing to GitLab.com Pages server](img/dns_add_new_a_record_example_updated_2018.png) -NOTE: **Note:** -Note that if you use your root domain for your GitLab Pages website **only**, and if -your domain registrar supports this feature, you can add a DNS apex `CNAME` -record instead of an `A` record. The main advantage of doing so is that when GitLab Pages -IP on GitLab.com changes for whatever reason, you don't need to update your `A` record. +CAUTION: **Caution:** +Note that if you use your root domain for your GitLab Pages website +**only**, and if your domain registrar supports this feature, you can +add a DNS apex `CNAME` record instead of an `A` record. The main +advantage of doing so is that when GitLab Pages IP on GitLab.com +changes for whatever reason, you don't need to update your `A` record. +There may be a few exceptions, but **this method is not recommended** +as it most likely won't work if you set an `MX` record for your root domain. #### DNS CNAME record @@ -114,14 +117,16 @@ co-exist, so you need to place the TXT record in a special subdomain of its own. #### TL;DR -If the domain has multiple uses (e.g., you host email on it as well): +For root domains (`domain.com`), set a DNS `A` record and verify your +domain's ownership with a TXT record: | From | DNS Record | To | | ---- | ---------- | -- | | domain.com | A | 35.185.44.232 | | domain.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff | -If the domain is dedicated to GitLab Pages use and no other services run on it: +For subdomains (`subdomain.domain.com`), set a DNS `CNAME` record and +verify your domain's ownership with a TXT record: | From | DNS Record | To | | ---- | ---------- | -- | diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md index 1213474b7d8..8a2f4e1b40e 100644 --- a/doc/workflow/repository_mirroring.md +++ b/doc/workflow/repository_mirroring.md @@ -88,6 +88,14 @@ The mirrored repository will be listed. For example, `https://*****:*****@github The repository will push soon. To force a push, click the appropriate button. +## Setting up a push mirror to another GitLab instance with 2FA activated + +1. On the destination GitLab instance, create a [personal access token](../user/profile/personal_access_tokens.md) with `API` scope. +1. On the source GitLab instance: + 1. Fill in the **Git repository URL** field using this format: `https://oauth2@<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`. + 1. Fill in **Password** field with the GitLab personal access token created on the destination GitLab instance. + 1. Click the **Mirror repository** button. + ## Pulling from a remote repository **[STARTER]** > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2. diff --git a/lib/api/api.rb b/lib/api/api.rb index 9cbfc0e35ff..4dd1b459554 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -109,6 +109,7 @@ module API mount ::API::Features mount ::API::Files mount ::API::GroupBoards + mount ::API::GroupLabels mount ::API::GroupMilestones mount ::API::Groups mount ::API::GroupVariables diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a1f0efa3c68..beb8ce349b4 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1019,12 +1019,17 @@ module API label.open_merge_requests_count(options[:current_user]) end - expose :priority do |label, options| - label.priority(options[:project]) + expose :subscribed do |label, options| + label.subscribed?(options[:current_user], options[:parent]) end + end - expose :subscribed do |label, options| - label.subscribed?(options[:current_user], options[:project]) + class GroupLabel < Label + end + + class ProjectLabel < Label + expose :priority do |label, options| + label.priority(options[:parent]) end end diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb new file mode 100644 index 00000000000..0dbc5f45a68 --- /dev/null +++ b/lib/api/group_labels.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module API + class GroupLabels < Grape::API + include PaginationParams + helpers ::API::Helpers::LabelHelpers + + before { authenticate! } + + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get all labels of the group' do + detail 'This feature was added in GitLab 11.8' + success Entities::GroupLabel + end + params do + use :pagination + end + get ':id/labels' do + get_labels(user_group, Entities::GroupLabel) + end + + desc 'Create a new label' do + detail 'This feature was added in GitLab 11.8' + success Entities::GroupLabel + end + params do + use :label_create_params + end + post ':id/labels' do + create_label(user_group, Entities::GroupLabel) + end + + desc 'Update an existing label. At least one optional parameter is required.' do + detail 'This feature was added in GitLab 11.8' + success Entities::GroupLabel + end + params do + requires :name, type: String, desc: 'The name of the label to be updated' + optional :new_name, type: String, desc: 'The new name of the label' + optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" + optional :description, type: String, desc: 'The new description of label' + at_least_one_of :new_name, :color, :description + end + put ':id/labels' do + update_label(user_group, Entities::GroupLabel) + end + + desc 'Delete an existing label' do + detail 'This feature was added in GitLab 11.8' + success Entities::GroupLabel + end + params do + requires :name, type: String, desc: 'The name of the label to be deleted' + end + delete ':id/labels' do + delete_label(user_group) + end + end + end +end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index e3d0b981065..2eb7b04711a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -84,8 +84,8 @@ module API page || not_found!('Wiki Page') end - def available_labels_for(label_parent) - search_params = { include_ancestor_groups: true } + def available_labels_for(label_parent, include_ancestor_groups: true) + search_params = { include_ancestor_groups: include_ancestor_groups } if label_parent.is_a?(Project) search_params[:project_id] = label_parent.id @@ -170,13 +170,6 @@ module API end end - def find_project_label(id) - labels = available_labels_for(user_project) - label = labels.find_by_id(id) || labels.find_by_title(id) - - label || not_found!('Label') - end - # rubocop: disable CodeReuse/ActiveRecord def find_project_issue(iid) IssuesFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid) diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb new file mode 100644 index 00000000000..c11e7d614ab --- /dev/null +++ b/lib/api/helpers/label_helpers.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module API + module Helpers + module LabelHelpers + extend Grape::API::Helpers + + params :label_create_params do + requires :name, type: String, desc: 'The name of the label to be created' + requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" + optional :description, type: String, desc: 'The description of label to be created' + end + + def find_label(parent, id, include_ancestor_groups: true) + labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups) + label = labels.find_by_id(id) || labels.find_by_title(id) + + label || not_found!('Label') + end + + def get_labels(parent, entity) + present paginate(available_labels_for(parent)), with: entity, current_user: current_user, parent: parent + end + + def create_label(parent, entity) + authorize! :admin_label, parent + + label = available_labels_for(parent).find_by_title(params[:name]) + conflict!('Label already exists') if label + + priority = params.delete(:priority) + label_params = declared_params(include_missing: false) + + label = + if parent.is_a?(Project) + ::Labels::CreateService.new(label_params).execute(project: parent) + else + ::Labels::CreateService.new(label_params).execute(group: parent) + end + + if label.persisted? + if parent.is_a?(Project) + label.prioritize!(parent, priority) if priority + end + + present label, with: entity, current_user: current_user, parent: parent + else + render_validation_error!(label) + end + end + + def update_label(parent, entity) + authorize! :admin_label, parent + + label = find_label(parent, params[:name], include_ancestor_groups: false) + update_priority = params.key?(:priority) + priority = params.delete(:priority) + + label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label) + render_validation_error!(label) unless label.valid? + + if parent.is_a?(Project) && update_priority + if priority.nil? + label.unprioritize!(parent) + else + label.prioritize!(parent, priority) + end + end + + present label, with: entity, current_user: current_user, parent: parent + end + + def delete_label(parent) + authorize! :admin_label, parent + + label = find_label(parent, params[:name], include_ancestor_groups: false) + + destroy_conditionally!(label) + end + end + end +end diff --git a/lib/api/labels.rb b/lib/api/labels.rb index d5eb2b94669..d729d3ee625 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -3,6 +3,7 @@ module API class Labels < Grape::API include PaginationParams + helpers ::API::Helpers::LabelHelpers before { authenticate! } @@ -11,62 +12,28 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get all labels of the project' do - success Entities::Label + success Entities::ProjectLabel end params do use :pagination end get ':id/labels' do - present paginate(available_labels_for(user_project)), with: Entities::Label, current_user: current_user, project: user_project + get_labels(user_project, Entities::ProjectLabel) end desc 'Create a new label' do - success Entities::Label + success Entities::ProjectLabel end params do - requires :name, type: String, desc: 'The name of the label to be created' - requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" - optional :description, type: String, desc: 'The description of label to be created' + use :label_create_params optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true end - # rubocop: disable CodeReuse/ActiveRecord post ':id/labels' do - authorize! :admin_label, user_project - - label = available_labels_for(user_project).find_by(title: params[:name]) - conflict!('Label already exists') if label - - priority = params.delete(:priority) - label = ::Labels::CreateService.new(declared_params(include_missing: false)).execute(project: user_project) - - if label.valid? - label.prioritize!(user_project, priority) if priority - present label, with: Entities::Label, current_user: current_user, project: user_project - else - render_validation_error!(label) - end - end - # rubocop: enable CodeReuse/ActiveRecord - - desc 'Delete an existing label' do - success Entities::Label - end - params do - requires :name, type: String, desc: 'The name of the label to be deleted' - end - # rubocop: disable CodeReuse/ActiveRecord - delete ':id/labels' do - authorize! :admin_label, user_project - - label = user_project.labels.find_by(title: params[:name]) - not_found!('Label') unless label - - destroy_conditionally!(label) + create_label(user_project, Entities::ProjectLabel) end - # rubocop: enable CodeReuse/ActiveRecord desc 'Update an existing label. At least one optional parameter is required.' do - success Entities::Label + success Entities::ProjectLabel end params do requires :name, type: String, desc: 'The name of the label to be updated' @@ -76,33 +43,19 @@ module API optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true at_least_one_of :new_name, :color, :description, :priority end - # rubocop: disable CodeReuse/ActiveRecord put ':id/labels' do - authorize! :admin_label, user_project - - label = user_project.labels.find_by(title: params[:name]) - not_found!('Label not found') unless label - - update_priority = params.key?(:priority) - priority = params.delete(:priority) - label_params = declared_params(include_missing: false) - # Rename new name to the actual label attribute name - label_params[:name] = label_params.delete(:new_name) if label_params.key?(:new_name) - - label = ::Labels::UpdateService.new(label_params).execute(label) - render_validation_error!(label) unless label.valid? - - if update_priority - if priority.nil? - label.unprioritize!(user_project) - else - label.prioritize!(user_project, priority) - end - end + update_label(user_project, Entities::ProjectLabel) + end - present label, with: Entities::Label, current_user: current_user, project: user_project + desc 'Delete an existing label' do + success Entities::ProjectLabel + end + params do + requires :name, type: String, desc: 'The name of the label to be deleted' + end + delete ':id/labels' do + delete_label(user_project) end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4179aaa93a0..df46b4446ff 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -343,6 +343,7 @@ module API end params do optional :merge_commit_message, type: String, desc: 'Custom merge commit message' + optional :squash_commit_message, type: String, desc: 'Custom squash commit message' optional :should_remove_source_branch, type: Boolean, desc: 'When true, the source branch will be deleted if possible' optional :merge_when_pipeline_succeeds, type: Boolean, @@ -370,6 +371,7 @@ module API merge_params = { commit_message: params[:merge_commit_message], + squash_commit_message: params[:squash_commit_message], should_remove_source_branch: params[:should_remove_source_branch] } diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 95371961398..b16faffe335 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -121,6 +121,7 @@ module API optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins' + optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated" ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| optional :"#{type}_key_restriction", diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index 74ad3c35a61..dfb54446ddf 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -2,51 +2,88 @@ module API class Subscriptions < Grape::API + helpers ::API::Helpers::LabelHelpers + before { authenticate! } - subscribable_types = { - 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) }, - 'issues' => proc { |id| find_project_issue(id) }, - 'labels' => proc { |id| find_project_label(id) } - } + subscribables = [ + { + type: 'merge_requests', + entity: Entities::MergeRequest, + source: Project, + finder: ->(id) { find_merge_request_with_access(id, :update_merge_request) } + }, + { + type: 'issues', + entity: Entities::Issue, + source: Project, + finder: ->(id) { find_project_issue(id) } + }, + { + type: 'labels', + entity: Entities::ProjectLabel, + source: Project, + finder: ->(id) { find_label(user_project, id) } + }, + { + type: 'labels', + entity: Entities::GroupLabel, + source: Group, + finder: ->(id) { find_label(user_group, id) } + } + ] - params do - requires :id, type: String, desc: 'The ID of a project' - requires :subscribable_id, type: String, desc: 'The ID of a resource' - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - subscribable_types.each do |type, finder| - type_singularized = type.singularize - entity_class = Entities.const_get(type_singularized.camelcase) + subscribables.each do |subscribable| + source_type = subscribable[:source].name.underscore + params do + requires :id, type: String, desc: "The #{source_type} ID" + requires :subscribable_id, type: String, desc: 'The ID of a resource' + end + resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Subscribe to a resource' do - success entity_class + success subscribable[:entity] end - post ":id/#{type}/:subscribable_id/subscribe" do - resource = instance_exec(params[:subscribable_id], &finder) + post ":id/#{subscribable[:type]}/:subscribable_id/subscribe" do + parent = parent_resource(source_type) + resource = instance_exec(params[:subscribable_id], &subscribable[:finder]) - if resource.subscribed?(current_user, user_project) + if resource.subscribed?(current_user, parent) not_modified! else - resource.subscribe(current_user, user_project) - present resource, with: entity_class, current_user: current_user, project: user_project + resource.subscribe(current_user, parent) + present resource, with: subscribable[:entity], current_user: current_user, project: parent, parent: parent end end desc 'Unsubscribe from a resource' do - success entity_class + success subscribable[:entity] end - post ":id/#{type}/:subscribable_id/unsubscribe" do - resource = instance_exec(params[:subscribable_id], &finder) + post ":id/#{subscribable[:type]}/:subscribable_id/unsubscribe" do + parent = parent_resource(source_type) + resource = instance_exec(params[:subscribable_id], &subscribable[:finder]) - if !resource.subscribed?(current_user, user_project) + if !resource.subscribed?(current_user, parent) not_modified! else - resource.unsubscribe(current_user, user_project) - present resource, with: entity_class, current_user: current_user, project: user_project + resource.unsubscribe(current_user, parent) + present resource, with: subscribable[:entity], current_user: current_user, project: parent, parent: parent end end end end + + private + + helpers do + def parent_resource(source_type) + case source_type + when 'project' + user_project + else + nil + end + end + end end end diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 184c7418e75..22ed1d8e7b4 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -93,7 +93,7 @@ module Backup progress.puts "Error: #{e}".color(:red) end else - restore_repo_success = gitlab_shell.create_repository(project.repository_storage, project.disk_path) + restore_repo_success = gitlab_shell.create_project_repository(project) end if restore_repo_success diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 7aa02009aa0..b2ef04d23d7 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -12,6 +12,9 @@ module Gitlab # Scopes used for OpenID Connect OPENID_SCOPES = [:openid].freeze + # OpenID Connect profile scopes + PROFILE_SCOPES = [:profile, :email].freeze + # Default scopes for OAuth applications that don't define their own DEFAULT_SCOPES = [:api].freeze @@ -284,7 +287,7 @@ module Gitlab # Other available scopes def optional_scopes - available_scopes + OPENID_SCOPES - DEFAULT_SCOPES + available_scopes + OPENID_SCOPES + PROFILE_SCOPES - DEFAULT_SCOPES end def registry_scopes diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index eaead41a720..75a3f17f549 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -65,9 +65,9 @@ module Gitlab def import_wiki return if project.wiki.repository_exists? - disk_path = project.wiki.disk_path - import_url = project.import_url.sub(/\.git\z/, ".git/wiki") - gitlab_shell.import_repository(project.repository_storage, disk_path, import_url) + wiki = WikiFormatter.new(project) + + gitlab_shell.import_wiki_repository(project, wiki) rescue StandardError => e errors << { type: :wiki, errors: e.message } end diff --git a/lib/gitlab/bitbucket_import/wiki_formatter.rb b/lib/gitlab/bitbucket_import/wiki_formatter.rb new file mode 100644 index 00000000000..b8ff43b777b --- /dev/null +++ b/lib/gitlab/bitbucket_import/wiki_formatter.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Gitlab + module BitbucketImport + class WikiFormatter + attr_reader :project + + def initialize(project) + @project = project + end + + def disk_path + project.wiki.disk_path + end + + def full_path + project.wiki.full_path + end + + def import_url + project.import_url.sub(/\.git\z/, ".git/wiki") + end + end + end +end diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml new file mode 100644 index 00000000000..9c534b2b8e7 --- /dev/null +++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml @@ -0,0 +1,121 @@ +# Read more about how to use this script on this blog post https://about.gitlab.com/2019/01/28/android-publishing-with-gitlab-and-fastlane/ +# You will also need to configure your build.gradle, Dockerfile, and fastlane configuration to make this work. +# If you are looking for a simpler template that does not publish, see the Android template. + +stages: + - environment + - build + - test + - internal + - alpha + - beta + - production + + +.updateContainerJob: + image: docker:stable + stage: environment + services: + - docker:dind + script: + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY + - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true + - docker build --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG . + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + +updateContainer: + extends: .updateContainerJob + only: + changes: + - Dockerfile + +ensureContainer: + extends: .updateContainerJob + allow_failure: true + before_script: + - "mkdir -p ~/.docker && echo '{\"experimental\": \"enabled\"}' > ~/.docker/config.json" + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY + # Skip update container `script` if the container already exists + # via https://gitlab.com/gitlab-org/gitlab-ce/issues/26866#note_97609397 -> https://stackoverflow.com/a/52077071/796832 + - docker manifest inspect $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG > /dev/null && exit || true + + +.build_job: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + stage: build + before_script: + # We store this binary file in a variable as hex with this command: `xxd -p android-app.jks` + # Then we convert the hex back to a binary file + - echo "$signing_jks_file_hex" | xxd -r -p - > android-signing-keystore.jks + - "export VERSION_CODE=$CI_PIPELINE_IID && echo $VERSION_CODE" + - "export VERSION_SHA=`echo ${CI_COMMIT_SHA:0:8}` && echo $VERSION_SHA" + after_script: + - rm -f android-signing-keystore.jks || true + artifacts: + paths: + - app/build/outputs + +buildDebug: + extends: .build_job + script: + - bundle exec fastlane buildDebug + +buildRelease: + extends: .build_job + script: + - bundle exec fastlane buildRelease + environment: + name: production + +testDebug: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + stage: test + dependencies: + - buildDebug + script: + - bundle exec fastlane test + +publishInternal: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + stage: internal + dependencies: + - buildRelease + when: manual + before_script: + - echo $google_play_service_account_api_key_json > ~/google_play_api_key.json + after_script: + - rm ~/google_play_api_key.json + script: + - bundle exec fastlane internal + +.promote_job: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + when: manual + dependencies: [] + before_script: + - echo $google_play_service_account_api_key_json > ~/google_play_api_key.json + after_script: + - rm ~/google_play_api_key.json + +promoteAlpha: + extends: .promote_job + stage: alpha + script: + - bundle exec fastlane promote_internal_to_alpha + +promoteBeta: + extends: .promote_job + stage: beta + script: + - bundle exec fastlane promote_alpha_to_beta + +promoteProduction: + extends: .promote_job + stage: production + # We only allow production promotion on `master` because + # it has its own production scoped secret variables + only: + - master + script: + - bundle exec fastlane promote_beta_to_production +
\ No newline at end of file diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml index 6e138639b71..c169e3f7686 100644 --- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml @@ -1,4 +1,6 @@ # Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny +# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template. + image: openjdk:8-jdk variables: diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index 75a5bf142d2..e369d26f22f 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -21,8 +21,8 @@ # # In order to deploy, you must have a Kubernetes cluster configured either # via a project integration, or via group/project variables. -# AUTO_DEVOPS_DOMAIN must also be set as a variable at the group or project -# level, or manually added below. +# KUBE_INGRESS_BASE_DOMAIN must also be set on the cluster settings, +# as a variable at the group or project level, or manually added below. # # Continuous deployment to production is enabled by default. # If you want to deploy to staging first, set STAGING_ENABLED environment variable. @@ -41,8 +41,8 @@ image: alpine:latest variables: - # AUTO_DEVOPS_DOMAIN is the application deployment domain and should be set as a variable at the group or project level. - # AUTO_DEVOPS_DOMAIN: domain.example.com + # KUBE_INGRESS_BASE_DOMAIN is the application deployment domain and should be set as a variable at the group or project level. + # KUBE_INGRESS_BASE_DOMAIN: domain.example.com POSTGRES_USER: user POSTGRES_PASSWORD: testing-password @@ -251,7 +251,7 @@ review: - persist_environment_url environment: name: review/$CI_COMMIT_REF_NAME - url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$AUTO_DEVOPS_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN on_stop: stop_review artifacts: paths: [environment_url.txt] @@ -306,7 +306,7 @@ staging: - deploy environment: name: staging - url: http://$CI_PROJECT_PATH_SLUG-staging.$AUTO_DEVOPS_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_INGRESS_BASE_DOMAIN only: refs: - master @@ -330,7 +330,7 @@ canary: - deploy canary environment: name: production - url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN when: manual only: refs: @@ -354,7 +354,7 @@ canary: - persist_environment_url environment: name: production - url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN artifacts: paths: [environment_url.txt] @@ -403,7 +403,7 @@ production_manual: - persist_environment_url environment: name: production - url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN + url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN artifacts: paths: [environment_url.txt] @@ -689,7 +689,7 @@ rollout 100%: --set application.database_url="$DATABASE_URL" \ --set application.secretName="$APPLICATION_SECRET_NAME" \ --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \ - --set service.commonName="le.$AUTO_DEVOPS_DOMAIN" \ + --set service.commonName="le.$KUBE_INGRESS_BASE_DOMAIN" \ --set service.url="$CI_ENVIRONMENT_URL" \ --set service.additionalHosts="$additional_hosts" \ --set replicaCount="$replicas" \ @@ -725,7 +725,7 @@ rollout 100%: --set application.database_url="$DATABASE_URL" \ --set application.secretName="$APPLICATION_SECRET_NAME" \ --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \ - --set service.commonName="le.$AUTO_DEVOPS_DOMAIN" \ + --set service.commonName="le.$KUBE_INGRESS_BASE_DOMAIN" \ --set service.url="$CI_ENVIRONMENT_URL" \ --set service.additionalHosts="$additional_hosts" \ --set replicaCount="$replicas" \ @@ -823,11 +823,24 @@ rollout 100%: kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE" } + + # Function to ensure backwards compatibility with AUTO_DEVOPS_DOMAIN + function ensure_kube_ingress_base_domain() { + if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then + export KUBE_INGRESS_BASE_DOMAIN=$AUTO_DEVOPS_DOMAIN + fi + } + function check_kube_domain() { - if [ -z ${AUTO_DEVOPS_DOMAIN+x} ]; then - echo "In order to deploy or use Review Apps, AUTO_DEVOPS_DOMAIN variable must be set" - echo "You can do it in Auto DevOps project settings or defining a variable at group or project level" + ensure_kube_ingress_base_domain + + if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then + echo "In order to deploy or use Review Apps," + echo "AUTO_DEVOPS_DOMAIN or KUBE_INGRESS_BASE_DOMAIN variables must be set" + echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings" + echo "or by defining a variable at group or project level." echo "You can also manually add it in .gitlab-ci.yml" + echo "AUTO_DEVOPS_DOMAIN support will be dropped on 12.0" false else true diff --git a/lib/gitlab/git/object_pool.rb b/lib/gitlab/git/object_pool.rb index 1c6242b444a..e93ca3e11f8 100644 --- a/lib/gitlab/git/object_pool.rb +++ b/lib/gitlab/git/object_pool.rb @@ -10,12 +10,13 @@ module Gitlab delegate :exists?, :size, to: :repository delegate :unlink_repository, :delete, to: :object_pool_service - attr_reader :storage, :relative_path, :source_repository + attr_reader :storage, :relative_path, :source_repository, :gl_project_path - def initialize(storage, relative_path, source_repository) + def initialize(storage, relative_path, source_repository, gl_project_path) @storage = storage @relative_path = relative_path @source_repository = source_repository + @gl_project_path = gl_project_path end def create @@ -31,12 +32,12 @@ module Gitlab end def to_gitaly_repository - Gitlab::GitalyClient::Util.repository(storage, relative_path, GL_REPOSITORY) + Gitlab::GitalyClient::Util.repository(storage, relative_path, GL_REPOSITORY, gl_project_path) end # Allows for reusing other RPCs by 'tricking' Gitaly to think its a repository def repository - @repository ||= Gitlab::Git::Repository.new(storage, relative_path, GL_REPOSITORY) + @repository ||= Gitlab::Git::Repository.new(storage, relative_path, GL_REPOSITORY, gl_project_path) end private diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 786c90f9272..54bbd531398 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -67,7 +67,7 @@ module Gitlab # Relative path of repo attr_reader :relative_path - attr_reader :storage, :gl_repository, :relative_path + attr_reader :storage, :gl_repository, :relative_path, :gl_project_path # This remote name has to be stable for all types of repositories that # can join an object pool. If it's structure ever changes, a migration @@ -78,10 +78,11 @@ module Gitlab # This initializer method is only used on the client side (gitlab-ce). # Gitaly-ruby uses a different initializer. - def initialize(storage, relative_path, gl_repository) + def initialize(storage, relative_path, gl_repository, gl_project_path) @storage = storage @relative_path = relative_path @gl_repository = gl_repository + @gl_project_path = gl_project_path @name = @relative_path.split("/").last end @@ -872,7 +873,7 @@ module Gitlab end def gitaly_repository - Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository) + Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository, @gl_project_path) end def gitaly_ref_client diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index 8a1abfbf874..a7e20d9429e 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -324,13 +324,40 @@ module Gitlab GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files) end - def search_files_by_content(ref, query) - request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query) - GitalyClient.call(@storage, :repository_service, :search_files_by_content, request).flat_map(&:matches) + def search_files_by_content(ref, query, chunked_response: true) + request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query, chunked_response: chunked_response) + response = GitalyClient.call(@storage, :repository_service, :search_files_by_content, request) + + search_results_from_response(response) end private + def search_results_from_response(gitaly_response) + matches = [] + current_match = +"" + + gitaly_response.each do |message| + next if message.nil? + + # Old client will ignore :chunked_response flag + # and return messages with `matches` key. + # This code path will be removed post 12.0 release + if message.matches.any? + matches += message.matches + else + current_match << message.match_data + + if message.end_of_match + matches << current_match + current_match = +"" + end + end + end + + matches + end + def gitaly_fetch_stream_to_file(save_path, rpc_name, request_class, timeout) request = request_class.new(repository: @gitaly_repo) response = GitalyClient.call( diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb index dce5d6a8ad0..899921f76e4 100644 --- a/lib/gitlab/gitaly_client/util.rb +++ b/lib/gitlab/gitaly_client/util.rb @@ -4,7 +4,7 @@ module Gitlab module GitalyClient module Util class << self - def repository(repository_storage, relative_path, gl_repository) + def repository(repository_storage, relative_path, gl_repository, gl_project_path) git_env = Gitlab::Git::HookEnv.all(gl_repository) git_object_directory = git_env['GIT_OBJECT_DIRECTORY_RELATIVE'].presence git_alternate_object_directories = Array.wrap(git_env['GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE']) @@ -14,14 +14,16 @@ module Gitlab relative_path: relative_path, gl_repository: gl_repository.to_s, git_object_directory: git_object_directory.to_s, - git_alternate_object_directories: git_alternate_object_directories + git_alternate_object_directories: git_alternate_object_directories, + gl_project_path: gl_project_path ) end def git_repository(gitaly_repository) Gitlab::Git::Repository.new(gitaly_repository.storage_name, gitaly_repository.relative_path, - gitaly_repository.gl_repository) + gitaly_repository.gl_repository, + gitaly_repository.gl_project_path) end end end diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb index bc3ea9e9226..e2dfb00dcc5 100644 --- a/lib/gitlab/github_import/importer/repository_importer.rb +++ b/lib/gitlab/github_import/importer/repository_importer.rb @@ -6,11 +6,12 @@ module Gitlab class RepositoryImporter include Gitlab::ShellAdapter - attr_reader :project, :client + attr_reader :project, :client, :wiki_formatter def initialize(project, client) @project = project @client = client + @wiki_formatter = ::Gitlab::LegacyGithubImport::WikiFormatter.new(project) end # Returns true if we should import the wiki for the project. @@ -57,9 +58,7 @@ module Gitlab end def import_wiki_repository - wiki_path = "#{project.disk_path}.wiki" - - gitlab_shell.import_repository(project.repository_storage, wiki_path, wiki_url) + gitlab_shell.import_wiki_repository(project, wiki_formatter) true rescue Gitlab::Shell::Error => e @@ -72,7 +71,7 @@ module Gitlab end def wiki_url - project.import_url.sub(/\.git\z/, '.wiki.git') + wiki_formatter.import_url end def update_clone_time diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb index c526d31a591..f3323c98af2 100644 --- a/lib/gitlab/legacy_github_import/importer.rb +++ b/lib/gitlab/legacy_github_import/importer.rb @@ -267,7 +267,7 @@ module Gitlab def import_wiki unless project.wiki.repository_exists? wiki = WikiFormatter.new(project) - gitlab_shell.import_repository(project.repository_storage, wiki.disk_path, wiki.import_url) + gitlab_shell.import_wiki_repository(project, wiki) end rescue Gitlab::Shell::Error => e # GitHub error message when the wiki repo has not been created, diff --git a/lib/gitlab/legacy_github_import/wiki_formatter.rb b/lib/gitlab/legacy_github_import/wiki_formatter.rb index ea52be5ee0f..cf1e21ad1e1 100644 --- a/lib/gitlab/legacy_github_import/wiki_formatter.rb +++ b/lib/gitlab/legacy_github_import/wiki_formatter.rb @@ -13,6 +13,10 @@ module Gitlab project.wiki.disk_path end + def full_path + project.wiki.full_path + end + def import_url project.import_url.sub(/\.git\z/, ".wiki.git") end diff --git a/lib/gitlab/metrics/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb index 4c4ec026823..4c5b849cc51 100644 --- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb +++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb @@ -23,13 +23,13 @@ module Gitlab def sample Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats| - unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active) - unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued) + unicorn_active_connections.set({ socket_type: 'tcp', socket_address: addr }, stats.active) + unicorn_queued_connections.set({ socket_type: 'tcp', socket_address: addr }, stats.queued) end Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats| - unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active) - unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued) + unicorn_active_connections.set({ socket_type: 'unix', socket_address: addr }, stats.active) + unicorn_queued_connections.set({ socket_type: 'unix', socket_address: addr }, stats.queued) end end diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index bdf21cf3134..1153e69d3de 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -64,27 +64,48 @@ module Gitlab end end + # Convenience methods for initializing a new repository with a Project model. + def create_project_repository(project) + create_repository(project.repository_storage, project.disk_path, project.full_path) + end + + def create_wiki_repository(project) + create_repository(project.repository_storage, project.wiki.disk_path, project.wiki.full_path) + end + # Init new repository # # storage - the shard key - # name - project disk path + # disk_path - project disk path + # gl_project_path - project name # # Ex. - # create_repository("default", "gitlab/gitlab-ci") + # create_repository("default", "path/to/gitlab-ci", "gitlab/gitlab-ci") # - def create_repository(storage, name) - relative_path = name.dup + def create_repository(storage, disk_path, gl_project_path) + relative_path = disk_path.dup relative_path << '.git' unless relative_path.end_with?('.git') - repository = Gitlab::Git::Repository.new(storage, relative_path, '') + # During creation of a repository, gl_repository may not be known + # because that depends on a yet-to-be assigned project ID in the + # database (e.g. project-1234), so for now it is blank. + repository = Gitlab::Git::Repository.new(storage, relative_path, '', gl_project_path) wrapped_gitaly_errors { repository.gitaly_repository_client.create_repository } true rescue => err # Once the Rugged codes gets removes this can be improved - Rails.logger.error("Failed to add repository #{storage}/#{name}: #{err}") + Rails.logger.error("Failed to add repository #{storage}/#{disk_path}: #{err}") false end + def import_wiki_repository(project, wiki_formatter) + import_repository(project.repository_storage, wiki_formatter.disk_path, wiki_formatter.import_url, project.wiki.full_path) + end + + def import_project_repository(project) + import_repository(project.repository_storage, project.disk_path, project.import_url, project.full_path) + end + # Import repository # # storage - project's storage name @@ -94,13 +115,13 @@ module Gitlab # Ex. # import_repository("nfs-file06", "gitlab/gitlab-ci", "https://gitlab.com/gitlab-org/gitlab-test.git") # - def import_repository(storage, name, url) + def import_repository(storage, name, url, gl_project_path) if url.start_with?('.', '/') raise Error.new("don't use disk paths with import_repository: #{url.inspect}") end relative_path = "#{name}.git" - cmd = GitalyGitlabProjects.new(storage, relative_path) + cmd = GitalyGitlabProjects.new(storage, relative_path, gl_project_path) success = cmd.import_project(url, git_timeout) raise Error, cmd.output unless success @@ -125,18 +146,13 @@ module Gitlab end # Fork repository to new path - # forked_from_storage - forked-from project's storage name - # forked_from_disk_path - project disk relative path - # forked_to_storage - forked-to project's storage name - # forked_to_disk_path - forked project disk relative path - # - # Ex. - # fork_repository("nfs-file06", "gitlab/gitlab-ci", "nfs-file07", "new-namespace/gitlab-ci") - def fork_repository(forked_from_storage, forked_from_disk_path, forked_to_storage, forked_to_disk_path) - forked_from_relative_path = "#{forked_from_disk_path}.git" - fork_args = [forked_to_storage, "#{forked_to_disk_path}.git"] + # source_project - forked-from Project + # target_project - forked-to Project + def fork_repository(source_project, target_project) + forked_from_relative_path = "#{source_project.disk_path}.git" + fork_args = [target_project.repository_storage, "#{target_project.disk_path}.git", target_project.full_path] - GitalyGitlabProjects.new(forked_from_storage, forked_from_relative_path).fork_repository(*fork_args) + GitalyGitlabProjects.new(source_project.repository_storage, forked_from_relative_path, source_project.full_path).fork_repository(*fork_args) end # Removes a repository from file system, using rm_diretory which is an alias @@ -397,16 +413,17 @@ module Gitlab end class GitalyGitlabProjects - attr_reader :shard_name, :repository_relative_path, :output + attr_reader :shard_name, :repository_relative_path, :output, :gl_project_path - def initialize(shard_name, repository_relative_path) + def initialize(shard_name, repository_relative_path, gl_project_path) @shard_name = shard_name @repository_relative_path = repository_relative_path @output = '' + @gl_project_path = gl_project_path end def import_project(source, _timeout) - raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil) + raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil, gl_project_path) Gitlab::GitalyClient::RepositoryService.new(raw_repository).import_repository(source) true @@ -415,9 +432,9 @@ module Gitlab false end - def fork_repository(new_shard_name, new_repository_relative_path) - target_repository = Gitlab::Git::Repository.new(new_shard_name, new_repository_relative_path, nil) - raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil) + def fork_repository(new_shard_name, new_repository_relative_path, new_project_name) + target_repository = Gitlab::Git::Repository.new(new_shard_name, new_repository_relative_path, nil, new_project_name) + raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil, gl_project_path) Gitlab::GitalyClient::RepositoryService.new(target_repository).fork_repository(raw_repository) rescue GRPC::BadStatus => e diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9e7cc8900c8..75d7074d100 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -22,16 +22,6 @@ msgstr "" msgid " or " msgstr "" -msgid "%d addition" -msgid_plural "%d additions" -msgstr[0] "" -msgstr[1] "" - -msgid "%d changed file" -msgid_plural "%d changed files" -msgstr[0] "" -msgstr[1] "" - msgid "%d commit" msgid_plural "%d commits" msgstr[0] "" @@ -42,11 +32,6 @@ msgid_plural "%d commits behind" msgstr[0] "" msgstr[1] "" -msgid "%d deleted" -msgid_plural "%d deletions" -msgstr[0] "" -msgstr[1] "" - msgid "%d exporter" msgid_plural "%d exporters" msgstr[0] "" @@ -138,9 +123,6 @@ msgstr "" msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" msgstr "" -msgid "%{nip_domain} can be used as an alternative to a custom domain." -msgstr "" - msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -900,15 +882,6 @@ msgstr "" msgid "Auto DevOps, runners and job artifacts" msgstr "" -msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." -msgstr "" - -msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." -msgstr "" - -msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." -msgstr "" - msgid "Auto-cancel redundant, pending pipelines" msgstr "" @@ -1281,9 +1254,6 @@ msgstr "" msgid "CICD|Deployment strategy" msgstr "" -msgid "CICD|Deployment strategy needs a domain name to work correctly." -msgstr "" - msgid "CICD|Jobs" msgstr "" @@ -1293,7 +1263,7 @@ msgstr "" msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found." msgstr "" -msgid "CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages." +msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly." msgstr "" msgid "CICD|instance enabled" @@ -1566,6 +1536,12 @@ msgstr "" msgid "Closed (moved)" msgstr "" +msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}." +msgstr "" + +msgid "ClusterIntegration| can be used instead of a custom domain." +msgstr "" + msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}" msgstr "" @@ -1596,6 +1572,9 @@ msgstr "" msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}" msgstr "" +msgid "ClusterIntegration|Alternatively" +msgstr "" + msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}" msgstr "" @@ -1617,6 +1596,9 @@ msgstr "" msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." msgstr "" +msgid "ClusterIntegration|Base domain" +msgstr "" + msgid "ClusterIntegration|CA Certificate" msgstr "" @@ -1941,6 +1923,9 @@ msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" +msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain." +msgstr "" + msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time." msgstr "" @@ -3292,6 +3277,11 @@ msgstr "" msgid "Fields on this page are now uneditable, you can configure" msgstr "" +msgid "File" +msgid_plural "Files" +msgstr[0] "" +msgstr[1] "" + msgid "File added" msgstr "" diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb index 12c2409a5a7..2de39b8ebf5 100644 --- a/qa/qa/page/project/settings/ci_cd.rb +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -13,9 +13,7 @@ module QA # rubocop:disable Naming/FileName view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do element :enable_auto_devops_field, 'check_box :enabled' # rubocop:disable QA/ElementWithPattern - element :domain_field, 'text_field :domain' # rubocop:disable QA/ElementWithPattern element :enable_auto_devops_button, "%strong= s_('CICD|Default to Auto DevOps pipeline')" # rubocop:disable QA/ElementWithPattern - element :domain_input, "%strong= _('Domain')" # rubocop:disable QA/ElementWithPattern element :save_changes_button, "submit _('Save changes')" # rubocop:disable QA/ElementWithPattern end @@ -31,10 +29,9 @@ module QA # rubocop:disable Naming/FileName end end - def enable_auto_devops_with_domain(domain) + def enable_auto_devops expand_section(:autodevops_settings) do check 'Default to Auto DevOps pipeline' - fill_in 'Domain', with: domain click_on 'Save changes' end end diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb index d67e5f6da20..986b31da528 100644 --- a/qa/qa/resource/kubernetes_cluster.rb +++ b/qa/qa/resource/kubernetes_cluster.rb @@ -6,12 +6,16 @@ module QA module Resource class KubernetesCluster < Base attr_writer :project, :cluster, - :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner + :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner, :domain attribute :ingress_ip do Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip) end + attribute :domain do + "#{ingress_ip}.nip.io" + end + def fabricate! @project.visit! diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index b0ff83db86b..5c8ec465143 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -52,13 +52,13 @@ module QA end kubernetes_cluster.populate(:ingress_ip) - @project.visit! Page::Project::Menu.act { click_ci_cd_settings } Page::Project::Settings::CICD.perform do |p| - p.enable_auto_devops_with_domain( - "#{kubernetes_cluster.ingress_ip}.nip.io") + p.enable_auto_devops end + + kubernetes_cluster.populate(:domain) end after(:all) do diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index 0f28499194e..360030102e0 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -429,12 +429,14 @@ describe Groups::ClustersController do end let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) } + let(:domain) { 'test-domain.com' } let(:params) do { cluster: { enabled: false, - name: 'my-new-cluster-name' + name: 'my-new-cluster-name', + base_domain: domain } } end @@ -447,6 +449,20 @@ describe Groups::ClustersController do expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster.domain).to eq('test-domain.com') + end + + context 'when domain is invalid' do + let(:domain) { 'not-a-valid-domain' } + + it 'should not update cluster attributes' do + go + + cluster.reload + expect(response).to render_template(:show) + expect(cluster.name).not_to eq('my-new-cluster-name') + expect(cluster.domain).not_to eq('test-domain.com') + end end context 'when format is json' do @@ -456,7 +472,8 @@ describe Groups::ClustersController do { cluster: { enabled: false, - name: 'my-new-cluster-name' + name: 'my-new-cluster-name', + domain: domain } } end diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb new file mode 100644 index 00000000000..0a9c4bcaf12 --- /dev/null +++ b/spec/features/clusters/cluster_detail_page_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Clusterable > Show page' do + let(:current_user) { create(:user) } + + before do + sign_in(current_user) + end + + shared_examples 'editing domain' do + before do + clusterable.add_maintainer(current_user) + end + + it 'allow the user to set domain' do + visit cluster_path + + within '#cluster-integration' do + fill_in('cluster_base_domain', with: 'test.com') + click_on 'Save changes' + end + + expect(page.status_code).to eq(200) + expect(page).to have_content('Kubernetes cluster was successfully updated.') + end + + context 'when there is a cluster with ingress and external ip' do + before do + cluster.create_application_ingress!(external_ip: '192.168.1.100') + + visit cluster_path + end + + it 'shows help text with the domain as an alternative to custom domain' do + within '#cluster-integration' do + expect(page).to have_content('Alternatively 192.168.1.100.nip.io can be used instead of a custom domain') + end + end + end + + context 'when there is no ingress' do + it 'alternative to custom domain is not shown' do + visit cluster_path + + within '#cluster-integration' do + expect(page).not_to have_content('can be used instead of a custom domain.') + end + end + end + end + + context 'when clusterable is a project' do + it_behaves_like 'editing domain' do + let(:clusterable) { create(:project) } + let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) } + let(:cluster_path) { project_cluster_path(clusterable, cluster) } + end + end + + context 'when clusterable is a group' do + it_behaves_like 'editing domain' do + let(:clusterable) { create(:group) } + let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) } + let(:cluster_path) { group_cluster_path(clusterable, cluster) } + end + end +end diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb index 50c723776a3..16c058ab6bd 100644 --- a/spec/features/merge_request/user_resolves_conflicts_spec.rb +++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb @@ -37,6 +37,8 @@ describe 'Merge request > User resolves conflicts', :js do click_on 'Changes' wait_for_requests + find('.js-toggle-tree-list').click + within find('.diff-file', text: 'files/ruby/popen.rb') do expect(page).to have_selector('.line_content.new', text: "vars = { 'PWD' => path }") expect(page).to have_selector('.line_content.new', text: "options = { chdir: path }") diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index 63d8decc2d2..aa91ade46ca 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -42,7 +42,7 @@ describe 'Merge request > User sees versions', :js do expect(page).to have_content 'latest version' end - expect(page).to have_content '8 changed files' + expect(page).to have_content '8 Files' end it_behaves_like 'allows commenting', @@ -76,7 +76,7 @@ describe 'Merge request > User sees versions', :js do end it 'shows comments that were last relevant at that version' do - expect(page).to have_content '5 changed files' + expect(page).to have_content '5 Files' position = Gitlab::Diff::Position.new( old_path: ".gitmodules", @@ -120,8 +120,15 @@ describe 'Merge request > User sees versions', :js do diff_id: merge_request_diff3.id, start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9' ) - expect(page).to have_content '4 changed files' - expect(page).to have_content '15 additions 6 deletions' + expect(page).to have_content '4 Files' + + additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-addition') + .ancestor('.diff-stats-group').text + deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion') + .ancestor('.diff-stats-group').text + + expect(additions_content).to eq '15' + expect(deletions_content).to eq '6' position = Gitlab::Diff::Position.new( old_path: ".gitmodules", @@ -141,8 +148,14 @@ describe 'Merge request > User sees versions', :js do end it 'show diff between new and old version' do - expect(page).to have_content '4 changed files' - expect(page).to have_content '15 additions 6 deletions' + additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-addition') + .ancestor('.diff-stats-group').text + deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion') + .ancestor('.diff-stats-group').text + + expect(page).to have_content '4 Files' + expect(additions_content).to eq '15' + expect(deletions_content).to eq '6' end it 'returns to latest version when "Show latest version" button is clicked' do @@ -150,7 +163,7 @@ describe 'Merge request > User sees versions', :js do page.within '.mr-version-dropdown' do expect(page).to have_content 'latest version' end - expect(page).to have_content '8 changed files' + expect(page).to have_content '8 Files' end it_behaves_like 'allows commenting', @@ -176,7 +189,7 @@ describe 'Merge request > User sees versions', :js do find('.btn-default').click click_link 'version 1' end - expect(page).to have_content '0 changed files' + expect(page).to have_content '0 Files' end end @@ -202,7 +215,7 @@ describe 'Merge request > User sees versions', :js do expect(page).to have_content 'version 1' end - expect(page).to have_content '0 changed files' + expect(page).to have_content '0 Files' end end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 6f8ec0015ad..4c85abe9971 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -98,14 +98,12 @@ describe "Projects > Settings > Pipelines settings" do expect(page).not_to have_content('instance enabled') expect(find_field('project_auto_devops_attributes_enabled')).not_to be_checked check 'Default to Auto DevOps pipeline' - fill_in('project_auto_devops_attributes_domain', with: 'test.com') click_on 'Save changes' end expect(page.status_code).to eq(200) expect(project.auto_devops).to be_present expect(project.auto_devops).to be_enabled - expect(project.auto_devops.domain).to eq('test.com') page.within '#autodevops-settings' do expect(find_field('project_auto_devops_attributes_enabled')).to be_checked @@ -113,29 +111,6 @@ describe "Projects > Settings > Pipelines settings" do end end end - - context 'when there is a cluster with ingress and external_ip' do - before do - cluster = create(:cluster, projects: [project]) - cluster.create_application_ingress!(external_ip: '192.168.1.100') - end - - it 'shows the help text with the nip.io domain as an alternative to custom domain' do - visit project_settings_ci_cd_path(project) - expect(page).to have_content('192.168.1.100.nip.io can be used as an alternative to a custom domain') - end - end - - context 'when there is no ingress' do - before do - create(:cluster, projects: [project]) - end - - it 'alternative to custom domain is not shown' do - visit project_settings_ci_cd_path(project) - expect(page).not_to have_content('can be used as an alternative to a custom domain') - end - end end describe 'runners registration token' do diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb index eb974c7c7fd..74fdfcf492e 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -79,14 +79,6 @@ describe 'Snippet', :js do expect(page).not_to have_xpath("//ol//li//ul") end end - - context 'with cached CommonMark html' do - let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) } - - it 'renders correctly' do - expect(page).not_to have_xpath("//ol//li//ul") - end - end end context 'switching to the simple viewer' do diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json new file mode 100644 index 00000000000..f6c327abfdd --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/group_labels.json @@ -0,0 +1,18 @@ +{ + "type": "array", + "items": { + "type": "object", + "properties" : { + "id" : { "type": "integer" }, + "name" : { "type": "string "}, + "color" : { "type": "string "}, + "description" : { "type": "string "}, + "open_issues_count" : { "type": "integer "}, + "closed_issues_count" : { "type": "integer "}, + "open_merge_requests_count" : { "type": "integer "}, + "subscribed" : { "type": "boolean" }, + "priority" : { "type": "null" } + }, + "additionalProperties": false + } +} diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb index 75c30dbfe48..223e562238d 100644 --- a/spec/helpers/auto_devops_helper_spec.rb +++ b/spec/helpers/auto_devops_helper_spec.rb @@ -90,39 +90,4 @@ describe AutoDevopsHelper do it { is_expected.to eq(false) } end end - - describe '.auto_devops_warning_message' do - subject { helper.auto_devops_warning_message(project) } - - context 'when the service is missing' do - before do - allow(helper).to receive(:missing_auto_devops_service?).and_return(true) - end - - context 'when the domain is missing' do - before do - allow(helper).to receive(:missing_auto_devops_domain?).and_return(true) - end - - it { is_expected.to match(/Auto Review Apps and Auto Deploy need a domain name and a .* to work correctly./) } - end - - context 'when the domain is not missing' do - before do - allow(helper).to receive(:missing_auto_devops_domain?).and_return(false) - end - - it { is_expected.to match(/Auto Review Apps and Auto Deploy need a .* to work correctly./) } - end - end - - context 'when the domain is missing' do - before do - allow(helper).to receive(:missing_auto_devops_service?).and_return(false) - allow(helper).to receive(:missing_auto_devops_domain?).and_return(true) - end - - it { is_expected.to eq('Auto Review Apps and Auto Deploy need a domain name to work correctly.') } - end - end end diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js index 2f0385454d7..e886f962d2f 100644 --- a/spec/javascripts/diffs/components/compare_versions_spec.js +++ b/spec/javascripts/diffs/components/compare_versions_spec.js @@ -10,6 +10,10 @@ describe('CompareVersions', () => { const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 }; beforeEach(() => { + store.state.diffs.addedLines = 10; + store.state.diffs.removedLines = 20; + store.state.diffs.diffFiles.push('test'); + vm = createComponentWithStore(Vue.extend(CompareVersionsComponent), store, { mergeRequestDiffs: diffsMockData, mergeRequestDiff: diffsMockData[0], diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index b77907ff26f..787a81fd88f 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -24,6 +24,10 @@ describe('diff_file_header', () => { beforeEach(() => { const diffFile = diffDiscussionMock.diff_file; + + diffFile.added_lines = 2; + diffFile.removed_lines = 1; + props = { diffFile: { ...diffFile }, canCurrentUserFork: false, diff --git a/spec/javascripts/diffs/components/diff_stats_spec.js b/spec/javascripts/diffs/components/diff_stats_spec.js new file mode 100644 index 00000000000..984b3026209 --- /dev/null +++ b/spec/javascripts/diffs/components/diff_stats_spec.js @@ -0,0 +1,33 @@ +import { shallowMount } from '@vue/test-utils'; +import DiffStats from '~/diffs/components/diff_stats.vue'; + +describe('diff_stats', () => { + it('does not render a group if diffFileLengths is not passed in', () => { + const wrapper = shallowMount(DiffStats, { + propsData: { + addedLines: 1, + removedLines: 2, + }, + }); + const groups = wrapper.findAll('.diff-stats-group'); + + expect(groups.length).toBe(2); + }); + + it('shows amount of files changed, lines added and lines removed when passed all props', () => { + const wrapper = shallowMount(DiffStats, { + propsData: { + addedLines: 100, + removedLines: 200, + diffFilesLength: 300, + }, + }); + const additions = wrapper.find('icon-stub[name="file-addition"]').element.parentNode; + const deletions = wrapper.find('icon-stub[name="file-deletion"]').element.parentNode; + const filesChanged = wrapper.find('icon-stub[name="doc-code"]').element.parentNode; + + expect(additions.textContent).toContain('100'); + expect(deletions.textContent).toContain('200'); + expect(filesChanged.textContent).toContain('300'); + }); +}); diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js index c5ef48a81e9..9e556698f34 100644 --- a/spec/javascripts/diffs/components/tree_list_spec.js +++ b/spec/javascripts/diffs/components/tree_list_spec.js @@ -35,12 +35,6 @@ describe('Diffs tree list component', () => { vm.$destroy(); }); - it('renders diff stats', () => { - expect(vm.$el.textContent).toContain('1 changed file'); - expect(vm.$el.textContent).toContain('10 additions'); - expect(vm.$el.textContent).toContain('20 deletions'); - }); - it('renders empty text', () => { expect(vm.$el.textContent).toContain('No files found'); }); diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js new file mode 100644 index 00000000000..19e27388eeb --- /dev/null +++ b/spec/javascripts/helpers/vue_test_utils_helper.js @@ -0,0 +1,19 @@ +/* eslint-disable import/prefer-default-export */ + +const vNodeContainsText = (vnode, text) => + (vnode.text && vnode.text.includes(text)) || + (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length); + +/** + * Determines whether a `shallowMount` Wrapper contains text + * within one of it's slots. This will also work on Wrappers + * acquired with `find()`, but only if it's parent Wrapper + * was shallowMounted. + * NOTE: Prefer checking the rendered output of a component + * wherever possible using something like `text()` instead. + * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted) + * @param {String} slotName + * @param {String} text + */ +export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) => + !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length; diff --git a/spec/javascripts/helpers/vue_test_utils_helper_spec.js b/spec/javascripts/helpers/vue_test_utils_helper_spec.js new file mode 100644 index 00000000000..41714066da5 --- /dev/null +++ b/spec/javascripts/helpers/vue_test_utils_helper_spec.js @@ -0,0 +1,48 @@ +import { shallowMount } from '@vue/test-utils'; +import { shallowWrapperContainsSlotText } from './vue_test_utils_helper'; + +describe('Vue test utils helpers', () => { + describe('shallowWrapperContainsSlotText', () => { + const mockText = 'text'; + const mockSlot = `<div>${mockText}</div>`; + let mockComponent; + + beforeEach(() => { + mockComponent = shallowMount( + { + render(h) { + h(`<div>mockedComponent</div>`); + }, + }, + { + slots: { + default: mockText, + namedSlot: mockSlot, + }, + }, + ); + }); + + it('finds text within shallowWrapper default slot', () => { + expect(shallowWrapperContainsSlotText(mockComponent, 'default', mockText)).toBe(true); + }); + + it('finds text within shallowWrapper named slot', () => { + expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', mockText)).toBe(true); + }); + + it('returns false when text is not present', () => { + const searchText = 'absent'; + + expect(shallowWrapperContainsSlotText(mockComponent, 'default', searchText)).toBe(false); + expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false); + }); + + it('searches with case-sensitivity', () => { + const searchText = mockText.toUpperCase(); + + expect(shallowWrapperContainsSlotText(mockComponent, 'default', searchText)).toBe(false); + expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false); + }); + }); +}); diff --git a/spec/javascripts/lib/utils/file_upload_spec.js b/spec/javascripts/lib/utils/file_upload_spec.js index 92c9cc70aaf..8f7092f63de 100644 --- a/spec/javascripts/lib/utils/file_upload_spec.js +++ b/spec/javascripts/lib/utils/file_upload_spec.js @@ -9,28 +9,56 @@ describe('File upload', () => { <span class="js-filename"></span> </form> `); + }); + + describe('when there is a matching button and input', () => { + beforeEach(() => { + fileUpload('.js-button', '.js-input'); + }); + + it('clicks file input after clicking button', () => { + const btn = document.querySelector('.js-button'); + const input = document.querySelector('.js-input'); + + spyOn(input, 'click'); + + btn.click(); + + expect(input.click).toHaveBeenCalled(); + }); + + it('updates file name text', () => { + const input = document.querySelector('.js-input'); - fileUpload('.js-button', '.js-input'); + input.value = 'path/to/file/index.js'; + + input.dispatchEvent(new CustomEvent('change')); + + expect(document.querySelector('.js-filename').textContent).toEqual('index.js'); + }); }); - it('clicks file input after clicking button', () => { - const btn = document.querySelector('.js-button'); + it('fails gracefully when there is no matching button', () => { const input = document.querySelector('.js-input'); + const btn = document.querySelector('.js-button'); + fileUpload('.js-not-button', '.js-input'); spyOn(input, 'click'); btn.click(); - expect(input.click).toHaveBeenCalled(); + expect(input.click).not.toHaveBeenCalled(); }); - it('updates file name text', () => { + it('fails gracefully when there is no matching input', () => { const input = document.querySelector('.js-input'); + const btn = document.querySelector('.js-button'); + fileUpload('.js-button', '.js-not-input'); - input.value = 'path/to/file/index.js'; + spyOn(input, 'click'); - input.dispatchEvent(new CustomEvent('change')); + btn.click(); - expect(document.querySelector('.js-filename').textContent).toEqual('index.js'); + expect(input.click).not.toHaveBeenCalled(); }); }); diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js new file mode 100644 index 00000000000..0b36fc9f5f7 --- /dev/null +++ b/spec/javascripts/monitoring/charts/area_spec.js @@ -0,0 +1,220 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlAreaChart } from '@gitlab/ui/dist/charts'; +import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; +import Area from '~/monitoring/components/charts/area.vue'; +import MonitoringStore from '~/monitoring/stores/monitoring_store'; +import MonitoringMock, { deploymentData } from '../mock_data'; + +describe('Area component', () => { + const mockWidgets = 'mockWidgets'; + let mockGraphData; + let areaChart; + let spriteSpy; + + beforeEach(() => { + const store = new MonitoringStore(); + store.storeMetrics(MonitoringMock.data); + store.storeDeploymentData(deploymentData); + + [mockGraphData] = store.groups[0].metrics; + + areaChart = shallowMount(Area, { + propsData: { + graphData: mockGraphData, + containerWidth: 0, + deploymentData: store.deploymentData, + }, + slots: { + default: mockWidgets, + }, + }); + + spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake( + () => new Promise(resolve => resolve()), + ); + }); + + afterEach(() => { + areaChart.destroy(); + }); + + it('renders chart title', () => { + expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); + }); + + it('contains graph widgets from slot', () => { + expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); + }); + + describe('wrapped components', () => { + describe('GitLab UI area chart', () => { + let glAreaChart; + + beforeEach(() => { + glAreaChart = areaChart.find(GlAreaChart); + }); + + it('is a Vue instance', () => { + expect(glAreaChart.isVueInstance()).toBe(true); + }); + + it('receives data properties needed for proper chart render', () => { + const props = glAreaChart.props(); + + expect(props.data).toBe(areaChart.vm.chartData); + expect(props.option).toBe(areaChart.vm.chartOptions); + expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText); + expect(props.thresholds).toBe(areaChart.props('alertData')); + }); + + it('recieves a tooltip title', () => { + const mockTitle = 'mockTitle'; + areaChart.vm.tooltip.title = mockTitle; + + expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true); + }); + + it('recieves tooltip content', () => { + const mockContent = 'mockContent'; + areaChart.vm.tooltip.content = mockContent; + + expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockContent)).toBe( + true, + ); + }); + + describe('when tooltip is showing deployment data', () => { + beforeEach(() => { + areaChart.vm.tooltip.isDeployment = true; + }); + + it('uses deployment title', () => { + expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', 'Deployed')).toBe( + true, + ); + }); + + it('renders commit sha in tooltip content', () => { + const mockSha = 'mockSha'; + areaChart.vm.tooltip.sha = mockSha; + + expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockSha)).toBe(true); + }); + }); + }); + }); + + describe('methods', () => { + describe('formatTooltipText', () => { + const mockDate = deploymentData[0].created_at; + const generateSeriesData = type => ({ + seriesData: [ + { + componentSubType: type, + value: [mockDate, 5.55555], + }, + ], + value: mockDate, + }); + + describe('series is of line type', () => { + beforeEach(() => { + areaChart.vm.formatTooltipText(generateSeriesData('line')); + }); + + it('formats tooltip title', () => { + expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM'); + }); + + it('formats tooltip content', () => { + expect(areaChart.vm.tooltip.content).toBe('CPU (Cores) 5.556'); + }); + }); + + describe('series is of scatter type', () => { + beforeEach(() => { + areaChart.vm.formatTooltipText(generateSeriesData('scatter')); + }); + + it('formats tooltip title', () => { + expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM'); + }); + + it('formats tooltip sha', () => { + expect(areaChart.vm.tooltip.sha).toBe('f5bcd1d9'); + }); + }); + }); + + describe('getScatterSymbol', () => { + beforeEach(() => { + areaChart.vm.getScatterSymbol(); + }); + + it('gets rocket svg path content for use as deployment data symbol', () => { + expect(spriteSpy).toHaveBeenCalledWith('rocket'); + }); + }); + + describe('onResize', () => { + const mockWidth = 233; + const mockHeight = 144; + + beforeEach(() => { + spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ + width: mockWidth, + height: mockHeight, + })); + areaChart.vm.onResize(); + }); + + it('sets area chart width', () => { + expect(areaChart.vm.width).toBe(mockWidth); + }); + + it('sets area chart height', () => { + expect(areaChart.vm.height).toBe(mockHeight); + }); + }); + }); + + describe('computed', () => { + describe('chartData', () => { + it('utilizes all data points', () => { + expect(Object.keys(areaChart.vm.chartData)).toEqual(['Cores']); + expect(areaChart.vm.chartData.Cores.length).toBe(297); + }); + + it('creates valid data', () => { + const data = areaChart.vm.chartData.Cores; + + expect( + data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number') + .length, + ).toBe(data.length); + }); + }); + + describe('scatterSeries', () => { + it('utilizes deployment data', () => { + expect(areaChart.vm.scatterSeries.data).toEqual([ + ['2017-05-31T21:23:37.881Z', 0], + ['2017-05-30T20:08:04.629Z', 0], + ['2017-05-30T17:42:38.409Z', 0], + ]); + }); + }); + + describe('xAxisLabel', () => { + it('constructs a label for the chart x-axis', () => { + expect(areaChart.vm.xAxisLabel).toBe('Core Usage'); + }); + }); + + describe('yAxisLabel', () => { + it('constructs a label for the chart y-axis', () => { + expect(areaChart.vm.yAxisLabel).toBe('CPU (Cores)'); + }); + }); + }); +}); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 97b9671c809..b1778029a77 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -25,15 +25,22 @@ export default propsData; describe('Dashboard', () => { let DashboardComponent; + let mock; beforeEach(() => { setFixtures(` <div class="prometheus-graphs"></div> <div class="layout-page"></div> `); + + mock = new MockAdapter(axios); DashboardComponent = Vue.extend(Dashboard); }); + afterEach(() => { + mock.restore(); + }); + describe('no metrics are available yet', () => { it('shows a getting started empty state when no metrics are present', () => { const component = new DashboardComponent({ @@ -47,16 +54,10 @@ describe('Dashboard', () => { }); describe('requests information to the server', () => { - let mock; beforeEach(() => { - mock = new MockAdapter(axios); mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); }); - afterEach(() => { - mock.restore(); - }); - it('shows up a loading state', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), @@ -152,15 +153,12 @@ describe('Dashboard', () => { }); describe('when the window resizes', () => { - let mock; beforeEach(() => { - mock = new MockAdapter(axios); mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); jasmine.clock().install(); }); afterEach(() => { - mock.restore(); jasmine.clock().uninstall(); }); diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index b4e2cd75d47..ffc7148fde2 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -326,6 +326,7 @@ export const metricsGroupsAPIResponse = { { id: 6, title: 'CPU usage', + y_label: 'CPU', weight: 1, queries: [ { diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 7ae45c40c28..348743081eb 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -165,7 +165,6 @@ export const note = { report_abuse_path: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_546&user_id=1', path: '/gitlab-org/gitlab-ce/notes/546', - cached_markdown_version: 11, }; export const discussionMock = { diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index 209a547c3b3..3b52f6666d0 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -11,7 +11,7 @@ describe Banzai::ObjectRenderer do ) end - let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) } + let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) } describe '#render' do context 'with cache' do diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 236808c0b69..a4a6338961e 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Auth do it 'optional_scopes contains all non-default scopes' do stub_container_registry_config(enabled: true) - expect(subject.optional_scopes).to eq %i[read_user sudo read_repository read_registry openid] + expect(subject.optional_scopes).to eq %i[read_user sudo read_repository read_registry openid profile email] end context 'registry_scopes' do diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index afd8f5da39f..a07c5371134 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -61,7 +61,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") } before do - gitlab_shell.create_repository(repository_storage, hashed_path) + gitlab_shell.create_repository(repository_storage, hashed_path, 'group/project') Gitlab::GitalyClient::StorageSettings.allow_disk_access do repository = Rugged::Repository.new(repo_path) repository.config['gitlab.fullpath'] = 'to/repo' diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 0def685f177..c432cc223b9 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -218,7 +218,7 @@ describe Gitlab::BitbucketImport::Importer do describe 'wiki import' do it 'is skipped when the wiki exists' do expect(project.wiki).to receive(:repository_exists?) { true } - expect(importer.gitlab_shell).not_to receive(:import_repository) + expect(importer.gitlab_shell).not_to receive(:import_wiki_repository) importer.execute @@ -227,11 +227,7 @@ describe Gitlab::BitbucketImport::Importer do it 'imports to the project disk_path' do expect(project.wiki).to receive(:repository_exists?) { false } - expect(importer.gitlab_shell).to receive(:import_repository).with( - project.repository_storage, - project.wiki.disk_path, - project.import_url + '/wiki' - ) + expect(importer.gitlab_shell).to receive(:import_wiki_repository) importer.execute diff --git a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb new file mode 100644 index 00000000000..795fd069ab2 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Gitlab::BitbucketImport::WikiFormatter do + let(:project) do + create(:project, + namespace: create(:namespace, path: 'gitlabhq'), + import_url: 'https://xxx@bitbucket.org/gitlabhq/sample.gitlabhq.git') + end + + subject(:wiki) { described_class.new(project) } + + describe '#disk_path' do + it 'appends .wiki to disk path' do + expect(wiki.disk_path).to eq project.wiki.disk_path + end + end + + describe '#full_path' do + it 'appends .wiki to project path' do + expect(wiki.full_path).to eq project.wiki.full_path + end + end + + describe '#import_url' do + it 'returns URL of the wiki repository' do + expect(wiki.import_url).to eq 'https://xxx@bitbucket.org/gitlabhq/sample.gitlabhq.git/wiki' + end + end +end diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index e704d1c673c..0010c0304eb 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" describe Gitlab::Git::Blame, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:blame) do Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md") end diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 1bcec04d28f..a1b5cea88c0 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe Gitlab::Git::Blob, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:rugged) do Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH)) end diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 0df282d0ae3..0764e525ede 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Branch, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:rugged) do Rugged::Repository.new(File.join(TestEnv.repos_path, repository.relative_path)) end @@ -64,7 +64,7 @@ describe Gitlab::Git::Branch, :seed_helper do context 'with active, stale and future branches' do let(:repository) do - Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') + Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project') end let(:user) { create(:user) } diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index db68062e433..2611ebed25b 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe Gitlab::Git::Commit, :seed_helper do include GitHelpers - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:rugged_repo) do Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH)) end @@ -146,7 +146,7 @@ describe Gitlab::Git::Commit, :seed_helper do end context 'with broken repo' do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH, '', 'group/project') } it 'returns nil' do expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_nil diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb index 771c71a16a9..65dfb93d0db 100644 --- a/spec/lib/gitlab/git/compare_spec.rb +++ b/spec/lib/gitlab/git/compare_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Compare, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: false) } let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: true) } diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 8a4415506c4..1d22329b670 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Diff, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:gitaly_diff) do Gitlab::GitalyClient::Diff.new( from_path: '.gitmodules', diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb index 53ed7c5a13a..e166628d4ca 100644 --- a/spec/lib/gitlab/git/remote_repository_spec.rb +++ b/spec/lib/gitlab/git/remote_repository_spec.rb @@ -1,15 +1,15 @@ require 'spec_helper' describe Gitlab::Git::RemoteRepository, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } subject { described_class.new(repository) } describe '#empty?' do using RSpec::Parameterized::TableSyntax where(:repository, :result) do - Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') | false - Gitlab::Git::Repository.new('default', 'does-not-exist.git', '') | true + Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') | false + Gitlab::Git::Repository.new('default', 'does-not-exist.git', '', 'group/project') | true end with_them do @@ -44,11 +44,11 @@ describe Gitlab::Git::RemoteRepository, :seed_helper do using RSpec::Parameterized::TableSyntax where(:other_repository, :result) do - repository | true - Gitlab::Git::Repository.new(repository.storage, repository.relative_path, '') | true - Gitlab::Git::Repository.new('broken', TEST_REPO_PATH, '') | false - Gitlab::Git::Repository.new(repository.storage, 'wrong/relative-path.git', '') | false - Gitlab::Git::Repository.new('broken', 'wrong/relative-path.git', '') | false + repository | true + Gitlab::Git::Repository.new(repository.storage, repository.relative_path, '', 'group/project') | true + Gitlab::Git::Repository.new('broken', TEST_REPO_PATH, '', 'group/project') | false + Gitlab::Git::Repository.new(repository.storage, 'wrong/relative-path.git', '', 'group/project') | false + Gitlab::Git::Repository.new('broken', 'wrong/relative-path.git', '', 'group/project') | false end with_them do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index a19e3e84f83..cf9e0cccc71 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -19,8 +19,10 @@ describe Gitlab::Git::Repository, :seed_helper do end end - let(:mutable_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:mutable_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project') } + let(:mutable_repository_path) { File.join(TestEnv.repos_path, mutable_repository.relative_path) } + let(:mutable_repository_rugged) { Rugged::Repository.new(mutable_repository_path) } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) } let(:repository_rugged) { Rugged::Repository.new(repository_path) } let(:storage_path) { TestEnv.repos_path } @@ -434,13 +436,13 @@ describe Gitlab::Git::Repository, :seed_helper do describe '#fetch_repository_as_mirror' do let(:new_repository) do - Gitlab::Git::Repository.new('default', 'my_project.git', '') + Gitlab::Git::Repository.new('default', 'my_project.git', '', 'group/project') end subject { new_repository.fetch_repository_as_mirror(repository) } before do - Gitlab::Shell.new.create_repository('default', 'my_project') + Gitlab::Shell.new.create_repository('default', 'my_project', 'group/project') end after do @@ -497,6 +499,48 @@ describe Gitlab::Git::Repository, :seed_helper do end end + describe '#search_files_by_content' do + let(:repository) { mutable_repository } + let(:repository_rugged) { mutable_repository_rugged } + + before do + repository.create_branch('search-files-by-content-branch', 'master') + new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'search-files-by-content-branch', 'committing something', 'search-files-by-content change') + new_commit_edit_new_file_on_branch(repository_rugged, 'anotherfile', 'search-files-by-content-branch', 'committing something', 'search-files-by-content change') + end + + after do + ensure_seeds + end + + shared_examples 'search files by content' do + it 'should have 2 items' do + expect(search_results.size).to eq(2) + end + + it 'should have the correct matching line' do + expect(search_results).to contain_exactly("search-files-by-content-branch:encoding/CHANGELOG\u00001\u0000search-files-by-content change\n", + "search-files-by-content-branch:anotherfile\u00001\u0000search-files-by-content change\n") + end + end + + it_should_behave_like 'search files by content' do + let(:search_results) do + repository.search_files_by_content('search-files-by-content', 'search-files-by-content-branch') + end + end + + it_should_behave_like 'search files by content' do + let(:search_results) do + repository.gitaly_repository_client.search_files_by_content( + 'search-files-by-content-branch', + 'search-files-by-content', + chunked_response: false + ) + end + end + end + describe '#find_remote_root_ref' do it 'gets the remote root ref from GitalyClient' do expect_any_instance_of(Gitlab::GitalyClient::RemoteService) @@ -544,7 +588,7 @@ describe Gitlab::Git::Repository, :seed_helper do # Add new commits so that there's a renamed file in the commit history @commit_with_old_name_id = new_commit_edit_old_file(repository_rugged).oid @rename_commit_id = new_commit_move_file(repository_rugged).oid - @commit_with_new_name_id = new_commit_edit_new_file(repository_rugged).oid + @commit_with_new_name_id = new_commit_edit_new_file(repository_rugged, "encoding/CHANGELOG", "Edit encoding/CHANGELOG", "I'm a new changelog with different text").oid end after do @@ -1230,7 +1274,7 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#gitattribute' do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_GITATTRIBUTES_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_GITATTRIBUTES_REPO_PATH, '', 'group/project') } after do ensure_seeds @@ -1249,7 +1293,7 @@ describe Gitlab::Git::Repository, :seed_helper do end context 'without gitattributes file' do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } it 'returns nil' do expect(repository.gitattribute("README.md", 'gitlab-language')).to eq(nil) @@ -1513,7 +1557,7 @@ describe Gitlab::Git::Repository, :seed_helper do context 'repository does not exist' do it 'raises NoRepository and does not call Gitaly WriteConfig' do - repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '') + repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project') expect(repository.gitaly_repository_client).not_to receive(:write_config) @@ -1803,7 +1847,7 @@ describe Gitlab::Git::Repository, :seed_helper do out: '/dev/null', err: '/dev/null') - empty_repo = described_class.new('default', 'empty-repo.git', '') + empty_repo = described_class.new('default', 'empty-repo.git', '', 'group/empty-repo') expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000' end @@ -1818,13 +1862,13 @@ describe Gitlab::Git::Repository, :seed_helper do File.truncate(File.join(storage_path, 'non-valid.git/HEAD'), 0) - non_valid = described_class.new('default', 'non-valid.git', '') + non_valid = described_class.new('default', 'non-valid.git', '', 'a/non-valid') expect { non_valid.checksum }.to raise_error(Gitlab::Git::Repository::InvalidRepository) end it 'raises Gitlab::Git::Repository::NoRepository error when there is no repo' do - broken_repo = described_class.new('default', 'a/path.git', '') + broken_repo = described_class.new('default', 'a/path.git', '', 'a/path') expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository) end @@ -1964,7 +2008,7 @@ describe Gitlab::Git::Repository, :seed_helper do end # Build the options hash that's passed to Rugged::Commit#create - def commit_options(repo, index, message) + def commit_options(repo, index, target, ref, message) options = {} options[:tree] = index.write_tree(repo) options[:author] = { @@ -1978,8 +2022,8 @@ describe Gitlab::Git::Repository, :seed_helper do time: Time.gm(2014, "mar", 3, 20, 15, 1) } options[:message] ||= message - options[:parents] = repo.empty? ? [] : [repo.head.target].compact - options[:update_ref] = "HEAD" + options[:parents] = repo.empty? ? [] : [target].compact + options[:update_ref] = ref options end @@ -1995,6 +2039,8 @@ describe Gitlab::Git::Repository, :seed_helper do options = commit_options( repo, index, + repo.head.target, + "HEAD", "Edit CHANGELOG in its original location" ) @@ -2003,19 +2049,24 @@ describe Gitlab::Git::Repository, :seed_helper do end # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the - # contents of encoding/CHANGELOG with new text. - def new_commit_edit_new_file(repo) - oid = repo.write("I'm a new changelog with different text", :blob) + # contents of the specified file_path with new text. + def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head) + oid = repo.write(text, :blob) index = repo.index - index.read_tree(repo.head.target.tree) - index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644) - - options = commit_options(repo, index, "Edit encoding/CHANGELOG") - + index.read_tree(branch.target.tree) + index.add(path: file_path, oid: oid, mode: 0100644) + options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message) sha = Rugged::Commit.create(repo, options) repo.lookup(sha) end + # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the + # contents of encoding/CHANGELOG with new text. + def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text) + branch = repo.branches[branch_name] + new_commit_edit_new_file(repo, file_path, commit_message, text, branch) + end + # Writes a new commit to the repo and returns a Rugged::Commit. Moves the # CHANGELOG file to the encoding/ directory. def new_commit_move_file(repo) @@ -2027,7 +2078,7 @@ describe Gitlab::Git::Repository, :seed_helper do index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644) index.remove("CHANGELOG") - options = commit_options(repo, index, "Move CHANGELOG to encoding/") + options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/") sha = Rugged::Commit.create(repo, options) repo.lookup(sha) diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index b51e3879f49..4c0291f64f0 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Tag, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } describe '#tags' do describe 'first tag' do diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index bec875fb03d..4a4d69490a3 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" describe Gitlab::Git::Tree, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } context :repo do let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) } diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb index aff47599ad6..d5508dbff5d 100644 --- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::GitalyClient::RemoteService do end describe '#fetch_internal_remote' do - let(:remote_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') } + let(:remote_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project') } it 'sends an fetch_internal_remote message and returns the result value' do expect_any_instance_of(Gitaly::RemoteService::Stub) diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb index 550db6db6d9..78a5e195ad1 100644 --- a/spec/lib/gitlab/gitaly_client/util_spec.rb +++ b/spec/lib/gitlab/gitaly_client/util_spec.rb @@ -7,6 +7,7 @@ describe Gitlab::GitalyClient::Util do let(:gl_repository) { 'project-1' } let(:git_object_directory) { '.git/objects' } let(:git_alternate_object_directory) { ['/dir/one', '/dir/two'] } + let(:gl_project_path) { 'namespace/myproject' } let(:git_env) do { 'GIT_OBJECT_DIRECTORY_RELATIVE' => git_object_directory, @@ -15,7 +16,7 @@ describe Gitlab::GitalyClient::Util do end subject do - described_class.repository(repository_storage, relative_path, gl_repository) + described_class.repository(repository_storage, relative_path, gl_repository, gl_project_path) end it 'creates a Gitaly::Repository with the given data' do @@ -27,6 +28,7 @@ describe Gitlab::GitalyClient::Util do expect(subject.gl_repository).to eq(gl_repository) expect(subject.git_object_directory).to eq(git_object_directory) expect(subject.git_alternate_object_directories).to eq(git_alternate_object_directory) + expect(subject.gl_project_path).to eq(gl_project_path) end end end diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb index 77f5b2ffa37..47233ea6ee2 100644 --- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb @@ -5,6 +5,14 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do let(:import_state) { double(:import_state) } let(:client) { double(:client) } + let(:wiki) do + double( + :wiki, + disk_path: 'foo.wiki', + full_path: 'group/foo.wiki' + ) + end + let(:project) do double( :project, @@ -15,7 +23,9 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do repository: repository, create_wiki: true, import_state: import_state, - lfs_enabled?: true + full_path: 'group/foo', + lfs_enabled?: true, + wiki: wiki ) end @@ -195,7 +205,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do it 'imports the wiki repository' do expect(importer.gitlab_shell) .to receive(:import_repository) - .with('foo', 'foo.wiki', 'foo.wiki.git') + .with('foo', 'foo.wiki', 'foo.wiki.git', 'group/foo.wiki') expect(importer.import_wiki_repository).to eq(true) end diff --git a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb index 7723533aee2..7519707293c 100644 --- a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb @@ -10,11 +10,17 @@ describe Gitlab::LegacyGithubImport::WikiFormatter do subject(:wiki) { described_class.new(project) } describe '#disk_path' do - it 'appends .wiki to project path' do + it 'appends .wiki to disk path' do expect(wiki.disk_path).to eq project.wiki.disk_path end end + describe '#full_path' do + it 'appends .wiki to project path' do + expect(wiki.full_path).to eq project.wiki.full_path + end + end + describe '#import_url' do it 'returns URL of the wiki repository' do expect(wiki.import_url).to eq 'https://xxx@github.com/gitlabhq/sample.gitlabhq.wiki.git' diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb index 771b633a2b9..4b03f3c2532 100644 --- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb +++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb @@ -37,7 +37,7 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do end it 'updates metrics type unix and with addr' do - labels = { type: 'unix', address: socket_address } + labels = { socket_type: 'unix', socket_address: socket_address } expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') @@ -69,7 +69,7 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do end it 'updates metrics type unix and with addr' do - labels = { type: 'tcp', address: tcp_socket_address } + labels = { socket_type: 'tcp', socket_address: tcp_socket_address } expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active') expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued') diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 6ce9d515a0f..033e1bf81a1 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -412,7 +412,7 @@ describe Gitlab::Shell do end it 'creates a repository' do - expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_truthy + expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_truthy expect(File.stat(created_path).mode & 0o777).to eq(0o770) @@ -427,7 +427,7 @@ describe Gitlab::Shell do # should cause #create_repository to fail. FileUtils.touch(created_path) - expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_falsy + expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_falsy end end @@ -474,13 +474,10 @@ describe Gitlab::Shell do end describe '#fork_repository' do + let(:target_project) { create(:project) } + subject do - gitlab_shell.fork_repository( - project.repository_storage, - project.disk_path, - 'nfs-file05', - 'fork/path' - ) + gitlab_shell.fork_repository(project, target_project) end it 'returns true when the command succeeds' do @@ -505,7 +502,7 @@ describe Gitlab::Shell do it 'returns true when the command succeeds' do expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository).with(import_url) - result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url) + result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url, project.full_path) expect(result).to be_truthy end @@ -516,7 +513,7 @@ describe Gitlab::Shell do expect_any_instance_of(Gitlab::Shell::GitalyGitlabProjects).to receive(:output) { 'error'} expect do - gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url) + gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url, project.full_path) end.to raise_error(Gitlab::Shell::Error, "error") end end diff --git a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb new file mode 100644 index 00000000000..2ffc0e65fee --- /dev/null +++ b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb') + +describe MigrateAutoDevOpsDomainToClusterDomain, :migration do + include MigrationHelpers::ClusterHelpers + + let(:migration) { described_class.new } + let(:project_auto_devops_table) { table(:project_auto_devops) } + let(:clusters_table) { table(:clusters) } + let(:cluster_projects_table) { table(:cluster_projects) } + + # Following lets are needed by MigrationHelpers::ClusterHelpers + 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 + setup_cluster_projects_with_domain(quantity: 20, domain: domain) + end + + context 'with ProjectAutoDevOps with no domain' do + let(:domain) { nil } + + it 'should not update cluster project' do + migrate! + + expect(clusters_without_domain.count).to eq(clusters_table.count) + end + end + + context 'with ProjectAutoDevOps with domain' do + let(:domain) { 'example-domain.com' } + + it 'should update all cluster projects' do + migrate! + + expect(clusters_with_domain.count).to eq(clusters_table.count) + end + end + + context 'when only some ProjectAutoDevOps have domain set' do + let(:domain) { 'example-domain.com' } + + before do + setup_cluster_projects_with_domain(quantity: 25, domain: nil) + end + + it 'should only update specific cluster projects' do + migrate! + + expect(clusters_with_domain.count).to eq(20) + + project_auto_devops_with_domain.each do |project_auto_devops| + cluster_project = Clusters::Project.find_by(project_id: project_auto_devops.project_id) + cluster = Clusters::Cluster.find(cluster_project.cluster_id) + + expect(cluster.domain).to be_present + end + + expect(clusters_without_domain.count).to eq(25) + + project_auto_devops_without_domain.each do |project_auto_devops| + cluster_project = Clusters::Project.find_by(project_id: project_auto_devops.project_id) + cluster = Clusters::Cluster.find(cluster_project.cluster_id) + + expect(cluster.domain).not_to be_present + end + end + end + + def setup_cluster_projects_with_domain(quantity:, domain:) + create_cluster_project_list(quantity) + + cluster_projects = cluster_projects_table.last(quantity) + + cluster_projects.each do |cluster_project| + specific_domain = "#{cluster_project.id}-#{domain}" if domain + + project_auto_devops_table.create( + project_id: cluster_project.project_id, + enabled: true, + domain: specific_domain + ) + end + end + + def project_auto_devops_with_domain + project_auto_devops_table.where.not("domain IS NULL OR domain = ''") + end + + def project_auto_devops_without_domain + project_auto_devops_table.where("domain IS NULL OR domain = ''") + end + + def clusters_with_domain + clusters_table.where.not("domain IS NULL OR domain = ''") + end + + def clusters_without_domain + clusters_table.where("domain IS NULL OR domain = ''") + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 96aa9a82b71..789e14e8a20 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -70,6 +70,13 @@ describe ApplicationSetting do .is_greater_than(0) end + it do + is_expected.to validate_numericality_of(:local_markdown_version) + .only_integer + .is_greater_than_or_equal_to(0) + .is_less_than(65536) + end + context 'key restrictions' do it 'supports all key types' do expect(described_class::SUPPORTED_KEY_TYPES).to contain_exactly(:rsa, :dsa, :ecdsa, :ed25519) diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 0161db740ee..92ce2b0999a 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -30,6 +30,7 @@ describe Clusters::Cluster do it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix } it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix } it { is_expected.to delegate_method(:available?).to(:application_knative).with_prefix } + it { is_expected.to delegate_method(:external_ip).to(:application_ingress).with_prefix } it { is_expected.to respond_to :project } @@ -514,4 +515,108 @@ describe Clusters::Cluster do it { is_expected.to be_falsey } end end + + describe '#kube_ingress_domain' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + subject { cluster.kube_ingress_domain } + + context 'with domain set in cluster' do + let(:cluster) { create(:cluster, :provided_by_gcp, :with_domain) } + + it { is_expected.to eq(cluster.domain) } + end + + context 'with no domain on cluster' do + context 'with a project cluster' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + + context 'with domain set at instance level' do + before do + stub_application_setting(auto_devops_domain: 'global_domain.com') + + it { is_expected.to eq('global_domain.com') } + end + end + + context 'with domain set on ProjectAutoDevops' do + before do + auto_devops = project.build_auto_devops(domain: 'legacy-ado-domain.com') + auto_devops.save + end + + it { is_expected.to eq('legacy-ado-domain.com') } + end + + context 'with domain set as environment variable on project' do + before do + variable = project.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'project-ado-domain.com') + variable.save + end + + it { is_expected.to eq('project-ado-domain.com') } + end + + context 'with domain set as environment variable on the group project' do + let(:group) { create(:group) } + + before do + project.update(parent_id: group.id) + variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com') + variable.save + end + + it { is_expected.to eq('group-ado-domain.com') } + end + end + + context 'with a group cluster' do + let(:cluster) { create(:cluster, :group, :provided_by_gcp) } + + context 'with domain set as environment variable for the group' do + let(:group) { cluster.group } + + before do + variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com') + variable.save + end + + it { is_expected.to eq('group-ado-domain.com') } + end + end + end + end + + describe '#predefined_variables' do + subject { cluster.predefined_variables } + + context 'with an instance domain' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + before do + stub_application_setting(auto_devops_domain: 'global_domain.com') + end + + it 'should include KUBE_INGRESS_BASE_DOMAIN' do + expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'global_domain.com') + end + end + + context 'with a cluster domain' do + let(:cluster) { create(:cluster, :provided_by_gcp, domain: 'example.com') } + + it 'should include KUBE_INGRESS_BASE_DOMAIN' do + expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'example.com') + end + end + + context 'with no domain' do + let(:cluster) { create(:cluster, :provided_by_gcp, :project) } + + it 'should return an empty array' do + expect(subject.to_hash).to be_empty + end + end + end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 6c8a223092e..c273fa7e164 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -297,6 +297,19 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end end end + + context 'with a domain' do + let!(:cluster) do + create(:cluster, :provided_by_gcp, :with_domain, + platform_kubernetes: kubernetes) + end + + it 'sets KUBE_INGRESS_BASE_DOMAIN' do + expect(subject).to include( + { key: 'KUBE_INGRESS_BASE_DOMAIN', value: cluster.domain, public: true } + ) + end + end end describe '#terminals' do diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 29197ef372e..447279f19a8 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -60,6 +60,10 @@ describe CacheMarkdownField do changes_applied end end + + def has_attribute?(attr_name) + attribute_names.include?(attr_name) + end end def thing_subclass(new_attr) @@ -72,8 +76,8 @@ describe CacheMarkdownField do let(:updated_markdown) { '`Bar`' } let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' } - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) } - let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 } before do stub_commonmark_sourcepos_disabled @@ -94,11 +98,11 @@ describe CacheMarkdownField do it { expect(thing.foo).to eq(markdown) } it { expect(thing.foo_html).to eq(html) } it { expect(thing.foo_html_changed?).not_to be_truthy } - it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } end context 'a changed markdown field' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) } before do thing.foo = updated_markdown @@ -139,7 +143,7 @@ describe CacheMarkdownField do end context 'a non-markdown field changed' do - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) } before do thing.bar = 'OK' @@ -160,7 +164,7 @@ describe CacheMarkdownField do end it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } end describe '#cached_html_up_to_date?' do @@ -174,21 +178,35 @@ describe CacheMarkdownField do is_expected.to be_falsy end - it 'returns false when the version is too early' do - thing.cached_markdown_version -= 1 + it 'returns false when the cached version is too old' do + thing.cached_markdown_version = cache_version - 1 is_expected.to be_falsy end - it 'returns false when the version is too late' do - thing.cached_markdown_version += 1 + it 'returns false when the cached version is in future' do + thing.cached_markdown_version = cache_version + 1 is_expected.to be_falsy end - it 'returns true when the version is just right' do + it 'returns false when the local version was bumped' do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) thing.cached_markdown_version = cache_version + is_expected.to be_falsy + end + + it 'returns true when the local version is default' do + thing.cached_markdown_version = cache_version + + is_expected.to be_truthy + end + + it 'returns true when the cached version is just right' do + allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2) + thing.cached_markdown_version = cache_version + 2 + is_expected.to be_truthy end @@ -221,14 +239,9 @@ describe CacheMarkdownField do describe '#latest_cached_markdown_version' do subject { thing.latest_cached_markdown_version } - it 'returns commonmark version' do - thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START + 1 - is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) - end - - it 'returns default version when version is nil' do + it 'returns default version' do thing.cached_markdown_version = nil - is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) + is_expected.to eq(cache_version) end end @@ -255,7 +268,7 @@ describe CacheMarkdownField do thing.cached_markdown_version = nil thing.refresh_markdown_cache - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) + expect(thing.cached_markdown_version).to eq(cache_version) end end @@ -336,7 +349,7 @@ describe CacheMarkdownField do expect(thing.foo_html).to eq(updated_html) expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) + expect(thing.cached_markdown_version).to eq(cache_version) end end @@ -356,7 +369,7 @@ describe CacheMarkdownField do expect(thing.foo_html).to eq(updated_html) expect(thing.baz_html).to eq(updated_html) - expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) + expect(thing.cached_markdown_version).to eq(cache_version) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ae137aa7b78..c1767ed0535 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1765,7 +1765,7 @@ describe Project do context 'using a regular repository' do it 'creates the repository' do expect(shell).to receive(:create_repository) - .with(project.repository_storage, project.disk_path) + .with(project.repository_storage, project.disk_path, project.full_path) .and_return(true) expect(project.repository).to receive(:after_create) @@ -1775,7 +1775,7 @@ describe Project do it 'adds an error if the repository could not be created' do expect(shell).to receive(:create_repository) - .with(project.repository_storage, project.disk_path) + .with(project.repository_storage, project.disk_path, project.full_path) .and_return(false) expect(project.repository).not_to receive(:after_create) @@ -1808,7 +1808,7 @@ describe Project do .and_return(false) allow(shell).to receive(:create_repository) - .with(project.repository_storage, project.disk_path) + .with(project.repository_storage, project.disk_path, project.full_path) .and_return(true) expect(project).to receive(:create_repository).with(force: true) @@ -1832,7 +1832,7 @@ describe Project do .and_return(false) expect(shell).to receive(:create_repository) - .with(project.repository_storage, project.disk_path) + .with(project.repository_storage, project.disk_path, project.full_path) .and_return(true) project.ensure_repository diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 48a43801b9f..3ccc706edf2 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -7,7 +7,7 @@ describe ProjectWiki do let(:repository) { project.repository } let(:gitlab_shell) { Gitlab::Shell.new } let(:project_wiki) { described_class.new(project, user) } - let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo') } + let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo', 'group/project.wiki') } let(:commit) { project_wiki.repository.head_commit } subject { project_wiki } @@ -75,7 +75,7 @@ describe ProjectWiki do # Create a fresh project which will not have a wiki project_wiki = described_class.new(create(:project), user) gitlab_shell = double(:gitlab_shell) - allow(gitlab_shell).to receive(:create_repository) + allow(gitlab_shell).to receive(:create_wiki_repository) allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell) expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4978c43c9b5..f78760bf567 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2291,6 +2291,7 @@ describe Repository do expect(subject).to be_a(Gitlab::Git::Repository) expect(subject.relative_path).to eq(project.disk_path + '.git') expect(subject.gl_repository).to eq("project-#{project.id}") + expect(subject.gl_project_path).to eq(project.full_path) end context 'with a wiki repository' do @@ -2300,6 +2301,7 @@ describe Repository do expect(subject).to be_a(Gitlab::Git::Repository) expect(subject.relative_path).to eq(project.disk_path + '.wiki.git') expect(subject.gl_repository).to eq("wiki-#{project.id}") + expect(subject.gl_project_path).to eq(project.full_path) end end end diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb index 3797960ac3d..7eeb2fae57d 100644 --- a/spec/models/resource_label_event_spec.rb +++ b/spec/models/resource_label_event_spec.rb @@ -81,14 +81,14 @@ RSpec.describe ResourceLabelEvent, type: :model do expect(subject.outdated_markdown?).to be true end - it 'returns true markdown is outdated' do - subject.attributes = { cached_markdown_version: 0 } + it 'returns true if markdown is outdated' do + subject.attributes = { cached_markdown_version: ((CacheMarkdownField::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 } expect(subject.outdated_markdown?).to be true end it 'returns false if label and reference are set' do - subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION } + subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 } expect(subject.outdated_markdown?).to be false end diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index e85e7a41017..bb1db9a3d51 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe BlobPresenter, :seed_helper do - let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') } + let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') } let(:git_blob) do Gitlab::Git::Blob.find( diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb new file mode 100644 index 00000000000..3769f8b78e4 --- /dev/null +++ b/spec/requests/api/group_labels_spec.rb @@ -0,0 +1,258 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::GroupLabels do + let(:user) { create(:user) } + let(:group) { create(:group) } + let!(:group_member) { create(:group_member, group: group, user: user) } + let!(:label1) { create(:group_label, title: 'feature', group: group) } + let!(:label2) { create(:group_label, title: 'bug', group: group) } + + describe 'GET :id/labels' do + it 'returns all available labels for the group' do + get api("/groups/#{group.id}/labels", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/group_labels') + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug') + end + end + + describe 'POST /groups/:id/labels' do + it 'returns created label when all params are given' do + post api("/groups/#{group.id}/labels", user), + params: { + name: 'Foo', + color: '#FFAABB', + description: 'test' + } + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq('Foo') + expect(json_response['color']).to eq('#FFAABB') + expect(json_response['description']).to eq('test') + end + + it 'returns created label when only required params are given' do + post api("/groups/#{group.id}/labels", user), + params: { + name: 'Foo & Bar', + color: '#FFAABB' + } + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('Foo & Bar') + expect(json_response['color']).to eq('#FFAABB') + expect(json_response['description']).to be_nil + end + + it 'returns a 400 bad request if name not given' do + post api("/groups/#{group.id}/labels", user), params: { color: '#FFAABB' } + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns a 400 bad request if color is not given' do + post api("/groups/#{group.id}/labels", user), params: { name: 'Foobar' } + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 409 if label already exists' do + post api("/groups/#{group.id}/labels", user), + params: { + name: label1.name, + color: '#FFAABB' + } + + expect(response).to have_gitlab_http_status(409) + expect(json_response['message']).to eq('Label already exists') + end + end + + describe 'DELETE /groups/:id/labels' do + it 'returns 204 for existing label' do + delete api("/groups/#{group.id}/labels", user), params: { name: label1.name } + + expect(response).to have_gitlab_http_status(204) + end + + it 'returns 404 for non existing label' do + delete api("/groups/#{group.id}/labels", user), params: { name: 'label2' } + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Label Not Found') + end + + it 'returns 400 for wrong parameters' do + delete api("/groups/#{group.id}/labels", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "does not delete parent's group labels", :nested_groups do + subgroup = create(:group, parent: group) + subgroup_label = create(:group_label, title: 'feature', group: subgroup) + + delete api("/groups/#{subgroup.id}/labels", user), params: { name: subgroup_label.name } + + expect(response).to have_gitlab_http_status(204) + expect(subgroup.labels.size).to eq(0) + expect(group.labels).to include(label1) + end + + it_behaves_like '412 response' do + let(:request) { api("/groups/#{group.id}/labels", user) } + let(:params) { { name: label1.name } } + end + end + + describe 'PUT /groups/:id/labels' do + it 'returns 200 if name and colors and description are changed' do + put api("/groups/#{group.id}/labels", user), + params: { + name: label1.name, + new_name: 'New Label', + color: '#FFFFFF', + description: 'test' + } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['name']).to eq('New Label') + expect(json_response['color']).to eq('#FFFFFF') + expect(json_response['description']).to eq('test') + end + + it "does not update parent's group label", :nested_groups do + subgroup = create(:group, parent: group) + subgroup_label = create(:group_label, title: 'feature', group: subgroup) + + put api("/groups/#{subgroup.id}/labels", user), + params: { + name: subgroup_label.name, + new_name: 'New Label' + } + + expect(response).to have_gitlab_http_status(200) + expect(subgroup.labels[0].name).to eq('New Label') + expect(label1.name).to eq('feature') + end + + it 'returns 404 if label does not exist' do + put api("/groups/#{group.id}/labels", user), + params: { + name: 'label2', + new_name: 'label3' + } + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns 400 if no label name given' do + put api("/groups/#{group.id}/labels", user), params: { new_name: label1.name } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('name is missing') + end + + it 'returns 400 if no new parameters given' do + put api("/groups/#{group.id}/labels", user), params: { name: label1.name } + + expect(response).to have_gitlab_http_status(400) + expect(json_response['error']).to eq('new_name, color, description are missing, '\ + 'at least one parameter must be provided') + end + end + + describe 'POST /groups/:id/labels/:label_id/subscribe' do + context 'when label_id is a label title' do + it 'subscribes to the label' do + post api("/groups/#{group.id}/labels/#{label1.title}/subscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(label1.title) + expect(json_response['subscribed']).to be_truthy + end + end + + context 'when label_id is a label ID' do + it 'subscribes to the label' do + post api("/groups/#{group.id}/labels/#{label1.id}/subscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(label1.title) + expect(json_response['subscribed']).to be_truthy + end + end + + context 'when user is already subscribed to label' do + before do + label1.subscribe(user) + end + + it 'returns 304' do + post api("/groups/#{group.id}/labels/#{label1.id}/subscribe", user) + + expect(response).to have_gitlab_http_status(304) + end + end + + context 'when label ID is not found' do + it 'returns 404 error' do + post api("/groups/#{group.id}/labels/1234/subscribe", user) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe 'POST /groups/:id/labels/:label_id/unsubscribe' do + before do + label1.subscribe(user) + end + + context 'when label_id is a label title' do + it 'unsubscribes from the label' do + post api("/groups/#{group.id}/labels/#{label1.title}/unsubscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(label1.title) + expect(json_response['subscribed']).to be_falsey + end + end + + context 'when label_id is a label ID' do + it 'unsubscribes from the label' do + post api("/groups/#{group.id}/labels/#{label1.id}/unsubscribe", user) + + expect(response).to have_gitlab_http_status(201) + expect(json_response['name']).to eq(label1.title) + expect(json_response['subscribed']).to be_falsey + end + end + + context 'when user is already unsubscribed from label' do + before do + label1.unsubscribe(user) + end + + it 'returns 304' do + post api("/groups/#{group.id}/labels/#{label1.id}/unsubscribe", user) + + expect(response).to have_gitlab_http_status(304) + end + end + + context 'when label ID is not found' do + it 'returns 404 error' do + post api("/groups/#{group.id}/labels/1234/unsubscribe", user) + + expect(response).to have_gitlab_http_status(404) + end + end + end +end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 51343287a13..0f5f6e38819 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -951,6 +951,29 @@ describe API::MergeRequests do expect(response).to have_gitlab_http_status(404) end + + describe "the squash_commit_message param" do + let(:squash_commit) do + project.repository.commits_between(json_response['diff_refs']['start_sha'], json_response['merge_commit_sha']).first + end + + it "results in a specific squash commit message when set" do + squash_commit_message = 'My custom squash commit message' + + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { + squash: true, + squash_commit_message: squash_commit_message + } + + expect(squash_commit.message.chomp).to eq(squash_commit_message) + end + + it "results in a default squash commit message when not set" do + put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { squash: true } + + expect(squash_commit.message).to eq(merge_request.default_squash_commit_message) + end + end end describe "PUT /projects/:id/merge_requests/:merge_request_iid" do diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 45fb1562e84..f33eb5b9e02 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -64,7 +64,8 @@ describe API::Settings, 'Settings' do performance_bar_allowed_group_path: group.full_path, instance_statistics_visibility_private: true, diff_max_patch_bytes: 150_000, - default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE + default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE, + local_markdown_version: 3 } expect(response).to have_gitlab_http_status(200) @@ -90,6 +91,7 @@ describe API::Settings, 'Settings' do expect(json_response['instance_statistics_visibility_private']).to be(true) expect(json_response['diff_max_patch_bytes']).to eq(150_000) expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE) + expect(json_response['local_markdown_version']).to eq(3) end end diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 2b148c1b563..2a455523e2c 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -35,7 +35,7 @@ describe 'OpenID Connect requests' do 'name' => 'Alice', 'nickname' => 'alice', 'email' => 'public@example.com', - 'email_verified' => true, + 'email_verified' => false, 'website' => 'https://example.com', 'profile' => 'http://localhost/alice', 'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png", @@ -111,6 +111,18 @@ describe 'OpenID Connect requests' do it 'does not include any unknown claims' do expect(json_response.keys).to eq %w[sub sub_legacy] + user_info_claims.keys end + + it 'includes email and email_verified claims' do + expect(json_response.keys).to include('email', 'email_verified') + end + + it 'has public email in email claim' do + expect(json_response['email']).to eq(user.public_email) + end + + it 'has false in email_verified claim' do + expect(json_response['email_verified']).to eq(false) + end end context 'ID token payload' do @@ -175,7 +187,35 @@ describe 'OpenID Connect requests' do expect(response).to have_gitlab_http_status(200) expect(json_response['issuer']).to eq('http://localhost') expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys') - expect(json_response['scopes_supported']).to eq(%w[api read_user sudo read_repository openid]) + expect(json_response['scopes_supported']).to eq(%w[api read_user sudo read_repository openid profile email]) + end + end + + context 'Application with OpenID and email scopes' do + let(:application) { create :oauth_application, scopes: 'openid email' } + + it 'token response includes an ID token' do + request_access_token! + + expect(json_response).to include 'id_token' + end + + context 'UserInfo payload' do + before do + request_user_info! + end + + it 'includes the email and email_verified claims' do + expect(json_response.keys).to include('email', 'email_verified') + end + + it 'has private email in email claim' do + expect(json_response['email']).to eq(user.email) + end + + it 'has true in email_verified claim' do + expect(json_response['email_verified']).to eq(true) + end end end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 54ce33dd103..d1b110b9806 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -116,7 +116,7 @@ describe Projects::CreateService, '#execute' do def wiki_repo(project) relative_path = ProjectWiki.new(project).disk_path + '.git' - Gitlab::Git::Repository.new(project.repository_storage, relative_path, 'foobar') + Gitlab::Git::Repository.new(project.repository_storage, relative_path, 'foobar', project.full_path) end end @@ -198,7 +198,7 @@ describe Projects::CreateService, '#execute' do context 'with legacy storage' do before do - gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing") + gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing", 'group/project') end after do @@ -234,7 +234,7 @@ describe Projects::CreateService, '#execute' do end before do - gitlab_shell.create_repository(repository_storage, hashed_path) + gitlab_shell.create_repository(repository_storage, hashed_path, 'group/project') end after do diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 26e8d829345..23ec29cce7b 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -119,7 +119,7 @@ describe Projects::ForkService do let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } before do - gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}") + gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}", "#{@to_user.namespace.full_path}/#{@from_project.path}") end after do diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 766276fdba3..aae50d5307f 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -201,7 +201,7 @@ describe Projects::TransferService do before do group.add_owner(user) - unless gitlab_shell.create_repository(repository_storage, "#{group.full_path}/#{project.path}") + unless gitlab_shell.create_repository(repository_storage, "#{group.full_path}/#{project.path}", project.full_path) raise 'failed to add repository' end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 8adfc63222e..90eaea9c872 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -232,7 +232,7 @@ describe Projects::UpdateService do let(:project) { create(:project, :legacy_storage, :repository, creator: user, namespace: user.namespace) } before do - gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing") + gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing", user.namespace.full_path) end after do diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb index cb1b9e6f5fb..2a2539c80b5 100644 --- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb +++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb @@ -7,56 +7,9 @@ describe 'projects/settings/ci_cd/_autodevops_form' do assign :project, project end - context 'when kubernetes is not active' do - context 'when auto devops domain is not defined' do - it 'shows warning message' do - render + it 'shows a warning message about Kubernetes cluster' do + render - expect(rendered).to have_css('.auto-devops-warning-message') - expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name and a') - expect(rendered).to have_link('Kubernetes cluster') - end - end - - context 'when auto devops domain is defined' do - before do - project.build_auto_devops(domain: 'example.com') - end - - it 'shows warning message' do - render - - expect(rendered).to have_css('.auto-devops-warning-message') - expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a') - expect(rendered).to have_link('Kubernetes cluster') - end - end - end - - context 'when kubernetes is active' do - before do - create(:kubernetes_service, project: project) - end - - context 'when auto devops domain is not defined' do - it 'shows warning message' do - render - - expect(rendered).to have_css('.auto-devops-warning-message') - expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name to work correctly.') - end - end - - context 'when auto devops domain is defined' do - before do - project.build_auto_devops(domain: 'example.com') - end - - it 'does not show warning message' do - render - - expect(rendered).not_to have_css('.auto-devops-warning-message') - end - end + expect(rendered).to have_text('You must add a Kubernetes cluster integration to this project with a domain in order for your deployment strategy to work correctly.') end end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index 781f91ac9ca..31bfe88d0bd 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -24,12 +24,7 @@ describe RepositoryForkWorker do end def expect_fork_repository - expect(shell).to receive(:fork_repository).with( - 'default', - project.disk_path, - forked_project.repository_storage, - forked_project.disk_path - ) + expect(shell).to receive(:fork_repository).with(project, forked_project) end describe 'when a worker was reset without cleanup' do |