summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean McGivern <sean@gitlab.com>2019-02-07 14:46:17 +0000
committerSean McGivern <sean@gitlab.com>2019-02-07 14:46:17 +0000
commitc3da8849c364885841fddf592346974ff4148c8d (patch)
tree32ee05526f2b193dbd92db8d13e84cf909f1777d
parent1a0bab0ab2896f021a5d3a80e84b5e9d6678f6c0 (diff)
parentc8fe0d6a8a76ee5865c71b5b6627608e5075797d (diff)
downloadgitlab-ce-c3da8849c364885841fddf592346974ff4148c8d.tar.gz
Merge branch 'master' into fabsrc/gitlab-ce-2105-add-setting-for-first-day-of-the-week
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue18
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue3
-rw-r--r--app/assets/javascripts/diffs/components/diff_stats.vue52
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue11
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js2
-rw-r--r--app/assets/javascripts/lib/utils/file_upload.js3
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue6
-rw-r--r--app/assets/javascripts/pages/dashboard/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/explore/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/environments/metrics/index.js2
-rw-r--r--app/assets/stylesheets/framework/files.scss20
-rw-r--r--app/assets/stylesheets/framework/variables.scss7
-rw-r--r--app/assets/stylesheets/pages/diff.scss52
-rw-r--r--app/assets/stylesheets/pages/projects.scss17
-rw-r--r--app/controllers/clusters/clusters_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb6
-rw-r--r--app/controllers/projects/environments_controller.rb5
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/helpers/application_settings_helper.rb3
-rw-r--r--app/helpers/auto_devops_helper.rb37
-rw-r--r--app/models/application_setting.rb7
-rw-r--r--app/models/clusters/cluster.rb39
-rw-r--r--app/models/clusters/platforms/kubernetes.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb23
-rw-r--r--app/models/pool_repository.rb4
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_auto_devops.rb5
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/repository.rb5
-rw-r--r--app/services/labels/update_service.rb1
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/views/clusters/clusters/_form.html.haml17
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml4
-rw-r--r--app/views/projects/diffs/_file.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml19
-rw-r--r--app/views/shared/projects/_project.html.haml41
-rw-r--r--app/workers/repository_fork_worker.rb10
-rw-r--r--changelogs/unreleased/44332-add-openid-profile-scopes.yml5
-rw-r--r--changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml5
-rw-r--r--changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml5
-rw-r--r--changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml5
-rw-r--r--changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml5
-rw-r--r--changelogs/unreleased/56788-unicorn-metric-labels.yml5
-rw-r--r--changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml5
-rw-r--r--changelogs/unreleased/adriel-remove-feature-flag.yml5
-rw-r--r--changelogs/unreleased/api-group-labels.yml5
-rw-r--r--changelogs/unreleased/fix-repo-settings-file-upload-error.yml5
-rw-r--r--changelogs/unreleased/gitaly-update-1.18.0.yml5
-rw-r--r--changelogs/unreleased/jlenny-NewAndroidTemplate.yml5
-rw-r--r--changelogs/unreleased/local-markdown-version-bkp3.yml5
-rw-r--r--changelogs/unreleased/support-chunking-in-client.yml5
-rw-r--r--config/initializers/doorkeeper_openid_connect.rb23
-rw-r--r--config/locales/doorkeeper.en.yml6
-rw-r--r--db/migrate/20140502125220_migrate_repo_size.rb2
-rw-r--r--db/migrate/20190130091630_add_local_cached_markdown_version.rb11
-rw-r--r--db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb49
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/index.md1
-rw-r--r--doc/administration/invalidate_markdown_cache.md16
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md2
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/group_labels.md201
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/settings.md7
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/development/contributing/merge_request_workflow.md3
-rw-r--r--doc/install/installation.md13
-rw-r--r--doc/topics/autodevops/index.md39
-rw-r--r--doc/user/group/clusters/index.md14
-rw-r--r--doc/user/project/clusters/index.md19
-rw-r--r--doc/user/project/pages/getting_started_part_three.md19
-rw-r--r--doc/workflow/repository_mirroring.md8
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb13
-rw-r--r--lib/api/group_labels.rb63
-rw-r--r--lib/api/helpers.rb11
-rw-r--r--lib/api/helpers/label_helpers.rb82
-rw-r--r--lib/api/labels.rb81
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/settings.rb1
-rw-r--r--lib/api/subscriptions.rb87
-rw-r--r--lib/backup/repository.rb2
-rw-r--r--lib/gitlab/auth.rb5
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb6
-rw-r--r--lib/gitlab/bitbucket_import/wiki_formatter.rb25
-rw-r--r--lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml121
-rw-r--r--lib/gitlab/ci/templates/Android.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml41
-rw-r--r--lib/gitlab/git/object_pool.rb9
-rw-r--r--lib/gitlab/git/repository.rb7
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb33
-rw-r--r--lib/gitlab/gitaly_client/util.rb8
-rw-r--r--lib/gitlab/github_import/importer/repository_importer.rb9
-rw-r--r--lib/gitlab/legacy_github_import/importer.rb2
-rw-r--r--lib/gitlab/legacy_github_import/wiki_formatter.rb4
-rw-r--r--lib/gitlab/metrics/samplers/unicorn_sampler.rb8
-rw-r--r--lib/gitlab/shell.rb67
-rw-r--r--locale/gitlab.pot52
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb5
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb6
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb21
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb69
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb31
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb25
-rw-r--r--spec/features/snippets/show_spec.rb8
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/group_labels.json18
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb35
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_stats_spec.js33
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js6
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js19
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper_spec.js48
-rw-r--r--spec/javascripts/lib/utils/file_upload_spec.js44
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js220
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js16
-rw-r--r--spec/javascripts/monitoring/mock_data.js1
-rw-r--r--spec/javascripts/notes/mock_data.js1
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb2
-rw-r--r--spec/lib/gitlab/auth_spec.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb29
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb2
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb4
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb4
-rw-r--r--spec/lib/gitlab/git/compare_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/git/remote_repository_spec.rb16
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb97
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/util_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb14
-rw-r--r--spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb4
-rw-r--r--spec/lib/gitlab/shell_spec.rb17
-rw-r--r--spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb106
-rw-r--r--spec/models/application_setting_spec.rb7
-rw-r--r--spec/models/clusters/cluster_spec.rb105
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb13
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb55
-rw-r--r--spec/models/project_spec.rb8
-rw-r--r--spec/models/project_wiki_spec.rb4
-rw-r--r--spec/models/repository_spec.rb2
-rw-r--r--spec/models/resource_label_event_spec.rb6
-rw-r--r--spec/presenters/blob_presenter_spec.rb2
-rw-r--r--spec/requests/api/group_labels_spec.rb258
-rw-r--r--spec/requests/api/merge_requests_spec.rb23
-rw-r--r--spec/requests/api/settings_spec.rb4
-rw-r--r--spec/requests/openid_connect_spec.rb44
-rw-r--r--spec/services/projects/create_service_spec.rb6
-rw-r--r--spec/services/projects/fork_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/services/projects/update_service_spec.rb2
-rw-r--r--spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb53
-rw-r--r--spec/workers/repository_fork_worker_spec.rb7
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
diff --git a/Gemfile b/Gemfile
index a2527b17b70..a3b01c275ce 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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