summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml2
-rw-r--r--CHANGELOG.md21
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock10
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js12
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js3
-rw-r--r--app/assets/javascripts/diffs/workers/tree_worker.js5
-rw-r--r--app/assets/javascripts/groups/transfer_dropdown.js3
-rw-r--r--app/assets/javascripts/mirrors/ssh_mirror.js2
-rw-r--r--app/assets/javascripts/monitoring/services/monitoring_service.js4
-rw-r--r--app/assets/javascripts/mr_popover/constants.js8
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue4
-rw-r--r--app/assets/stylesheets/pages/search.scss2
-rw-r--r--app/controllers/projects/environments_controller.rb8
-rw-r--r--app/graphql/resolvers/issues_resolver.rb6
-rw-r--r--app/helpers/markup_helper.rb2
-rw-r--r--app/models/application_record.rb17
-rw-r--r--app/models/ci/bridge.rb3
-rw-r--r--app/models/ci/build.rb3
-rw-r--r--app/models/ci/pipeline.rb12
-rw-r--r--app/models/concerns/ci/contextable.rb8
-rw-r--r--app/models/concerns/ci/pipeline_delegator.rb21
-rw-r--r--app/models/concerns/has_ref.rb3
-rw-r--r--app/models/deployment.rb6
-rw-r--r--app/models/notification_recipient.rb20
-rw-r--r--app/models/repository.rb13
-rw-r--r--app/serializers/concerns/user_status_tooltip.rb2
-rw-r--r--app/services/git/base_hooks_service.rb14
-rw-r--r--app/services/tags/destroy_service.rb11
-rw-r--r--app/services/todos/destroy/entity_leave_service.rb13
-rw-r--r--app/views/clusters/clusters/show.html.haml2
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/help/index.html.haml3
-rw-r--r--app/views/notify/new_merge_request_email.html.haml2
-rw-r--r--app/views/projects/cleanup/_show.html.haml2
-rw-r--r--app/views/projects/default_branch/_show.html.haml2
-rw-r--r--app/views/projects/deploy_keys/_index.html.haml2
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml2
-rw-r--r--app/views/projects/new.html.haml4
-rw-r--r--app/views/projects/protected_branches/shared/_index.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_index.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/search/results/_wiki_blob.html.haml2
-rw-r--r--app/workers/pipeline_schedule_worker.rb28
-rw-r--r--app/workers/post_receive.rb4
-rw-r--r--changelogs/unreleased/55948-help-text-formatting-wiki.yml5
-rw-r--r--changelogs/unreleased/60821-deployment-jobs-broken-as-of-11-10-0.yml5
-rw-r--r--changelogs/unreleased/60855-mr-popover-is-not-attached-in-system-notes.yml5
-rw-r--r--changelogs/unreleased/61036-fix-ingress-base-domain-text.yml5
-rw-r--r--changelogs/unreleased/fix-ci-commit-ref-name-and-slug.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.36.0.yml5
-rw-r--r--changelogs/unreleased/lock-pipeline-schedule-worker.yml5
-rw-r--r--changelogs/unreleased/pl-upgrade-letter_opener_web.yml5
-rw-r--r--changelogs/unreleased/secure-disallow-read-user-scope-to-read-project-events.yml5
-rw-r--r--changelogs/unreleased/sh-fix-slow-partial-rendering.yml5
-rw-r--r--changelogs/unreleased/wiki-search-results-fix.yml5
-rw-r--r--doc/ci/yaml/README.md14
-rw-r--r--doc/development/documentation/styleguide.md13
-rw-r--r--doc/development/testing_guide/end_to_end_tests.md5
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/events.rb49
-rw-r--r--lib/api/helpers/events_helpers.rb31
-rw-r--r--lib/api/project_events.rb29
-rw-r--r--lib/gitlab/action_view_output/context.rb41
-rw-r--r--lib/gitlab/data_builder/push.rb12
-rw-r--r--lib/gitlab/git/repository.rb6
-rw-r--r--lib/gitlab/git_access.rb6
-rw-r--r--lib/gitlab/profiler.rb2
-rw-r--r--lib/gitlab/sidekiq_config.rb4
-rw-r--r--locale/gitlab.pot15
-rw-r--r--package.json2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb10
-rw-r--r--qa/qa/vendor/github/page/login.rb4
-rw-r--r--rubocop/cop/include_action_view_context.rb31
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb8
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb36
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js35
-rw-r--r--spec/frontend/vue_shared/components/notes/system_note_spec.js7
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb155
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb11
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb6
-rw-r--r--spec/lib/gitlab/profiler_spec.rb6
-rw-r--r--spec/models/application_record_spec.rb19
-rw-r--r--spec/models/ci/bridge_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb18
-rw-r--r--spec/models/ci/pipeline_spec.rb48
-rw-r--r--spec/models/deployment_spec.rb6
-rw-r--r--spec/models/notification_recipient_spec.rb40
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb11
-rw-r--r--spec/models/repository_spec.rb65
-rw-r--r--spec/requests/api/events_spec.rb135
-rw-r--r--spec/requests/api/project_events_spec.rb156
-rw-r--r--spec/rubocop/cop/include_action_view_context_spec.rb45
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb14
-rw-r--r--spec/spec_helper.rb1
-rw-r--r--spec/support/helpers/graphql_helpers.rb13
-rw-r--r--spec/support/shared_examples/models/chat_service_spec.rb2
-rw-r--r--spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb6
-rw-r--r--spec/views/projects/issues/show.html.haml_spec.rb27
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb12
-rw-r--r--yarn.lock20
109 files changed, 1094 insertions, 459 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f1573dba32a..44beccd966a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-71.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 935b494b6f7..fbf8925e30a 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -16,7 +16,7 @@
gitlab:assets:compile:
<<: *assets-compile-cache
extends: .dedicated-no-docs-pull-cache-job
- image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.21-chrome-71.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
+ image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.21-chrome-73.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
dependencies:
- setup-test-env
services:
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index bdc6ce234b8..01e71a7faf1 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -86,7 +86,7 @@
.rspec-metadata-pg-10: &rspec-metadata-pg-10
<<: *rspec-metadata
<<: *use-pg-10
- image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-71.0-node-10.x-yarn-1.12-postgresql-10-graphicsmagick-1.3.29"
+ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.21-chrome-73.0-node-10.x-yarn-1.12-postgresql-10-graphicsmagick-1.3.29"
.rspec-metadata-mysql: &rspec-metadata-mysql
<<: *rspec-metadata
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd4c0e479cc..8e1ffeaebd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,16 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.10.2 (2019-04-25)
+
+### Security (4 changes)
+
+- Loosen regex for exception sanitization. !3076
+- Resolve: moving an issue to private repo leaks namespace and project name.
+- Escape path in new merge request mail.
+- Stop sending emails to users who can't read commit.
+
+
## 11.10.1 (2019-04-23)
### Fixed (2 changes)
@@ -253,6 +263,17 @@ entry.
- Removes EE differences for environment_item.vue.
+## 11.9.10 (2019-04-26)
+
+### Security (5 changes)
+
+- Loosen regex for exception sanitization. !3077
+- Resolve: moving an issue to private repo leaks namespace and project name.
+- Escape path in new merge request mail.
+- Stop sending emails to users who can't read commit.
+- Upgrade Rails to 5.0.7.2.
+
+
## 11.9.9 (2019-04-23)
### Performance (1 change)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index a2d87226ac2..39fc130ef85 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.35.0 \ No newline at end of file
+1.36.0
diff --git a/Gemfile b/Gemfile
index a350f194f62..95d65ec30c7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -79,6 +79,7 @@ gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
# GraphQL API
gem 'graphql', '~> 1.8.0'
gem 'graphiql-rails', '~> 1.4.10'
+gem 'apollo_upload_server', '~> 2.0.0.beta3'
# Disable strong_params so that Mash does not respond to :permitted?
gem 'hashie-forbidden_attributes'
@@ -308,7 +309,7 @@ group :development do
gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false
- gem 'letter_opener_web', '~> 1.3.0'
+ gem 'letter_opener_web', '~> 1.3.4'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
# Better errors handler
diff --git a/Gemfile.lock b/Gemfile.lock
index 64f2f78a4f8..c08f0c24ba6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -52,6 +52,9 @@ GEM
public_suffix (>= 2.0.2, < 4.0)
aes_key_wrap (1.0.1)
akismet (2.0.0)
+ apollo_upload_server (2.0.0.beta.3)
+ graphql (>= 1.8)
+ rails (>= 4.2)
arel (8.0.0)
asana (0.8.1)
faraday (~> 0.9)
@@ -441,9 +444,9 @@ GEM
rest-client (~> 2.0)
launchy (2.4.3)
addressable (~> 2.3)
- letter_opener (1.4.1)
+ letter_opener (1.7.0)
launchy (~> 2.2)
- letter_opener_web (1.3.0)
+ letter_opener_web (1.3.4)
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
@@ -988,6 +991,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 6.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
+ apollo_upload_server (~> 2.0.0.beta3)
asana (~> 0.8.1)
asciidoctor (~> 1.5.8)
asciidoctor-plantuml (= 0.0.8)
@@ -1089,7 +1093,7 @@ DEPENDENCIES
kaminari (~> 1.0)
knapsack (~> 1.17)
kubeclient (~> 4.2.2)
- letter_opener_web (~> 1.3.0)
+ letter_opener_web (~> 1.3.4)
license_finder (~> 5.4)
licensee (~> 8.9)
lograge (~> 0.5)
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 4f47f1b6550..8461e01de7b 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -279,14 +279,10 @@ export default class Clusters {
this.store.acknowledgeSuccessfulUpdate(appId);
}
- toggleIngressDomainHelpText(ingressPreviousState, ingressNewState) {
- const { externalIp, status } = ingressNewState;
- const helpTextHidden = status !== APPLICATION_STATUS.INSTALLED || !externalIp;
- const domainSnippetText = `${externalIp}${INGRESS_DOMAIN_SUFFIX}`;
-
- if (ingressPreviousState.status !== status) {
- this.ingressDomainHelpText.classList.toggle('hide', helpTextHidden);
- this.ingressDomainSnippet.textContent = domainSnippetText;
+ toggleIngressDomainHelpText({ externalIp }, { externalIp: newExternalIp }) {
+ if (externalIp !== newExternalIp) {
+ this.ingressDomainHelpText.classList.toggle('hide', !newExternalIp);
+ this.ingressDomainSnippet.textContent = `${newExternalIp}${INGRESS_DOMAIN_SUFFIX}`;
}
}
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 4de425b48e7..3f0a9f2602c 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -12,6 +12,7 @@ import stageStagingComponent from './components/stage_staging_component.vue';
import stageTestComponent from './components/stage_test_component.vue';
import CycleAnalyticsService from './cycle_analytics_service';
import CycleAnalyticsStore from './cycle_analytics_store';
+import { __ } from '~/locale';
Vue.use(Translate);
@@ -61,7 +62,7 @@ export default () => {
methods: {
handleError() {
this.store.setErrorState(true);
- return new Flash('There was an error while fetching cycle analytics data.');
+ return new Flash(__('There was an error while fetching cycle analytics data.'));
},
initDropdown() {
const $dropdown = $('.js-ca-dropdown');
diff --git a/app/assets/javascripts/diffs/workers/tree_worker.js b/app/assets/javascripts/diffs/workers/tree_worker.js
index 534d737c77e..415c463fd19 100644
--- a/app/assets/javascripts/diffs/workers/tree_worker.js
+++ b/app/assets/javascripts/diffs/workers/tree_worker.js
@@ -4,6 +4,11 @@ import { generateTreeList } from '../store/utils';
// eslint-disable-next-line no-restricted-globals
self.addEventListener('message', e => {
const { data } = e;
+
+ if (data === undefined) {
+ return;
+ }
+
const { treeEntries, tree } = generateTreeList(data);
// eslint-disable-next-line no-restricted-globals
diff --git a/app/assets/javascripts/groups/transfer_dropdown.js b/app/assets/javascripts/groups/transfer_dropdown.js
index 26510fcdb2a..ce0c9256148 100644
--- a/app/assets/javascripts/groups/transfer_dropdown.js
+++ b/app/assets/javascripts/groups/transfer_dropdown.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { __ } from '~/locale';
export default class TransferDropdown {
constructor() {
@@ -13,7 +14,7 @@ export default class TransferDropdown {
}
buildDropdown() {
- const extraOptions = [{ id: '', text: 'No parent group' }, 'divider'];
+ const extraOptions = [{ id: '', text: __('No parent group') }, 'divider'];
this.groupDropdown.glDropdown({
selectable: true,
diff --git a/app/assets/javascripts/mirrors/ssh_mirror.js b/app/assets/javascripts/mirrors/ssh_mirror.js
index 547c078ec55..f7e80950803 100644
--- a/app/assets/javascripts/mirrors/ssh_mirror.js
+++ b/app/assets/javascripts/mirrors/ssh_mirror.js
@@ -290,7 +290,7 @@ export default class SSHMirror {
this.setSSHPublicKey(data.import_data_attributes.ssh_public_key);
})
.catch(() => {
- Flash(_('Unable to regenerate public ssh key.'));
+ Flash(__('Unable to regenerate public ssh key.'));
});
}
diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js
index 5fcc2c8cfac..1efa5189996 100644
--- a/app/assets/javascripts/monitoring/services/monitoring_service.js
+++ b/app/assets/javascripts/monitoring/services/monitoring_service.js
@@ -1,7 +1,7 @@
import axios from '../../lib/utils/axios_utils';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
-import { s__ } from '../../locale';
+import { s__, __ } from '../../locale';
const MAX_REQUESTS = 3;
@@ -15,7 +15,7 @@ function backOffRequest(makeRequestCallback) {
if (requestCounter < MAX_REQUESTS) {
next();
} else {
- stop(new Error('Failed to connect to the prometheus server'));
+ stop(new Error(__('Failed to connect to the prometheus server')));
}
} else {
stop(resp);
diff --git a/app/assets/javascripts/mr_popover/constants.js b/app/assets/javascripts/mr_popover/constants.js
index 433df844c80..c13c417cc18 100644
--- a/app/assets/javascripts/mr_popover/constants.js
+++ b/app/assets/javascripts/mr_popover/constants.js
@@ -1,10 +1,12 @@
+import { __ } from '~/locale';
+
export const mrStates = {
merged: 'merged',
closed: 'closed',
};
export const humanMRStates = {
- merged: 'Merged',
- closed: 'Closed',
- open: 'Open',
+ merged: __('Merged'),
+ closed: __('Closed'),
+ open: __('Open'),
};
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
index f01a51da0b3..ba63683f5c0 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
@@ -1,10 +1,12 @@
+import { __ } from '~/locale';
+
const viewers = {
image: {
id: 'image',
},
markdown: {
id: 'markdown',
- previewTitle: 'Preview Markdown',
+ previewTitle: __('Preview Markdown'),
},
};
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index acc179b3834..3c86b7e4c61 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -22,6 +22,7 @@ import noteHeader from '~/notes/components/note_header.vue';
import Icon from '~/vue_shared/components/icon.vue';
import TimelineEntryItem from './timeline_entry_item.vue';
import { spriteIcon } from '../../../lib/utils/common_utils';
+import initMRPopovers from '~/mr_popover/';
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
@@ -71,6 +72,9 @@ export default {
);
},
},
+ mounted() {
+ initMRPopovers(this.$el.querySelectorAll('.gfm-merge_request'));
+ },
};
</script>
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index 0c99ff5341c..37071a57bb3 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -75,6 +75,8 @@ input[type='checkbox']:hover {
}
.search-input-wrap {
+ width: 100%;
+
.search-icon,
.clear-icon {
position: absolute;
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 1f619c8fd2f..4aa572ade73 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -135,13 +135,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics
- # Currently, this acts as a hint to load the metrics details into the cache
- # if they aren't there already
- @metrics = environment.metrics || {}
-
respond_to do |format|
format.html
format.json do
+ # Currently, this acts as a hint to load the metrics details into the cache
+ # if they aren't there already
+ @metrics = environment.metrics || {}
+
render json: @metrics, status: @metrics.any? ? :ok : :no_content
end
end
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index b98d8bd1fff..54d32a688bf 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -44,6 +44,12 @@ module Resolvers
alias_method :project, :object
def resolve(**args)
+ # The project could have been loaded in batch by `BatchLoader`.
+ # At this point we need the `id` of the project to query for issues, so
+ # make sure it's loaded and not `nil` before continueing.
+ project.sync if project.respond_to?(:sync)
+ return Issue.none if project.nil?
+
# Will need to be be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54520
args[:project_id] = project.id
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index ad77f99fe44..dce4168ad7b 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -4,7 +4,7 @@ require 'nokogiri'
module MarkupHelper
include ActionView::Helpers::TagHelper
- include ActionView::Context
+ include ::Gitlab::ActionViewOutput::Context
def plain?(filename)
Gitlab::MarkupHelper.plain?(filename)
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 9d71f250466..d1d01368972 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -17,6 +17,19 @@ class ApplicationRecord < ActiveRecord::Base
where(nil).pluck(self.primary_key)
end
+ def self.safe_ensure_unique(retries: 0)
+ transaction(requires_new: true) do
+ yield
+ end
+ rescue ActiveRecord::RecordNotUnique
+ if retries > 0
+ retries -= 1
+ retry
+ end
+
+ false
+ end
+
def self.safe_find_or_create_by!(*args)
safe_find_or_create_by(*args).tap do |record|
record.validate! unless record.persisted?
@@ -24,10 +37,8 @@ class ApplicationRecord < ActiveRecord::Base
end
def self.safe_find_or_create_by(*args)
- transaction(requires_new: true) do
+ safe_ensure_unique(retries: 1) do
find_or_create_by(*args)
end
- rescue ActiveRecord::RecordNotUnique
- retry
end
end
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index 0d8d7d95791..644716ba8e7 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -4,6 +4,7 @@ module Ci
class Bridge < CommitStatus
include Ci::Processable
include Ci::Contextable
+ include Ci::PipelineDelegator
include Importable
include AfterCommitQueue
include HasRef
@@ -13,8 +14,6 @@ module Ci
belongs_to :trigger_request
validates :ref, presence: true
- delegate :merge_request_event?, to: :pipeline
-
def self.retry(bridge, current_user)
raise NotImplementedError
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e5236051118..5a2ead41578 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -6,6 +6,7 @@ module Ci
include Ci::Processable
include Ci::Metadatable
include Ci::Contextable
+ include Ci::PipelineDelegator
include TokenAuthenticatable
include AfterCommitQueue
include ObjectStorage::BackgroundMove
@@ -49,8 +50,6 @@ module Ci
delegate :terminal_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project
delegate :trigger_short_token, to: :trigger_request, allow_nil: true
- delegate :merge_request_event?, :merge_request_ref?,
- :legacy_detached_merge_request_pipeline?, to: :pipeline
##
# Since Gitlab 11.5, deployments records started being created right after
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index bbd21eb0e78..2b7835d7fab 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -759,6 +759,18 @@ module Ci
user == current_user
end
+ def source_ref
+ if triggered_by_merge_request?
+ merge_request.source_branch
+ else
+ ref
+ end
+ end
+
+ def source_ref_slug
+ Gitlab::Utils.slugify(source_ref.to_s)
+ end
+
private
def ci_yaml_from_repo
diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb
index 4986a42dbd2..e1d5ce7f7d4 100644
--- a/app/models/concerns/ci/contextable.rb
+++ b/app/models/concerns/ci/contextable.rb
@@ -70,8 +70,8 @@ module Ci
variables.append(key: 'CI_COMMIT_SHA', value: sha)
variables.append(key: 'CI_COMMIT_SHORT_SHA', value: short_sha)
variables.append(key: 'CI_COMMIT_BEFORE_SHA', value: before_sha)
- variables.append(key: 'CI_COMMIT_REF_NAME', value: ref)
- variables.append(key: 'CI_COMMIT_REF_SLUG', value: ref_slug)
+ variables.append(key: 'CI_COMMIT_REF_NAME', value: source_ref)
+ variables.append(key: 'CI_COMMIT_REF_SLUG', value: source_ref_slug)
variables.append(key: "CI_COMMIT_TAG", value: ref) if tag?
variables.append(key: "CI_PIPELINE_TRIGGERED", value: 'true') if trigger_request
variables.append(key: "CI_JOB_MANUAL", value: 'true') if action?
@@ -85,8 +85,8 @@ module Ci
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_BUILD_REF', value: sha)
variables.append(key: 'CI_BUILD_BEFORE_SHA', value: before_sha)
- variables.append(key: 'CI_BUILD_REF_NAME', value: ref)
- variables.append(key: 'CI_BUILD_REF_SLUG', value: ref_slug)
+ variables.append(key: 'CI_BUILD_REF_NAME', value: source_ref)
+ variables.append(key: 'CI_BUILD_REF_SLUG', value: source_ref_slug)
variables.append(key: 'CI_BUILD_NAME', value: name)
variables.append(key: 'CI_BUILD_STAGE', value: stage)
variables.append(key: "CI_BUILD_TAG", value: ref) if tag?
diff --git a/app/models/concerns/ci/pipeline_delegator.rb b/app/models/concerns/ci/pipeline_delegator.rb
new file mode 100644
index 00000000000..dbc5ed1bc9a
--- /dev/null
+++ b/app/models/concerns/ci/pipeline_delegator.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+##
+# This module is mainly used by child associations of `Ci::Pipeline` that needs to look up
+# single source of truth. For example, `Ci::Build` has `git_ref` method, which behaves
+# slightly different from `Ci::Pipeline`'s `git_ref`. This is very confusing as
+# the system could behave differently time to time.
+# We should have a single interface in `Ci::Pipeline` and access the method always.
+module Ci
+ module PipelineDelegator
+ extend ActiveSupport::Concern
+
+ included do
+ delegate :merge_request_event?,
+ :merge_request_ref?,
+ :source_ref,
+ :source_ref_slug,
+ :legacy_detached_merge_request_pipeline?, to: :pipeline
+ end
+ end
+end
diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb
index 413cd36dcaa..fa0cf5ddfd2 100644
--- a/app/models/concerns/has_ref.rb
+++ b/app/models/concerns/has_ref.rb
@@ -1,5 +1,8 @@
# frozen_string_literal: true
+##
+# We will disable `ref` and `sha` attributes in `Ci::Build` in the future
+# and remove this module in favor of Ci::PipelineDelegator.
module HasRef
extend ActiveSupport::Concern
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index f5fdf285522..92c7311014a 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -85,7 +85,11 @@ class Deployment < ApplicationRecord
end
def cluster
- project.deployment_platform(environment: environment.name)&.cluster
+ platform = project.deployment_platform(environment: environment.name)
+
+ if platform.present? && platform.respond_to?(:cluster)
+ platform.cluster
+ end
end
def execute_hooks
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index 377ac3febb6..6889e0d776b 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -119,15 +119,19 @@ class NotificationRecipient
return @read_ability if instance_variable_defined?(:@read_ability)
@read_ability =
- case @target
- when Issuable
- :"read_#{@target.to_ability_name}"
- when Ci::Pipeline
+ if @target.is_a?(Ci::Pipeline)
:read_build # We have build trace in pipeline emails
- when ActiveRecord::Base
- :"read_#{@target.class.model_name.name.underscore}"
- else
- nil
+ elsif default_ability_for_target
+ :"read_#{default_ability_for_target}"
+ end
+ end
+
+ def default_ability_for_target
+ @default_ability_for_target ||=
+ if @target.respond_to?(:to_ability_name)
+ @target.to_ability_name
+ elsif @target.class.respond_to?(:model_name)
+ @target.class.model_name.name.underscore
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 51ab2247a03..8b728c4f6b2 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1065,6 +1065,19 @@ class Repository
blob.data
end
+ def create_if_not_exists
+ return if exists?
+
+ raw.create_repository
+ after_create
+ end
+
+ def blobs_metadata(paths, ref = 'HEAD')
+ references = Array.wrap(paths).map { |path| [ref, path] }
+
+ Gitlab::Git::Blob.batch_metadata(raw, references).map { |raw_blob| Blob.decorate(raw_blob) }
+ end
+
private
# TODO Generice finder, later split this on finders by Ref or Oid
diff --git a/app/serializers/concerns/user_status_tooltip.rb b/app/serializers/concerns/user_status_tooltip.rb
index 633b117d392..a81e377691e 100644
--- a/app/serializers/concerns/user_status_tooltip.rb
+++ b/app/serializers/concerns/user_status_tooltip.rb
@@ -3,7 +3,7 @@
module UserStatusTooltip
extend ActiveSupport::Concern
include ActionView::Helpers::TagHelper
- include ActionView::Context
+ include ::Gitlab::ActionViewOutput::Context
include EmojiHelper
include UsersHelper
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index a8478e3a904..9d371e234ee 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -73,13 +73,13 @@ module Git
def push_data
@push_data ||= Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- limited_commits,
- event_message,
+ project: project,
+ user: current_user,
+ oldrev: params[:oldrev],
+ newrev: params[:newrev],
+ ref: params[:ref],
+ commits: limited_commits,
+ message: event_message,
commits_count: commits_count,
push_options: params[:push_options] || {}
)
diff --git a/app/services/tags/destroy_service.rb b/app/services/tags/destroy_service.rb
index cab507946b4..4f6ae07be7d 100644
--- a/app/services/tags/destroy_service.rb
+++ b/app/services/tags/destroy_service.rb
@@ -41,12 +41,11 @@ module Tags
def build_push_data(tag)
Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- tag.dereferenced_target.sha,
- Gitlab::Git::BLANK_SHA,
- "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}",
- [])
+ project: project,
+ user: current_user,
+ oldrev: tag.dereferenced_target.sha,
+ newrev: Gitlab::Git::BLANK_SHA,
+ ref: "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}")
end
end
end
diff --git a/app/services/todos/destroy/entity_leave_service.rb b/app/services/todos/destroy/entity_leave_service.rb
index ebfb20132d0..4743e9b02ce 100644
--- a/app/services/todos/destroy/entity_leave_service.rb
+++ b/app/services/todos/destroy/entity_leave_service.rb
@@ -37,8 +37,8 @@ module Todos
private
def enqueue_private_features_worker
- project_ids.each do |project_id|
- TodosDestroyer::PrivateFeaturesWorker.perform_async(project_id, user.id)
+ projects.each do |project|
+ TodosDestroyer::PrivateFeaturesWorker.perform_async(project.id, user.id)
end
end
@@ -62,9 +62,8 @@ module Todos
end
# rubocop: enable CodeReuse/ActiveRecord
- override :project_ids
# rubocop: disable CodeReuse/ActiveRecord
- def project_ids
+ def projects
condition = case entity
when Project
{ id: entity.id }
@@ -72,13 +71,13 @@ module Todos
{ namespace_id: non_member_groups }
end
- Project.where(condition).select(:id)
+ Project.where(condition)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def non_authorized_projects
- project_ids.where('id NOT IN (?)', user.authorized_projects.select(:id))
+ projects.where('id NOT IN (?)', user.authorized_projects.select(:id))
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -110,7 +109,7 @@ module Todos
authorized_reporter_projects = user
.authorized_projects(Gitlab::Access::REPORTER).select(:id)
- Issue.where(project_id: project_ids, confidential: true)
+ Issue.where(project_id: projects, confidential: true)
.where('project_id NOT IN(?)', authorized_reporter_projects)
.where('author_id != ?', user.id)
.where('id NOT IN (?)', assigned_ids)
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index e38a16e7a1a..80d706ae3d3 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -4,7 +4,7 @@
- page_title _('Kubernetes Cluster')
- manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
- status_path = clusterable.cluster_status_cluster_path(@cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster)
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 2f635757902..0c8f86c2822 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,6 +1,6 @@
- breadcrumb_title "General Settings"
- @content_class = "limit-container-width" unless fluid_layout
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded') }
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index d0f5cd94002..d21496ee0aa 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -1,7 +1,7 @@
- breadcrumb_title "CI / CD Settings"
- page_title "CI / CD"
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings#ci-variables.no-animate{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index 916f98a62d1..75e4dc46c9b 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -1,6 +1,7 @@
%div
- if Gitlab::CurrentSettings.help_page_text.present?
- = markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
+ .prepend-top-default.md
+ = markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr
%h1
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 77d2e65d285..9ab648e2a64 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -3,7 +3,7 @@
#{link_to @merge_request.author_name, user_url(@merge_request.author)} created a merge request:
%p.details
- != merge_path_description(@merge_request, '&rarr;')
+ = merge_path_description(@merge_request, '→')
- if @merge_request.assignees.any?
%p
diff --git a/app/views/projects/cleanup/_show.html.haml b/app/views/projects/cleanup/_show.html.haml
index 888be4ee282..ed3c9890efd 100644
--- a/app/views/projects/cleanup/_show.html.haml
+++ b/app/views/projects/cleanup/_show.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.no-animate#cleanup{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/default_branch/_show.html.haml b/app/views/projects/default_branch/_show.html.haml
index ff6a9d49a61..59efcde5825 100644
--- a/app/views/projects/default_branch/_show.html.haml
+++ b/app/views/projects/default_branch/_show.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.no-animate#default-branch-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/deploy_keys/_index.html.haml b/app/views/projects/deploy_keys/_index.html.haml
index 24d665761cc..fcf27351a21 100644
--- a/app/views/projects/deploy_keys/_index.html.haml
+++ b/app/views/projects/deploy_keys/_index.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.qa-deploy-keys-settings.settings.no-animate#js-deploy-keys-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index c04530dc62c..c15b84d0aac 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -1,7 +1,7 @@
- breadcrumb_title _("General Settings")
- page_title _("General")
- @content_class = "limit-container-width" unless fluid_layout
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.general-settings.no-animate.expanded#js-general-settings
.settings-header
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 0bf664d5b66..715c36fa9aa 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -15,7 +15,7 @@
.issuable-status-box.status-box.status-box-issue-closed{ class: issue_button_visibility(@issue, false) }
= sprite_icon('mobile-issue-close', size: 16, css_class: 'd-block d-sm-none')
.d-none.d-sm-block
- - if @issue.moved?
+ - if @issue.moved? && can?(current_user, :read_issue, @issue.moved_to)
- moved_link_start = "<a href=\"#{issue_path(@issue.moved_to)}\" class=\"text-white text-underline\">".html_safe
- moved_link_end = '</a>'.html_safe
= s_('IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})').html_safe % {moved_link_start: moved_link_start,
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index a1ec2c887c2..0cd00d3e708 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
%section.settings.project-mirror-settings.js-mirror-settings.no-animate.qa-mirroring-repositories-settings#js-push-remote-settings{ class: ('expanded' if expanded) }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 95027634de2..d7e16dbd40c 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -16,6 +16,7 @@
= _('A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}.').html_safe % { among_other_things_link: among_other_things_link }
%p
= _('All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings.')
+ = render_if_exists 'projects/new_ci_cd_banner_external_repo'
%p
- pages_getting_started_guide = link_to _('Pages getting started guide'), help_page_path("user/project/pages/getting_started_part_two", anchor: "fork-a-project-to-get-started-from"), target: '_blank'
= _('Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}.').html_safe % { pages_getting_started_guide: pages_getting_started_guide }
@@ -42,6 +43,7 @@
%a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' }
%span.d-none.d-sm-block Import project
%span.d-block.d-sm-none Import
+ = render_if_exists 'projects/new_ci_cd_only_project_tab', active_tab: active_tab
.tab-content.gitlab-tab-content
.tab-pane{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' }
@@ -68,6 +70,8 @@
%h4 No import options available
%p Contact an administrator to enable options for importing your project.
+ = render_if_exists 'projects/new_ci_cd_only_project_pane', active_tab: active_tab
+
.save-project-loader.d-none
.center
%h2
diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml
index 539b184e5c2..63748d8d85f 100644
--- a/app/views/projects/protected_branches/shared/_index.html.haml
+++ b/app/views/projects/protected_branches/shared/_index.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.qa-protected-branches-settings.settings.no-animate#js-protected-branches-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/protected_tags/shared/_index.html.haml b/app/views/projects/protected_tags/shared/_index.html.haml
index 9a50a51e4be..b0c87ac8c17 100644
--- a/app/views/projects/protected_tags/shared/_index.html.haml
+++ b/app/views/projects/protected_tags/shared/_index.html.haml
@@ -1,4 +1,4 @@
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 548b7c06867..5e3e1076c2c 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -2,7 +2,7 @@
- page_title _("CI / CD Settings")
- page_title _("CI / CD")
-- expanded = Rails.env.test?
+- expanded = expanded_by_default?
- general_expanded = @project.errors.empty? ? expanded : true
%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index b351ecd4edf..5847751b268 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,5 +1,5 @@
- project = find_project_for_result_blob(projects, wiki_blob)
- wiki_blob = parse_search_result(wiki_blob)
-- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
+- wiki_blob_link = project_wiki_path(project, Pathname.new(wiki_blob.filename).sub_ext(''))
= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: wiki_blob.filename, blob_link: wiki_blob_link }
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index 02a69ea3b54..8a9ee7808e4 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -3,20 +3,26 @@
class PipelineScheduleWorker
include ApplicationWorker
include CronjobQueue
+ include ::Gitlab::ExclusiveLeaseHelpers
+
+ EXCLUSIVE_LOCK_KEY = 'pipeline_schedules:run:lock'
+ LOCK_TIMEOUT = 50.minutes
# rubocop: disable CodeReuse/ActiveRecord
def perform
- Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
- .preload(:owner, :project).find_each do |schedule|
-
- Ci::CreatePipelineService.new(schedule.project,
- schedule.owner,
- ref: schedule.ref)
- .execute!(:schedule, ignore_skip_ci: true, save_on_errors: true, schedule: schedule)
- rescue => e
- error(schedule, e)
- ensure
- schedule.schedule_next_run!
+ in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
+ Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
+ .preload(:owner, :project).find_each do |schedule|
+
+ schedule.schedule_next_run!
+
+ Ci::CreatePipelineService.new(schedule.project,
+ schedule.owner,
+ ref: schedule.ref)
+ .execute!(:schedule, ignore_skip_ci: true, save_on_errors: true, schedule: schedule)
+ rescue => e
+ error(schedule, e)
+ end
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 337efa7919b..9a9c0c9d803 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -21,8 +21,10 @@ class PostReceive
if repo_type.wiki?
process_wiki_changes(post_received)
- else
+ elsif repo_type.project?
process_project_changes(post_received)
+ else
+ # Other repos don't have hooks for now
end
end
diff --git a/changelogs/unreleased/55948-help-text-formatting-wiki.yml b/changelogs/unreleased/55948-help-text-formatting-wiki.yml
new file mode 100644
index 00000000000..e1e0475a117
--- /dev/null
+++ b/changelogs/unreleased/55948-help-text-formatting-wiki.yml
@@ -0,0 +1,5 @@
+---
+title: Format extra help page text like wiki
+merge_request: 26782
+author: Bastian Blank
+type: fixed
diff --git a/changelogs/unreleased/60821-deployment-jobs-broken-as-of-11-10-0.yml b/changelogs/unreleased/60821-deployment-jobs-broken-as-of-11-10-0.yml
new file mode 100644
index 00000000000..88584352d42
--- /dev/null
+++ b/changelogs/unreleased/60821-deployment-jobs-broken-as-of-11-10-0.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Kubernetes service template deployment jobs broken as of 11.10.0
+merge_request: 27687
+author:
+type: fixed
diff --git a/changelogs/unreleased/60855-mr-popover-is-not-attached-in-system-notes.yml b/changelogs/unreleased/60855-mr-popover-is-not-attached-in-system-notes.yml
new file mode 100644
index 00000000000..d8bc0fbb4d4
--- /dev/null
+++ b/changelogs/unreleased/60855-mr-popover-is-not-attached-in-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug where system note MR has no popover
+merge_request: 27747
+author:
+type: fixed
diff --git a/changelogs/unreleased/61036-fix-ingress-base-domain-text.yml b/changelogs/unreleased/61036-fix-ingress-base-domain-text.yml
new file mode 100644
index 00000000000..32f0e023923
--- /dev/null
+++ b/changelogs/unreleased/61036-fix-ingress-base-domain-text.yml
@@ -0,0 +1,5 @@
+---
+title: Fix base domain help text update
+merge_request: 27746
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-ci-commit-ref-name-and-slug.yml b/changelogs/unreleased/fix-ci-commit-ref-name-and-slug.yml
new file mode 100644
index 00000000000..c34bc6d8b52
--- /dev/null
+++ b/changelogs/unreleased/fix-ci-commit-ref-name-and-slug.yml
@@ -0,0 +1,5 @@
+---
+title: Make `CI_COMMIT_REF_NAME` and `SLUG` variable idempotent
+merge_request: 27663
+author:
+type: fixed
diff --git a/changelogs/unreleased/gitaly-version-v1.36.0.yml b/changelogs/unreleased/gitaly-version-v1.36.0.yml
new file mode 100644
index 00000000000..22fdca8da80
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.36.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.36.0
+merge_request: 27831
+author:
+type: changed
diff --git a/changelogs/unreleased/lock-pipeline-schedule-worker.yml b/changelogs/unreleased/lock-pipeline-schedule-worker.yml
new file mode 100644
index 00000000000..1b889f01620
--- /dev/null
+++ b/changelogs/unreleased/lock-pipeline-schedule-worker.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent concurrent execution of PipelineScheduleWorker
+merge_request: 27781
+author:
+type: performance
diff --git a/changelogs/unreleased/pl-upgrade-letter_opener_web.yml b/changelogs/unreleased/pl-upgrade-letter_opener_web.yml
new file mode 100644
index 00000000000..9891344215a
--- /dev/null
+++ b/changelogs/unreleased/pl-upgrade-letter_opener_web.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade letter_opener_web to support Rails 5.1
+merge_request: 27829
+author:
+type: fixed
diff --git a/changelogs/unreleased/secure-disallow-read-user-scope-to-read-project-events.yml b/changelogs/unreleased/secure-disallow-read-user-scope-to-read-project-events.yml
new file mode 100644
index 00000000000..4a91bfa8827
--- /dev/null
+++ b/changelogs/unreleased/secure-disallow-read-user-scope-to-read-project-events.yml
@@ -0,0 +1,5 @@
+---
+title: Allow to see project events only with api scope token
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-fix-slow-partial-rendering.yml b/changelogs/unreleased/sh-fix-slow-partial-rendering.yml
new file mode 100644
index 00000000000..0f65a6f8d69
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-slow-partial-rendering.yml
@@ -0,0 +1,5 @@
+---
+title: Fix slow performance with compiling HAML templates
+merge_request: 27782
+author:
+type: performance
diff --git a/changelogs/unreleased/wiki-search-results-fix.yml b/changelogs/unreleased/wiki-search-results-fix.yml
new file mode 100644
index 00000000000..693867eb385
--- /dev/null
+++ b/changelogs/unreleased/wiki-search-results-fix.yml
@@ -0,0 +1,5 @@
+---
+title: fix wiki search result links in titles
+merge_request: 27400
+author: khm
+type: fixed
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 54fc7865dd1..03383d11c14 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -2065,8 +2065,8 @@ include:
file: /templates/docker-workflow.yml
```
-The `/templates/docker-workflow.yml` present in `/group/my-project` includes two local files
-of the `/group/my-project`:
+The `/templates/docker-workflow.yml` present in `group/my-project` includes two local files
+of the `group/my-project`:
```yaml
include:
@@ -2074,14 +2074,14 @@ include:
- local: : /templates/docker-testing.yml
```
-Our `/templates/docker-build.yml` present in `/group/my-project` adds a `docker-build` job:
+Our `/templates/docker-build.yml` present in `group/my-project` adds a `docker-build` job:
```yaml
docker-build:
script: docker build -t my-image .
```
-Our second `/templates/docker-test.yml` present in `/group/my-project` adds a `docker-test` job:
+Our second `/templates/docker-test.yml` present in `group/my-project` adds a `docker-test` job:
```yaml
docker-test:
@@ -2479,9 +2479,9 @@ This can only be used when `custom_build_dir` is enabled in the [Runner's
configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnerscustom_build_dir-section).
This is the default configuration for `docker` and `kubernetes` executor.
-By default, GitLab Runner clones the repository in a unique subpath of the
-`$CI_BUILDS_DIR` directory. However, your project might require the code in a
-specific directory (Go projects, for example). In that case, you can specify
+By default, GitLab Runner clones the repository in a unique subpath of the
+`$CI_BUILDS_DIR` directory. However, your project might require the code in a
+specific directory (Go projects, for example). In that case, you can specify
the `GIT_CLONE_PATH` variable to tell the Runner in which directory to clone the
repository:
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index fbca99fbfea..9d8d5afedad 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -362,16 +362,23 @@ For other punctuation rules, please refer to the
E.g., instead of writing something like `Read more about GitLab Issue Boards [here](LINK)`,
write `Read more about [GitLab Issue Boards](LINK)`.
-### Links to confidential issues
+### Links requiring permissions
-Don't link directly to [confidential issues](../../user/project/issues/confidential_issues.md). These will fail for:
+Don't link directly to:
+
+- [Confidential issues](../../user/project/issues/confidential_issues.md).
+- Project features that require [special permissions](../../user/permissions.md) to view.
+
+These will fail for:
- Those without sufficient permissions.
- Automated link checkers.
Instead:
-- Mention in the text that the information is contained in a confidential issue. This will reduce confusion.
+- To reduce confusion, mention in the text that the information is either:
+ - Contained in a confidential issue.
+ - Requires special permission to a project to view.
- Provide a link in back ticks (`` ` ``) so that those with access to the issue can easily navigate to it.
Example:
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md
index 51fe19c3d9e..fc7aaedca29 100644
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ b/doc/development/testing_guide/end_to_end_tests.md
@@ -99,13 +99,13 @@ subgraph gitlab-qa pipeline
1. When packages are ready, and available in the registry, a final step in the
[Omnibus GitLab][omnibus-gitlab] pipeline, triggers a new
- [GitLab QA pipeline][gitlab-qa-pipelines]. It also waits for a resulting status.
+ GitLab QA pipeline (those with access can view them at `https://gitlab.com/gitlab-org/gitlab-qa/pipelines`). It also waits for a resulting status.
1. GitLab QA pulls images from the registry, spins-up containers and runs tests
against a test environment that has been just orchestrated by the `gitlab-qa`
tool.
-1. The result of the [GitLab QA pipeline][gitlab-qa-pipelines] is being
+1. The result of the GitLab QA pipeline is being
propagated upstream, through Omnibus, back to the CE / EE merge request.
#### Using the `review-qa-all` jobs
@@ -146,7 +146,6 @@ you can find an issue you would like to work on in
[omnibus-gitlab]: https://gitlab.com/gitlab-org/omnibus-gitlab
[gitlab-qa]: https://gitlab.com/gitlab-org/gitlab-qa
-[gitlab-qa-pipelines]: https://gitlab.com/gitlab-org/gitlab-qa/pipelines
[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
[quality-nightly-pipelines]: https://gitlab.com/gitlab-org/quality/nightly/pipelines
[quality-staging-pipelines]: https://gitlab.com/gitlab-org/quality/staging/pipelines
diff --git a/lib/api/api.rb b/lib/api/api.rb
index bf8ddba6f0d..a572cca24e9 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -134,6 +134,7 @@ module API
mount ::API::Pipelines
mount ::API::PipelineSchedules
mount ::API::ProjectClusters
+ mount ::API::ProjectEvents
mount ::API::ProjectExport
mount ::API::ProjectImport
mount ::API::ProjectHooks
diff --git a/lib/api/events.rb b/lib/api/events.rb
index b98aa9f31e1..e4c017fab42 100644
--- a/lib/api/events.rb
+++ b/lib/api/events.rb
@@ -4,34 +4,11 @@ module API
class Events < Grape::API
include PaginationParams
include APIGuard
+ helpers ::API::Helpers::EventsHelpers
- helpers do
- params :event_filter_params do
- optional :action, type: String, values: Event.actions, desc: 'Event action to filter on'
- optional :target_type, type: String, values: Event.target_types, desc: 'Event target type to filter on'
- optional :before, type: Date, desc: 'Include only events created before this date'
- optional :after, type: Date, desc: 'Include only events created after this date'
- end
-
- params :sort_params do
- optional :sort, type: String, values: %w[asc desc], default: 'desc',
- desc: 'Return events sorted in ascending and descending order'
- end
-
- def present_events(events)
- events = paginate(events)
-
- present events, with: Entities::Event
- end
-
- def find_events(source)
- EventsFinder.new(params.merge(source: source, current_user: current_user, with_associations: true)).execute
- end
- end
+ allow_access_with_scope :read_user, if: -> (request) { request.get? }
resource :events do
- allow_access_with_scope :read_user, if: -> (request) { request.get? }
-
desc "List currently authenticated user's events" do
detail 'This feature was introduced in GitLab 9.3.'
success Entities::Event
@@ -55,8 +32,6 @@ module API
requires :id, type: String, desc: 'The ID or Username of the user'
end
resource :users do
- allow_access_with_scope :read_user, if: -> (request) { request.get? }
-
desc 'Get the contribution events of a specified user' do
detail 'This feature was introduced in GitLab 8.13.'
success Entities::Event
@@ -76,25 +51,5 @@ module API
present_events(events)
end
end
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc "List a Project's visible events" do
- success Entities::Event
- end
- params do
- use :pagination
- use :event_filter_params
- use :sort_params
- end
-
- get ":id/events" do
- events = find_events(user_project)
-
- present_events(events)
- end
- end
end
end
diff --git a/lib/api/helpers/events_helpers.rb b/lib/api/helpers/events_helpers.rb
new file mode 100644
index 00000000000..bf3b76bb92d
--- /dev/null
+++ b/lib/api/helpers/events_helpers.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module EventsHelpers
+ extend Grape::API::Helpers
+
+ params :event_filter_params do
+ optional :action, type: String, values: Event.actions, desc: 'Event action to filter on'
+ optional :target_type, type: String, values: Event.target_types, desc: 'Event target type to filter on'
+ optional :before, type: Date, desc: 'Include only events created before this date'
+ optional :after, type: Date, desc: 'Include only events created after this date'
+ end
+
+ params :sort_params do
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return events sorted in ascending and descending order'
+ end
+
+ def present_events(events)
+ events = paginate(events)
+
+ present events, with: Entities::Event
+ end
+
+ def find_events(source)
+ EventsFinder.new(params.merge(source: source, current_user: current_user, with_associations: true)).execute
+ end
+ end
+ end
+end
diff --git a/lib/api/project_events.rb b/lib/api/project_events.rb
new file mode 100644
index 00000000000..734311e1142
--- /dev/null
+++ b/lib/api/project_events.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module API
+ class ProjectEvents < Grape::API
+ include PaginationParams
+ include APIGuard
+ helpers ::API::Helpers::EventsHelpers
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc "List a Project's visible events" do
+ success Entities::Event
+ end
+ params do
+ use :pagination
+ use :event_filter_params
+ use :sort_params
+ end
+
+ get ":id/events" do
+ events = find_events(user_project)
+
+ present_events(events)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/action_view_output/context.rb b/lib/gitlab/action_view_output/context.rb
new file mode 100644
index 00000000000..9fbc9811636
--- /dev/null
+++ b/lib/gitlab/action_view_output/context.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+# This file was simplified from https://raw.githubusercontent.com/rails/rails/195f39804a7a4a0034f25e8704220e03d95a752a/actionview/lib/action_view/context.rb.
+#
+# It is only needed by modules that need to call ActionView helper
+# methods (e.g. those in
+# https://github.com/rails/rails/tree/c4d3e202e10ae627b3b9c34498afb45450652421/actionview/lib/action_view/helpers)
+# to generate tags outside of a Rails controller (e.g. API, Sidekiq,
+# etc.).
+#
+# In Rails 5, ActionView::Context automatically includes CompiledTemplates.
+# This means that any module that includes ActionView::Context is now a descendant
+# of CompiledTemplates.
+#
+# When a partial is rendered for the first time, it runs
+# Module#module_eval, which will evaluate a string source that defines a
+# new method. For example:
+#
+# def _app_views_profiles_show_html_haml___1285955918103175884_70307801785400(local_assigns, output_buffer)
+# "hello world"
+# end
+#
+# When a new method is defined, the Ruby interpreter clears the method
+# cache for all descendants, and all methods for those modules will have
+# to be redefined. This can lead to a significant performance penalty.
+#
+# Rails 6 fixes this behavior by moving out the `include
+# CompiledTemplates` into ActionView::Base so that including `ActionView::Context`
+# doesn't quietly affect other modules in this way.
+
+if Rails::VERSION::STRING.start_with?('6')
+ raise 'This module is no longer needed in Rails 6. Use ActionView::Context instead.'
+end
+
+module Gitlab
+ module ActionViewOutput
+ module Context
+ attr_accessor :output_buffer, :view_flow
+ end
+ end
+end
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index af385d7d4ca..40bda3410e1 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -58,7 +58,10 @@ module Gitlab
# }
#
# rubocop:disable Metrics/ParameterLists
- def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil, push_options: {})
+ def build(
+ project:, user:, ref:, oldrev: nil, newrev: nil,
+ commits: [], commits_count: nil, message: nil, push_options: {})
+
commits = Array(commits)
# Total commits count
@@ -113,7 +116,12 @@ module Gitlab
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}"
commits = project.repository.commits(project.default_branch.to_s, limit: 3)
- build(project, user, commits.last&.id, commits.first&.id, ref, commits)
+ build(project: project,
+ user: user,
+ oldrev: commits.last&.id,
+ newrev: commits.first&.id,
+ ref: ref,
+ commits: commits)
end
def sample_data
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index c12cb6a6434..55bd77f6c4a 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -118,6 +118,12 @@ module Gitlab
gitaly_repository_client.exists?
end
+ def create_repository
+ wrapped_gitaly_errors do
+ gitaly_repository_client.create_repository
+ end
+ end
+
# Returns an Array of branch names
# sorted by name ASC
def branch_names
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index cb80ed64eff..4b626509008 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -85,7 +85,7 @@ module Gitlab
check_push_access!
end
- ::Gitlab::GitAccessResult::Success.new(console_messages: check_for_console_messages(cmd))
+ success_result(cmd)
end
def guest_can_download_code?
@@ -365,6 +365,10 @@ module Gitlab
protected
+ def success_result(cmd)
+ ::Gitlab::GitAccessResult::Success.new(console_messages: check_for_console_messages(cmd))
+ end
+
def changes_list
@changes_list ||= Gitlab::ChangesList.new(changes == ANY ? [] : changes)
end
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 28ed587f5c7..890228e5e78 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -73,7 +73,7 @@ module Gitlab
result = with_custom_logger(logger) do
with_user(user) do
- RubyProf.profile { app.public_send(verb, url, post_data, headers) } # rubocop:disable GitlabSecurity/PublicSend
+ RubyProf.profile { app.public_send(verb, url, params: post_data, headers: headers) } # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
index fb303e3fb0c..c102fa14cfc 100644
--- a/lib/gitlab/sidekiq_config.rb
+++ b/lib/gitlab/sidekiq_config.rb
@@ -7,7 +7,7 @@ module Gitlab
module SidekiqConfig
QUEUE_CONFIG_PATHS = %w[app/workers/all_queues.yml ee/app/workers/all_queues.yml].freeze
- # This method is called by `bin/sidekiq-cluster` in EE, which runs outside
+ # This method is called by `ee/bin/sidekiq-cluster` in EE, which runs outside
# of bundler/Rails context, so we cannot use any gem or Rails methods.
def self.worker_queues(rails_path = Rails.root.to_s)
@worker_queues ||= {}
@@ -19,7 +19,7 @@ module Gitlab
end
end
- # This method is called by `bin/sidekiq-cluster` in EE, which runs outside
+ # This method is called by `ee/bin/sidekiq-cluster` in EE, which runs outside
# of bundler/Rails context, so we cannot use any gem or Rails methods.
def self.expand_queues(queues, all_queues = self.worker_queues)
return [] if queues.empty?
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b973cd2dd8e..c8b583575c8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3958,6 +3958,9 @@ msgstr ""
msgid "Failed to check related branches."
msgstr ""
+msgid "Failed to connect to the prometheus server"
+msgstr ""
+
msgid "Failed to create repository via gitlab-shell"
msgstr ""
@@ -6038,6 +6041,9 @@ msgstr ""
msgid "No other labels with such name or description"
msgstr ""
+msgid "No parent group"
+msgstr ""
+
msgid "No preview for this file type"
msgstr ""
@@ -6685,6 +6691,9 @@ msgstr ""
msgid "Preview"
msgstr ""
+msgid "Preview Markdown"
+msgstr ""
+
msgid "Preview changes"
msgstr ""
@@ -9153,6 +9162,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error while fetching cycle analytics data."
+msgstr ""
+
msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
msgstr ""
@@ -9796,6 +9808,9 @@ msgstr ""
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to regenerate public ssh key."
+msgstr ""
+
msgid "Unable to schedule a pipeline to run immediately"
msgstr ""
diff --git a/package.json b/package.json
index d2ee29fe06d..7981ec850a2 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
"@babel/preset-env": "^7.3.1",
"@gitlab/csslab": "^1.9.0",
"@gitlab/svgs": "^1.59.0",
- "@gitlab/ui": "^3.5.0",
+ "@gitlab/ui": "^3.7.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-upload-client": "^10.0.0",
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
index 309ae6cd986..e689ba4c69c 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
@@ -3,11 +3,6 @@
module QA
context 'Create' do
describe 'Wiki management' do
- def validate_content(content)
- expect(page).to have_content('Wiki was successfully updated')
- expect(page).to have_content(/#{content}/)
- end
-
it 'user creates, edits, clones, and pushes to the wiki' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
@@ -38,6 +33,11 @@ module QA
expect(page).to have_content('My Third Wiki Content')
end
+
+ def validate_content(content)
+ expect(page).to have_content('Wiki was successfully updated')
+ expect(page).to have_content(/#{content}/)
+ end
end
end
end
diff --git a/qa/qa/vendor/github/page/login.rb b/qa/qa/vendor/github/page/login.rb
index 6d8f9aa7c12..120ba6e6c06 100644
--- a/qa/qa/vendor/github/page/login.rb
+++ b/qa/qa/vendor/github/page/login.rb
@@ -12,9 +12,7 @@ module QA
fill_in 'password', with: QA::Runtime::Env.github_password
click_on 'Sign in'
- unless has_no_text?("Authorize GitLab-OAuth")
- click_on 'Authorize gitlab-qa' if has_button?('Authorize gitlab-qa')
- end
+ click_on 'Authorize gitlab-qa' if has_button?('Authorize gitlab-qa')
end
end
end
diff --git a/rubocop/cop/include_action_view_context.rb b/rubocop/cop/include_action_view_context.rb
new file mode 100644
index 00000000000..14662a33e95
--- /dev/null
+++ b/rubocop/cop/include_action_view_context.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require_relative '../spec_helpers'
+
+module RuboCop
+ module Cop
+ # Cop that makes sure workers include `::Gitlab::ActionViewOutput::Context`, not `ActionView::Context`.
+ class IncludeActionViewContext < RuboCop::Cop::Cop
+ include SpecHelpers
+
+ MSG = 'Include `::Gitlab::ActionViewOutput::Context`, not `ActionView::Context`, for Rails 5.'.freeze
+
+ def_node_matcher :includes_action_view_context?, <<~PATTERN
+ (send nil? :include (const (const nil? :ActionView) :Context))
+ PATTERN
+
+ def on_send(node)
+ return if in_spec?(node)
+ return unless includes_action_view_context?(node)
+
+ add_offense(node.arguments.first, location: :expression)
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.replace(node.source_range, '::Gitlab::ActionViewOutput::Context')
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 50eab6f9270..ce6bdbf292c 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -4,6 +4,7 @@ require_relative 'cop/gitlab/predicate_memoization'
require_relative 'cop/gitlab/httparty'
require_relative 'cop/gitlab/finder_with_find_by'
require_relative 'cop/gitlab/union'
+require_relative 'cop/include_action_view_context'
require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/safe_params'
require_relative 'cop/active_record_association_reload'
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index c1c4be45168..a62422d0229 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -342,11 +342,9 @@ describe Projects::EnvironmentsController do
end
context 'when environment has no metrics' do
- before do
- expect(environment).to receive(:metrics).and_return(nil)
- end
-
it 'returns a metrics page' do
+ expect(environment).not_to receive(:metrics)
+
get :metrics, params: environment_params
expect(response).to be_ok
@@ -354,6 +352,8 @@ describe Projects::EnvironmentsController do
context 'when requesting metrics as JSON' do
it 'returns a metrics JSON document' do
+ expect(environment).to receive(:metrics).and_return(nil)
+
get :metrics, params: environment_params(format: :json)
expect(response).to have_gitlab_http_status(204)
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 7225ca65492..6d4facd0649 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -14,22 +14,36 @@ describe 'User searches for wiki pages', :js do
include_examples 'top right search form'
- it 'finds a page' do
- find('.js-search-project-dropdown').click
+ shared_examples 'search wiki blobs' do
+ it 'finds a page' do
+ find('.js-search-project-dropdown').click
- page.within('.project-filter') do
- click_link(project.full_name)
- end
+ page.within('.project-filter') do
+ click_link(project.full_name)
+ end
+
+ fill_in('dashboard_search', with: 'content')
+ find('.btn-search').click
+
+ page.within('.search-filter') do
+ click_link('Wiki')
+ end
- fill_in('dashboard_search', with: 'content')
- find('.btn-search').click
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_link(wiki_page.title, href: project_wiki_path(project, wiki_page.slug))
+ end
+ end
+ end
- page.within('.search-filter') do
- click_link('Wiki')
+ context 'when searching by content' do
+ it_behaves_like 'search wiki blobs' do
+ let(:search_term) { 'content' }
end
+ end
- page.within('.results') do
- expect(find(:css, '.search-results')).to have_link(wiki_page.title)
+ context 'when searching by title' do
+ it_behaves_like 'search wiki blobs' do
+ let(:search_term) { 'test_wiki' }
end
end
end
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index 5103cb4f69f..a61103397eb 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -6,7 +6,7 @@ import { loadHTMLFixture } from 'helpers/fixtures';
import { setTestTimeout } from 'helpers/timeout';
import $ from 'jquery';
-const { INSTALLING, INSTALLABLE, INSTALLED, NOT_INSTALLABLE } = APPLICATION_STATUS;
+const { INSTALLING, INSTALLABLE, INSTALLED } = APPLICATION_STATUS;
describe('Clusters', () => {
setTestTimeout(1000);
@@ -317,13 +317,12 @@ describe('Clusters', () => {
let ingressNewState;
beforeEach(() => {
- ingressPreviousState = { status: INSTALLABLE };
- ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' };
+ ingressPreviousState = { externalIp: null };
+ ingressNewState = { externalIp: '127.0.0.1' };
});
- describe(`when ingress application new status is ${INSTALLED}`, () => {
+ describe(`when ingress have an external ip assigned`, () => {
beforeEach(() => {
- ingressNewState.status = INSTALLED;
cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
});
@@ -338,31 +337,11 @@ describe('Clusters', () => {
});
});
- describe(`when ingress application new status is different from ${INSTALLED}`, () => {
+ describe(`when ingress does not have an external ip assigned`, () => {
it('hides custom domain help text', () => {
- ingressNewState.status = NOT_INSTALLABLE;
- cluster.ingressDomainHelpText.classList.remove('hide');
-
- cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
-
- expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true);
- });
- });
-
- describe('when ingress application new status and old status are the same', () => {
- it('does not display custom domain help text', () => {
- ingressPreviousState.status = INSTALLED;
- ingressNewState.status = ingressPreviousState.status;
-
- cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
-
- expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true);
- });
- });
-
- describe(`when ingress new status is ${INSTALLED} and there isn’t an ip assigned`, () => {
- it('does not display custom domain help text', () => {
+ ingressPreviousState.externalIp = '127.0.0.1';
ingressNewState.externalIp = null;
+ cluster.ingressDomainHelpText.classList.remove('hide');
cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js
index adcb1c858aa..dc66150ab8d 100644
--- a/spec/frontend/vue_shared/components/notes/system_note_spec.js
+++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js
@@ -1,6 +1,9 @@
import Vue from 'vue';
import issueSystemNote from '~/vue_shared/components/notes/system_note.vue';
import createStore from '~/notes/stores';
+import initMRPopovers from '~/mr_popover/index';
+
+jest.mock('~/mr_popover/index', () => jest.fn());
describe('system note component', () => {
let vm;
@@ -56,4 +59,8 @@ describe('system note component', () => {
it('removes wrapping paragraph from note HTML', () => {
expect(vm.$el.querySelector('.system-note-message').innerHTML).toEqual('<span>closed</span>');
});
+
+ it('should initMRPopovers onMount', () => {
+ expect(initMRPopovers).toHaveBeenCalled();
+ });
});
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 5f9c180cbb7..399a33dae75 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -4,104 +4,119 @@ describe Resolvers::IssuesResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
- set(:project) { create(:project) }
- set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) }
- set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) }
- set(:label1) { create(:label, project: project) }
- set(:label2) { create(:label, project: project) }
-
- before do
- project.add_developer(current_user)
- create(:label_link, label: label1, target: issue1)
- create(:label_link, label: label1, target: issue2)
- create(:label_link, label: label2, target: issue2)
- end
-
- describe '#resolve' do
- it 'finds all issues' do
- expect(resolve_issues).to contain_exactly(issue1, issue2)
- end
- it 'filters by state' do
- expect(resolve_issues(state: 'opened')).to contain_exactly(issue1)
- expect(resolve_issues(state: 'closed')).to contain_exactly(issue2)
+ context "with a project" do
+ set(:project) { create(:project) }
+ set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) }
+ set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) }
+ set(:label1) { create(:label, project: project) }
+ set(:label2) { create(:label, project: project) }
+
+ before do
+ project.add_developer(current_user)
+ create(:label_link, label: label1, target: issue1)
+ create(:label_link, label: label1, target: issue2)
+ create(:label_link, label: label2, target: issue2)
end
- it 'filters by labels' do
- expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2)
- expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
- end
+ describe '#resolve' do
+ it 'finds all issues' do
+ expect(resolve_issues).to contain_exactly(issue1, issue2)
+ end
- describe 'filters by created_at' do
- it 'filters by created_before' do
- expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1)
+ it 'filters by state' do
+ expect(resolve_issues(state: 'opened')).to contain_exactly(issue1)
+ expect(resolve_issues(state: 'closed')).to contain_exactly(issue2)
end
- it 'filters by created_after' do
- expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2)
+ it 'filters by labels' do
+ expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2)
+ expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
end
- end
- describe 'filters by updated_at' do
- it 'filters by updated_before' do
- expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1)
+ describe 'filters by created_at' do
+ it 'filters by created_before' do
+ expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1)
+ end
+
+ it 'filters by created_after' do
+ expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
end
- it 'filters by updated_after' do
- expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2)
+ describe 'filters by updated_at' do
+ it 'filters by updated_before' do
+ expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1)
+ end
+
+ it 'filters by updated_after' do
+ expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
end
- end
- describe 'filters by closed_at' do
- let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) }
+ describe 'filters by closed_at' do
+ let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) }
- it 'filters by closed_before' do
- expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3)
+ it 'filters by closed_before' do
+ expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3)
+ end
+
+ it 'filters by closed_after' do
+ expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2)
+ end
end
- it 'filters by closed_after' do
- expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2)
+ it 'searches issues' do
+ expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
end
- end
- it 'searches issues' do
- expect(resolve_issues(search: 'foo')).to contain_exactly(issue2)
- end
+ it 'sort issues' do
+ expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1]
+ end
- it 'sort issues' do
- expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1]
- end
+ it 'returns issues user can see' do
+ project.add_guest(current_user)
- it 'returns issues user can see' do
- project.add_guest(current_user)
+ create(:issue, confidential: true)
- create(:issue, confidential: true)
+ expect(resolve_issues).to contain_exactly(issue1, issue2)
+ end
- expect(resolve_issues).to contain_exactly(issue1, issue2)
- end
+ it 'finds a specific issue with iid' do
+ expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
+ end
- it 'finds a specific issue with iid' do
- expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1)
- end
+ it 'finds a specific issue with iids' do
+ expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
+ end
- it 'finds a specific issue with iids' do
- expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1)
- end
+ it 'finds multiple issues with iids' do
+ expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
+ .to contain_exactly(issue1, issue2)
+ end
- it 'finds multiple issues with iids' do
- expect(resolve_issues(iids: [issue1.iid, issue2.iid]))
- .to contain_exactly(issue1, issue2)
- end
+ it 'finds only the issues within the project we are looking at' do
+ another_project = create(:project)
+ iids = [issue1, issue2].map(&:iid)
+
+ iids.each do |iid|
+ create(:issue, project: another_project, iid: iid)
+ end
- it 'finds only the issues within the project we are looking at' do
- another_project = create(:project)
- iids = [issue1, issue2].map(&:iid)
+ expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
+ end
+ end
+ end
- iids.each do |iid|
- create(:issue, project: another_project, iid: iid)
+ context "when passing a non existent, batch loaded project" do
+ let(:project) do
+ BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _|
+ loader.call("non-existent-path", nil)
end
+ end
- expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2)
+ it "returns nil without breaking" do
+ expect(resolve_issues(iids: ["don't", "break"])).to be_empty
end
end
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index 0c4decc6518..46ad674a1eb 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -23,9 +23,12 @@ describe Gitlab::DataBuilder::Push do
describe '.build' do
let(:data) do
- described_class.build(project, user, Gitlab::Git::BLANK_SHA,
- '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
- 'refs/tags/v1.1.0')
+ described_class.build(
+ project: project,
+ user: user,
+ oldrev: Gitlab::Git::BLANK_SHA,
+ newrev: '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
+ ref: 'refs/tags/v1.1.0')
end
it { expect(data).to be_a(Hash) }
@@ -47,7 +50,7 @@ describe Gitlab::DataBuilder::Push do
include_examples 'deprecated repository hook data'
it 'does not raise an error when given nil commits' do
- expect { described_class.build(spy, spy, spy, spy, 'refs/tags/v1.1.0', nil) }
+ expect { described_class.build(project: spy, user: spy, ref: 'refs/tags/v1.1.0', commits: nil) }
.not_to raise_error
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 45fe5d72937..5f8a2848944 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -95,6 +95,12 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#create_repository' do
+ it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :create_repository do
+ subject { repository.create_repository }
+ end
+ end
+
describe '#branch_names' do
subject { repository.branch_names }
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 9f2214f7ce7..5af52db7a1f 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -27,13 +27,13 @@ describe Gitlab::Profiler do
it 'sends a POST request when data is passed' do
post_data = '{"a":1}'
- expect(app).to receive(:post).with(anything, post_data, anything)
+ expect(app).to receive(:post).with(anything, params: post_data, headers: anything)
described_class.profile('/', post_data: post_data)
end
it 'uses the private_token for auth if given' do
- expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
+ expect(app).to receive(:get).with('/', params: nil, headers: { 'Private-Token' => private_token })
expect(app).to receive(:get).with('/api/v4/users')
described_class.profile('/', private_token: private_token)
@@ -51,7 +51,7 @@ describe Gitlab::Profiler do
user = double(:user)
expect(described_class).to receive(:with_user).with(nil).and_call_original
- expect(app).to receive(:get).with('/', nil, 'Private-Token' => private_token)
+ expect(app).to receive(:get).with('/', params: nil, headers: { 'Private-Token' => private_token })
expect(app).to receive(:get).with('/api/v4/users')
described_class.profile('/', user: user, private_token: private_token)
diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb
index fd25132ed3a..cc90a998d3f 100644
--- a/spec/models/application_record_spec.rb
+++ b/spec/models/application_record_spec.rb
@@ -11,6 +11,25 @@ describe ApplicationRecord do
end
end
+ describe '.safe_ensure_unique' do
+ let(:model) { build(:suggestion) }
+ let(:klass) { model.class }
+
+ before do
+ allow(model).to receive(:save).and_raise(ActiveRecord::RecordNotUnique)
+ end
+
+ it 'returns false when ActiveRecord::RecordNotUnique is raised' do
+ expect(model).to receive(:save).once
+ expect(klass.safe_ensure_unique { model.save }).to be_falsey
+ end
+
+ it 'retries based on retry count specified' do
+ expect(model).to receive(:save).exactly(3).times
+ expect(klass.safe_ensure_unique(retries: 2) { model.save }).to be_falsey
+ end
+ end
+
describe '.safe_find_or_create_by' do
it 'creates the user avoiding race conditions' do
expect(Suggestion).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique)
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 44b5af5e5aa..eb32198265b 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -10,6 +10,8 @@ describe Ci::Bridge do
create(:ci_bridge, pipeline: pipeline)
end
+ it { is_expected.to include_module(Ci::PipelineDelegator) }
+
describe '#tags' do
it 'only has a bridge tag' do
expect(bridge.tags).to eq [:bridge]
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 339483d4f7d..59ec7310391 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -28,6 +28,7 @@ describe Ci::Build do
it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) }
it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
+ it { is_expected.to include_module(Ci::PipelineDelegator) }
it { is_expected.to be_a(ArtifactMigratable) }
@@ -2273,6 +2274,19 @@ describe Ci::Build do
it { user_variables.each { |v| is_expected.to include(v) } }
end
+ context 'when build belongs to a pipeline for merge request' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_branch: 'improve/awesome') }
+ let(:pipeline) { merge_request.all_pipelines.first }
+ let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) }
+
+ it 'returns values based on source ref' do
+ is_expected.to include(
+ { key: 'CI_COMMIT_REF_NAME', value: 'improve/awesome', public: true, masked: false },
+ { key: 'CI_COMMIT_REF_SLUG', value: 'improve-awesome', public: true, masked: false }
+ )
+ end
+ end
+
context 'when build has an environment' do
let(:environment_variables) do
[
@@ -2664,6 +2678,8 @@ describe Ci::Build do
)
end
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
+
it 'returns static predefined variables' do
expect(build.variables.size).to be >= 28
expect(build.variables)
@@ -2713,6 +2729,8 @@ describe Ci::Build do
)
end
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
+
it 'does not persist the build' do
expect(build).to be_valid
expect(build).not_to be_persisted
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 3c823b78be7..9d0cd654f13 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -382,6 +382,54 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#source_ref' do
+ subject { pipeline.source_ref }
+
+ let(:pipeline) { create(:ci_pipeline, ref: 'feature') }
+
+ it 'returns source ref' do
+ is_expected.to eq('feature')
+ end
+
+ context 'when the pipeline is a detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: merge_request.ref_path)
+ end
+
+ it 'returns source ref' do
+ is_expected.to eq(merge_request.source_branch)
+ end
+ end
+ end
+
+ describe '#source_ref_slug' do
+ subject { pipeline.source_ref_slug }
+
+ let(:pipeline) { create(:ci_pipeline, ref: 'feature') }
+
+ it 'slugifies with the source ref' do
+ expect(Gitlab::Utils).to receive(:slugify).with('feature')
+
+ subject
+ end
+
+ context 'when the pipeline is a detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: merge_request.ref_path)
+ end
+
+ it 'slugifies with the source ref of the merge request' do
+ expect(Gitlab::Utils).to receive(:slugify).with(merge_request.source_branch)
+
+ subject
+ end
+ end
+ end
+
describe '.triggered_for_branch' do
subject { described_class.triggered_for_branch(ref) }
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 0ca758429b8..f51322e1404 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -400,6 +400,12 @@ describe Deployment do
it { is_expected.to be_nil }
end
+ context 'project uses the kubernetes service for deployments' do
+ let!(:service) { create(:kubernetes_service, project: project) }
+
+ it { is_expected.to be_nil }
+ end
+
context 'project has a deployment platform' do
let!(:cluster) { create(:cluster, projects: [project]) }
let!(:platform) { create(:cluster_platform_kubernetes, cluster: cluster) }
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index 3710f2be287..1b1ede6b14c 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -9,11 +9,43 @@ describe NotificationRecipient do
subject(:recipient) { described_class.new(user, :watch, target: target, project: project) }
- it 'denies access to a target when cross project access is denied' do
- allow(Ability).to receive(:allowed?).and_call_original
- expect(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(false)
+ describe '#has_access?' do
+ before do
+ allow(user).to receive(:can?).and_call_original
+ end
+
+ context 'user cannot read cross project' do
+ it 'returns false' do
+ expect(user).to receive(:can?).with(:read_cross_project).and_return(false)
+ expect(recipient.has_access?).to eq false
+ end
+ end
+
+ context 'user cannot read build' do
+ let(:target) { build(:ci_pipeline) }
+
+ it 'returns false' do
+ expect(user).to receive(:can?).with(:read_build, target).and_return(false)
+ expect(recipient.has_access?).to eq false
+ end
+ end
- expect(recipient.has_access?).to be_falsy
+ context 'user cannot read commit' do
+ let(:target) { build(:commit) }
+
+ it 'returns false' do
+ expect(user).to receive(:can?).with(:read_commit, target).and_return(false)
+ expect(recipient.has_access?).to eq false
+ end
+ end
+
+ context 'target has no policy' do
+ let(:target) { double.as_null_object }
+
+ it 'returns true' do
+ expect(recipient.has_access?).to eq true
+ end
+ end
end
context '#notification_setting' do
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index fd9e33c1781..a04b984c1f6 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -98,12 +98,11 @@ describe HipchatService do
context 'tag_push events' do
let(:push_sample_data) do
Gitlab::DataBuilder::Push.build(
- project,
- user,
- Gitlab::Git::BLANK_SHA,
- '1' * 40,
- 'refs/tags/test',
- [])
+ project: project,
+ user: user,
+ oldrev: Gitlab::Git::BLANK_SHA,
+ newrev: '1' * 40,
+ ref: 'refs/tags/test')
end
it "calls Hipchat API for tag push events" do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 4c354593b57..43ec1125087 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2487,4 +2487,69 @@ describe Repository do
repository.merge_base('master', 'fix')
end
end
+
+ describe '#create_if_not_exists' do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+
+ it 'creates the repository if it did not exist' do
+ expect { repository.create_if_not_exists }.to change { repository.exists? }.from(false).to(true)
+ end
+
+ it 'calls out to the repository client to create a repo' do
+ expect(repository.raw.gitaly_repository_client).to receive(:create_repository)
+
+ repository.create_if_not_exists
+ end
+
+ context 'it does nothing if the repository already existed' do
+ let(:project) { create(:project, :repository) }
+
+ it 'does nothing if the repository already existed' do
+ expect(repository.raw.gitaly_repository_client).not_to receive(:create_repository)
+
+ repository.create_if_not_exists
+ end
+ end
+
+ context 'when the repository exists but the cache is not up to date' do
+ let(:project) { create(:project, :repository) }
+
+ it 'does not raise errors' do
+ allow(repository).to receive(:exists?).and_return(false)
+ expect(repository.raw).to receive(:create_repository).and_call_original
+
+ expect { repository.create_if_not_exists }.not_to raise_error
+ end
+ end
+ end
+
+ describe "#blobs_metadata" do
+ set(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ def expect_metadata_blob(thing)
+ expect(thing).to be_a(Blob)
+ expect(thing.data).to be_empty
+ end
+
+ it "returns blob metadata in batch for HEAD" do
+ result = repository.blobs_metadata(["bar/branch-test.txt", "README.md", "does/not/exist"])
+
+ expect_metadata_blob(result.first)
+ expect_metadata_blob(result.second)
+ expect(result.size).to eq(2)
+ end
+
+ it "returns blob metadata for a specified ref" do
+ result = repository.blobs_metadata(["files/ruby/feature.rb"], "feature")
+
+ expect_metadata_blob(result.first)
+ end
+
+ it "performs a single gitaly call", :request_store do
+ expect { repository.blobs_metadata(["bar/branch-test.txt", "readme.txt", "does/not/exist"]) }
+ .to change { Gitlab::GitalyClient.get_request_count }.by(1)
+ end
+ end
end
diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb
index 065b16c6221..018691e8099 100644
--- a/spec/requests/api/events_spec.rb
+++ b/spec/requests/api/events_spec.rb
@@ -164,139 +164,4 @@ describe API::Events do
expect(json_response['message']).to eq('404 User Not Found')
end
end
-
- describe 'GET /projects/:id/events' do
- context 'when unauthenticated ' do
- it 'returns 404 for private project' do
- get api("/projects/#{private_project.id}/events")
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it 'returns 200 status for a public project' do
- public_project = create(:project, :public)
-
- get api("/projects/#{public_project.id}/events")
-
- expect(response).to have_gitlab_http_status(200)
- end
- end
-
- context 'with inaccessible events' do
- let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) }
- let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) }
- let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) }
- let(:public_issue) { create(:closed_issue, project: public_project, author: user) }
- let!(:public_event) { create(:event, project: public_project, author: user, target: public_issue, action: Event::CLOSED) }
-
- it 'returns only accessible events' do
- get api("/projects/#{public_project.id}/events", non_member)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response.size).to eq(1)
- end
-
- it 'returns all events when the user has access' do
- get api("/projects/#{public_project.id}/events", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response.size).to eq(2)
- end
- end
-
- context 'pagination' do
- let(:public_project) { create(:project, :public) }
-
- before do
- create(:event,
- project: public_project,
- target: create(:issue, project: public_project, title: 'Issue 1'),
- action: Event::CLOSED,
- created_at: Date.parse('2018-12-10'))
- create(:event,
- project: public_project,
- target: create(:issue, confidential: true, project: public_project, title: 'Confidential event'),
- action: Event::CLOSED,
- created_at: Date.parse('2018-12-11'))
- create(:event,
- project: public_project,
- target: create(:issue, project: public_project, title: 'Issue 2'),
- action: Event::CLOSED,
- created_at: Date.parse('2018-12-12'))
- end
-
- it 'correctly returns the second page without inaccessible events' do
- get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 2 }
-
- titles = json_response.map { |event| event['target_title'] }
-
- expect(titles.first).to eq('Issue 1')
- expect(titles).not_to include('Confidential event')
- end
-
- it 'correctly returns the first page without inaccessible events' do
- get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 1 }
-
- titles = json_response.map { |event| event['target_title'] }
-
- expect(titles.first).to eq('Issue 2')
- expect(titles).not_to include('Confidential event')
- end
- end
-
- context 'when not permitted to read' do
- it 'returns 404' do
- get api("/projects/#{private_project.id}/events", non_member)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- context 'when authenticated' do
- it 'returns project events' do
- get api("/projects/#{private_project.id}/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.size).to eq(1)
- end
-
- it 'returns 404 if project does not exist' do
- get api("/projects/1234/events", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- context 'when exists some events' do
- let(:merge_request1) { create(:merge_request, :closed, author: user, assignees: [user], source_project: private_project, title: 'Test') }
- let(:merge_request2) { create(:merge_request, :closed, author: user, assignees: [user], source_project: private_project, title: 'Test') }
-
- before do
- create_event(merge_request1)
- end
-
- it 'avoids N+1 queries' do
- control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
- get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
- end.count
-
- create_event(merge_request2)
-
- expect do
- get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
- end.not_to exceed_all_query_limit(control_count)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response.size).to eq(2)
- expect(json_response.map { |r| r['target_id'] }).to match_array([merge_request1.id, merge_request2.id])
- end
-
- def create_event(target)
- create(:event, project: private_project, author: user, target: target)
- end
- end
- end
end
diff --git a/spec/requests/api/project_events_spec.rb b/spec/requests/api/project_events_spec.rb
new file mode 100644
index 00000000000..43df9993eb9
--- /dev/null
+++ b/spec/requests/api/project_events_spec.rb
@@ -0,0 +1,156 @@
+require 'spec_helper'
+
+describe API::ProjectEvents do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:private_project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) }
+ let(:closed_issue) { create(:closed_issue, project: private_project, author: user) }
+ let!(:closed_issue_event) { create(:event, project: private_project, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 12, 30)) }
+
+ describe 'GET /projects/:id/events' do
+ context 'when unauthenticated ' do
+ it 'returns 404 for private project' do
+ get api("/projects/#{private_project.id}/events")
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 200 status for a public project' do
+ public_project = create(:project, :public)
+
+ get api("/projects/#{public_project.id}/events")
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'with inaccessible events' do
+ let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) }
+ let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) }
+ let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) }
+ let(:public_issue) { create(:closed_issue, project: public_project, author: user) }
+ let!(:public_event) { create(:event, project: public_project, author: user, target: public_issue, action: Event::CLOSED) }
+
+ it 'returns only accessible events' do
+ get api("/projects/#{public_project.id}/events", non_member)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(1)
+ end
+
+ it 'returns all events when the user has access' do
+ get api("/projects/#{public_project.id}/events", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(2)
+ end
+ end
+
+ context 'pagination' do
+ let(:public_project) { create(:project, :public) }
+
+ before do
+ create(:event,
+ project: public_project,
+ target: create(:issue, project: public_project, title: 'Issue 1'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-10'))
+ create(:event,
+ project: public_project,
+ target: create(:issue, confidential: true, project: public_project, title: 'Confidential event'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-11'))
+ create(:event,
+ project: public_project,
+ target: create(:issue, project: public_project, title: 'Issue 2'),
+ action: Event::CLOSED,
+ created_at: Date.parse('2018-12-12'))
+ end
+
+ it 'correctly returns the second page without inaccessible events' do
+ get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 2 }
+
+ titles = json_response.map { |event| event['target_title'] }
+
+ expect(titles.first).to eq('Issue 1')
+ expect(titles).not_to include('Confidential event')
+ end
+
+ it 'correctly returns the first page without inaccessible events' do
+ get api("/projects/#{public_project.id}/events", user), params: { per_page: 2, page: 1 }
+
+ titles = json_response.map { |event| event['target_title'] }
+
+ expect(titles.first).to eq('Issue 2')
+ expect(titles).not_to include('Confidential event')
+ end
+ end
+
+ context 'when not permitted to read' do
+ it 'returns 404' do
+ get api("/projects/#{private_project.id}/events", non_member)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns project events' do
+ get api("/projects/#{private_project.id}/events?action=closed&target_type=issue&after=2016-12-1&before=2016-12-31", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ end
+
+ it 'returns 404 if project does not exist' do
+ get api("/projects/1234/events", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ context 'when the requesting token does not have "api" scope' do
+ let(:token) { create(:personal_access_token, scopes: ['read_user'], user: user) }
+
+ it 'returns a "403" response' do
+ get api("/projects/#{private_project.id}/events", personal_access_token: token)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
+ context 'when exists some events' do
+ let(:merge_request1) { create(:merge_request, :closed, author: user, assignees: [user], source_project: private_project, title: 'Test') }
+ let(:merge_request2) { create(:merge_request, :closed, author: user, assignees: [user], source_project: private_project, title: 'Test') }
+
+ before do
+ create_event(merge_request1)
+ end
+
+ it 'avoids N+1 queries' do
+ control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
+ end.count
+
+ create_event(merge_request2)
+
+ expect do
+ get api("/projects/#{private_project.id}/events", user), params: { target_type: :merge_request }
+ end.not_to exceed_all_query_limit(control_count)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |r| r['target_id'] }).to match_array([merge_request1.id, merge_request2.id])
+ end
+
+ def create_event(target)
+ create(:event, project: private_project, author: user, target: target)
+ end
+ end
+ end
+end
diff --git a/spec/rubocop/cop/include_action_view_context_spec.rb b/spec/rubocop/cop/include_action_view_context_spec.rb
new file mode 100644
index 00000000000..c888555b54f
--- /dev/null
+++ b/spec/rubocop/cop/include_action_view_context_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../rubocop/cop/include_action_view_context'
+
+describe RuboCop::Cop::IncludeActionViewContext do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when `ActionView::Context` is included' do
+ let(:source) { 'include ActionView::Context' }
+ let(:correct_source) { 'include ::Gitlab::ActionViewOutput::Context' }
+
+ it 'registers an offense' do
+ inspect_source(source)
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq(['ActionView::Context'])
+ end
+ end
+
+ it 'autocorrects to the right version' do
+ autocorrected = autocorrect_source(source)
+
+ expect(autocorrected).to eq(correct_source)
+ end
+ end
+
+ context 'when `ActionView::Context` is not included' do
+ it 'registers no offense' do
+ inspect_source('include Context')
+
+ aggregate_failures do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 1447b9d4126..2a553e18807 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -75,6 +75,13 @@ describe Todos::Destroy::EntityLeaveService do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
+ it 'enqueues the PrivateFeaturesWorker' do
+ expect(TodosDestroyer::PrivateFeaturesWorker)
+ .to receive(:perform_async).with(project.id, user.id)
+
+ subject
+ end
+
context 'confidential issues' do
context 'when a user is not an author of confidential issue' do
it 'removes only confidential issues todos' do
@@ -246,6 +253,13 @@ describe Todos::Destroy::EntityLeaveService do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
+ it 'enqueues the PrivateFeaturesWorker' do
+ expect(TodosDestroyer::PrivateFeaturesWorker)
+ .to receive(:perform_async).with(project.id, user.id)
+
+ subject
+ end
+
context 'when user is not member' do
it 'removes only confidential issues todos' do
expect { subject }.to change { Todo.count }.from(5).to(4)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8ca4c172707..fbc5fcea7b9 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -53,6 +53,7 @@ RSpec.configure do |config|
config.display_try_failure_messages = true
config.infer_spec_type_from_file_location!
+ config.full_backtrace = true
config.define_derived_metadata(file_path: %r{/spec/}) do |metadata|
location = metadata[:location]
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 2f4e6e4c934..b49d743fb9a 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -61,7 +61,14 @@ module GraphqlHelpers
def variables_for_mutation(name, input)
graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h
- { input_variable_name_for_mutation(name) => graphql_input }.to_json
+ result = { input_variable_name_for_mutation(name) => graphql_input }
+
+ # Avoid trying to serialize multipart data into JSON
+ if graphql_input.values.none? { |value| io_value?(value) }
+ result.to_json
+ else
+ result
+ end
end
def input_variable_name_for_mutation(mutation_name)
@@ -162,6 +169,10 @@ module GraphqlHelpers
field.arguments.values.any? { |argument| argument.type.non_null? }
end
+ def io_value?(value)
+ Array.wrap(value).any? { |v| v.respond_to?(:to_io) }
+ end
+
def field_type(field)
field_type = field.type
diff --git a/spec/support/shared_examples/models/chat_service_spec.rb b/spec/support/shared_examples/models/chat_service_spec.rb
index 9d3ce5e2be1..0a302e7d030 100644
--- a/spec/support/shared_examples/models/chat_service_spec.rb
+++ b/spec/support/shared_examples/models/chat_service_spec.rb
@@ -70,7 +70,7 @@ shared_examples_for "chat service" do |service_name|
context "with not default branch" do
let(:sample_data) do
- Gitlab::DataBuilder::Push.build(project, user, nil, nil, "not-the-default-branch")
+ Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "not-the-default-branch")
end
context "when notify_only_default_branch enabled" do
diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
index c31346374f4..36c486dbdd6 100644
--- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
@@ -275,7 +275,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
it 'does not notify push events if they are not for the default branch' do
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test"
- push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+ push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref)
chat_service.execute(push_sample_data)
@@ -292,7 +292,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
it 'still notifies about pushed tags' do
ref = "#{Gitlab::Git::TAG_REF_PREFIX}test"
- push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+ push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref)
chat_service.execute(push_sample_data)
@@ -307,7 +307,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
it 'notifies about all push events' do
ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}test"
- push_sample_data = Gitlab::DataBuilder::Push.build(project, user, nil, nil, ref, [])
+ push_sample_data = Gitlab::DataBuilder::Push.build(project: project, user: user, ref: ref)
chat_service.execute(push_sample_data)
diff --git a/spec/views/projects/issues/show.html.haml_spec.rb b/spec/views/projects/issues/show.html.haml_spec.rb
index 1d9c6d36ad7..1ca9eaf8fdb 100644
--- a/spec/views/projects/issues/show.html.haml_spec.rb
+++ b/spec/views/projects/issues/show.html.haml_spec.rb
@@ -19,6 +19,7 @@ describe 'projects/issues/show' do
context 'when the issue is closed' do
before do
allow(issue).to receive(:closed?).and_return(true)
+ allow(view).to receive(:current_user).and_return(user)
end
context 'when the issue was moved' do
@@ -28,16 +29,30 @@ describe 'projects/issues/show' do
issue.moved_to = new_issue
end
- it 'shows "Closed (moved)" if an issue has been moved' do
- render
+ context 'when user can see the moved issue' do
+ before do
+ project.add_developer(user)
+ end
- expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
+ it 'shows "Closed (moved)" if an issue has been moved' do
+ render
+
+ expect(rendered).to have_selector('.status-box-issue-closed:not(.hidden)', text: 'Closed (moved)')
+ end
+
+ it 'links "moved" to the new issue the original issue was moved to' do
+ render
+
+ expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
+ end
end
- it 'links "moved" to the new issue the original issue was moved to' do
- render
+ context 'when user cannot see moved issue' do
+ it 'does not show moved issue link' do
+ render
- expect(rendered).to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
+ expect(rendered).not_to have_selector("a[href=\"#{issue_path(new_issue)}\"]", text: 'moved')
+ end
end
end
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index f23910d23be..8c604b13297 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe PipelineScheduleWorker do
+ include ExclusiveLeaseHelpers
+
subject { described_class.new.perform }
set(:project) { create(:project, :repository) }
@@ -39,6 +41,16 @@ describe PipelineScheduleWorker do
it_behaves_like 'successful scheduling'
+ context 'when exclusive lease has already been taken by the other instance' do
+ before do
+ stub_exclusive_lease_taken(described_class::EXCLUSIVE_LOCK_KEY, timeout: described_class::LOCK_TIMEOUT)
+ end
+
+ it 'raises an error and does not start creating pipelines' do
+ expect { subject }.to raise_error(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+ end
+ end
+
context 'when the latest commit contains [ci skip]' do
before do
allow_any_instance_of(Ci::Pipeline)
diff --git a/yarn.lock b/yarn.lock
index a0446b652ac..d97659ab5fa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -663,12 +663,13 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.59.0.tgz#affcf9596d736836d37469bb4aea2226ac03e087"
integrity sha512-dokGyyLRRsoBKO70KP1g+ZsDGyTK/RIHWDmvWI6Bx5AxQ3UqAzVXn2OIb3owjJAexyRG1uBmJrriiVVyHznQ4g==
-"@gitlab/ui@^3.5.0":
- version "3.5.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-3.5.0.tgz#31ecfc16e3f7663545f31ddf07e02bba96a6d138"
- integrity sha512-eDD++hhGJuH59g2QcGshuou9/NLcLfse4Abm9KOIWIaYI3NPWW2KRGwLHPB6H0d5W0/X5pyWYQvXgF7JE2ZXbA==
+"@gitlab/ui@^3.7.0":
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-3.7.0.tgz#8d0892ae54ddcb3c309bd970c57a433af6098edf"
+ integrity sha512-DEIPfem9P5j0DyzZp0M62SbLQu1D4feiNO0oAYN8bJrgiMC8H3VEJwiyplNItSwFYa985O1xOr3B81eTiZEWDQ==
dependencies:
"@babel/standalone" "^7.0.0"
+ "@gitlab/vue-toasted" "^1.2.1"
bootstrap-vue "^2.0.0-rc.11"
copy-to-clipboard "^3.0.8"
echarts "^4.2.0-rc.2"
@@ -678,7 +679,11 @@
url-search-params-polyfill "^5.0.0"
vue "^2.5.21"
vue-loader "^15.4.2"
- vue-toasted "^1.1.26"
+
+"@gitlab/vue-toasted@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/vue-toasted/-/vue-toasted-1.2.1.tgz#f407b5aa710863e5b7f021f4a1f66160331ab263"
+ integrity sha512-ve2PLxKqrwNpsd+4bV5zGJT5+H5N/VJBZoFS2Vp1mH5cUDBYIHTzDmbS6AbBGUDh0F3TxmFMiqfXfpO/1VjBNQ==
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
@@ -10983,11 +10988,6 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
-vue-toasted@^1.1.26:
- version "1.1.26"
- resolved "https://registry.yarnpkg.com/vue-toasted/-/vue-toasted-1.1.26.tgz#1333d1a42157ab78389c3810023a49ba94e69c7b"
- integrity sha512-Z4/gfPcqdzsRvif7UITrZOkh3C6jm0yQKJyr9kX31IGWXor5dNipE1Sc5SnlL5RLmY7vlLa+SqIjc9Gbpy7V0g==
-
vue-virtual-scroll-list@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.3.1.tgz#efcb83d3a3dcc69cd886fa4de1130a65493e8f76"