summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml5
-rw-r--r--.gitlab/ci/dev-fixtures.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml3
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml7
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/memory.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/pages.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml1
-rw-r--r--.gitlab/issue_templates/Feature proposal.md4
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js153
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue1
-rw-r--r--app/assets/javascripts/user_popovers.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue54
-rw-r--r--app/assets/stylesheets/framework/blocks.scss14
-rw-r--r--app/assets/stylesheets/framework/mixins.scss18
-rw-r--r--app/assets/stylesheets/pages/profile.scss19
-rw-r--r--app/controllers/projects/tags/releases_controller.rb6
-rw-r--r--app/helpers/submodule_helper.rb2
-rw-r--r--app/helpers/users_helper.rb15
-rw-r--r--app/models/concerns/bulk_insert_safe.rb62
-rw-r--r--app/models/concerns/has_repository.rb3
-rw-r--r--app/models/project.rb15
-rw-r--r--app/models/project_wiki.rb25
-rw-r--r--app/models/release.rb2
-rw-r--r--app/services/ci/update_ci_ref_status_service.rb2
-rw-r--r--app/services/projects/update_repository_storage_service.rb13
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/help/ui.html.haml15
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/users/_cover_controls.html.haml2
-rw-r--r--app/views/users/_profile_basic_info.html.haml2
-rw-r--r--app/views/users/show.html.haml47
-rw-r--r--changelogs/unreleased/207962-deploy-ecs.yml5
-rw-r--r--changelogs/unreleased/208887-optimize-project-counters-with-slack-service.yml5
-rw-r--r--changelogs/unreleased/209783-follow-up-from-resolve-notifications-for-when-pipelines-are-fixed.yml5
-rw-r--r--changelogs/unreleased/24072-user-profile-add-job-title.yml5
-rw-r--r--changelogs/unreleased/27880-clearing-release-note-from-the-tags-page-deletes-release.yml5
-rw-r--r--changelogs/unreleased/fj-fix-bug-hook-env.yml5
-rw-r--r--changelogs/unreleased/mermaid-fix.yml5
-rw-r--r--config/environments/development.rb3
-rw-r--r--config/initializers/5_backend.rb2
-rw-r--r--db/migrate/20200306192548_add_index_on_project_id_and_type_to_services.rb22
-rw-r--r--db/migrate/20200306193236_add_index_on_creator_id_and_created_at_to_projects.rb22
-rw-r--r--db/schema.rb4
-rw-r--r--doc/api/releases/index.md2
-rw-r--r--doc/ci/cloud_deployment/index.md58
-rw-r--r--doc/ci/img/ecs_dashboard_v12_9.pngbin0 -> 109395 bytes
-rw-r--r--doc/development/elasticsearch.md6
-rw-r--r--lib/api/internal/base.rb2
-rw-r--r--lib/api/releases.rb2
-rw-r--r--lib/backup/repository.rb34
-rw-r--r--lib/gitlab/ci/templates/Deploy-ECS.gitlab-ci.yml36
-rw-r--r--lib/gitlab/shell.rb100
-rw-r--r--lib/system_check/gitlab_shell_check.rb2
-rw-r--r--lib/tasks/gitlab/info.rake9
-rw-r--r--locale/gitlab.pot20
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/merge_request/show.rb5
-rw-r--r--qa/qa/page/project/web_ide/edit.rb15
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb42
-rw-r--r--spec/controllers/projects/tags/releases_controller_spec.rb10
-rw-r--r--spec/features/markdown/mermaid_spec.rb40
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb52
-rw-r--r--spec/features/users/show_spec.rb28
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js107
-rw-r--r--spec/helpers/users_helper_spec.rb38
-rw-r--r--spec/lib/backup/repository_spec.rb6
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/shell_spec.rb40
-rw-r--r--spec/models/concerns/bulk_insert_safe_spec.rb146
-rw-r--r--spec/models/project_spec.rb46
-rw-r--r--spec/models/project_wiki_spec.rb20
-rw-r--r--spec/models/release_spec.rb1
-rw-r--r--spec/requests/api/internal/base_spec.rb58
-rw-r--r--spec/requests/api/releases_spec.rb36
-rw-r--r--spec/services/ci/update_ci_ref_status_service_spec.rb8
-rw-r--r--spec/services/projects/fork_service_spec.rb2
-rw-r--r--spec/services/projects/update_repository_storage_service_spec.rb8
-rw-r--r--spec/services/releases/update_service_spec.rb6
-rw-r--r--spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb12
-rw-r--r--yarn.lock8
86 files changed, 1070 insertions, 542 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f5cef674375..be1818391ca 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,6 +12,11 @@ stages:
- post-qa
- pages
+# always use `gitlab-org` runners
+default:
+ tags:
+ - gitlab-org
+
workflow:
rules:
# If `$FORCE_GITLAB_CI` is set, create a pipeline.
diff --git a/.gitlab/ci/dev-fixtures.gitlab-ci.yml b/.gitlab/ci/dev-fixtures.gitlab-ci.yml
index 32f8ff45b14..e77a75d2822 100644
--- a/.gitlab/ci/dev-fixtures.gitlab-ci.yml
+++ b/.gitlab/ci/dev-fixtures.gitlab-ci.yml
@@ -1,6 +1,5 @@
.run-dev-fixtures:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .default-before_script
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 5b1f375d30e..946db0c4be8 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -1,6 +1,5 @@
.review-docs:
extends:
- - .default-tags
- .default-retry
- .docs:rules:review-docs
allow_failure: true
@@ -42,7 +41,6 @@ review-docs-cleanup:
docs lint:
extends:
- - .default-tags
- .default-retry
- .docs:rules:docs-lint
image: "registry.gitlab.com/gitlab-org/gitlab-docs:lint"
@@ -64,7 +62,6 @@ docs lint:
graphql-reference-verify:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .default-before_script
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 3ce61ec6e54..8685ccc5432 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -10,7 +10,6 @@
.gitlab:assets:compile-metadata:
extends:
- - .default-tags
- .default-retry
- .default-before_script
- .assets-compile-cache
@@ -64,7 +63,6 @@ gitlab:assets:compile pull-cache:
.compile-assets-metadata:
extends:
- - .default-tags
- .default-retry
- .default-before_script
- .assets-compile-cache
@@ -122,7 +120,6 @@ compile-assets pull-cache as-if-foss:
.frontend-fixtures-base:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .default-before_script
@@ -160,7 +157,6 @@ frontend-fixtures-as-if-foss:
.frontend-job-base:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .default-before_script
@@ -242,7 +238,6 @@ jest-as-if-foss:
coverage-frontend:
extends:
- - .default-tags
- .default-retry
- .frontend:rules:default-frontend-jobs
needs: ["jest"]
@@ -262,7 +257,6 @@ coverage-frontend:
.qa-frontend-node:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .frontend:rules:qa-frontend-node
@@ -290,7 +284,6 @@ qa-frontend-node:latest:
webpack-dev-server:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .frontend:rules:default-frontend-jobs
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index bd2e49bde31..d5c89eacbb2 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -1,7 +1,3 @@
-.default-tags:
- tags:
- - gitlab-org
-
.default-retry:
retry:
max: 2 # This is confusing but this means "3 runs at max".
diff --git a/.gitlab/ci/memory.gitlab-ci.yml b/.gitlab/ci/memory.gitlab-ci.yml
index a3ec5dfab9f..b2267b03c5f 100644
--- a/.gitlab/ci/memory.gitlab-ci.yml
+++ b/.gitlab/ci/memory.gitlab-ci.yml
@@ -1,6 +1,5 @@
.only-code-memory-job-base:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .default-before_script
diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml
index 7c6f25cfe6b..983f675d1d6 100644
--- a/.gitlab/ci/pages.gitlab-ci.yml
+++ b/.gitlab/ci/pages.gitlab-ci.yml
@@ -1,6 +1,5 @@
pages:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .pages:rules
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index c25f30f39a8..b0713c0944a 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -1,6 +1,5 @@
.qa-job-base:
extends:
- - .default-tags
- .default-retry
stage: test
needs: []
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 2f014824c91..f9074adeaba 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -7,7 +7,6 @@
.rails-job-base:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .default-before_script
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index c8d633bcdf3..14b1561ec1a 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -1,6 +1,5 @@
.review-docker:
extends:
- - .default-tags
- .default-retry
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.6
services:
@@ -29,7 +28,6 @@ build-qa-image:
review-cleanup:
extends:
- - .default-tags
- .default-retry
- .review:rules:review-cleanup
stage: prepare
@@ -46,7 +44,6 @@ review-cleanup:
review-build-cng:
extends:
- - .default-tags
- .default-retry
- .review:rules:mr-and-schedule
image: ruby:2.6-alpine
@@ -63,7 +60,6 @@ review-build-cng:
.review-workflow-base:
extends:
- - .default-tags
- .default-retry
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
variables:
@@ -217,7 +213,6 @@ review-performance:
parallel-spec-reports:
extends:
- - .default-tags
- .review:rules:mr-only-manual
image: ruby:2.6-alpine
stage: post-qa
@@ -244,7 +239,6 @@ parallel-spec-reports:
danger-review:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .review:rules:danger
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
index b4b911ab607..b1918961f3e 100644
--- a/.gitlab/ci/setup.gitlab-ci.yml
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -2,7 +2,6 @@
# rubygems.org in the future.
cache gems:
extends:
- - .default-tags
- .default-retry
- .default-cache
- .default-before_script
@@ -21,7 +20,6 @@ cache gems:
.minimal-job:
extends:
- - .default-tags
- .default-retry
needs: []
diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml
index 6c0286ed18a..ab31dd59299 100644
--- a/.gitlab/ci/yaml.gitlab-ci.yml
+++ b/.gitlab/ci/yaml.gitlab-ci.yml
@@ -2,7 +2,6 @@
# This uses rules from project root `.yamllint`.
lint-ci-gitlab:
extends:
- - .default-tags
- .default-retry
- .yaml:rules
image: sdesbure/yamllint:latest
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index 4e52a69f415..2bbef723b21 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -59,6 +59,10 @@ See the test engineering planning process and reach out to your counterpart Soft
<!-- Which leads to: in which enterprise tier should this feature go? See https://about.gitlab.com/handbook/product/pricing/#four-tiers -->
+### Is this a cross-stage feature?
+
+<!-- Communicate if this change will affect multiple Stage Groups or product areas. We recommend always start with the assumption that a feature request will have an impact into another Group. Loop in the most relevant PM and Product Designer from that Group to provide strategic support to help align the Group's broader plan and vision, as well as to avoid UX and technical debt. https://about.gitlab.com/handbook/product/#cross-stage-features -->
+
### Links / references
/label ~feature
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index b5e17a0587d..fe63ebd470d 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -1,6 +1,7 @@
import flash from '~/flash';
import $ from 'jquery';
-import { sprintf, __ } from '../../locale';
+import { __, sprintf } from '~/locale';
+import { once } from 'lodash';
// Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class.
@@ -18,14 +19,10 @@ import { sprintf, __ } from '../../locale';
// This is an arbitrary number; Can be iterated upon when suitable.
const MAX_CHAR_LIMIT = 5000;
+let mermaidModule = {};
-function renderMermaids($els) {
- if (!$els.length) return;
-
- // A diagram may have been truncated in search results which will cause errors, so abort the render.
- if (document.querySelector('body').dataset.page === 'search:show') return;
-
- import(/* webpackChunkName: 'mermaid' */ 'mermaid')
+function importMermaidModule() {
+ return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
mermaid.initialize({
// mermaid core options
@@ -41,63 +38,127 @@ function renderMermaids($els) {
securityLevel: 'strict',
});
+ mermaidModule = mermaid;
+
+ return mermaid;
+ })
+ .catch(err => {
+ flash(sprintf(__("Can't load mermaid module: %{err}"), { err }));
+ // eslint-disable-next-line no-console
+ console.error(err);
+ });
+}
+
+function fixElementSource(el) {
+ // Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
+ const source = el.textContent.replace(/<br\s*\/>/g, '<br>');
+
+ // Remove any extra spans added by the backend syntax highlighting.
+ Object.assign(el, { textContent: source });
+
+ return { source };
+}
+
+function renderMermaidEl(el) {
+ mermaidModule.init(undefined, el, id => {
+ const source = el.textContent;
+ const svg = document.getElementById(id);
+
+ // As of https://github.com/knsv/mermaid/commit/57b780a0d,
+ // Mermaid will make two init callbacks:one to initialize the
+ // flow charts, and another to initialize the Gannt charts.
+ // Guard against an error caused by double initialization.
+ if (svg.classList.contains('mermaid')) {
+ return;
+ }
+
+ svg.classList.add('mermaid');
+
+ // pre > code > svg
+ svg.closest('pre').replaceWith(svg);
+
+ // We need to add the original source into the DOM to allow Copy-as-GFM
+ // to access it.
+ const sourceEl = document.createElement('text');
+ sourceEl.classList.add('source');
+ sourceEl.setAttribute('display', 'none');
+ sourceEl.textContent = source;
+
+ svg.appendChild(sourceEl);
+ });
+}
+
+function renderMermaids($els) {
+ if (!$els.length) return;
+
+ // A diagram may have been truncated in search results which will cause errors, so abort the render.
+ if (document.querySelector('body').dataset.page === 'search:show') return;
+
+ importMermaidModule()
+ .then(() => {
let renderedChars = 0;
$els.each((i, el) => {
- // Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
- const source = el.textContent.replace(/<br\s*\/>/g, '<br>');
-
+ const { source } = fixElementSource(el);
/**
* Restrict the rendering to a certain amount of character to
* prevent mermaidjs from hanging up the entire thread and
* causing a DoS.
*/
if ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT) {
- el.textContent = sprintf(
- __(
- 'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.',
- ),
- { charLimit: MAX_CHAR_LIMIT },
- );
+ const html = `
+ <div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
+ <div>
+ <div class="display-flex">
+ <div>${__(
+ 'Warning: Displaying this diagram might cause performance issues on this page.',
+ )}</div>
+ <div class="gl-alert-actions">
+ <button class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md new-gl-button">Display</button>
+ </div>
+ </div>
+ <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </div>
+ </div>
+ `;
+
+ const $parent = $(el).parent();
+
+ if (!$parent.hasClass('lazy-alert-shown')) {
+ $parent.after(html);
+ $parent.addClass('lazy-alert-shown');
+ }
+
return;
}
renderedChars += source.length;
- // Remove any extra spans added by the backend syntax highlighting.
- Object.assign(el, { textContent: source });
-
- mermaid.init(undefined, el, id => {
- const svg = document.getElementById(id);
-
- // As of https://github.com/knsv/mermaid/commit/57b780a0d,
- // Mermaid will make two init callbacks:one to initialize the
- // flow charts, and another to initialize the Gannt charts.
- // Guard against an error caused by double initialization.
- if (svg.classList.contains('mermaid')) {
- return;
- }
-
- svg.classList.add('mermaid');
-
- // pre > code > svg
- svg.closest('pre').replaceWith(svg);
- // We need to add the original source into the DOM to allow Copy-as-GFM
- // to access it.
- const sourceEl = document.createElement('text');
- sourceEl.classList.add('source');
- sourceEl.setAttribute('display', 'none');
- sourceEl.textContent = source;
-
- svg.appendChild(sourceEl);
- });
+ renderMermaidEl(el);
});
})
.catch(err => {
- flash(`Can't load mermaid module: ${err}`);
+ flash(sprintf(__('Encountered an error while rendering: %{err}'), { err }));
+ // eslint-disable-next-line no-console
+ console.error(err);
});
}
+const hookLazyRenderMermaidEvent = once(() => {
+ $(document.body).on('click', '.js-lazy-render-mermaid', function eventHandler() {
+ const parent = $(this).closest('.js-lazy-render-mermaid-container');
+ const pre = parent.prev();
+
+ const el = pre.find('.js-render-mermaid');
+
+ parent.remove();
+
+ renderMermaidEl(el);
+ });
+});
+
export default function renderMermaid($els) {
if (!$els.length) return;
@@ -112,4 +173,6 @@ export default function renderMermaid($els) {
renderMermaids($(this).find('.js-render-mermaid'));
}
});
+
+ hookLazyRenderMermaidEvent();
}
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index bfb760f3579..c93a95e490a 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -308,6 +308,7 @@ export default {
'is-added': file.tempFile,
}"
class="multi-file-editor-holder"
+ data-qa-selector="editor_container"
@focusout="triggerFilesChange"
></div>
<content-viewer
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
index 5cc22f62262..f8c1c3634c2 100644
--- a/app/assets/javascripts/user_popovers.js
+++ b/app/assets/javascripts/user_popovers.js
@@ -39,6 +39,7 @@ const populateUserInfo = user => {
location: userData.location,
bio: userData.bio,
organization: userData.organization,
+ jobTitle: userData.job_title,
loaded: true,
});
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index b85be8b9652..c38272ab239 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -121,6 +121,7 @@ export default {
data-placement="bottom"
tabindex="0"
role="button"
+ data-qa-selector="open_in_web_ide_button"
>
{{ s__('mrWidget|Open in Web IDE') }}
</a>
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index ca25d9ee738..602d4ab89e1 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -1,8 +1,10 @@
<script>
-import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
+import { GlPopover, GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarImage from '../user_avatar/user_avatar_image.vue';
import { glEmojiTag } from '../../../emoji';
+import { s__ } from '~/locale';
+import { isString } from 'lodash';
export default {
name: 'UserPopover',
@@ -10,6 +12,7 @@ export default {
Icon,
GlPopover,
GlSkeletonLoading,
+ GlSprintf,
UserAvatarImage,
},
props: {
@@ -45,8 +48,27 @@ export default {
nameIsLoading() {
return !this.user.name;
},
- jobInfoIsLoading() {
- return !this.user.loaded && this.user.organization === null;
+ workInformationIsLoading() {
+ return !this.user.loaded && this.workInformation === null;
+ },
+ workInformation() {
+ const { jobTitle, organization } = this.user;
+
+ if (organization && jobTitle) {
+ return {
+ message: s__('Profile|%{job_title} at %{organization}'),
+ placeholders: { job_title: jobTitle, organization },
+ };
+ } else if (organization) {
+ return organization;
+ } else if (jobTitle) {
+ return jobTitle;
+ }
+
+ return null;
+ },
+ workInformationShouldUseSprintf() {
+ return !isString(this.workInformation);
},
locationIsLoading() {
return !this.user.loaded && this.user.location === null;
@@ -72,16 +94,30 @@ export default {
<gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" />
</div>
<div class="text-secondary">
- <div v-if="user.bio" class="js-bio d-flex mb-1">
+ <div v-if="user.bio" class="d-flex mb-1">
<icon name="profile" class="category-icon flex-shrink-0" />
- <span class="ml-1">{{ user.bio }}</span>
+ <span ref="bio" class="ml-1">{{ user.bio }}</span>
</div>
- <div v-if="user.organization" class="js-organization d-flex mb-1">
- <icon v-show="!jobInfoIsLoading" name="work" class="category-icon flex-shrink-0" />
- <span class="ml-1">{{ user.organization }}</span>
+ <div v-if="workInformation" class="d-flex mb-1">
+ <icon
+ v-show="!workInformationIsLoading"
+ name="work"
+ class="category-icon flex-shrink-0"
+ />
+ <span ref="workInformation" class="ml-1">
+ <gl-sprintf v-if="workInformationShouldUseSprintf" :message="workInformation.message">
+ <template
+ v-for="(placeholder, slotName) in workInformation.placeholders"
+ v-slot:[slotName]
+ >
+ <span :key="slotName">{{ placeholder }}</span>
+ </template>
+ </gl-sprintf>
+ <span v-else>{{ workInformation }}</span>
+ </span>
</div>
<gl-skeleton-loading
- v-if="jobInfoIsLoading"
+ v-if="workInformationIsLoading"
:lines="1"
class="animation-container-small mb-1"
/>
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 0e4080ce201..f922d8bcaab 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -161,13 +161,17 @@
}
.cover-controls {
- position: absolute;
- top: 10px;
- right: 10px;
+ @include media-breakpoint-up(sm) {
+ position: absolute;
+ top: 1rem;
+ right: 1.25rem;
+ }
&.left {
- left: 10px;
- right: auto;
+ @include media-breakpoint-up(sm) {
+ left: 1.25rem;
+ right: auto;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index fd448ee24ed..621a4eddc34 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -401,3 +401,21 @@
line-height: 16px;
text-align: center;
}
+
+@mixin middle-dot-divider {
+ &::after {
+ // Duplicate `content` property used as a fallback
+ // scss-lint:disable DuplicateProperty
+ content: '\00B7'; // middle dot fallback if browser does not support alternative content
+ content: '\00B7' / ''; // tell screen readers to ignore the content https://www.w3.org/TR/css-content-3/#accessibility
+ padding: 0 0.375rem;
+ font-weight: $gl-font-weight-bold;
+ }
+
+ &:last-child {
+ &::after {
+ content: '';
+ padding: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 43cf0d4bd70..82b3698287c 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -74,17 +74,12 @@
// Middle dot divider between each element in a list of items.
.middle-dot-divider {
- &::after {
- content: '\00B7'; // Middle Dot
- padding: 0 6px;
- font-weight: $gl-font-weight-bold;
- }
+ @include middle-dot-divider;
+}
- &:last-child {
- &::after {
- content: '';
- padding: 0;
- }
+.middle-dot-divider-sm {
+ @include media-breakpoint-up(sm) {
+ @include middle-dot-divider;
}
}
@@ -202,10 +197,6 @@
}
.user-profile {
- .cover-controls a {
- margin-left: 5px;
- }
-
.profile-header {
margin: 0 $gl-padding;
diff --git a/app/controllers/projects/tags/releases_controller.rb b/app/controllers/projects/tags/releases_controller.rb
index 5e4c601a693..c1f4cbce054 100644
--- a/app/controllers/projects/tags/releases_controller.rb
+++ b/app/controllers/projects/tags/releases_controller.rb
@@ -12,11 +12,7 @@ class Projects::Tags::ReleasesController < Projects::ApplicationController
end
def update
- if release_params[:description].present?
- release.update(release_params)
- else
- release.destroy
- end
+ release.update(release_params) if release.persisted? || release_params[:description].present?
redirect_to project_tag_path(@project, tag.name)
end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 32c613ab4ad..1149b168383 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -66,7 +66,7 @@ module SubmoduleHelper
project].join('')
url_with_dotgit = url_no_dotgit + '.git'
- url_with_dotgit == Gitlab::Shell.new.url_to_repo([namespace, '/', project].join(''))
+ url_with_dotgit == Gitlab::Shell.url_to_repo([namespace, '/', project].join(''))
end
def relative_self_url?(url)
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index e87bb27cf62..c1bca6b4c41 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -91,6 +91,21 @@ module UsersHelper
end
end
+ def work_information(user)
+ return unless user
+
+ organization = user.organization
+ job_title = user.job_title
+
+ if organization.present? && job_title.present?
+ s_('Profile|%{job_title} at %{organization}') % { job_title: job_title, organization: organization }
+ elsif job_title.present?
+ job_title
+ elsif organization.present?
+ organization
+ end
+ end
+
private
def get_profile_tabs
diff --git a/app/models/concerns/bulk_insert_safe.rb b/app/models/concerns/bulk_insert_safe.rb
index a4814fc0d48..a61db2dc148 100644
--- a/app/models/concerns/bulk_insert_safe.rb
+++ b/app/models/concerns/bulk_insert_safe.rb
@@ -61,12 +61,13 @@ module BulkInsertSafe
super
end
- # Inserts the given ActiveRecord [items] to the table mapped to this class via [InsertAll].
+ # Inserts the given ActiveRecord [items] to the table mapped to this class.
# Items will be inserted in batches of a given size, where insertion semantics are
- # "atomic across all batches", i.e. either all items will be inserted or none.
+ # "atomic across all batches".
#
# @param [Boolean] validate Whether validations should run on [items]
# @param [Integer] batch_size How many items should at most be inserted at once
+ # @param [Boolean] skip_duplicates Marks duplicates as allowed, and skips inserting them
# @param [Proc] handle_attributes Block that will receive each item attribute hash
# prior to insertion for further processing
#
@@ -75,26 +76,65 @@ module BulkInsertSafe
# - [ActiveRecord::RecordInvalid] on entity validation failures
# - [ActiveRecord::RecordNotUnique] on duplicate key errors
#
- # @return true if all items succeeded to be inserted, throws otherwise.
+ # @return true if operation succeeded, throws otherwise.
#
- def bulk_insert!(items, validate: true, batch_size: DEFAULT_BATCH_SIZE, &handle_attributes)
- return true if items.empty?
-
- _bulk_insert_in_batches(items, batch_size, validate, &handle_attributes)
+ def bulk_insert!(items, validate: true, skip_duplicates: false, batch_size: DEFAULT_BATCH_SIZE, &handle_attributes)
+ _bulk_insert_all!(items,
+ validate: validate,
+ on_duplicate: skip_duplicates ? :skip : :raise,
+ unique_by: nil,
+ batch_size: batch_size,
+ &handle_attributes)
+ end
- true
+ # Upserts the given ActiveRecord [items] to the table mapped to this class.
+ # Items will be inserted or updated in batches of a given size,
+ # where insertion semantics are "atomic across all batches".
+ #
+ # @param [Boolean] validate Whether validations should run on [items]
+ # @param [Integer] batch_size How many items should at most be inserted at once
+ # @param [Symbol/Array] unique_by Defines index or columns to use to consider item duplicate
+ # @param [Proc] handle_attributes Block that will receive each item attribute hash
+ # prior to insertion for further processing
+ #
+ # Unique indexes can be identified by columns or name:
+ # - unique_by: :isbn
+ # - unique_by: %i[ author_id name ]
+ # - unique_by: :index_books_on_isbn
+ #
+ # Note that this method will throw on the following occasions:
+ # - [PrimaryKeySetError] when primary keys are set on entities prior to insertion
+ # - [ActiveRecord::RecordInvalid] on entity validation failures
+ # - [ActiveRecord::RecordNotUnique] on duplicate key errors
+ #
+ # @return true if operation succeeded, throws otherwise.
+ #
+ def bulk_upsert!(items, unique_by:, validate: true, batch_size: DEFAULT_BATCH_SIZE, &handle_attributes)
+ _bulk_insert_all!(items,
+ validate: validate,
+ on_duplicate: :update,
+ unique_by: unique_by,
+ batch_size: batch_size,
+ &handle_attributes)
end
private
- def _bulk_insert_in_batches(items, batch_size, validate_items, &handle_attributes)
+ def _bulk_insert_all!(items, on_duplicate:, unique_by:, validate:, batch_size:, &handle_attributes)
+ return true if items.empty?
+
transaction do
items.each_slice(batch_size) do |item_batch|
- attributes = _bulk_insert_item_attributes(item_batch, validate_items, &handle_attributes)
+ attributes = _bulk_insert_item_attributes(
+ item_batch, validate, &handle_attributes)
- insert_all!(attributes)
+ ActiveRecord::InsertAll
+ .new(self, attributes, on_duplicate: on_duplicate, unique_by: unique_by)
+ .execute
end
end
+
+ true
end
def _bulk_insert_item_attributes(items, validate_items)
diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb
index 0887236e65e..cc792eab2e0 100644
--- a/app/models/concerns/has_repository.rb
+++ b/app/models/concerns/has_repository.rb
@@ -9,7 +9,6 @@
# needs any special behavior.
module HasRepository
extend ActiveSupport::Concern
- include Gitlab::ShellAdapter
include AfterCommitQueue
include Referable
include Gitlab::Utils::StrongMemoize
@@ -78,7 +77,7 @@ module HasRepository
end
def url_to_repo
- gitlab_shell.url_to_repo(full_path)
+ Gitlab::Shell.url_to_repo(full_path)
end
def ssh_url_to_repo
diff --git a/app/models/project.rb b/app/models/project.rb
index 6d9a0e5e813..4017fe31b84 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1460,13 +1460,14 @@ class Project < ApplicationRecord
# Forked import is handled asynchronously
return if forked? && !force
- if gitlab_shell.create_project_repository(self)
- repository.after_create
- true
- else
- errors.add(:base, _('Failed to create repository via gitlab-shell'))
- false
- end
+ repository.create_repository
+ repository.after_create
+
+ true
+ rescue => err
+ Gitlab::ErrorTracking.track_exception(err, project: { id: id, full_path: full_path, disk_path: disk_path })
+ errors.add(:base, _('Failed to create repository'))
+ false
end
def hook_attrs(backward: true)
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 8ed7811b468..f8528a41634 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
class ProjectWiki
- include Gitlab::ShellAdapter
include Storage::LegacyProjectWiki
+ include Gitlab::Utils::StrongMemoize
MARKUPS = {
'Markdown' => :markdown,
@@ -47,7 +47,7 @@ class ProjectWiki
end
def url_to_repo
- gitlab_shell.url_to_repo(full_path)
+ Gitlab::Shell.url_to_repo(full_path)
end
def ssh_url_to_repo
@@ -64,14 +64,15 @@ class ProjectWiki
# Returns the Gitlab::Git::Wiki object.
def wiki
- @wiki ||= begin
- gl_repository = Gitlab::GlRepository::WIKI.identifier_for_container(project)
- raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository, full_path)
+ strong_memoize(:wiki) do
+ repository.create_if_not_exists
+ raise CouldNotCreateWikiError unless repository_exists?
- create_repo!(raw_repository) unless raw_repository.exists?
-
- Gitlab::Git::Wiki.new(raw_repository)
+ Gitlab::Git::Wiki.new(repository.raw)
end
+ rescue => err
+ Gitlab::ErrorTracking.track_exception(err, project_wiki: { project_id: project.id, full_path: full_path, disk_path: disk_path })
+ raise CouldNotCreateWikiError
end
def repository_exists?
@@ -193,14 +194,6 @@ class ProjectWiki
private
- def create_repo!(raw_repository)
- gitlab_shell.create_wiki_repository(project)
-
- raise CouldNotCreateWikiError unless raw_repository.exists?
-
- repository.after_create
- end
-
def commit_details(action, message = nil, title = nil)
commit_message = message.presence || default_message(action, title)
git_user = Gitlab::Git::User.from_gitlab(@user)
diff --git a/app/models/release.rb b/app/models/release.rb
index 2543717895f..45c2a56d764 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -24,7 +24,7 @@ class Release < ApplicationRecord
accepts_nested_attributes_for :links, allow_destroy: true
- validates :description, :project, :tag, presence: true
+ validates :project, :tag, presence: true
validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") }
scope :sorted, -> { order(released_at: :desc) }
diff --git a/app/services/ci/update_ci_ref_status_service.rb b/app/services/ci/update_ci_ref_status_service.rb
index e5e5b94b629..4f7ac4d11b0 100644
--- a/app/services/ci/update_ci_ref_status_service.rb
+++ b/app/services/ci/update_ci_ref_status_service.rb
@@ -22,7 +22,7 @@ module Ci
begin
retry_optimistic_lock(ref) do
next false if ref.persisted? &&
- (ref.last_updated_by_pipeline_id || 0) >= pipeline.id
+ (ref.last_updated_by_pipeline_id || 0) > pipeline.id
ref.update(status: next_status(ref.status, pipeline.status),
last_updated_by_pipeline: pipeline)
diff --git a/app/services/projects/update_repository_storage_service.rb b/app/services/projects/update_repository_storage_service.rb
index 0adfd4f8fd7..0602089a3ab 100644
--- a/app/services/projects/update_repository_storage_service.rb
+++ b/app/services/projects/update_repository_storage_service.rb
@@ -52,11 +52,14 @@ module Projects
checksum = repository.checksum
# Initialize a git repository on the target path
- gitlab_shell.create_repository(new_storage_key, raw_repository.relative_path, full_path)
- new_repository = Gitlab::Git::Repository.new(new_storage_key,
- raw_repository.relative_path,
- raw_repository.gl_repository,
- full_path)
+ new_repository = Gitlab::Git::Repository.new(
+ new_storage_key,
+ raw_repository.relative_path,
+ raw_repository.gl_repository,
+ full_path
+ )
+
+ new_repository.create_repository
new_repository.replicate(raw_repository)
new_checksum = new_repository.checksum
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index f5f6175d3d8..68f761c75d8 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -99,7 +99,7 @@
%p
GitLab Shell
%span.float-right
- = Gitlab::Shell.new.version
+ = Gitlab::Shell.version
%p
GitLab Workhorse
%span.float-right
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 038befac420..d71650ae50c 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -1,5 +1,6 @@
- page_title "UI Development Kit", "Help"
- lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare."
+- link_classes = "flex-grow-1 mx-1 "
.gitlab-ui-dev-kit
%h1 GitLab UI development kit
@@ -64,7 +65,12 @@
Cover block for profile page with avatar, name and description
%code .cover-block
.example
- .cover-block
+ .cover-block.user-cover-block
+ = render layout: 'users/cover_controls' do
+ = link_to '#', class: link_classes + 'btn btn-default' do
+ = icon('pencil')
+ = link_to '#', class: link_classes + 'btn btn-default' do
+ = icon('rss')
.avatar-holder
= image_tag avatar_icon_for_email('admin@example.com', 90), class: "avatar s90", alt: ''
.cover-title
@@ -73,13 +79,6 @@
.cover-desc.cgray
= lorem
- .cover-controls
- = link_to '#', class: 'btn btn-default' do
- = icon('pencil')
- &nbsp;
- = link_to '#', class: 'btn btn-default' do
- = icon('rss')
-
%h2#lists Lists
.lead
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 49533c18c8f..86e157ee042 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -90,7 +90,6 @@
.row
= render 'profiles/name', form: f, user: @user
= f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' }
- = f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { prompt: _('Select your role') }, required: true, class: 'input-md'
= render_if_exists 'profiles/email_settings', form: f
= f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
@@ -101,6 +100,7 @@
= f.text_field :location, readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) }
- else
= f.text_field :location, label: s_('Profiles|Location'), class: 'input-lg', placeholder: s_("Profiles|City, country")
+ = f.text_field :job_title, class: 'input-md'
= f.text_field :organization, label: s_('Profiles|Organization'), class: 'input-md', help: s_("Profiles|Who you represent or work for")
= f.text_area :bio, label: s_('Profiles|Bio'), rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters")
%hr
diff --git a/app/views/users/_cover_controls.html.haml b/app/views/users/_cover_controls.html.haml
new file mode 100644
index 00000000000..43278e9d232
--- /dev/null
+++ b/app/views/users/_cover_controls.html.haml
@@ -0,0 +1,2 @@
+.cover-controls.d-flex.px-2.pb-4.d-sm-block.p-sm-0
+ = yield
diff --git a/app/views/users/_profile_basic_info.html.haml b/app/views/users/_profile_basic_info.html.haml
index af0a766bab0..c431a72d0e7 100644
--- a/app/views/users/_profile_basic_info.html.haml
+++ b/app/views/users/_profile_basic_info.html.haml
@@ -1,4 +1,4 @@
-%p
+%p.mb-1.mb-sm-2.mt-2.mt-sm-3
%span.middle-dot-divider
@#{@user.username}
- if can?(current_user, :read_user_profile, @user)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 3c164588b13..9f5124afc16 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -4,30 +4,31 @@
- page_title @user.blocked? ? s_('UserProfile|Blocked user') : @user.name
- page_description @user.bio
- header_title @user.name, user_path(@user)
+- link_classes = "flex-grow-1 mx-1 "
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
.user-profile
.cover-block.user-cover-block{ class: [('border-bottom' if profile_tabs.empty?)] }
- .cover-controls
+ = render layout: 'users/cover_controls' do
- if @user == current_user
- = link_to profile_path, class: 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do
+ = link_to profile_path, class: link_classes + 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do
= icon('pencil')
- elsif current_user
- if @user.abuse_report
- %button.btn.btn-danger{ title: s_('UserProfile|Already reported for abuse'),
+ %button{ class: link_classes + 'btn btn-danger mr-1', title: s_('UserProfile|Already reported for abuse'),
data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } }
= icon('exclamation-circle')
- else
- = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn',
+ = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: link_classes + 'btn',
title: s_('UserProfile|Report abuse'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('exclamation-circle')
- if can?(current_user, :read_user_profile, @user)
- = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: s_('UserProfile|Subscribe'), 'aria-label': 'Subscribe' do
+ = link_to user_path(@user, rss_url_options), class: link_classes + 'btn btn-default has-tooltip', title: s_('UserProfile|Subscribe'), 'aria-label': 'Subscribe' do
= icon('rss')
- if current_user && current_user.admin?
- = link_to [:admin, @user], class: 'btn btn-default', title: s_('UserProfile|View user in admin area'),
+ = link_to [:admin, @user], class: link_classes + 'btn btn-default', title: s_('UserProfile|View user in admin area'),
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('users')
@@ -51,10 +52,18 @@
= emoji_icon(@user.status.emoji)
= markdown_field(@user.status, :message)
= render "users/profile_basic_info"
- .cover-desc.cgray
- - unless @user.public_email.blank?
- .profile-link-holder.middle-dot-divider
- = link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link'
+ .cover-desc.cgray.mb-1.mb-sm-2
+ - unless @user.location.blank?
+ .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mb-1.mb-sm-0
+ = sprite_icon('location', size: 16, css_class: 'vertical-align-sub fgray')
+ %span.vertical-align-middle
+ = @user.location
+ - unless work_information(@user).blank?
+ .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline
+ = sprite_icon('work', size: 16, css_class: 'vertical-align-middle fgray')
+ %span.vertical-align-middle
+ = work_information(@user)
+ .cover-desc.cgray.mb-1.mb-sm-2
- unless @user.skype.blank?
.profile-link-holder.middle-dot-divider
= link_to "skype:#{@user.skype}", title: "Skype" do
@@ -64,24 +73,18 @@
= link_to linkedin_url(@user), title: "LinkedIn", target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('linkedin-square')
- unless @user.twitter.blank?
- .profile-link-holder.middle-dot-divider
+ .profile-link-holder.middle-dot-divider-sm
= link_to twitter_url(@user), title: "Twitter", target: '_blank', rel: 'noopener noreferrer nofollow' do
= icon('twitter-square')
- unless @user.website_url.blank?
- .profile-link-holder.middle-dot-divider
+ .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0
= link_to @user.short_website_url, @user.full_website_url, class: 'text-link', target: '_blank', rel: 'me noopener noreferrer nofollow'
- - unless @user.location.blank?
- .profile-link-holder.middle-dot-divider
- = sprite_icon('location', size: 16, css_class: 'vertical-align-sub')
- = @user.location
- - unless @user.organization.blank?
- .profile-link-holder.middle-dot-divider
- = sprite_icon('work', size: 16, css_class: 'vertical-align-sub')
- = @user.organization
-
+ - unless @user.public_email.blank?
+ .profile-link-holder.middle-dot-divider-sm.d-block.d-sm-inline.mt-1.mt-sm-0
+ = link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link'
- if @user.bio.present?
.cover-desc.cgray
- %p.profile-user-bio
+ %p.profile-user-bio.font-italic
= @user.bio
- unless profile_tabs.empty?
diff --git a/changelogs/unreleased/207962-deploy-ecs.yml b/changelogs/unreleased/207962-deploy-ecs.yml
new file mode 100644
index 00000000000..d7bc7f5ed01
--- /dev/null
+++ b/changelogs/unreleased/207962-deploy-ecs.yml
@@ -0,0 +1,5 @@
+---
+title: Add CI template to deploy to ECS
+merge_request: 26371
+author:
+type: added
diff --git a/changelogs/unreleased/208887-optimize-project-counters-with-slack-service.yml b/changelogs/unreleased/208887-optimize-project-counters-with-slack-service.yml
new file mode 100644
index 00000000000..e63461764b0
--- /dev/null
+++ b/changelogs/unreleased/208887-optimize-project-counters-with-slack-service.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize Project related count with slack service
+merge_request: 26686
+author:
+type: performance
diff --git a/changelogs/unreleased/209783-follow-up-from-resolve-notifications-for-when-pipelines-are-fixed.yml b/changelogs/unreleased/209783-follow-up-from-resolve-notifications-for-when-pipelines-are-fixed.yml
new file mode 100644
index 00000000000..dcf162adc85
--- /dev/null
+++ b/changelogs/unreleased/209783-follow-up-from-resolve-notifications-for-when-pipelines-are-fixed.yml
@@ -0,0 +1,5 @@
+---
+title: Correctly send notification on pipeline retry
+merge_request: 26803
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/24072-user-profile-add-job-title.yml b/changelogs/unreleased/24072-user-profile-add-job-title.yml
new file mode 100644
index 00000000000..0e77bd197fb
--- /dev/null
+++ b/changelogs/unreleased/24072-user-profile-add-job-title.yml
@@ -0,0 +1,5 @@
+---
+title: Add "Job Title" field in user settings and display on profile
+merge_request: 25155
+author:
+type: added
diff --git a/changelogs/unreleased/27880-clearing-release-note-from-the-tags-page-deletes-release.yml b/changelogs/unreleased/27880-clearing-release-note-from-the-tags-page-deletes-release.yml
new file mode 100644
index 00000000000..97d990069da
--- /dev/null
+++ b/changelogs/unreleased/27880-clearing-release-note-from-the-tags-page-deletes-release.yml
@@ -0,0 +1,5 @@
+---
+title: 27880 Make release notes optional and do not delete release when they are removed
+merge_request: 26231
+author: Pavlo Dudchenko
+type: changed
diff --git a/changelogs/unreleased/fj-fix-bug-hook-env.yml b/changelogs/unreleased/fj-fix-bug-hook-env.yml
new file mode 100644
index 00000000000..351dcdee689
--- /dev/null
+++ b/changelogs/unreleased/fj-fix-bug-hook-env.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug setting hook env with personal snippets
+merge_request: 27235
+author:
+type: fixed
diff --git a/changelogs/unreleased/mermaid-fix.yml b/changelogs/unreleased/mermaid-fix.yml
new file mode 100644
index 00000000000..54bdfba6a15
--- /dev/null
+++ b/changelogs/unreleased/mermaid-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Add functionality to render individual mermaids
+merge_request: 26564
+author:
+type: changed
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 41d20b5062b..25d57467060 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -11,6 +11,9 @@ Rails.application.configure do
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
+ # Show a warning when a large data set is loaded into memory
+ config.active_record.warn_on_records_fetched_greater_than = 1000
+
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
index 482613dacc9..46854af9b55 100644
--- a/config/initializers/5_backend.rb
+++ b/config/initializers/5_backend.rb
@@ -1,6 +1,6 @@
unless Rails.env.test?
required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
- current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
+ current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version)
unless current_version.valid? && required_version <= current_version
warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell."
diff --git a/db/migrate/20200306192548_add_index_on_project_id_and_type_to_services.rb b/db/migrate/20200306192548_add_index_on_project_id_and_type_to_services.rb
new file mode 100644
index 00000000000..9deb3c2832d
--- /dev/null
+++ b/db/migrate/20200306192548_add_index_on_project_id_and_type_to_services.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddIndexOnProjectIdAndTypeToServices < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_services_on_project_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :services, [:project_id, :type]
+
+ remove_concurrent_index_by_name :services, INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :services, :project_id, name: INDEX_NAME
+
+ remove_concurrent_index :services, [:project_id, :type]
+ end
+end
diff --git a/db/migrate/20200306193236_add_index_on_creator_id_and_created_at_to_projects.rb b/db/migrate/20200306193236_add_index_on_creator_id_and_created_at_to_projects.rb
new file mode 100644
index 00000000000..913383d32f4
--- /dev/null
+++ b/db/migrate/20200306193236_add_index_on_creator_id_and_created_at_to_projects.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class AddIndexOnCreatorIdAndCreatedAtToProjects < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_projects_on_creator_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :projects, [:creator_id, :created_at]
+
+ remove_concurrent_index_by_name :projects, INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :projects, :creator_id, name: INDEX_NAME
+
+ remove_concurrent_index :projects, [:creator_id, :created_at]
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 741bb2b9262..83aa4a32ffb 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -3486,8 +3486,8 @@ ActiveRecord::Schema.define(version: 2020_03_12_163407) do
t.index ["created_at", "id"], name: "index_projects_api_created_at_id_desc", order: { id: :desc }
t.index ["created_at", "id"], name: "index_projects_api_vis20_created_at", where: "(visibility_level = 20)"
t.index ["created_at", "id"], name: "index_projects_on_created_at_and_id"
+ t.index ["creator_id", "created_at"], name: "index_projects_on_creator_id_and_created_at"
t.index ["creator_id", "created_at"], name: "index_projects_on_mirror_creator_id_created_at", where: "((mirror = true) AND (mirror_trigger_builds = true))"
- t.index ["creator_id"], name: "index_projects_on_creator_id"
t.index ["description"], name: "index_projects_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["id", "repository_storage", "last_repository_updated_at"], name: "idx_projects_on_repository_storage_last_repository_updated_at"
t.index ["id"], name: "index_on_id_partial_with_legacy_storage", where: "((storage_version < 2) OR (storage_version IS NULL))"
@@ -3961,7 +3961,7 @@ ActiveRecord::Schema.define(version: 2020_03_12_163407) do
t.boolean "comment_on_event_enabled", default: true, null: false
t.boolean "template", default: false
t.boolean "instance", default: false, null: false
- t.index ["project_id"], name: "index_services_on_project_id"
+ t.index ["project_id", "type"], name: "index_services_on_project_id_and_type"
t.index ["template"], name: "index_services_on_template"
t.index ["type", "instance"], name: "index_services_on_type_and_instance", unique: true, where: "(instance IS TRUE)"
t.index ["type", "template"], name: "index_services_on_type_and_template", unique: true, where: "(template IS TRUE)"
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index ffd4efdca5d..6e98076b7e2 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -328,7 +328,7 @@ POST /projects/:id/releases
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `name` | string | no | The release name. |
| `tag_name` | string | yes | The tag where the release will be created from. |
-| `description` | string | yes | The description of the release. You can use [Markdown](../../user/markdown.md). |
+| `description` | string | no | The description of the release. You can use [Markdown](../../user/markdown.md). |
| `ref` | string | yes, if `tag_name` doesn't exist | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. |
| `milestones` | array of string | no | The title of each milestone the release is associated with. |
| `assets:links` | array of hash | no | An array of assets links. |
diff --git a/doc/ci/cloud_deployment/index.md b/doc/ci/cloud_deployment/index.md
index ccb6b60ecc9..ccff302750c 100644
--- a/doc/ci/cloud_deployment/index.md
+++ b/doc/ci/cloud_deployment/index.md
@@ -61,3 +61,61 @@ To do so, please make sure to [push your image into your ECR
repository](https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html)
before referencing it in your `.gitlab-ci.yml` file and replace the `image`
path to point to your ECR.
+
+### Deploy your application to AWS Elastic Container Service (ECS)
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/207962) in GitLab 12.9.
+
+GitLab provides a series of [CI templates that you can include in your project](../yaml/README.md#include).
+To automate deployments of your application to your [Amazon Elastic Container Service](https://aws.amazon.com/ecs/) (AWS ECS)
+cluster, you can `include` the `Deploy-ECS.gitlab-ci.yml` template in your `.gitlab-ci.yml` file.
+
+Before getting started with this process, you need a cluster on AWS ECS, as well as related
+components, like an ECS service, ECS task definition, a database on AWS RDS, etc.
+[Read more about AWS ECS](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html).
+
+After you're all set up on AWS ECS, follow these steps:
+
+1. Make sure your AWS credentials are set up as environment variables for your
+ project. You can follow [the steps above](#aws) to complete this setup.
+1. Add these variables to your project's `.gitlab-ci.yml` file:
+
+ ```yaml
+ variables:
+ CI_AWS_ECS_CLUSTER: my-cluster
+ CI_AWS_ECS_SERVICE: my-service
+ CI_AWS_ECS_TASK_DEFINITION: my-task-definition
+ ```
+
+ Three variables are defined in this snippet:
+
+ - `CI_AWS_ECS_CLUSTER`: The name of your AWS ECS cluster that you're
+ targeting for your deployments.
+ - `CI_AWS_ECS_SERVICE`: The name of the targeted service tied to
+ your AWS ECS cluster.
+ - `CI_AWS_ECS_TASK_DEFINITION`: The name of the task definition tied
+ to the service mentioned above.
+
+ You can find these names after selecting the targeted cluster on your [AWS ECS dashboard](https://console.aws.amazon.com/ecs/home):
+
+ ![AWS ECS dashboard](../img/ecs_dashboard_v12_9.png)
+
+1. Include this template in `.gitlab-ci.yml`:
+
+ ```yaml
+ include:
+ - template: Deploy-ECS.gitlab-ci.yml
+ ```
+
+ The `Deploy-ECS` template ships with GitLab and is available [on
+ GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Deploy-ECS.gitlab-ci.yml).
+
+1. Commit and push your updated `.gitlab-ci.yml` to your project's repository, and you're done!
+
+ Your application Docker image will be rebuilt and pushed to the GitLab registry.
+ Then the targeted task definition will be updated with the location of the new
+ Docker image, and a new revision will be created in ECS as result.
+
+ Finally, your AWS ECS service will be updated with the new revision of the
+ task definition, making the cluster pull the newest version of your
+ application.
diff --git a/doc/ci/img/ecs_dashboard_v12_9.png b/doc/ci/img/ecs_dashboard_v12_9.png
new file mode 100644
index 00000000000..bebd6f7903c
--- /dev/null
+++ b/doc/ci/img/ecs_dashboard_v12_9.png
Binary files differ
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 69113fe8030..16a86c06317 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -36,11 +36,7 @@ Additionally, if you need large repos or multiple forks for testing, please cons
The Elasticsearch integration depends on an external indexer. We ship an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). The user must trigger the initial indexing via a rake task but, after this is done, GitLab itself will trigger reindexing when required via `after_` callbacks on create, update, and destroy that are inherited from [/ee/app/models/concerns/elastic/application_versioned_search.rb](https://gitlab.com/gitlab-org/gitlab/blob/master/ee/app/models/concerns/elastic/application_versioned_search.rb).
-After initial indexing is complete, updates proceed in one of two ways, depending on the `:elastic_bulk_incremental_updates` feature flag.
-
-If disabled, every create, update, or delete operation on an Elasticsearch-tracked model enqueues a new `ElasticIndexerWorker` Sidekiq job which takes care of updating just that document. This is quite inefficient.
-
-If the feature flag is enabled, create, update, and delete operations for all models except projects (see [#207494](https://gitlab.com/gitlab-org/gitlab/issues/207494)) are tracked in a Redis [`ZSET`](https://redis.io/topics/data-types#sorted-sets) instead. A regular `sidekiq-cron` `ElasticIndexBulkCronWorker` processes this queue, updating many Elasticsearch documents at a time with the [Bulk Request API](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html).
+After initial indexing is complete, create, update, and delete operations for all models except projects (see [#207494](https://gitlab.com/gitlab-org/gitlab/issues/207494)) are tracked in a Redis [`ZSET`](https://redis.io/topics/data-types#sorted-sets). A regular `sidekiq-cron` `ElasticIndexBulkCronWorker` processes this queue, updating many Elasticsearch documents at a time with the [Bulk Request API](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html).
Search queries are generated by the concerns found in [ee/app/models/concerns/elastic](https://gitlab.com/gitlab-org/gitlab/tree/master/ee/app/models/concerns/elastic). These concerns are also in charge of access control, and have been a historic source of security bugs so please pay close attention to them!
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index cca037b0705..9c37b610cca 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -40,7 +40,7 @@ module API
# Stores some Git-specific env thread-safely
env = parse_env
- Gitlab::Git::HookEnv.set(gl_repository, env) if project
+ Gitlab::Git::HookEnv.set(gl_repository, env) if container
actor.update_last_used_at!
access_checker = access_checker_for(actor, params[:protocol])
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index 6e7a99bf0bb..1be263ac80d 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -46,7 +46,7 @@ module API
params do
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
optional :name, type: String, desc: 'The name of the release'
- requires :description, type: String, desc: 'The release notes'
+ optional :description, type: String, desc: 'The release notes'
optional :ref, type: String, desc: 'The commit sha or branch name'
optional :assets, type: Hash do
optional :links, type: Array do
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 123a695be13..1c5108b12ab 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -4,7 +4,6 @@ require 'yaml'
module Backup
class Repository
- include Gitlab::ShellAdapter
attr_reader :progress
def initialize(progress)
@@ -71,23 +70,14 @@ module Backup
def restore
Project.find_each(batch_size: 1000) do |project|
progress.print " * #{project.full_path} ... "
- path_to_project_bundle = path_to_bundle(project)
- project.repository.remove rescue nil
- restore_repo_success = nil
-
- if File.exist?(path_to_project_bundle)
+ restore_repo_success =
begin
- project.repository.create_from_bundle(path_to_project_bundle)
- restore_custom_hooks(project)
- restore_repo_success = true
- rescue => e
- restore_repo_success = false
- progress.puts "Error: #{e}".color(:red)
+ try_restore_repository(project)
+ rescue => err
+ progress.puts "Error: #{err}".color(:red)
+ false
end
- else
- restore_repo_success = gitlab_shell.create_project_repository(project)
- end
if restore_repo_success
progress.puts "[DONE]".color(:green)
@@ -118,6 +108,20 @@ module Backup
protected
+ def try_restore_repository(project)
+ path_to_project_bundle = path_to_bundle(project)
+ project.repository.remove rescue nil
+
+ if File.exist?(path_to_project_bundle)
+ project.repository.create_from_bundle(path_to_project_bundle)
+ restore_custom_hooks(project)
+ else
+ project.repository.create_repository
+ end
+
+ true
+ end
+
def path_to_bundle(project)
File.join(backup_repos_path, project.disk_path + '.bundle')
end
diff --git a/lib/gitlab/ci/templates/Deploy-ECS.gitlab-ci.yml b/lib/gitlab/ci/templates/Deploy-ECS.gitlab-ci.yml
new file mode 100644
index 00000000000..ecca1731579
--- /dev/null
+++ b/lib/gitlab/ci/templates/Deploy-ECS.gitlab-ci.yml
@@ -0,0 +1,36 @@
+stages:
+ - build
+ - test
+ - review
+ - deploy
+ - production
+
+include:
+ - template: Jobs/Build.gitlab-ci.yml
+
+.deploy_to_ecs:
+ image: registry.gitlab.com/gitlab-org/cloud-deploy:latest
+ script:
+ - ecs update-task-definition
+
+review:
+ extends: .deploy_to_ecs
+ stage: review
+ environment:
+ name: review/$CI_COMMIT_REF_NAME
+ only:
+ refs:
+ - branches
+ - tags
+ except:
+ refs:
+ - master
+
+production:
+ extends: .deploy_to_ecs
+ stage: production
+ environment:
+ name: production
+ only:
+ refs:
+ - master
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index e7158148b59..6053ad8d08e 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -6,8 +6,6 @@ require 'securerandom'
module Gitlab
class Shell
- GITLAB_SHELL_ENV_VARS = %w(GIT_TERMINAL_PROMPT).freeze
-
Error = Class.new(StandardError)
class << self
@@ -36,8 +34,31 @@ module Gitlab
.join('GITLAB_SHELL_VERSION')).strip
end
+ # Return GitLab shell version
+ #
+ # @return [String] version
+ def version
+ @version ||= File.read(gitlab_shell_version_file).chomp if File.readable?(gitlab_shell_version_file)
+ end
+
+ # Return a SSH url for a given project path
+ #
+ # @param [String] full_path project path (URL)
+ # @return [String] SSH URL
+ def url_to_repo(full_path)
+ Gitlab.config.gitlab_shell.ssh_path_prefix + "#{full_path}.git"
+ end
+
private
+ def gitlab_shell_path
+ File.expand_path(Gitlab.config.gitlab_shell.path)
+ end
+
+ def gitlab_shell_version_file
+ File.join(gitlab_shell_path, 'VERSION')
+ end
+
# Create (if necessary) and link the secret token file
def generate_and_link_secret_token
secret_file = Gitlab.config.gitlab_shell.secret_file
@@ -56,47 +77,6 @@ module Gitlab
end
end
- # Initialize a new project repository using a Project model
- #
- # @param [Project] project
- # @return [Boolean] whether repository could be created
- def create_project_repository(project)
- create_repository(project.repository_storage, project.disk_path, project.full_path)
- end
-
- # Initialize a new wiki repository using a Project model
- #
- # @param [Project] project
- # @return [Boolean] whether repository could be created
- def create_wiki_repository(project)
- create_repository(project.repository_storage, project.wiki.disk_path, project.wiki.full_path)
- end
-
- # Init new repository
- #
- # @example Create a repository
- # create_repository("default", "path/to/gitlab-ci", "gitlab/gitlab-ci")
- #
- # @param [String] storage the shard key
- # @param [String] disk_path project path on disk
- # @param [String] gl_project_path project name
- # @return [Boolean] whether repository could be created
- def create_repository(storage, disk_path, gl_project_path)
- relative_path = disk_path.dup
- relative_path << '.git' unless relative_path.end_with?('.git')
-
- # 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}/#{disk_path}: #{err}") # rubocop:disable Gitlab/RailsLogger
- false
- end
-
# Import wiki repository from external service
#
# @param [Project] project
@@ -238,25 +218,6 @@ module Gitlab
false
end
- # Return a SSH url for a given project path
- #
- # @param [String] full_path project path (URL)
- # @return [String] SSH URL
- def url_to_repo(full_path)
- Gitlab.config.gitlab_shell.ssh_path_prefix + "#{full_path}.git"
- end
-
- # Return GitLab shell version
- #
- # @return [String] version
- def version
- gitlab_shell_version_file = "#{gitlab_shell_path}/VERSION"
-
- if File.readable?(gitlab_shell_version_file)
- File.read(gitlab_shell_version_file).chomp
- end
- end
-
# Check if repository exists on disk
#
# @example Check if repository exists
@@ -271,23 +232,8 @@ module Gitlab
false
end
- # Return hooks folder path used by projects
- #
- # @return [String] path
- def hooks_path
- File.join(gitlab_shell_path, 'hooks')
- end
-
protected
- def gitlab_shell_path
- File.expand_path(Gitlab.config.gitlab_shell.path)
- end
-
- def gitlab_shell_user_home
- File.expand_path("~#{Gitlab.config.gitlab_shell.ssh_user}")
- end
-
def full_path(storage, dir_name)
raise ArgumentError.new("Directory name can't be blank") if dir_name.blank?
diff --git a/lib/system_check/gitlab_shell_check.rb b/lib/system_check/gitlab_shell_check.rb
index 31c4ec33247..f539719ce87 100644
--- a/lib/system_check/gitlab_shell_check.rb
+++ b/lib/system_check/gitlab_shell_check.rb
@@ -50,7 +50,7 @@ module SystemCheck
end
def gitlab_shell_version
- Gitlab::Shell.new.version
+ Gitlab::Shell.version
end
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index 5809f632c5a..d85c8fc7949 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -82,15 +82,10 @@ namespace :gitlab do
puts "Using Omniauth:\t#{Gitlab::Auth.omniauth_enabled? ? "yes".color(:green) : "no"}"
puts "Omniauth Providers: #{omniauth_providers.join(', ')}" if Gitlab::Auth.omniauth_enabled?
- # check Gitolite version
- gitlab_shell_version_file = "#{Gitlab.config.gitlab_shell.path}/VERSION"
- if File.readable?(gitlab_shell_version_file)
- gitlab_shell_version = File.read(gitlab_shell_version_file)
- end
-
+ # check Gitlab Shell version
puts ""
puts "GitLab Shell".color(:yellow)
- puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}"
+ puts "Version:\t#{Gitlab::Shell.version || "unknown".color(:red)}"
puts "Repository storage paths:"
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
Gitlab.config.repositories.storages.each do |name, repository_storage|
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1c39a581b2f..20d9990ddcf 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3256,6 +3256,9 @@ msgstr ""
msgid "Can't find variable: ZiteReader"
msgstr ""
+msgid "Can't load mermaid module: %{err}"
+msgstr ""
+
msgid "Can't remove group members without group managed account"
msgstr ""
@@ -3304,9 +3307,6 @@ msgstr ""
msgid "Cannot refer to a group milestone by an internal id!"
msgstr ""
-msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
-msgstr ""
-
msgid "Cannot show preview. For previews on sketch files, they must have the file format introduced by Sketch version 43 and above."
msgstr ""
@@ -7492,6 +7492,9 @@ msgstr ""
msgid "Enabling this will only make licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public."
msgstr ""
+msgid "Encountered an error while rendering: %{err}"
+msgstr ""
+
msgid "End date"
msgstr ""
@@ -8380,7 +8383,7 @@ msgstr ""
msgid "Failed to create a branch for this issue. Please try again."
msgstr ""
-msgid "Failed to create repository via gitlab-shell"
+msgid "Failed to create repository"
msgstr ""
msgid "Failed to create resources"
@@ -15148,6 +15151,9 @@ msgstr ""
msgid "Profiles|your account"
msgstr ""
+msgid "Profile|%{job_title} at %{organization}"
+msgstr ""
+
msgid "Profiling - Performance bar"
msgstr ""
@@ -17679,9 +17685,6 @@ msgstr ""
msgid "Select user"
msgstr ""
-msgid "Select your role"
-msgstr ""
-
msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
msgstr ""
@@ -22362,6 +22365,9 @@ msgstr ""
msgid "Warning:"
msgstr ""
+msgid "Warning: Displaying this diagram might cause performance issues on this page."
+msgstr ""
+
msgid "We could not determine the path to remove the epic"
msgstr ""
diff --git a/package.json b/package.json
index 4088357f4cc..3f4718c1236 100644
--- a/package.json
+++ b/package.json
@@ -96,7 +96,7 @@
"katex": "^0.10.0",
"lodash": "^4.17.15",
"marked": "^0.3.12",
- "mermaid": "^8.4.5",
+ "mermaid": "^8.4.8",
"monaco-editor": "^0.18.1",
"monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index b69990bae9c..0da40b35938 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -14,6 +14,7 @@ module QA
element :dropdown_toggle
element :download_email_patches
element :download_plain_diff
+ element :open_in_web_ide_button
end
view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue' do
@@ -219,6 +220,10 @@ module QA
def wait_for_loading
finished_loading? && has_no_element?(:skeleton_note)
end
+
+ def click_open_in_web_ide
+ click_element :open_in_web_ide_button
+ end
end
end
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 73b0856b445..a9b82ac6046 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -48,6 +48,10 @@ module QA
element :start_new_mr_checkbox
end
+ view 'app/assets/javascripts/ide/components/repo_editor.vue' do
+ element :editor_container
+ end
+
def has_file?(file_name)
within_element(:file_list) do
page.has_content? file_name
@@ -113,6 +117,17 @@ module QA
raise "The changes do not appear to have been committed successfully." unless commit_success_msg_shown
end
+
+ def add_to_modified_content(content)
+ finished_loading?
+ modified_text_area.set content
+ end
+
+ def modified_text_area
+ within_element(:editor_container) do
+ find('.modified textarea.inputarea')
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb
new file mode 100644
index 00000000000..c37ad6d4318
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/review_merge_request_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create', quarantine: { type: :new } do
+ describe 'Review a merge request in Web IDE' do
+ let(:new_file) { 'awesome_new_file.txt' }
+ let(:review_text) { 'Reviewed ' }
+
+ let(:merge_request) do
+ Resource::MergeRequest.fabricate_via_api! do |mr|
+ mr.file_name = new_file
+ mr.file_content = 'Text'
+ end
+ end
+
+ before do
+ Flow::Login.sign_in
+
+ merge_request.visit!
+ end
+
+ it 'opens and edits a merge request in Web IDE' do
+ Page::MergeRequest::Show.perform do |show|
+ show.click_open_in_web_ide
+ end
+
+ Page::Project::WebIDE::Edit.perform do |ide|
+ ide.has_file?(new_file)
+ ide.add_to_modified_content(review_text)
+ ide.commit_changes
+ end
+
+ merge_request.visit!
+
+ Page::MergeRequest::Show.perform do |show|
+ show.click_diffs_tab
+ expect(show).to have_content(review_text)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/tags/releases_controller_spec.rb b/spec/controllers/projects/tags/releases_controller_spec.rb
index da87756d782..cb12e074732 100644
--- a/spec/controllers/projects/tags/releases_controller_spec.rb
+++ b/spec/controllers/projects/tags/releases_controller_spec.rb
@@ -67,13 +67,13 @@ describe Projects::Tags::ReleasesController do
expect(response).to have_gitlab_http_status(:found)
end
- it 'deletes release when description is empty' do
- initial_releases_count = project.releases.count
+ it 'does not delete release when description is empty' do
+ expect do
+ update_release(tag, "")
+ end.not_to change { project.releases.count }
- response = update_release(release.tag, "")
+ expect(release.reload.description).to eq("")
- expect(initial_releases_count).to eq(1)
- expect(project.releases.count).to eq(0)
expect(response).to have_gitlab_http_status(:found)
end
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index 1cd5760c30e..4bf7edf98ca 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -38,7 +38,9 @@ describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue)
- expected = '<text><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
+ wait_for_requests
+
+ expected = '<text style=""><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
expect(page.html.scan(expected).count).to be(4)
end
@@ -121,4 +123,40 @@ describe 'Mermaid rendering', :js do
expect(svg[:width].to_i).to eq(100)
expect(svg[:height].to_i).to eq(0)
end
+
+ it 'display button when diagram exceeds length', :js do
+ graph_edges = "A-->B;B-->A;" * 420
+
+ description = <<~MERMAID
+ ```mermaid
+ graph LR
+ #{graph_edges}
+ ```
+ MERMAID
+
+ project = create(:project, :public)
+ issue = create(:issue, project: project, description: description)
+
+ visit project_issue_path(project, issue)
+
+ page.within('.description') do
+ expect(page).not_to have_selector('svg')
+
+ expect(page).to have_selector('pre.mermaid')
+
+ expect(page).to have_selector('.lazy-alert-shown')
+
+ expect(page).to have_selector('.js-lazy-render-mermaid-container')
+ end
+
+ wait_for_requests
+
+ find('.js-lazy-render-mermaid').click
+
+ page.within('.description') do
+ expect(page).to have_selector('svg')
+
+ expect(page).not_to have_selector('.js-lazy-render-mermaid-container')
+ end
+ end
end
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index 9839b3d6c80..171dfb353f0 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -15,6 +15,11 @@ describe 'User edit profile' do
wait_for_requests if respond_to?(:wait_for_requests)
end
+ def visit_user
+ visit user_path(user)
+ wait_for_requests
+ end
+
it 'changes user profile' do
fill_in 'user_skype', with: 'testskype'
fill_in 'user_linkedin', with: 'testlinkedin'
@@ -22,8 +27,8 @@ describe 'User edit profile' do
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab'
+ fill_in 'user_job_title', with: 'Frontend Engineer'
fill_in 'user_organization', with: 'GitLab'
- select 'Data Analyst', from: 'user_role'
submit_settings
expect(user.reload).to have_attributes(
@@ -32,8 +37,8 @@ describe 'User edit profile' do
twitter: 'testtwitter',
website_url: 'testurl',
bio: 'I <3 GitLab',
- organization: 'GitLab',
- role: 'data_analyst'
+ job_title: 'Frontend Engineer',
+ organization: 'GitLab'
)
expect(find('#user_location').value).to eq 'Ukraine'
@@ -94,11 +99,6 @@ describe 'User edit profile' do
end
context 'user status', :js do
- def visit_user
- visit user_path(user)
- wait_for_requests
- end
-
def select_emoji(emoji_name, is_modal = false)
emoji_menu_class = is_modal ? '.js-modal-status-emoji-menu' : '.js-status-emoji-menu'
toggle_button = find('.js-toggle-emoji-menu')
@@ -381,4 +381,40 @@ describe 'User edit profile' do
end
end
end
+
+ context 'work information', :js do
+ context 'when job title and organziation are entered' do
+ it "shows job title and organzation on user's profile" do
+ fill_in 'user_job_title', with: 'Frontend Engineer'
+ fill_in 'user_organization', with: 'GitLab - work info test'
+ submit_settings
+
+ visit_user
+
+ expect(page).to have_content('Frontend Engineer at GitLab - work info test')
+ end
+ end
+
+ context 'when only job title is entered' do
+ it "shows only job title on user's profile" do
+ fill_in 'user_job_title', with: 'Frontend Engineer - work info test'
+ submit_settings
+
+ visit_user
+
+ expect(page).to have_content('Frontend Engineer - work info test')
+ end
+ end
+
+ context 'when only organization is entered' do
+ it "shows only organization on user's profile" do
+ fill_in 'user_organization', with: 'GitLab - work info test'
+ submit_settings
+
+ visit_user
+
+ expect(page).to have_content('GitLab - work info test')
+ end
+ end
+ end
end
diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb
index 8c2b555305a..a45389a7ed5 100644
--- a/spec/features/users/show_spec.rb
+++ b/spec/features/users/show_spec.rb
@@ -26,6 +26,34 @@ describe 'User page' do
expect(page).not_to have_content("This user has a private profile")
end
+
+ context 'work information' do
+ subject { visit(user_path(user)) }
+
+ it 'shows job title and organization details' do
+ user.update(organization: 'GitLab - work info test', job_title: 'Frontend Engineer')
+
+ subject
+
+ expect(page).to have_content('Frontend Engineer at GitLab - work info test')
+ end
+
+ it 'shows job title' do
+ user.update(organization: nil, job_title: 'Frontend Engineer - work info test')
+
+ subject
+
+ expect(page).to have_content('Frontend Engineer - work info test')
+ end
+
+ it 'shows organization details' do
+ user.update(organization: 'GitLab - work info test', job_title: '')
+
+ subject
+
+ expect(page).to have_content('GitLab - work info test')
+ end
+ end
end
context 'with private profile' do
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index a8bbc80d2df..a2e2d2447d5 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -1,4 +1,4 @@
-import { GlSkeletonLoading } from '@gitlab/ui';
+import { GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
import Icon from '~/vue_shared/components/icon.vue';
@@ -11,6 +11,7 @@ const DEFAULT_PROPS = {
location: 'Vienna',
bio: null,
organization: null,
+ jobTitle: null,
status: null,
},
};
@@ -39,6 +40,9 @@ describe('User Popover Component', () => {
target: findTarget(),
...props,
},
+ stubs: {
+ 'gl-sprintf': GlSprintf,
+ },
...options,
});
};
@@ -56,6 +60,7 @@ describe('User Popover Component', () => {
location: null,
bio: null,
organization: null,
+ jobTitle: null,
status: null,
},
},
@@ -85,51 +90,125 @@ describe('User Popover Component', () => {
});
describe('job data', () => {
- it('should show only bio if no organization is available', () => {
- const user = { ...DEFAULT_PROPS.user, bio: 'Engineer' };
+ const findWorkInformation = () => wrapper.find({ ref: 'workInformation' });
+ const findBio = () => wrapper.find({ ref: 'bio' });
+
+ it('should show only bio if organization and job title are not available', () => {
+ const user = { ...DEFAULT_PROPS.user, bio: 'My super interesting bio' };
createWrapper({ user });
- expect(wrapper.text()).toContain('Engineer');
+ expect(findBio().text()).toBe('My super interesting bio');
+ expect(findWorkInformation().exists()).toBe(false);
});
- it('should show only organization if no bio is available', () => {
+ it('should show only organization if job title is not available', () => {
const user = { ...DEFAULT_PROPS.user, organization: 'GitLab' };
createWrapper({ user });
- expect(wrapper.text()).toContain('GitLab');
+ expect(findWorkInformation().text()).toBe('GitLab');
+ });
+
+ it('should show only job title if organization is not available', () => {
+ const user = { ...DEFAULT_PROPS.user, jobTitle: 'Frontend Engineer' };
+
+ createWrapper({ user });
+
+ expect(findWorkInformation().text()).toBe('Frontend Engineer');
+ });
+
+ it('should show organization and job title if they are both available', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ organization: 'GitLab',
+ jobTitle: 'Frontend Engineer',
+ };
+
+ createWrapper({ user });
+
+ expect(findWorkInformation().text()).toBe('Frontend Engineer at GitLab');
+ });
+
+ it('should display bio and job info in separate lines', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ bio: 'My super interesting bio',
+ organization: 'GitLab',
+ };
+
+ createWrapper({ user });
+
+ expect(findBio().text()).toBe('My super interesting bio');
+ expect(findWorkInformation().text()).toBe('GitLab');
});
- it('should display bio and organization in separate lines', () => {
- const user = { ...DEFAULT_PROPS.user, bio: 'Engineer', organization: 'GitLab' };
+ it('should not encode special characters in bio', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ bio: 'I like <html> & CSS',
+ };
createWrapper({ user });
- expect(wrapper.find('.js-bio').text()).toContain('Engineer');
- expect(wrapper.find('.js-organization').text()).toContain('GitLab');
+ expect(findBio().text()).toBe('I like <html> & CSS');
});
- it('should not encode special characters in bio and organization', () => {
+ it('should not encode special characters in organization', () => {
const user = {
...DEFAULT_PROPS.user,
- bio: 'Manager & Team Lead',
organization: 'Me & my <funky> Company',
};
createWrapper({ user });
- expect(wrapper.find('.js-bio').text()).toContain('Manager & Team Lead');
- expect(wrapper.find('.js-organization').text()).toContain('Me & my <funky> Company');
+ expect(findWorkInformation().text()).toBe('Me & my <funky> Company');
+ });
+
+ it('should not encode special characters in job title', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ jobTitle: 'Manager & Team Lead',
+ };
+
+ createWrapper({ user });
+
+ expect(findWorkInformation().text()).toBe('Manager & Team Lead');
+ });
+
+ it('should not encode special characters when both job title and organization are set', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ jobTitle: 'Manager & Team Lead',
+ organization: 'Me & my <funky> Company',
+ };
+
+ createWrapper({ user });
+
+ expect(findWorkInformation().text()).toBe('Manager & Team Lead at Me & my <funky> Company');
});
it('shows icon for bio', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ bio: 'My super interesting bio',
+ };
+
+ createWrapper({ user });
+
expect(wrapper.findAll(Icon).filter(icon => icon.props('name') === 'profile').length).toEqual(
1,
);
});
it('shows icon for organization', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ organization: 'GitLab',
+ };
+
+ createWrapper({ user });
+
expect(wrapper.findAll(Icon).filter(icon => icon.props('name') === 'work').length).toEqual(1);
});
});
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index 8479f8509f5..893d5cde24a 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -178,4 +178,42 @@ describe UsersHelper do
end
end
end
+
+ describe '#work_information' do
+ subject { helper.work_information(user) }
+
+ context 'when both job_title and organization are present' do
+ let(:user) { build(:user, organization: 'GitLab', job_title: 'Frontend Engineer') }
+
+ it 'returns job title concatenated with organization' do
+ is_expected.to eq('Frontend Engineer at GitLab')
+ end
+ end
+
+ context 'when only organization is present' do
+ let(:user) { build(:user, organization: 'GitLab') }
+
+ it "returns organization" do
+ is_expected.to eq('GitLab')
+ end
+ end
+
+ context 'when only job_title is present' do
+ let(:user) { build(:user, job_title: 'Frontend Engineer') }
+
+ it 'returns job title' do
+ is_expected.to eq('Frontend Engineer')
+ end
+ end
+
+ context 'when neither organization nor job_title are present' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'when user parameter is nil' do
+ let(:user) { nil }
+
+ it { is_expected.to be_nil }
+ end
+ end
end
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index 2ac1b0d2583..e0afa256581 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -50,9 +50,9 @@ describe Backup::Repository do
describe 'command failure' do
before do
- allow_next_instance_of(Gitlab::Shell) do |instance|
- allow(instance).to receive(:create_repository).and_return(false)
- end
+ # Allow us to set expectations on the project directly
+ expect(Project).to receive(:find_each).and_yield(project)
+ expect(project.repository).to receive(:create_repository) { raise 'Fail in tests' }
end
context 'hashed storage' do
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index 7fef763f64d..23df970957a 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -173,10 +173,6 @@ describe Gitlab::LegacyGithubImport::Importer do
]
}
- unless project.gitea_import?
- error[:errors] << { type: :release, url: "#{api_root}/repos/octocat/Hello-World/releases/2", errors: "Validation failed: Description can't be blank" }
- end
-
described_class.new(project).execute
expect(project.import_state.last_error).to eq error.to_json
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 280d4fba5a2..3ea9f71d3a6 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -7,18 +7,17 @@ describe Gitlab::Shell do
let_it_be(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:gitlab_shell) { described_class.new }
- let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
- let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
- before do
- allow(Project).to receive(:find).and_return(project)
- end
-
- it { is_expected.to respond_to :create_repository }
it { is_expected.to respond_to :remove_repository }
it { is_expected.to respond_to :fork_repository }
- it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") }
+ describe '.url_to_repo' do
+ let(:full_path) { 'diaspora/disaspora-rails' }
+
+ subject { described_class.url_to_repo(full_path) }
+
+ it { is_expected.to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + full_path + '.git') }
+ end
describe 'memoized secret_token' do
let(:secret_file) { 'tmp/tests/.secret_shell_test' }
@@ -49,37 +48,12 @@ describe Gitlab::Shell do
describe 'projects commands' do
let(:gitlab_shell_path) { File.expand_path('tmp/tests/gitlab-shell') }
let(:projects_path) { File.join(gitlab_shell_path, 'bin/gitlab-projects') }
- let(:gitlab_shell_hooks_path) { File.join(gitlab_shell_path, 'hooks') }
before do
allow(Gitlab.config.gitlab_shell).to receive(:path).and_return(gitlab_shell_path)
allow(Gitlab.config.gitlab_shell).to receive(:git_timeout).and_return(800)
end
- describe '#create_repository' do
- let(:repository_storage) { 'default' }
- let(:repository_storage_path) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- Gitlab.config.repositories.storages[repository_storage].legacy_disk_path
- end
- end
- let(:repo_name) { 'project/path' }
- let(:created_path) { File.join(repository_storage_path, repo_name + '.git') }
-
- after do
- FileUtils.rm_rf(created_path)
- end
-
- it 'returns false when the command fails' do
- FileUtils.mkdir_p(File.dirname(created_path))
- # This file will block the creation of the repo's .git directory. That
- # should cause #create_repository to fail.
- FileUtils.touch(created_path)
-
- expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_falsy
- end
- end
-
describe '#remove_repository' do
let!(:project) { create(:project, :repository, :legacy_storage) }
let(:disk_path) { "#{project.disk_path}.git" }
diff --git a/spec/models/concerns/bulk_insert_safe_spec.rb b/spec/models/concerns/bulk_insert_safe_spec.rb
index 0cc355ea694..a8e56cb8bdd 100644
--- a/spec/models/concerns/bulk_insert_safe_spec.rb
+++ b/spec/models/concerns/bulk_insert_safe_spec.rb
@@ -22,6 +22,18 @@ describe BulkInsertSafe do
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_32,
insecure_mode: false
+
+ default_value_for :enum_value, 'case_1'
+ default_value_for :secret_value, 'my-secret'
+ default_value_for :sha_value, '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
+
+ def self.valid_list(count)
+ Array.new(count) { |n| new(name: "item-#{n}") }
+ end
+
+ def self.invalid_list(count)
+ Array.new(count) { new }
+ end
end
module InheritedUnsafeMethods
@@ -48,6 +60,8 @@ describe BulkInsertSafe do
t.text :encrypted_secret_value, null: false
t.string :encrypted_secret_value_iv, null: false
t.binary :sha_value, null: false, limit: 20
+
+ t.index :name, unique: true
end
end
@@ -60,87 +74,95 @@ describe BulkInsertSafe do
end
end
- def build_valid_items_for_bulk_insertion
- Array.new(10) do |n|
- BulkInsertItem.new(
- name: "item-#{n}",
- enum_value: 'case_1',
- secret_value: 'my-secret',
- sha_value: '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
- )
+ describe BulkInsertItem do
+ it_behaves_like 'a BulkInsertSafe model', described_class do
+ let(:valid_items_for_bulk_insertion) { described_class.valid_list(10) }
+ let(:invalid_items_for_bulk_insertion) { described_class.invalid_list(10) }
end
- end
- def build_invalid_items_for_bulk_insertion
- Array.new(10) do
- BulkInsertItem.new(
- name: nil, # requires `name` to be set
- enum_value: 'case_1',
- secret_value: 'my-secret',
- sha_value: '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
- )
+ context 'when inheriting class methods' do
+ it 'raises an error when method is not bulk-insert safe' do
+ expect { described_class.include(InheritedUnsafeMethods) }
+ .to raise_error(described_class::MethodNotAllowedError)
+ end
+
+ it 'does not raise an error when method is bulk-insert safe' do
+ expect { described_class.include(InheritedSafeMethods) }.not_to raise_error
+ end
end
- end
- it_behaves_like 'a BulkInsertSafe model', BulkInsertItem do
- let(:valid_items_for_bulk_insertion) { build_valid_items_for_bulk_insertion }
- let(:invalid_items_for_bulk_insertion) { build_invalid_items_for_bulk_insertion }
- end
+ context 'primary keys' do
+ it 'raises error if primary keys are set prior to insertion' do
+ item = described_class.new(name: 'valid', id: 10)
- context 'when inheriting class methods' do
- it 'raises an error when method is not bulk-insert safe' do
- expect { BulkInsertItem.include(InheritedUnsafeMethods) }.to(
- raise_error(subject::MethodNotAllowedError))
+ expect { described_class.bulk_insert!([item]) }
+ .to raise_error(described_class::PrimaryKeySetError)
+ end
end
- it 'does not raise an error when method is bulk-insert safe' do
- expect { BulkInsertItem.include(InheritedSafeMethods) }.not_to raise_error
- end
- end
+ describe '.bulk_insert!' do
+ it 'inserts items in the given number of batches' do
+ items = described_class.valid_list(10)
+
+ expect(ActiveRecord::InsertAll).to receive(:new).twice.and_call_original
- context 'primary keys' do
- it 'raises error if primary keys are set prior to insertion' do
- items = build_valid_items_for_bulk_insertion
- items.each_with_index do |item, n|
- item.id = n
+ described_class.bulk_insert!(items, batch_size: 5)
end
- expect { BulkInsertItem.bulk_insert!(items) }.to raise_error(subject::PrimaryKeySetError)
- end
- end
+ it 'items can be properly fetched from database' do
+ items = described_class.valid_list(10)
- describe '.bulk_insert!' do
- it 'inserts items in the given number of batches' do
- items = build_valid_items_for_bulk_insertion
- expect(items.size).to eq(10)
- expect(BulkInsertItem).to receive(:insert_all!).twice
+ described_class.bulk_insert!(items)
- BulkInsertItem.bulk_insert!(items, batch_size: 5)
- end
+ attribute_names = described_class.attribute_names - %w[id]
+ expect(described_class.last(items.size).pluck(*attribute_names)).to eq(
+ items.pluck(*attribute_names))
+ end
- it 'items can be properly fetched from database' do
- items = build_valid_items_for_bulk_insertion
+ it 'rolls back the transaction when any item is invalid' do
+ # second batch is bad
+ all_items = described_class.valid_list(10) +
+ described_class.invalid_list(10)
- BulkInsertItem.bulk_insert!(items)
+ expect do
+ described_class.bulk_insert!(all_items, batch_size: 2) rescue nil
+ end.not_to change { described_class.count }
+ end
- attribute_names = BulkInsertItem.attribute_names - %w[id]
- expect(BulkInsertItem.last(items.size).pluck(*attribute_names)).to eq(
- items.pluck(*attribute_names))
+ it 'does nothing and returns true when items are empty' do
+ expect(described_class.bulk_insert!([])).to be(true)
+ expect(described_class.count).to eq(0)
+ end
end
- it 'rolls back the transaction when any item is invalid' do
- # second batch is bad
- all_items = build_valid_items_for_bulk_insertion + build_invalid_items_for_bulk_insertion
- batch_size = all_items.size / 2
+ context 'when duplicate items are to be inserted' do
+ let!(:existing_object) { described_class.create!(name: 'duplicate', secret_value: 'old value') }
+ let(:new_object) { described_class.new(name: 'duplicate', secret_value: 'new value') }
+
+ describe '.bulk_insert!' do
+ context 'when skip_duplicates is set to false' do
+ it 'raises an exception' do
+ expect { described_class.bulk_insert!([new_object], skip_duplicates: false) }
+ .to raise_error(ActiveRecord::RecordNotUnique)
+ end
+ end
+
+ context 'when skip_duplicates is set to true' do
+ it 'does not update existing object' do
+ described_class.bulk_insert!([new_object], skip_duplicates: true)
+
+ expect(existing_object.reload.secret_value).to eq('old value')
+ end
+ end
+ end
- expect do
- BulkInsertItem.bulk_insert!(all_items, batch_size: batch_size) rescue nil
- end.not_to change { BulkInsertItem.count }
- end
+ describe '.bulk_upsert!' do
+ it 'updates existing object' do
+ described_class.bulk_upsert!([new_object], unique_by: %w[name])
- it 'does nothing and returns true when items are empty' do
- expect(BulkInsertItem.bulk_insert!([])).to be(true)
- expect(BulkInsertItem.count).to eq(0)
+ expect(existing_object.reload.secret_value).to eq('new value')
+ end
+ end
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 6d9b46c9941..2c6cfce7247 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1921,30 +1921,15 @@ describe Project do
describe '#create_repository' do
let(:project) { create(:project, :repository) }
- let(:shell) { Gitlab::Shell.new }
-
- before do
- allow(project).to receive(:gitlab_shell).and_return(shell)
- end
context 'using a regular repository' do
it 'creates the repository' do
- expect(shell).to receive(:create_repository)
- .with(project.repository_storage, project.disk_path, project.full_path)
- .and_return(true)
-
- expect(project.repository).to receive(:after_create)
-
+ expect(project.repository).to receive(:create_repository)
expect(project.create_repository).to eq(true)
end
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, project.full_path)
- .and_return(false)
-
- expect(project.repository).not_to receive(:after_create)
-
+ expect(project.repository).to receive(:create_repository) { raise 'Fail in test' }
expect(project.create_repository).to eq(false)
expect(project.errors).not_to be_empty
end
@@ -1953,7 +1938,7 @@ describe Project do
context 'using a forked repository' do
it 'does nothing' do
expect(project).to receive(:forked?).and_return(true)
- expect(shell).not_to receive(:create_repository)
+ expect(project.repository).not_to receive(:create_repository)
project.create_repository
end
@@ -1962,28 +1947,16 @@ describe Project do
describe '#ensure_repository' do
let(:project) { create(:project, :repository) }
- let(:shell) { Gitlab::Shell.new }
-
- before do
- allow(project).to receive(:gitlab_shell).and_return(shell)
- end
it 'creates the repository if it not exist' do
- allow(project).to receive(:repository_exists?)
- .and_return(false)
-
- allow(shell).to receive(:create_repository)
- .with(project.repository_storage, project.disk_path, project.full_path)
- .and_return(true)
-
+ allow(project).to receive(:repository_exists?).and_return(false)
expect(project).to receive(:create_repository).with(force: true)
project.ensure_repository
end
it 'does not create the repository if it exists' do
- allow(project).to receive(:repository_exists?)
- .and_return(true)
+ allow(project).to receive(:repository_exists?).and_return(true)
expect(project).not_to receive(:create_repository)
@@ -1992,13 +1965,8 @@ describe Project do
it 'creates the repository if it is a fork' do
expect(project).to receive(:forked?).and_return(true)
-
- allow(project).to receive(:repository_exists?)
- .and_return(false)
-
- expect(shell).to receive(:create_repository)
- .with(project.repository_storage, project.disk_path, project.full_path)
- .and_return(true)
+ expect(project).to receive(:repository_exists?).and_return(false)
+ expect(project.repository).to receive(:create_repository) { true }
project.ensure_repository
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index af23f121bdc..2d660d1deab 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -34,7 +34,7 @@ describe ProjectWiki do
describe "#url_to_repo" do
it "returns the correct ssh url to the repo" do
- expect(subject.url_to_repo).to eq(gitlab_shell.url_to_repo(subject.full_path))
+ expect(subject.url_to_repo).to eq(Gitlab::Shell.url_to_repo(subject.full_path))
end
end
@@ -97,9 +97,7 @@ describe ProjectWiki do
it "raises CouldNotCreateWikiError if it can't create the wiki repository" 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_wiki_repository)
- allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell)
+ expect(project_wiki.repository).to receive(:create_if_not_exists) { false }
expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
end
@@ -416,26 +414,12 @@ describe ProjectWiki do
end
end
- describe '#create_repo!' do
- let(:project) { create(:project) }
-
- it 'creates a repository' do
- expect(raw_repository.exists?).to eq(false)
- expect(subject.repository).to receive(:after_create)
-
- subject.send(:create_repo!, raw_repository)
-
- expect(raw_repository.exists?).to eq(true)
- end
- end
-
describe '#ensure_repository' do
let(:project) { create(:project) }
it 'creates the repository if it not exist' do
expect(raw_repository.exists?).to eq(false)
- expect(subject).to receive(:create_repo!).and_call_original
subject.ensure_repository
expect(raw_repository.exists?).to eq(true)
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 85e398c7d5f..3884b8138be 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -20,7 +20,6 @@ RSpec.describe Release do
describe 'validation' do
it { is_expected.to validate_presence_of(:project) }
- it { is_expected.to validate_presence_of(:description) }
it { is_expected.to validate_presence_of(:tag) }
context 'when a release exists in the database without a name' do
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index be592ac6a5c..c3e52975d6d 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -262,6 +262,8 @@ describe API::Internal::Base do
describe "POST /internal/allowed", :clean_gitlab_redis_shared_state do
context "access granted" do
+ let(:env) { {} }
+
around do |example|
Timecop.freeze { example.run }
end
@@ -270,30 +272,32 @@ describe API::Internal::Base do
project.add_developer(user)
end
- context 'with env passed as a JSON' do
- let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_container(project) }
-
- it 'sets env in RequestStore' do
- obj_dir_relative = './objects'
- alt_obj_dirs_relative = ['./alt-objects-1', './alt-objects-2']
+ shared_examples 'sets hook env' do
+ context 'with env passed as a JSON' do
+ let(:obj_dir_relative) { './objects' }
+ let(:alt_obj_dirs_relative) { ['./alt-objects-1', './alt-objects-2'] }
+ let(:env) do
+ {
+ GIT_OBJECT_DIRECTORY_RELATIVE: obj_dir_relative,
+ GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: alt_obj_dirs_relative
+ }
+ end
- expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, {
- 'GIT_OBJECT_DIRECTORY_RELATIVE' => obj_dir_relative,
- 'GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE' => alt_obj_dirs_relative
- })
+ it 'sets env in RequestStore' do
+ expect(Gitlab::Git::HookEnv).to receive(:set).with(gl_repository, env.stringify_keys)
- push(key, project.wiki, env: {
- GIT_OBJECT_DIRECTORY_RELATIVE: obj_dir_relative,
- GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE: alt_obj_dirs_relative
- }.to_json)
+ subject
- expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
end
end
context "git push with project.wiki" do
+ subject { push(key, project.wiki, env: env.to_json) }
+
it 'responds with success' do
- push(key, project.wiki)
+ subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["status"]).to be_truthy
@@ -301,6 +305,10 @@ describe API::Internal::Base do
expect(json_response["gl_repository"]).to eq("wiki-#{project.id}")
expect(user.reload.last_activity_on).to be_nil
end
+
+ it_behaves_like 'sets hook env' do
+ let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_container(project) }
+ end
end
context "git pull with project.wiki" do
@@ -328,8 +336,10 @@ describe API::Internal::Base do
end
context 'git push with personal snippet' do
+ subject { push(key, personal_snippet, env: env.to_json) }
+
it 'responds with success' do
- push(key, personal_snippet)
+ subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["status"]).to be_truthy
@@ -338,8 +348,9 @@ describe API::Internal::Base do
expect(user.reload.last_activity_on).to be_nil
end
- it_behaves_like 'snippets with disabled feature flag' do
- subject { push(key, personal_snippet) }
+ it_behaves_like 'snippets with disabled feature flag'
+ it_behaves_like 'sets hook env' do
+ let(:gl_repository) { Gitlab::GlRepository::SNIPPET.identifier_for_container(personal_snippet) }
end
end
@@ -360,8 +371,10 @@ describe API::Internal::Base do
end
context 'git push with project snippet' do
+ subject { push(key, project_snippet, env: env.to_json) }
+
it 'responds with success' do
- push(key, project_snippet)
+ subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response["status"]).to be_truthy
@@ -370,8 +383,9 @@ describe API::Internal::Base do
expect(user.reload.last_activity_on).to be_nil
end
- it_behaves_like 'snippets with disabled feature flag' do
- subject { push(key, project_snippet) }
+ it_behaves_like 'snippets with disabled feature flag'
+ it_behaves_like 'sets hook env' do
+ let(:gl_repository) { Gitlab::GlRepository::SNIPPET.identifier_for_container(project_snippet) }
end
end
diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb
index 4eb6e87c254..41999ca6e60 100644
--- a/spec/requests/api/releases_spec.rb
+++ b/spec/requests/api/releases_spec.rb
@@ -406,6 +406,22 @@ describe API::Releases do
expect(project.releases.last.description).to eq('Super nice release')
end
+ it 'creates a new release without description' do
+ params = {
+ name: 'New release without description',
+ tag_name: 'v0.1',
+ released_at: '2019-03-25 10:00:00'
+ }
+
+ expect do
+ post api("/projects/#{project.id}/releases", maintainer), params: params
+ end.to change { Release.count }.by(1)
+
+ expect(project.releases.last.name).to eq('New release without description')
+ expect(project.releases.last.tag).to eq('v0.1')
+ expect(project.releases.last.description).to eq(nil)
+ end
+
it 'sets the released_at to the current time if the released_at parameter is not provided' do
now = Time.zone.parse('2015-08-25 06:00:00Z')
Timecop.freeze(now) do
@@ -451,26 +467,6 @@ describe API::Releases do
expect(project.releases.last.released_at).to eq('2019-03-25T01:00:00Z')
end
- context 'when description is empty' do
- let(:params) do
- {
- name: 'New release',
- tag_name: 'v0.1',
- description: ''
- }
- end
-
- it 'returns an error as validation failure' do
- expect do
- post api("/projects/#{project.id}/releases", maintainer), params: params
- end.not_to change { Release.count }
-
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message'])
- .to eq("Validation failed: Description can't be blank")
- end
- end
-
it 'matches response schema' do
post api("/projects/#{project.id}/releases", maintainer), params: params
diff --git a/spec/services/ci/update_ci_ref_status_service_spec.rb b/spec/services/ci/update_ci_ref_status_service_spec.rb
index 2b069452a55..8b60586318d 100644
--- a/spec/services/ci/update_ci_ref_status_service_spec.rb
+++ b/spec/services/ci/update_ci_ref_status_service_spec.rb
@@ -119,6 +119,14 @@ describe Ci::UpdateCiRefStatusService do
it_behaves_like 'does a noop'
end
+ context 'pipeline is retried' do
+ before do
+ ci_ref.update!(last_updated_by_pipeline: pipeline)
+ end
+
+ it_behaves_like 'updates ci_ref'
+ end
+
context 'ref is stale' do
let(:pipeline1) { create(:ci_pipeline, :success, project: ci_ref.project, ref: ci_ref.ref, tag: ci_ref.tag) }
let(:pipeline2) { create(:ci_pipeline, :success, project: ci_ref.project, ref: ci_ref.ref, tag: ci_ref.tag) }
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 731febe75b3..55987c6fa0f 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -312,6 +312,8 @@ describe Projects::ForkService do
# Stub everything required to move a project to a Gitaly shard that does not exist
stub_storage_settings('test_second_storage' => { 'path' => TestEnv::SECOND_STORAGE_PATH })
+ allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_repository)
+ .and_return(true)
allow_any_instance_of(Gitlab::Git::Repository).to receive(:replicate)
allow_any_instance_of(Gitlab::Git::Repository).to receive(:checksum)
.and_return(::Gitlab::Git::BLANK_SHA)
diff --git a/spec/services/projects/update_repository_storage_service_spec.rb b/spec/services/projects/update_repository_storage_service_spec.rb
index 106a639ba28..23ce6f9165d 100644
--- a/spec/services/projects/update_repository_storage_service_spec.rb
+++ b/spec/services/projects/update_repository_storage_service_spec.rb
@@ -32,6 +32,8 @@ describe Projects::UpdateRepositoryStorageService do
project.repository.path_to_repo
end
+ expect(project_repository_double).to receive(:create_repository)
+ .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
@@ -58,6 +60,8 @@ describe Projects::UpdateRepositoryStorageService do
context 'when the move fails' do
it 'unmarks the repository as read-only without updating the repository storage' do
+ expect(project_repository_double).to receive(:create_repository)
+ .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
.and_raise(Gitlab::Git::CommandError)
@@ -73,6 +77,8 @@ describe Projects::UpdateRepositoryStorageService do
context 'when the checksum does not match' do
it 'unmarks the repository as read-only without updating the repository storage' do
+ expect(project_repository_double).to receive(:create_repository)
+ .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
@@ -91,6 +97,8 @@ describe Projects::UpdateRepositoryStorageService do
let!(:pool) { create(:pool_repository, :ready, source_project: project) }
it 'leaves the pool' do
+ expect(project_repository_double).to receive(:create_repository)
+ .and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb
index f6c70873540..7f1849e39a4 100644
--- a/spec/services/releases/update_service_spec.rb
+++ b/spec/services/releases/update_service_spec.rb
@@ -44,12 +44,6 @@ describe Releases::UpdateService do
it_behaves_like 'a failed update'
end
- context 'with an invalid update' do
- let(:new_description) { '' }
-
- it_behaves_like 'a failed update'
- end
-
context 'when a milestone is passed in' do
let(:milestone) { create(:milestone, project: project, title: 'v1.0') }
let(:params_with_milestone) { params.merge!({ milestones: [new_title] }) }
diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
index e30c620c4b1..b22379b8b68 100644
--- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb
@@ -22,11 +22,15 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
context 'when the move succeeds', :clean_gitlab_redis_shared_state do
before do
+ allow(project_repository_double).to receive(:create_repository)
+ .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
+ allow(repository_double).to receive(:create_repository)
+ .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
@@ -90,11 +94,15 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
context "when the move of the #{repository_type} repository fails" do
it 'unmarks the repository as read-only without updating the repository storage' do
+ allow(project_repository_double).to receive(:create_repository)
+ .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
+ allow(repository_double).to receive(:create_repository)
+ .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
.and_raise(Gitlab::Git::CommandError)
@@ -111,11 +119,15 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
context "when the checksum of the #{repository_type} repository does not match" do
it 'unmarks the repository as read-only without updating the repository storage' do
+ allow(project_repository_double).to receive(:create_repository)
+ .and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
+ allow(repository_double).to receive(:create_repository)
+ .and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
diff --git a/yarn.lock b/yarn.lock
index 692021ab90c..62af0d03600 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7766,10 +7766,10 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
-mermaid@^8.4.5:
- version "8.4.5"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.4.5.tgz#48d5722cbc72be2ad01002795835d7ca1b48e000"
- integrity sha512-oJWgZBtT2rvAdmqHvKjDwb3tOut1+ksfgDdZrVhhNcdzNibzGPjCsmMPpVXjkFYzKZCVunIbAkfxltSuaGIhaw==
+mermaid@^8.4.8:
+ version "8.4.8"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.4.8.tgz#8adcfdbc505d6bca52df167cff690427c9727b60"
+ integrity sha512-sumTNBFwMX7oMQgogdr3NhgTeQOiwcEsm23rQ4KHGW7tpmvMwER1S+1gjCSSnqlmM/zw7Ga7oesYCYicKboRwQ==
dependencies:
"@braintree/sanitize-url" "^3.1.0"
crypto-random-string "^3.0.1"