Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock18
-rw-r--r--app/assets/javascripts/environments/components/deploy_board.vue2
-rw-r--r--app/assets/javascripts/helpers/startup_css_helper.js2
-rw-r--r--app/assets/javascripts/merge_request/components/status_box.vue94
-rw-r--r--app/assets/javascripts/merge_request/eventhub.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js19
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js7
-rw-r--r--app/controllers/import/bulk_imports_controller.rb12
-rw-r--r--app/controllers/projects/mattermosts_controller.rb2
-rw-r--r--app/helpers/application_helper.rb8
-rw-r--r--app/helpers/ci/triggers_helper.rb (renamed from app/helpers/triggers_helper.rb)2
-rw-r--r--app/helpers/startup_css_helper.rb7
-rw-r--r--app/models/ci/commit_with_pipeline.rb38
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/commit_status.rb4
-rw-r--r--app/models/commit_with_pipeline.rb2
-rw-r--r--app/models/concerns/enums/ci/commit_status.rb36
-rw-r--r--app/models/concerns/enums/commit_status.rb35
-rw-r--r--app/models/project_services/mattermost_slash_commands_service.rb2
-rw-r--r--app/models/project_services/slack_slash_commands_service.rb2
-rw-r--r--app/serializers/merge_request_poll_cached_widget_entity.rb8
-rw-r--r--app/services/issuable_base_service.rb4
-rw-r--r--app/views/layouts/_head.html.haml2
-rw-r--r--app/views/layouts/_startup_css.haml2
-rw-r--r--app/views/layouts/_startup_css_activation.haml2
-rw-r--r--app/views/projects/issues/import_csv/_button.html.haml2
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml10
-rw-r--r--app/views/shared/empty_states/icons/_deploy_board.svg1
-rw-r--r--changelogs/unreleased/254325-remove-stages-from-dast-template.yml5
-rw-r--r--changelogs/unreleased/277134_populate_finding_uuid_values_for_vulnerability_feedback.yml6
-rw-r--r--changelogs/unreleased/296842-invalidate-reviewer-counter-on-create.yml5
-rw-r--r--changelogs/unreleased/fj-forbid-push-when-snippet-read-only.yml5
-rw-r--r--changelogs/unreleased/ph-290850-mrStatusBoxPoll.yml5
-rw-r--r--changelogs/unreleased/sh-disable-rebase-on-conflicts.yml5
-rw-r--r--changelogs/unreleased/update-puma-to-v-5.yml5
-rw-r--r--changelogs/unreleased/yo-master-patch-74981.yml5
-rw-r--r--config/feature_flags/development/startup_css.yml8
-rw-r--r--config/feature_flags/development/variable_inside_variable.yml8
-rw-r--r--config/puma.example.development.rb5
-rw-r--r--config/puma.rb.example7
-rw-r--r--config/puma_actioncable.example.development.rb5
-rw-r--r--config/routes/import.rb1
-rw-r--r--db/post_migrate/20201211090634_schedule_populate_finding_uuid_for_vulnerability_feedback.rb25
-rw-r--r--db/schema_migrations/202012110906341
-rw-r--r--doc/.vale/gitlab/Acronyms.yml1
-rw-r--r--doc/administration/packages/container_registry.md20
-rw-r--r--doc/ci/variables/predefined_variables.md38
-rw-r--r--doc/ci/yaml/README.md52
-rw-r--r--doc/user/application_security/dast/index.md33
-rw-r--r--lib/expand_variables.rb6
-rw-r--r--lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback.rb128
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml45
-rw-r--r--lib/gitlab/ci/variables/collection/sorted.rb77
-rw-r--r--lib/gitlab/git_access_snippet.rb14
-rw-r--r--lib/gitlab/gon_helper.rb4
-rw-r--r--lib/gitlab/quick_actions/merge_request_actions.rb5
-rw-r--r--locale/gitlab.pot6
-rw-r--r--package.json2
-rw-r--r--qa/qa/specs/features/api/3_create/gitaly/gitaly_mtls_spec.rb43
-rw-r--r--spec/controllers/import/bulk_imports_controller_spec.rb16
-rw-r--r--spec/features/dashboard/projects_spec.rb2
-rw-r--r--spec/frontend/helpers/startup_css_helper_spec.js17
-rw-r--r--spec/frontend/merge_request/components/status_box_spec.js99
-rw-r--r--spec/helpers/ci/triggers_helper_spec.rb31
-rw-r--r--spec/lib/expand_variables_spec.rb37
-rw-r--r--spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb113
-rw-r--r--spec/lib/gitlab/ci/variables/collection/sorted_spec.rb251
-rw-r--r--spec/lib/gitlab/git_access_snippet_spec.rb37
-rw-r--r--spec/migrations/schedule_populate_finding_uuid_for_vulnerability_feedback_spec.rb37
-rw-r--r--spec/models/ci/commit_with_pipeline_spec.rb (renamed from spec/models/commit_with_pipeline_spec.rb)2
-rw-r--r--spec/services/merge_requests/create_service_spec.rb6
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb10
-rw-r--r--spec/views/layouts/_head.html.haml_spec.rb2
-rw-r--r--yarn.lock8
76 files changed, 1416 insertions, 163 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 0fafd5869d9..355607c17ac 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -112,7 +112,7 @@
.use-kaniko:
image:
- name: gcr.io/kaniko-project/executor:debug-v0.20.0
+ name: gcr.io/kaniko-project/executor:debug-v1.3.0
entrypoint: [""]
before_script:
- source scripts/utils.sh
diff --git a/Gemfile b/Gemfile
index ebfa3796724..37964d454fa 100644
--- a/Gemfile
+++ b/Gemfile
@@ -184,8 +184,8 @@ group :unicorn do
end
group :puma do
- gem 'gitlab-puma', '~> 4.3.3.gitlab.2', require: false
- gem 'gitlab-puma_worker_killer', '~> 0.1.1.gitlab.1', require: false
+ gem 'puma', '~> 5.1.1', require: false
+ gem 'puma_worker_killer', '~> 0.3.1', require: false
end
# State machine
@@ -410,7 +410,7 @@ group :test do
gem 'rspec_profiling', '~> 0.0.6'
gem 'rspec-parameterized', require: false
- gem 'capybara', '~> 3.33.0'
+ gem 'capybara', '~> 3.34.0'
gem 'capybara-screenshot', '~> 1.0.22'
gem 'selenium-webdriver', '~> 3.142'
diff --git a/Gemfile.lock b/Gemfile.lock
index 427596e619c..47538b82386 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -152,7 +152,7 @@ GEM
bundler (>= 1.2.0, < 3)
thor (~> 0.18)
byebug (11.1.3)
- capybara (3.33.0)
+ capybara (3.34.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
@@ -447,11 +447,6 @@ GEM
gitlab-markup (1.7.1)
gitlab-net-dns (0.9.1)
gitlab-pg_query (1.3.1)
- gitlab-puma (4.3.5.gitlab.3)
- nio4r (~> 2.0)
- gitlab-puma_worker_killer (0.1.1.gitlab.1)
- get_process_mem (~> 0.2)
- gitlab-puma (>= 2.7, < 5)
gitlab-sidekiq-fetcher (0.5.2)
sidekiq (~> 5)
gitlab-styles (5.4.0)
@@ -876,6 +871,11 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.6)
+ puma (5.1.1)
+ nio4r (~> 2.0)
+ puma_worker_killer (0.3.1)
+ get_process_mem (~> 0.2)
+ puma (>= 2.7)
pyu-ruby-sasl (0.0.3.3)
raabro (1.1.6)
racc (1.5.2)
@@ -1301,7 +1301,7 @@ DEPENDENCIES
browser (~> 4.2)
bullet (~> 6.1.0)
bundler-audit (~> 0.6.1)
- capybara (~> 3.33.0)
+ capybara (~> 3.34.0)
capybara-screenshot (~> 1.0.22)
carrierwave (~> 1.3)
charlock_holmes (~> 0.7.7)
@@ -1365,8 +1365,6 @@ DEPENDENCIES
gitlab-mail_room (~> 0.0.8)
gitlab-markup (~> 1.7.1)
gitlab-net-dns (~> 0.9.1)
- gitlab-puma (~> 4.3.3.gitlab.2)
- gitlab-puma_worker_killer (~> 0.1.1.gitlab.1)
gitlab-sidekiq-fetcher (= 0.5.2)
gitlab-styles (~> 5.4.0)
gitlab_chronic_duration (~> 0.10.6.2)
@@ -1460,6 +1458,8 @@ DEPENDENCIES
prometheus-client-mmap (~> 0.12.0)
pry-byebug (~> 3.9.0)
pry-rails (~> 0.3.9)
+ puma (~> 5.1.1)
+ puma_worker_killer (~> 0.3.1)
rack (~> 2.2.3)
rack-attack (~> 6.3.0)
rack-cors (~> 1.0.6)
diff --git a/app/assets/javascripts/environments/components/deploy_board.vue b/app/assets/javascripts/environments/components/deploy_board.vue
index 28e812d5220..07cb968d8d3 100644
--- a/app/assets/javascripts/environments/components/deploy_board.vue
+++ b/app/assets/javascripts/environments/components/deploy_board.vue
@@ -18,7 +18,7 @@ import {
GlTooltipDirective,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
-import deployBoardSvg from 'empty_states/icons/_deploy_board.svg';
+import deployBoardSvg from '@gitlab/svgs/dist/illustrations/deploy-boards.svg';
import instanceComponent from '~/vue_shared/components/deployment_instance.vue';
import { n__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
diff --git a/app/assets/javascripts/helpers/startup_css_helper.js b/app/assets/javascripts/helpers/startup_css_helper.js
index 0f62a13bd8c..6e19979721c 100644
--- a/app/assets/javascripts/helpers/startup_css_helper.js
+++ b/app/assets/javascripts/helpers/startup_css_helper.js
@@ -22,7 +22,7 @@ const handleStartupEvents = () => {
/* For `waitForCSSLoaded` methods, see docs.gitlab.com/ee/development/fe_guide/performance.html#important-considerations */
export const waitForCSSLoaded = (action = () => {}) => {
- if (!gon?.features?.startupCss || allLinksLoaded()) {
+ if (allLinksLoaded()) {
return new Promise((resolve) => {
action();
resolve();
diff --git a/app/assets/javascripts/merge_request/components/status_box.vue b/app/assets/javascripts/merge_request/components/status_box.vue
new file mode 100644
index 00000000000..c29f7b86df9
--- /dev/null
+++ b/app/assets/javascripts/merge_request/components/status_box.vue
@@ -0,0 +1,94 @@
+<script>
+import { GlIcon, GlSprintf, GlLink } from '@gitlab/ui';
+import { __ } from '~/locale';
+import mrEventHub from '../eventhub';
+
+const CLASSES = {
+ opened: 'status-box-open',
+ closed: 'status-box-mr-closed',
+ merged: 'status-box-mr-merged',
+};
+
+const STATUS = {
+ opened: [__('Open'), 'issue-open-m'],
+ closed: [__('Closed'), 'close'],
+ merged: [__('Merged'), 'git-merge'],
+};
+
+export default {
+ components: {
+ GlIcon,
+ GlSprintf,
+ GlLink,
+ },
+ props: {
+ initialState: {
+ type: String,
+ required: true,
+ },
+ initialIsReverted: {
+ type: Boolean,
+ required: true,
+ },
+ initialRevertedPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ state: this.initialState,
+ isReverted: this.initialIsReverted,
+ revertedPath: this.initialRevertedPath,
+ };
+ },
+ computed: {
+ statusBoxClass() {
+ return CLASSES[this.state];
+ },
+ statusHumanName() {
+ return STATUS[this.state][0];
+ },
+ statusIconName() {
+ return STATUS[this.state][1];
+ },
+ },
+ created() {
+ mrEventHub.$on('mr.state.updated', this.updateState);
+ },
+ beforeDestroy() {
+ mrEventHub.$off('mr.state.updated', this.updateState);
+ },
+ methods: {
+ updateState({ state, reverted, revertedPath }) {
+ this.state = state;
+ this.reverted = reverted;
+ this.revertedPath = revertedPath;
+ },
+ },
+};
+</script>
+
+<template>
+ <div :class="statusBoxClass" class="issuable-status-box status-box">
+ <gl-icon
+ :name="statusIconName"
+ class="gl-display-block gl-display-sm-none!"
+ data-testid="status-icon"
+ />
+ <span class="gl-display-none gl-display-sm-block">
+ <gl-sprintf v-if="isReverted" :message="__('Merged (%{linkStart}reverted%{linkEnd})')">
+ <template #link="{ content }">
+ <gl-link
+ :href="revertedPath"
+ class="gl-reset-color! gl-text-decoration-underline"
+ data-testid="reverted-link"
+ >{{ content }}</gl-link
+ >
+ </template>
+ </gl-sprintf>
+ <template v-else>{{ statusHumanName }}</template>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/merge_request/eventhub.js b/app/assets/javascripts/merge_request/eventhub.js
new file mode 100644
index 00000000000..e31806ad199
--- /dev/null
+++ b/app/assets/javascripts/merge_request/eventhub.js
@@ -0,0 +1,3 @@
+import createEventHub from '~/helpers/event_hub_factory';
+
+export default createEventHub();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
index 27e10367a48..7657cea5bcd 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
@@ -1,12 +1,14 @@
+import Vue from 'vue';
import ZenMode from '~/zen_mode';
import initIssuableSidebar from '~/init_issuable_sidebar';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
-import { handleLocationHash } from '~/lib/utils/common_utils';
+import { handleLocationHash, parseBoolean } from '~/lib/utils/common_utils';
import initPipelines from '~/commit/pipelines/pipelines_bundle';
import initSourcegraph from '~/sourcegraph';
import loadAwardsHandler from '~/awards_handler';
import initInviteMemberTrigger from '~/invite_member/init_invite_member_trigger';
import initInviteMemberModal from '~/invite_member/init_invite_member_modal';
+import StatusBox from '~/merge_request/components/status_box.vue';
export default function () {
new ZenMode(); // eslint-disable-line no-new
@@ -18,4 +20,19 @@ export default function () {
loadAwardsHandler();
initInviteMemberModal();
initInviteMemberTrigger();
+
+ const el = document.querySelector('.js-mr-status-box');
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ render(h) {
+ return h(StatusBox, {
+ props: {
+ initialState: el.dataset.state,
+ initialIsReverted: parseBoolean(el.dataset.isReverted),
+ initialRevertedPath: el.dataset.revertedPath,
+ },
+ });
+ },
+ });
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index c0b1f3866f6..da4dc777c3e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -1,5 +1,6 @@
import { format } from 'timeago.js';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
+import mrEventHub from '~/merge_request/eventhub';
import { stateKey } from './state_maps';
import { formatDate } from '../../lib/utils/datetime_utility';
import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
@@ -154,6 +155,12 @@ export default class MergeRequestStore {
this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false;
this.setState();
+
+ mrEventHub.$emit('mr.state.updated', {
+ state: this.mergeRequestState,
+ reverted: data.reverted,
+ reverted_path: data.revertedPath,
+ });
}
setGraphqlData(project) {
diff --git a/app/controllers/import/bulk_imports_controller.rb b/app/controllers/import/bulk_imports_controller.rb
index e5e98dd44a4..7394e8bf615 100644
--- a/app/controllers/import/bulk_imports_controller.rb
+++ b/app/controllers/import/bulk_imports_controller.rb
@@ -8,6 +8,8 @@ class Import::BulkImportsController < ApplicationController
feature_category :importers
+ POLLING_INTERVAL = 3_000
+
rescue_from BulkImports::Clients::Http::ConnectionError, with: :bulk_import_connection_error
def configure
@@ -34,6 +36,12 @@ class Import::BulkImportsController < ApplicationController
render json: :ok
end
+ def realtime_changes
+ Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
+
+ render json: current_user_bulk_imports.to_json(only: [:id], methods: [:status_name])
+ end
+
private
def serialized_importable_data
@@ -152,4 +160,8 @@ class Import::BulkImportsController < ApplicationController
def sanitized_filter_param
@filter ||= sanitize(params[:filter])&.downcase
end
+
+ def current_user_bulk_imports
+ current_user.bulk_imports.gitlab
+ end
end
diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb
index 63a36732d87..ac204427885 100644
--- a/app/controllers/projects/mattermosts_controller.rb
+++ b/app/controllers/projects/mattermosts_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class Projects::MattermostsController < Projects::ApplicationController
- include TriggersHelper
+ include Ci::TriggersHelper
include ActionView::Helpers::AssetUrlHelper
layout 'project_settings'
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 512ba7e2a66..f2a9a927ded 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -4,8 +4,6 @@ require 'digest/md5'
require 'uri'
module ApplicationHelper
- include StartupCssHelper
-
# See https://docs.gitlab.com/ee/development/ee_features.html#code-in-appviews
# rubocop: disable CodeReuse/ActiveRecord
# We allow partial to be nil so that collection views can be passed in
@@ -250,11 +248,7 @@ module ApplicationHelper
end
def stylesheet_link_tag_defer(path)
- if use_startup_css?
- stylesheet_link_tag(path, media: "print", crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil)
- else
- stylesheet_link_tag(path, media: "all")
- end
+ stylesheet_link_tag(path, media: "print", crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil)
end
def outdated_browser?
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/ci/triggers_helper.rb
index 5cfdc0971f0..01555b6e2cc 100644
--- a/app/helpers/triggers_helper.rb
+++ b/app/helpers/ci/triggers_helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module TriggersHelper
+module Ci::TriggersHelper
def builds_trigger_url(project_id, ref: nil)
if ref.nil?
"#{Settings.gitlab.url}/api/v4/projects/#{project_id}/trigger/pipeline"
diff --git a/app/helpers/startup_css_helper.rb b/app/helpers/startup_css_helper.rb
deleted file mode 100644
index b54e19bfc0d..00000000000
--- a/app/helpers/startup_css_helper.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module StartupCssHelper
- def use_startup_css?
- (Feature.enabled?(:startup_css) || params[:startup_css] == 'true' || cookies['startup_css'] == 'true') && !Rails.env.test?
- end
-end
diff --git a/app/models/ci/commit_with_pipeline.rb b/app/models/ci/commit_with_pipeline.rb
new file mode 100644
index 00000000000..7f952fb77a0
--- /dev/null
+++ b/app/models/ci/commit_with_pipeline.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class Ci::CommitWithPipeline < SimpleDelegator
+ include Presentable
+
+ def initialize(commit)
+ @latest_pipelines = {}
+ super(commit)
+ end
+
+ def pipelines
+ project.ci_pipelines.where(sha: sha)
+ end
+
+ def last_pipeline
+ strong_memoize(:last_pipeline) do
+ pipelines.last
+ end
+ end
+
+ def latest_pipeline(ref = nil)
+ @latest_pipelines.fetch(ref) do |ref|
+ @latest_pipelines[ref] = latest_pipeline_for_project(ref, project)
+ end
+ end
+
+ def latest_pipeline_for_project(ref, pipeline_project)
+ pipeline_project.ci_pipelines.latest_pipeline_per_commit(id, ref)[id]
+ end
+
+ def set_latest_pipeline_for_ref(ref, pipeline)
+ @latest_pipelines[ref] = pipeline
+ end
+
+ def status(ref = nil)
+ latest_pipeline(ref)&.status
+ end
+end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6c2a818fc12..6a5d69f2e73 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -148,7 +148,7 @@ class Commit
to: :with_pipeline
def with_pipeline
- @with_pipeline ||= CommitWithPipeline.new(self)
+ @with_pipeline ||= Ci::CommitWithPipeline.new(self)
end
def id
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index b0aecbfc86d..f1f0fc3b11c 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -80,9 +80,9 @@ class CommitStatus < ApplicationRecord
merge(or_conditions)
end
- # We use `Enums::CommitStatus.failure_reasons` here so that EE can more easily
+ # We use `Enums::Ci::CommitStatus.failure_reasons` here so that EE can more easily
# extend this `Hash` with new values.
- enum_with_nil failure_reason: Enums::CommitStatus.failure_reasons
+ enum_with_nil failure_reason: Enums::Ci::CommitStatus.failure_reasons
##
# We still create some CommitStatuses outside of CreatePipelineService.
diff --git a/app/models/commit_with_pipeline.rb b/app/models/commit_with_pipeline.rb
index f382ae8f55a..7f952fb77a0 100644
--- a/app/models/commit_with_pipeline.rb
+++ b/app/models/commit_with_pipeline.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CommitWithPipeline < SimpleDelegator
+class Ci::CommitWithPipeline < SimpleDelegator
include Presentable
def initialize(commit)
diff --git a/app/models/concerns/enums/ci/commit_status.rb b/app/models/concerns/enums/ci/commit_status.rb
new file mode 100644
index 00000000000..48b4a402974
--- /dev/null
+++ b/app/models/concerns/enums/ci/commit_status.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+module Enums
+ module Ci
+ module CommitStatus
+ # Returns the Hash to use for creating the `failure_reason` enum for
+ # `CommitStatus`.
+ def self.failure_reasons
+ {
+ unknown_failure: nil,
+ script_failure: 1,
+ api_failure: 2,
+ stuck_or_timeout_failure: 3,
+ runner_system_failure: 4,
+ missing_dependency_failure: 5,
+ runner_unsupported: 6,
+ stale_schedule: 7,
+ job_execution_timeout: 8,
+ archived_failure: 9,
+ unmet_prerequisites: 10,
+ scheduler_failure: 11,
+ data_integrity_failure: 12,
+ forward_deployment_failure: 13,
+ insufficient_bridge_permissions: 1_001,
+ downstream_bridge_project_not_found: 1_002,
+ invalid_bridge_trigger: 1_003,
+ bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data
+ downstream_pipeline_creation_failed: 1_007,
+ secrets_provider_not_found: 1_008,
+ reached_max_descendant_pipelines_depth: 1_009
+ }
+ end
+ end
+ end
+end
+
+Enums::Ci::CommitStatus.prepend_if_ee('EE::Enums::Ci::CommitStatus')
diff --git a/app/models/concerns/enums/commit_status.rb b/app/models/concerns/enums/commit_status.rb
deleted file mode 100644
index faeed7276ab..00000000000
--- a/app/models/concerns/enums/commit_status.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module Enums
- module CommitStatus
- # Returns the Hash to use for creating the `failure_reason` enum for
- # `CommitStatus`.
- def self.failure_reasons
- {
- unknown_failure: nil,
- script_failure: 1,
- api_failure: 2,
- stuck_or_timeout_failure: 3,
- runner_system_failure: 4,
- missing_dependency_failure: 5,
- runner_unsupported: 6,
- stale_schedule: 7,
- job_execution_timeout: 8,
- archived_failure: 9,
- unmet_prerequisites: 10,
- scheduler_failure: 11,
- data_integrity_failure: 12,
- forward_deployment_failure: 13,
- insufficient_bridge_permissions: 1_001,
- downstream_bridge_project_not_found: 1_002,
- invalid_bridge_trigger: 1_003,
- bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data
- downstream_pipeline_creation_failed: 1_007,
- secrets_provider_not_found: 1_008,
- reached_max_descendant_pipelines_depth: 1_009
- }
- end
- end
-end
-
-Enums::CommitStatus.prepend_if_ee('EE::Enums::CommitStatus')
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 0fd85e3a5a9..f39d3947e5b 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class MattermostSlashCommandsService < SlashCommandsService
- include TriggersHelper
+ include Ci::TriggersHelper
prop_accessor :token
diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb
index 01ded0495a7..548f3623504 100644
--- a/app/models/project_services/slack_slash_commands_service.rb
+++ b/app/models/project_services/slack_slash_commands_service.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class SlackSlashCommandsService < SlashCommandsService
- include TriggersHelper
+ include Ci::TriggersHelper
def title
'Slack slash commands'
diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb
index 1db4ec37d4a..df7c7d2defa 100644
--- a/app/serializers/merge_request_poll_cached_widget_entity.rb
+++ b/app/serializers/merge_request_poll_cached_widget_entity.rb
@@ -114,6 +114,14 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity
end
end
+ expose :reverted do |merge_request|
+ merge_request.reverted_by_merge_request?(current_user)
+ end
+
+ expose :reverted_path, if: -> (mr) { mr.reverted_by_merge_request?(current_user) } do |merge_request|
+ merge_request_path(merge_request.reverting_merge_request(current_user))
+ end
+
private
delegate :current_user, to: :request
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 60e5293e218..6d41d449683 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -158,7 +158,9 @@ class IssuableBaseService < BaseService
after_create(issuable)
execute_hooks(issuable)
- invalidate_cache_counts(issuable, users: issuable.assignees)
+
+ users_to_invalidate = issuable.allows_reviewers? ? issuable.assignees | issuable.reviewers : issuable.assignees
+ invalidate_cache_counts(issuable, users: users_to_invalidate)
issuable.update_project_counter_caches
end
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index bdd506ab3be..7aa57331c51 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -43,8 +43,6 @@
= stylesheet_link_tag_defer "application"
= yield :page_specific_styles
= stylesheet_link_tag_defer "application_utilities"
- - unless use_startup_css?
- = stylesheet_link_tag_defer "themes/#{user_application_theme_css_filename}" if user_application_theme_css_filename
= stylesheet_link_tag "disable_animations", media: "all" if Rails.env.test? || Gitlab.config.gitlab['disable_animations']
= stylesheet_link_tag_defer "highlight/themes/#{user_color_scheme}"
diff --git a/app/views/layouts/_startup_css.haml b/app/views/layouts/_startup_css.haml
index 2f674f79b2f..35b91c8d35e 100644
--- a/app/views/layouts/_startup_css.haml
+++ b/app/views/layouts/_startup_css.haml
@@ -1,5 +1,3 @@
-- return unless use_startup_css?
-
- startup_filename = current_path?("sessions#new") ? 'signin' : user_application_theme == 'gl-dark' ? 'dark' : 'general'
%style{ type: "text/css" }
diff --git a/app/views/layouts/_startup_css_activation.haml b/app/views/layouts/_startup_css_activation.haml
index 5fb53385acc..7dfb9cd1530 100644
--- a/app/views/layouts/_startup_css_activation.haml
+++ b/app/views/layouts/_startup_css_activation.haml
@@ -1,5 +1,3 @@
-- return unless use_startup_css?
-
= javascript_tag do
:plain
document.querySelectorAll('link[media="print"]').forEach(linkTag => {
diff --git a/app/views/projects/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml
index ea8f53f7342..e60460687a9 100644
--- a/app/views/projects/issues/import_csv/_button.html.haml
+++ b/app/views/projects/issues/import_csv/_button.html.haml
@@ -2,7 +2,7 @@
- can_edit = can?(current_user, :admin_project, @project)
.dropdown.btn-group
- %button.btn.rounded-right.text-center{ class: ('has-tooltip' if type == :icon), title: (_('Import issues') if type == :icon),
+ %button.btn.gl-button.rounded-right.text-center{ class: ('has-tooltip' if type == :icon), title: (_('Import issues') if type == :icon),
data: { toggle: 'dropdown', qa_selector: 'import_issues_button' }, 'aria-label' => _('Import issues'), 'aria-haspopup' => 'true', 'aria-expanded' => 'false' }
- if type == :icon
= sprite_icon('import')
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index cc749a07a4e..c423df49b2b 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -3,6 +3,8 @@
- can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request)
- state_human_name, state_icon_name = state_name_with_icon(@merge_request)
- are_close_and_open_buttons_hidden = merge_request_button_hidden?(@merge_request, true) && merge_request_button_hidden?(@merge_request, false)
+- is_reverted = @merge_request.reverted_by_merge_request?(current_user)
+- reverted_mr_path = is_reverted ? merge_request_path(@merge_request.reverting_merge_request(current_user)) : nil
- if @merge_request.closed_or_merged_without_fork?
.gl-alert.gl-alert-danger.gl-mb-5
@@ -12,11 +14,11 @@
.detail-page-header.border-bottom-0.pt-0.pb-0
.detail-page-header-body
- .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
- = sprite_icon(state_icon_name, css_class: 'd-block d-sm-none')
- %span.d-none.d-sm-block
+ .issuable-status-box.status-box.js-mr-status-box{ class: status_box_class(@merge_request), data: { state: @merge_request.state, is_reverted: is_reverted.to_s, reverted_path: reverted_mr_path } }
+ = sprite_icon(state_icon_name, css_class: 'gl-display-block gl-display-sm-none!')
+ %span.gl-display-none.gl-display-sm-block
- if @merge_request.reverted_by_merge_request?(current_user)
- = _('Merged (%{reverted})').html_safe % { reverted: link_to(s_('MergeRequest|reverted'), merge_request_path(@merge_request.reverting_merge_request(current_user)), class: 'gl-reset-color! gl-text-decoration-underline') }
+ = _('Merged (%{reverted})').html_safe % { reverted: link_to(s_('MergeRequest|reverted'), reverted_mr_path, class: 'gl-reset-color! gl-text-decoration-underline') }
- else
= state_human_name
diff --git a/app/views/shared/empty_states/icons/_deploy_board.svg b/app/views/shared/empty_states/icons/_deploy_board.svg
deleted file mode 100644
index 475a6744693..00000000000
--- a/app/views/shared/empty_states/icons/_deploy_board.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg width="55" height="44" viewBox="0 0 55 44" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><g transform="translate(1.488 .803)"><rect stroke="#E5E5E5" stroke-width="1.6" fill="#FFF" width="42" height="42" rx="4"/><rect stroke="#E7E7E7" stroke-width="1.6" fill="#FFF" x="6" y="12" width="7.171" height="24" rx="2"/><rect stroke="#B5A7DD" stroke-width="1.6" fill="#FFF" x="6" y="5" width="32.17" height="3.5" rx="1.75"/><rect stroke="#FDE5D8" stroke-width="1.6" fill="#FFF" x="17" y="12" width="21.17" height="24" rx="2"/><rect fill="#E52C5A" x="19.74" y="14.197" width="7.171" height="2.408" rx="1.204"/><rect fill="#E5E5E5" x="28.35" y="18.272" width="7.171" height="2.408" rx="1.204"/><rect fill="#E5E5E5" x="19.74" y="26.697" width="15.943" height="2.408" rx="1.204"/><rect fill="#E5E5E5" x="19.74" y="30.697" width="15.943" height="2.408" rx="1.204"/><rect fill="#E52C5A" x="21.911" y="21.272" width="4.78" height="2.408" rx="1.204"/><rect fill="#FC8A51" x="28.472" y="22.429" width="7.171" height="2.408" rx="1.204"/><rect fill="#FDE5D8" x="26.691" y="8.429" width="2.39" height="2.408" rx="1.195"/><rect fill="#FF8340" x="8.512" y="14.85" width="2.39" height="2.408" rx="1.195"/><rect fill="#E52C5A" x="8.512" y="19.197" width="2.39" height="2.408" rx="1.195"/><rect fill="#FF8340" x="8.512" y="31.197" width="2.39" height="2.408" rx="1.195"/><rect fill="#E7E7E7" x="8.512" y="27.197" width="2.39" height="2.408" rx="1.195"/><rect fill="#B5A7DD" x="8.512" y="23.197" width="2.39" height="2.408" rx="1.195"/></g><g transform="rotate(-45 33.371 -12.99)"><ellipse stroke="#6B4FBB" stroke-width="3.2" fill-opacity=".1" fill="#FFF" cx="11.951" cy="12.041" rx="11.951" ry="12.041"/><path d="M5.536 22.29c5.716 3.3 13.046 1.307 16.37-4.452 3.326-5.759 1.387-13.103-4.329-16.403" stroke="#6B4FBB" stroke-width="3.2" fill-opacity=".3" fill="#FFF"/><rect fill="#6B4FBB" x="9.561" y="23.279" width="4.78" height="13.646" rx="2.39"/></g></g></svg>
diff --git a/changelogs/unreleased/254325-remove-stages-from-dast-template.yml b/changelogs/unreleased/254325-remove-stages-from-dast-template.yml
new file mode 100644
index 00000000000..4df5ddfa5d2
--- /dev/null
+++ b/changelogs/unreleased/254325-remove-stages-from-dast-template.yml
@@ -0,0 +1,5 @@
+---
+title: Add DAST.latest.gitlab-ci.yml
+merge_request: 50539
+author:
+type: added
diff --git a/changelogs/unreleased/277134_populate_finding_uuid_values_for_vulnerability_feedback.yml b/changelogs/unreleased/277134_populate_finding_uuid_values_for_vulnerability_feedback.yml
new file mode 100644
index 00000000000..dc84ff7cb4d
--- /dev/null
+++ b/changelogs/unreleased/277134_populate_finding_uuid_values_for_vulnerability_feedback.yml
@@ -0,0 +1,6 @@
+---
+title: Populate `finding_uuid` attribute for the existing `vulnerability_feedback`
+ records
+merge_request: 49807
+author:
+type: added
diff --git a/changelogs/unreleased/296842-invalidate-reviewer-counter-on-create.yml b/changelogs/unreleased/296842-invalidate-reviewer-counter-on-create.yml
new file mode 100644
index 00000000000..c06a634706e
--- /dev/null
+++ b/changelogs/unreleased/296842-invalidate-reviewer-counter-on-create.yml
@@ -0,0 +1,5 @@
+---
+title: Invalidate reviews counter cache when MR gets created
+merge_request: 51316
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-forbid-push-when-snippet-read-only.yml b/changelogs/unreleased/fj-forbid-push-when-snippet-read-only.yml
new file mode 100644
index 00000000000..b6d0b1386fc
--- /dev/null
+++ b/changelogs/unreleased/fj-forbid-push-when-snippet-read-only.yml
@@ -0,0 +1,5 @@
+---
+title: Forbid snippet pushes when repo is read-only
+merge_request: 51318
+author:
+type: fixed
diff --git a/changelogs/unreleased/ph-290850-mrStatusBoxPoll.yml b/changelogs/unreleased/ph-290850-mrStatusBoxPoll.yml
new file mode 100644
index 00000000000..76bf9e6cd3e
--- /dev/null
+++ b/changelogs/unreleased/ph-290850-mrStatusBoxPoll.yml
@@ -0,0 +1,5 @@
+---
+title: Update merge request status box without reloading page
+merge_request: 50761
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-disable-rebase-on-conflicts.yml b/changelogs/unreleased/sh-disable-rebase-on-conflicts.yml
new file mode 100644
index 00000000000..c0514929d2a
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-rebase-on-conflicts.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent rebase from being run in quick action when there are conflicts
+merge_request: 51243
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-puma-to-v-5.yml b/changelogs/unreleased/update-puma-to-v-5.yml
new file mode 100644
index 00000000000..fa80a47718f
--- /dev/null
+++ b/changelogs/unreleased/update-puma-to-v-5.yml
@@ -0,0 +1,5 @@
+---
+title: Update puma & puma_worker_killer to upstream
+merge_request: 48897
+author:
+type: changed
diff --git a/changelogs/unreleased/yo-master-patch-74981.yml b/changelogs/unreleased/yo-master-patch-74981.yml
new file mode 100644
index 00000000000..7de75a89d2e
--- /dev/null
+++ b/changelogs/unreleased/yo-master-patch-74981.yml
@@ -0,0 +1,5 @@
+---
+title: Fix import issues button style
+merge_request: 50969
+author: Yogi (@yo)
+type: fixed
diff --git a/config/feature_flags/development/startup_css.yml b/config/feature_flags/development/startup_css.yml
deleted file mode 100644
index 88a1cd77c95..00000000000
--- a/config/feature_flags/development/startup_css.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: startup_css
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39713
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/238718
-milestone: '13.3'
-type: development
-group: group::editor
-default_enabled: false
diff --git a/config/feature_flags/development/variable_inside_variable.yml b/config/feature_flags/development/variable_inside_variable.yml
new file mode 100644
index 00000000000..1e75576a97a
--- /dev/null
+++ b/config/feature_flags/development/variable_inside_variable.yml
@@ -0,0 +1,8 @@
+---
+name: variable_inside_variable
+introduced_by_url:
+rollout_issue_url:
+milestone: '13.7'
+type: development
+group: group::runner
+default_enabled: false
diff --git a/config/puma.example.development.rb b/config/puma.example.development.rb
index ecbfac660c9..7e616ee4768 100644
--- a/config/puma.example.development.rb
+++ b/config/puma.example.development.rb
@@ -79,6 +79,11 @@ tag 'gitlab-puma-worker'
#
worker_timeout 60
+# https://github.com/puma/puma/blob/master/5.0-Upgrade.md#lower-latency-better-throughput
+if defined?(wait_for_less_busy_worker)
+ wait_for_less_busy_worker ENV.fetch('PUMA_WAIT_FOR_LESS_BUSY_WORKER', 0.001).to_f
+end
+
# Use json formatter
require_relative "/home/git/gitlab/lib/gitlab/puma_logging/json_formatter"
diff --git a/config/puma.rb.example b/config/puma.rb.example
index cd7adca157f..25e38626260 100644
--- a/config/puma.rb.example
+++ b/config/puma.rb.example
@@ -69,10 +69,15 @@ tag 'gitlab-puma-worker'
#
worker_timeout 60
+# https://github.com/puma/puma/blob/master/5.0-Upgrade.md#lower-latency-better-throughput
+if defined?(wait_for_less_busy_worker)
+ wait_for_less_busy_worker ENV.fetch('PUMA_WAIT_FOR_LESS_BUSY_WORKER', 0.001).to_f
+end
+
# Use json formatter
require_relative "/home/git/gitlab/lib/gitlab/puma_logging/json_formatter"
json_formatter = Gitlab::PumaLogging::JSONFormatter.new
log_formatter do |str|
json_formatter.call(str)
-end \ No newline at end of file
+end
diff --git a/config/puma_actioncable.example.development.rb b/config/puma_actioncable.example.development.rb
index c975f9e4f9b..106932606e0 100644
--- a/config/puma_actioncable.example.development.rb
+++ b/config/puma_actioncable.example.development.rb
@@ -79,6 +79,11 @@ tag 'gitlab-actioncable-puma-worker'
#
worker_timeout 60
+# https://github.com/puma/puma/blob/master/5.0-Upgrade.md#lower-latency-better-throughput
+if defined?(wait_for_less_busy_worker)
+ wait_for_less_busy_worker ENV.fetch('PUMA_WAIT_FOR_LESS_BUSY_WORKER', 0.001).to_f
+end
+
# Use json formatter
require_relative "/home/git/gitlab/lib/gitlab/puma_logging/json_formatter"
diff --git a/config/routes/import.rb b/config/routes/import.rb
index 6c99b0320de..5f94fb8d058 100644
--- a/config/routes/import.rb
+++ b/config/routes/import.rb
@@ -63,6 +63,7 @@ namespace :import do
resource :bulk_imports, only: [:create] do
post :configure
get :status
+ get :realtime_changes
end
resource :manifest, only: [:create, :new], controller: :manifest do
diff --git a/db/post_migrate/20201211090634_schedule_populate_finding_uuid_for_vulnerability_feedback.rb b/db/post_migrate/20201211090634_schedule_populate_finding_uuid_for_vulnerability_feedback.rb
new file mode 100644
index 00000000000..3ecb48dab0f
--- /dev/null
+++ b/db/post_migrate/20201211090634_schedule_populate_finding_uuid_for_vulnerability_feedback.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class SchedulePopulateFindingUuidForVulnerabilityFeedback < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ MIGRATION_CLASS = 'PopulateFindingUuidForVulnerabilityFeedback'
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 1000
+
+ disable_ddl_transaction!
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ Gitlab::BackgroundMigration::PopulateFindingUuidForVulnerabilityFeedback::VulnerabilityFeedback,
+ MIGRATION_CLASS,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/schema_migrations/20201211090634 b/db/schema_migrations/20201211090634
new file mode 100644
index 00000000000..0c11b8f85fb
--- /dev/null
+++ b/db/schema_migrations/20201211090634
@@ -0,0 +1 @@
+5117b71950bec3c6c746eaf4851c111a335bf2280075ddc2c73b60a30e23d907 \ No newline at end of file
diff --git a/doc/.vale/gitlab/Acronyms.yml b/doc/.vale/gitlab/Acronyms.yml
index 2c28da972da..5e6a106af55 100644
--- a/doc/.vale/gitlab/Acronyms.yml
+++ b/doc/.vale/gitlab/Acronyms.yml
@@ -58,6 +58,7 @@ exceptions:
- GPL
- GUI
- HAML
+ - HEAD
- HIPAA
- HTML
- HTTP
diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md
index 2b840c8dd6a..0a597677f7a 100644
--- a/doc/administration/packages/container_registry.md
+++ b/doc/administration/packages/container_registry.md
@@ -867,6 +867,26 @@ understand the implications.
WARNING:
This is a destructive operation.
+When you run `registry-garbage-collect` with the -m flag, garbage collection unlinks manifests that
+are part of a multi-arch manifest, unless they're tagged in the same repository.
+See [this issue](https://gitlab.com/gitlab-org/container-registry/-/issues/149) for details.
+
+To work around this issue, instead of:
+
+```plaintext
+myrepo/multiarchmanifest:latest
+myrepo/manifest/amd-64:latest
+myrepo/manifest/arm:latest
+```
+
+Use:
+
+```plaintext
+myrepo/multiarchmanifest:latest
+myrepo/manifest:amd-64-latest
+myrepo/manifest:arm-latest
+```
+
The GitLab Container Registry follows the same default workflow as Docker Distribution:
retain untagged manifests and all layers, even ones that are not referenced directly. All content
can be accessed by using context addressable identifiers.
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 7c8f37317a5..4fb011556f4 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -14,10 +14,8 @@ Some of the predefined environment variables are available only if a minimum
version of [GitLab Runner](https://docs.gitlab.com/runner/) is used. Consult the table below to find the
version of GitLab Runner that's required.
-NOTE:
-Starting with GitLab 9.0, we have deprecated some variables. Read the
-[9.0 Renaming](deprecated_variables.md#gitlab-90-renamed-variables) section to find out their replacements.
-**To avoid problems with deprecated and removed variables in future releases, you are strongly advised to use the new variables.**
+In GitLab 9.0, some [variable were deprecated](deprecated_variables.md#gitlab-90-renamed-variables).
+You should ensure you are using the latest variables.
You can add a command to your `.gitlab-ci.yml` file to
[output the values of all variables available for a job](README.md#list-all-environment-variables).
@@ -37,28 +35,28 @@ Kubernetes-specific environment variables are detailed in the
| `CI_COMMIT_MESSAGE` | 10.8 | all | The full commit message. |
| `CI_COMMIT_REF_NAME` | 9.0 | all | The branch or tag name for which project is built |
| `CI_COMMIT_REF_PROTECTED` | 11.11 | all | `true` if the job is running on a protected reference, `false` if not |
-| `CI_COMMIT_REF_SLUG` | 9.0 | all | `$CI_COMMIT_REF_NAME` lowercased, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
+| `CI_COMMIT_REF_SLUG` | 9.0 | all | `$CI_COMMIT_REF_NAME` in lowercase, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in URLs, host names and domain names. |
| `CI_COMMIT_SHA` | 9.0 | all | The commit revision for which project is built |
| `CI_COMMIT_SHORT_SHA` | 11.7 | all | The first eight characters of `CI_COMMIT_SHA` |
| `CI_COMMIT_BRANCH` | 12.6 | 0.5 | The commit branch name. Present in branch pipelines, including pipelines for the default branch. Not present in merge request pipelines or tag pipelines. |
| `CI_COMMIT_TAG` | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
| `CI_COMMIT_TITLE` | 10.8 | all | The title of the commit - the full first line of the message |
| `CI_COMMIT_TIMESTAMP` | 13.4 | all | The timestamp of the commit in the ISO 8601 format. |
-| `CI_CONCURRENT_ID` | all | 11.10 | Unique ID of build execution within a single executor. |
-| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | Unique ID of build execution within a single executor and project. |
+| `CI_CONCURRENT_ID` | all | 11.10 | Unique ID of build execution in a single executor. |
+| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | Unique ID of build execution in a single executor and project. |
| `CI_CONFIG_PATH` | 9.4 | 0.5 | The path to CI configuration file. Defaults to `.gitlab-ci.yml` |
| `CI_DEBUG_TRACE` | all | 1.7 | Whether [debug logging (tracing)](README.md#debug-logging) is enabled |
| `CI_DEFAULT_BRANCH` | 12.4 | all | The name of the default branch for the project. |
-| `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` | 13.7 | all | The image prefix for pulling images through the Dependency Proxy. |
-| `CI_DEPENDENCY_PROXY_SERVER` | 13.7 | all | The server for logging in to the Dependency Proxy. This is equivelant to `$CI_SERVER_HOST:$CI_SERVER_PORT`. |
-| `CI_DEPENDENCY_PROXY_PASSWORD` | 13.7 | all | The password to use to pull images through the Dependency Proxy. |
-| `CI_DEPENDENCY_PROXY_USER` | 13.7 | all | The username to use to pull images through the Dependency Proxy. |
+| `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX` | 13.7 | all | The image prefix for pulling images through the Dependency Proxy. |
+| `CI_DEPENDENCY_PROXY_SERVER` | 13.7 | all | The server for logging in to the Dependency Proxy. This is equivalent to `$CI_SERVER_HOST:$CI_SERVER_PORT`. |
+| `CI_DEPENDENCY_PROXY_PASSWORD` | 13.7 | all | The password to use to pull images through the Dependency Proxy. |
+| `CI_DEPENDENCY_PROXY_USER` | 13.7 | all | The username to use to pull images through the Dependency Proxy. |
| `CI_DEPLOY_FREEZE` | 13.2 | all | Included with the value `true` if the pipeline runs during a [deploy freeze window](../../user/project/releases/index.md#prevent-unintentional-releases-by-setting-a-deploy-freeze). |
| `CI_DEPLOY_PASSWORD` | 10.8 | all | Authentication password of the [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token), only present if the Project has one related. |
| `CI_DEPLOY_USER` | 10.8 | all | Authentication username of the [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token), only present if the Project has one related. |
| `CI_DISPOSABLE_ENVIRONMENT` | all | 10.1 | Marks that the job is executed in a disposable environment (something that is created only for this job and disposed of/destroyed after the execution - all executors except `shell` and `ssh`). If the environment is disposable, it is set to true, otherwise it is not defined at all. |
| `CI_ENVIRONMENT_NAME` | 8.15 | all | The name of the environment for this job. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
-| `CI_ENVIRONMENT_SLUG` | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, etc. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
+| `CI_ENVIRONMENT_SLUG` | 8.15 | all | A simplified version of the environment name, suitable for inclusion in DNS, URLs, Kubernetes labels, and so on. Only present if [`environment:name`](../yaml/README.md#environmentname) is set. |
| `CI_ENVIRONMENT_URL` | 9.3 | all | The URL of the environment for this job. Only present if [`environment:url`](../yaml/README.md#environmenturl) is set. |
| `CI_EXTERNAL_PULL_REQUEST_IID` | 12.3 | all | Pull Request ID from GitHub if the [pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the pull request is open. |
| `CI_EXTERNAL_PULL_REQUEST_SOURCE_REPOSITORY` | 13.3 | all | The source repository name of the pull request if [the pipelines are for external pull requests](../ci_cd_for_external_repos/index.md#pipelines-for-external-pull-requests). Available only if `only: [external_pull_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the pull request is open. |
@@ -85,9 +83,9 @@ Kubernetes-specific environment variables are detailed in the
| `CI_MERGE_REQUEST_LABELS` | 11.9 | all | Comma-separated label names of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
| `CI_MERGE_REQUEST_MILESTONE` | 11.9 | all | The milestone title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
| `CI_MERGE_REQUEST_PROJECT_ID` | 11.6 | all | The ID of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
-| `CI_MERGE_REQUEST_PROJECT_PATH` | 11.6 | all | The path of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
-| `CI_MERGE_REQUEST_PROJECT_URL` | 11.6 | all | The URL of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
-| `CI_MERGE_REQUEST_REF_PATH` | 11.6 | all | The ref path of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
+| `CI_MERGE_REQUEST_PROJECT_PATH` | 11.6 | all | The path of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (for example `namespace/awesome-project`). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
+| `CI_MERGE_REQUEST_PROJECT_URL` | 11.6 | all | The URL of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (for example `http://192.168.10.15:3000/namespace/awesome-project`). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
+| `CI_MERGE_REQUEST_REF_PATH` | 11.6 | all | The ref path of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). (for example `refs/merge-requests/1/head`). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
| `CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | 11.6 | all | The source branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
| `CI_MERGE_REQUEST_SOURCE_BRANCH_SHA` | 11.9 | all | The HEAD SHA of the source branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used, the merge request is created, and the pipeline is a [merged result pipeline](../merge_request_pipelines/pipelines_for_merged_results/index.md). **(PREMIUM)** |
| `CI_MERGE_REQUEST_SOURCE_PROJECT_ID` | 11.6 | all | The ID of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). Available only if `only: [merge_requests]` or [`rules`](../yaml/README.md#rules) syntax is used and the merge request is created. |
@@ -111,12 +109,12 @@ Kubernetes-specific environment variables are detailed in the
| `CI_PROJECT_CONFIG_PATH` | 13.8 | all | The CI configuration path for the project
| `CI_PROJECT_DIR` | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. |
| `CI_PROJECT_ID` | all | all | The unique ID of the current project that GitLab CI/CD uses internally |
-| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project that is currently being built. For example, if the project URL is `gitlab.example.com/group-name/project-1`, the `CI_PROJECT_NAME` would be `project-1`. |
-| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or group name) that is currently being built |
-| `CI_PROJECT_ROOT_NAMESPACE` | 13.2 | 0.5 | The **root** project namespace (username or group name) that is currently being built. For example, if `CI_PROJECT_NAMESPACE` is `root-group/child-group/grandchild-group`, `CI_PROJECT_ROOT_NAMESPACE` would be `root-group`. |
+| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project that is being built. For example, if the project URL is `gitlab.example.com/group-name/project-1`, the `CI_PROJECT_NAME` would be `project-1`. |
+| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or group name) that is being built |
+| `CI_PROJECT_ROOT_NAMESPACE` | 13.2 | 0.5 | The **root** project namespace (username or group name) that is being built. For example, if `CI_PROJECT_NAMESPACE` is `root-group/child-group/grandchild-group`, `CI_PROJECT_ROOT_NAMESPACE` would be `root-group`. |
| `CI_PROJECT_PATH` | 8.10 | 0.5 | The namespace with project name |
-| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
-| `CI_PROJECT_REPOSITORY_LANGUAGES` | 12.3 | all | Comma-separated, lowercased list of the languages used in the repository (e.g. `ruby,javascript,html,css`) |
+| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` in lowercase and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
+| `CI_PROJECT_REPOSITORY_LANGUAGES` | 12.3 | all | Comma-separated, lowercase list of the languages used in the repository (for example `ruby,javascript,html,css`) |
| `CI_PROJECT_TITLE` | 12.4 | all | The human-readable project name as displayed in the GitLab web interface. |
| `CI_PROJECT_URL` | 8.10 | 0.5 | The HTTP(S) address to access project |
| `CI_PROJECT_VISIBILITY` | 10.3 | all | The project visibility (internal, private, public) |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index d6219a41660..20c6941686a 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -2275,6 +2275,58 @@ job3:
- deploy_to_staging
```
+#### `allow_failure:exit_codes`
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/273157) in GitLab 13.8.
+> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
+> - It's disabled on GitLab.com.
+> - It's not recommended for production use.
+> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-allow_failureexit_codes). **(CORE ONLY)**
+
+WARNING:
+This feature might not be available to you. Check the **version history** note above for details.
+
+Use `allow_failure:exit_codes` to dynamically control if a job should be allowed
+to fail. You can list which exit codes are not considered failures. The job fails
+for any other exit code:
+
+```yaml
+test_job_1:
+ script:
+ - echo "Run a script that results in exit code 1. This job fails."
+ - exit 1
+ allow_failure:
+ exit_codes: 137
+
+test_job_2:
+ script:
+ - echo "Run a script that results in exit code 137. This job is allowed to fail."
+ - exit 137
+ allow_failure:
+ exit_codes:
+ - 137
+ - 255
+```
+
+##### Enable or disable `allow_failure:exit_codes` **(CORE ONLY)**
+
+`allow_failure:exit_codes` is under development and not ready for production use. It is
+deployed behind a feature flag that is **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
+can enable it.
+
+To enable it:
+
+```ruby
+Feature.enable(:ci_allow_failure_with_exit_codes)
+```
+
+To disable it:
+
+```ruby
+Feature.disable(:ci_allow_failure_with_exit_codes)
+```
+
### `when`
`when` is used to implement jobs that are run in case of failure or despite the
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index 27e246913fa..0f7f490cdcf 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -86,6 +86,20 @@ variables:
DAST_WEBSITE: https://example.com
```
+### Latest template
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254325) in GitLab 13.8
+
+To use the latest version of the DAST template, include
+`DAST.latest.gitlab-ci.yml` instead of `DAST.gitlab-ci.yml`.
+See the CI [docs](../../../development/cicd/templates.md#latest-version)
+on template versioning for more information.
+
+Please note that the latest version may include breaking changes. Check the
+[DAST troubleshooting guide](#troubleshooting) if you experience problems.
+
+### Template options
+
There are two ways to define the URL to be scanned by DAST:
1. Set the `DAST_WEBSITE` [variable](../../../ci/yaml/README.md#variables).
@@ -1040,6 +1054,25 @@ If your DAST job exceeds the job timeout and you need to reduce the scan duratio
For information on this, see the [general Application Security troubleshooting section](../../../ci/pipelines/job_artifacts.md#error-message-no-files-to-upload).
+### Getting error `dast job: chosen stage does not exist` when including DAST CI template
+
+Newer versions of the DAST CI template do not define stages in order to avoid
+overwriting stages from other CI files. If you've recently started using
+`DAST.latest.gitlab-ci.yml` or upgraded to a new major release of GitLab and
+began receiving this error, you will need to define a `dast` stage with your
+other stages. Please note that you must have a running application for DAST to
+scan. If your application is set up in your pipeline, it must be deployed
+ in a stage _before_ the `dast` stage:
+
+```yaml
+stages:
+ - deploy # DAST needs a running application to scan
+ - dast
+
+include:
+ - template: DAST.latest.gitlab-ci.yml
+```
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb
index dc8f9d0c970..06cebab8f0a 100644
--- a/lib/expand_variables.rb
+++ b/lib/expand_variables.rb
@@ -16,6 +16,12 @@ module ExpandVariables
end
end
+ def possible_var_reference?(value)
+ return unless value
+
+ %w[$ %].any? { |symbol| value.include?(symbol) }
+ end
+
private
def replace_with(value, variables)
diff --git a/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback.rb b/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback.rb
new file mode 100644
index 00000000000..52b09e07fd5
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class populates the `finding_uuid` attribute for
+ # the existing `vulnerability_feedback` records.
+ class PopulateFindingUuidForVulnerabilityFeedback
+ REPORT_TYPES = {
+ sast: 0,
+ dependency_scanning: 1,
+ container_scanning: 2,
+ dast: 3,
+ secret_detection: 4,
+ coverage_fuzzing: 5,
+ api_fuzzing: 6
+ }.freeze
+
+ class VulnerabilityFeedback < ActiveRecord::Base # rubocop:disable Style/Documentation
+ include EachBatch
+
+ self.table_name = 'vulnerability_feedback'
+
+ enum category: REPORT_TYPES
+
+ scope :in_range, -> (start, stop) { where(id: start..stop) }
+ scope :without_uuid, -> { where(finding_uuid: nil) }
+
+ def self.load_vulnerability_findings
+ all.to_a.tap { |collection| collection.each(&:vulnerability_finding) }
+ end
+
+ def set_finding_uuid
+ return unless vulnerability_finding.present? && vulnerability_finding.primary_identifier.present?
+
+ update_column(:finding_uuid, calculated_uuid)
+ rescue StandardError => error
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error)
+ end
+
+ def vulnerability_finding
+ BatchLoader.for(finding_key).batch(replace_methods: false) do |finding_keys, loader|
+ project_ids = finding_keys.map { |key| key[:project_id] }
+ categories = finding_keys.map { |key| key[:category] }
+ fingerprints = finding_keys.map { |key| key[:project_fingerprint] }
+
+ findings = Finding.with_primary_identifier.where(
+ project_id: project_ids.uniq,
+ report_type: categories.uniq,
+ project_fingerprint: fingerprints.uniq
+ ).to_a
+
+ finding_keys.each do |finding_key|
+ loader.call(
+ finding_key,
+ findings.find { |f| finding_key == f.finding_key }
+ )
+ end
+ end
+ end
+
+ private
+
+ def calculated_uuid
+ Gitlab::UUID.v5(uuid_components)
+ end
+
+ def uuid_components
+ [
+ category,
+ vulnerability_finding.primary_identifier.fingerprint,
+ vulnerability_finding.location_fingerprint,
+ project_id
+ ].join('-')
+ end
+
+ def finding_key
+ {
+ project_id: project_id,
+ category: category,
+ project_fingerprint: project_fingerprint
+ }
+ end
+ end
+
+ class Finding < ActiveRecord::Base # rubocop:disable Style/Documentation
+ include ShaAttribute
+
+ self.table_name = 'vulnerability_occurrences'
+
+ sha_attribute :project_fingerprint
+ sha_attribute :location_fingerprint
+
+ belongs_to :primary_identifier, class_name: 'Gitlab::BackgroundMigration::PopulateFindingUuidForVulnerabilityFeedback::Identifier'
+
+ enum report_type: REPORT_TYPES
+
+ scope :with_primary_identifier, -> { includes(:primary_identifier) }
+
+ def finding_key
+ {
+ project_id: project_id,
+ category: report_type,
+ project_fingerprint: project_fingerprint
+ }
+ end
+ end
+
+ class Identifier < ActiveRecord::Base # rubocop:disable Style/Documentation
+ self.table_name = 'vulnerability_identifiers'
+ end
+
+ def perform(*range)
+ feedback = VulnerabilityFeedback.without_uuid.in_range(*range).load_vulnerability_findings
+ feedback.each(&:set_finding_uuid)
+
+ log_info(feedback.count)
+ end
+
+ def log_info(feedback_count)
+ ::Gitlab::BackgroundMigration::Logger.info(
+ migrator: self.class.name,
+ message: '`finding_uuid` attributes has been set',
+ count: feedback_count
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
new file mode 100644
index 00000000000..dc3e1ab5ef8
--- /dev/null
+++ b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml
@@ -0,0 +1,45 @@
+# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast/
+
+# Configure the scanning tool through the environment variables.
+# List of the variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
+# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
+
+variables:
+ DAST_VERSION: 1
+ # Setting this variable will affect all Security templates
+ # (SAST, Dependency Scanning, ...)
+ SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
+
+dast:
+ stage: dast
+ image:
+ name: "$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION"
+ variables:
+ GIT_STRATEGY: none
+ allow_failure: true
+ script:
+ - export DAST_WEBSITE=${DAST_WEBSITE:-$(cat environment_url.txt)}
+ - if [ -z "$DAST_WEBSITE$DAST_API_SPECIFICATION" ]; then echo "Either DAST_WEBSITE or DAST_API_SPECIFICATION must be set. See https://docs.gitlab.com/ee/user/application_security/dast/#configuration for more details." && exit 1; fi
+ - /analyze
+ artifacts:
+ reports:
+ dast: gl-dast-report.json
+ rules:
+ - if: $DAST_DISABLED
+ when: never
+ - if: $DAST_DISABLED_FOR_DEFAULT_BRANCH &&
+ $CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
+ when: never
+ - if: $CI_DEFAULT_BRANCH != $CI_COMMIT_REF_NAME &&
+ $REVIEW_DISABLED && $DAST_WEBSITE == null &&
+ $DAST_API_SPECIFICATION == null
+ when: never
+ - if: $CI_COMMIT_BRANCH &&
+ $CI_KUBERNETES_ACTIVE &&
+ $GITLAB_FEATURES =~ /\bdast\b/
+ - if: $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bdast\b/ &&
+ $DAST_WEBSITE
+ - if: $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bdast\b/ &&
+ $DAST_API_SPECIFICATION
diff --git a/lib/gitlab/ci/variables/collection/sorted.rb b/lib/gitlab/ci/variables/collection/sorted.rb
new file mode 100644
index 00000000000..6abc6a5644f
--- /dev/null
+++ b/lib/gitlab/ci/variables/collection/sorted.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Variables
+ class Collection
+ class Sorted
+ include TSort
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(variables)
+ @variables = variables
+ end
+
+ def valid?
+ errors.nil?
+ end
+
+ # errors sorts an array of variables, ignoring unknown variable references,
+ # and returning an error string if a circular variable reference is found
+ def errors
+ return if Feature.disabled?(:variable_inside_variable)
+
+ strong_memoize(:errors) do
+ # Check for cyclic dependencies and build error message in that case
+ errors = each_strongly_connected_component.filter_map do |component|
+ component.map { |v| v[:key] }.inspect if component.size > 1
+ end
+
+ "circular variable reference detected: #{errors.join(', ')}" if errors.any?
+ end
+ end
+
+ # sort sorts an array of variables, ignoring unknown variable references.
+ # If a circular variable reference is found, the original array is returned
+ def sort
+ return @variables if Feature.disabled?(:variable_inside_variable)
+ return @variables if errors
+
+ tsort
+ end
+
+ private
+
+ def tsort_each_node(&block)
+ @variables.each(&block)
+ end
+
+ def tsort_each_child(variable, &block)
+ each_variable_reference(variable[:value], &block)
+ end
+
+ def input_vars
+ strong_memoize(:input_vars) do
+ @variables.index_by { |env| env.fetch(:key) }
+ end
+ end
+
+ def walk_references(value)
+ return unless ExpandVariables.possible_var_reference?(value)
+
+ value.scan(ExpandVariables::VARIABLES_REGEXP) do |var_ref|
+ yield(input_vars, var_ref.first)
+ end
+ end
+
+ def each_variable_reference(value)
+ walk_references(value) do |vars_hash, ref_var_name|
+ variable = vars_hash.dig(ref_var_name)
+ yield variable if variable
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git_access_snippet.rb b/lib/gitlab/git_access_snippet.rb
index 482881daca6..88a75f72840 100644
--- a/lib/gitlab/git_access_snippet.rb
+++ b/lib/gitlab/git_access_snippet.rb
@@ -59,7 +59,7 @@ module Gitlab
# TODO: Investigate if expanding actor/authentication types are needed.
# https://gitlab.com/gitlab-org/gitlab/issues/202190
if actor && !allowed_actor?
- raise ForbiddenError, ERROR_MESSAGES[:authentication_mechanism]
+ raise ForbiddenError, error_message(:authentication_mechanism)
end
super
@@ -71,14 +71,18 @@ module Gitlab
override :check_push_access!
def check_push_access!
- raise ForbiddenError, ERROR_MESSAGES[:update_snippet] unless user
+ raise ForbiddenError, error_message(:update_snippet) unless user
+
+ if snippet&.repository_read_only?
+ raise ForbiddenError, error_message(:read_only)
+ end
check_change_access!
end
def check_snippet_accessibility!
if snippet.blank?
- raise NotFoundError, ERROR_MESSAGES[:snippet_not_found]
+ raise NotFoundError, error_message(:snippet_not_found)
end
end
@@ -94,14 +98,14 @@ module Gitlab
passed = guest_can_download_code? || user_can_download_code?
unless passed
- raise ForbiddenError, ERROR_MESSAGES[:read_snippet]
+ raise ForbiddenError, error_message(:read_snippet)
end
end
override :check_change_access!
def check_change_access!
unless user_can_push?
- raise ForbiddenError, ERROR_MESSAGES[:update_snippet]
+ raise ForbiddenError, error_message(:update_snippet)
end
check_size_before_push!
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 10e8f00874d..0ba535b500e 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -4,7 +4,6 @@
module Gitlab
module GonHelper
- include StartupCssHelper
include WebpackHelper
def add_gon_variables
@@ -49,9 +48,6 @@ module Gitlab
push_frontend_feature_flag(:usage_data_api, default_enabled: true)
push_frontend_feature_flag(:security_auto_fix, default_enabled: false)
push_frontend_feature_flag(:gl_tooltips, default_enabled: :yaml)
-
- # Startup CSS feature is a special one as it can be enabled by means of cookies and params
- gon.push({ features: { 'startupCss' => use_startup_css? } }, true)
end
# Exposes the state of a feature flag to the frontend code.
diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb
index 70c581c6454..31cc3f930c0 100644
--- a/lib/gitlab/quick_actions/merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/merge_request_actions.rb
@@ -57,6 +57,11 @@ module Gitlab
access_check.can_push_to_branch?(merge_request.source_branch)
end
command :rebase do
+ if quick_action_target.cannot_be_merged?
+ @execution_message[:rebase] = _('This merge request cannot be rebased while there are conflicts.')
+ next
+ end
+
if quick_action_target.rebase_in_progress?
@execution_message[:rebase] = _('A rebase is already in progress.')
next
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d153dc7c0e0..0c189fc5cca 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17670,6 +17670,9 @@ msgstr ""
msgid "Merged"
msgstr ""
+msgid "Merged (%{linkStart}reverted%{linkEnd})"
+msgstr ""
+
msgid "Merged (%{reverted})"
msgstr ""
@@ -28835,6 +28838,9 @@ msgstr ""
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr ""
+msgid "This merge request cannot be rebased while there are conflicts."
+msgstr ""
+
msgid "This merge request does not have accessibility reports"
msgstr ""
diff --git a/package.json b/package.json
index 516d8f4c565..47669e7ed8d 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "1.178.0",
"@gitlab/tributejs": "1.0.0",
- "@gitlab/ui": "25.7.2",
+ "@gitlab/ui": "25.7.3",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-4",
"@rails/ujs": "^6.0.3-4",
diff --git a/qa/qa/specs/features/api/3_create/gitaly/gitaly_mtls_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/gitaly_mtls_spec.rb
new file mode 100644
index 00000000000..8c3b8d88a29
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/gitaly/gitaly_mtls_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.describe 'Create' do
+ context 'Gitaly', :orchestrated, :mtls do
+ describe 'Using mTLS' do
+ let(:intial_commit_message) { 'Initial commit' }
+ let(:first_added_commit_message) { 'commit over git' }
+ let(:second_added_commit_message) { 'commit over api' }
+
+ it 'pushes to gitaly', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1118' do
+ project = Resource::Project.fabricate! do |project|
+ project.name = "mTLS"
+ project.initialize_with_readme = true
+ end
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.new_branch = false
+ push.commit_message = first_added_commit_message
+ push.file_content = 'First commit'
+ end
+
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.project = project
+ commit.commit_message = second_added_commit_message
+ commit.add_files([
+ {
+ file_path: "file-#{SecureRandom.hex(8)}",
+ content: 'Second commit'
+ }
+ ])
+ end
+
+ expect(project.commits.map { |commit| commit[:message].chomp })
+ .to include(intial_commit_message)
+ .and include(first_added_commit_message)
+ .and include(second_added_commit_message)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/import/bulk_imports_controller_spec.rb b/spec/controllers/import/bulk_imports_controller_spec.rb
index 66825a81ac0..d1c138617bb 100644
--- a/spec/controllers/import/bulk_imports_controller_spec.rb
+++ b/spec/controllers/import/bulk_imports_controller_spec.rb
@@ -149,6 +149,22 @@ RSpec.describe Import::BulkImportsController do
end
end
+ describe 'GET realtime_changes' do
+ let_it_be(:bulk_import) { create(:bulk_import, :created, user: user) }
+
+ it 'returns bulk imports created by current user' do
+ get :realtime_changes
+
+ expect(json_response).to eq([{ 'id' => bulk_import.id, 'status_name' => bulk_import.status_name.to_s }])
+ end
+
+ it 'sets a Poll-Interval header' do
+ get :realtime_changes
+
+ expect(response.headers['Poll-Interval']).to eq(Import::BulkImportsController::POLLING_INTERVAL.to_s)
+ end
+ end
+
describe 'POST create' do
let(:instance_url) { "http://fake-intance" }
let(:pat) { "fake-pat" }
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index f870adbbdb6..8705c22c41a 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -259,7 +259,7 @@ RSpec.describe 'Dashboard Projects' do
# 4. ProjectsHelper#load_pipeline_status
# 5. RendersMemberAccess#preload_max_member_access_for_collection
# 6. User#max_member_access_for_project_ids
- # 7. CommitWithPipeline#last_pipeline
+ # 7. Ci::CommitWithPipeline#last_pipeline
expect { visit dashboard_projects_path }.not_to exceed_query_limit(control_count + 7)
end
diff --git a/spec/frontend/helpers/startup_css_helper_spec.js b/spec/frontend/helpers/startup_css_helper_spec.js
index 0484f3d6852..703bdbd342f 100644
--- a/spec/frontend/helpers/startup_css_helper_spec.js
+++ b/spec/frontend/helpers/startup_css_helper_spec.js
@@ -39,24 +39,7 @@ describe('waitForCSSLoaded', () => {
});
});
- describe('with startup css disabled', () => {
- gon.features = {
- startupCss: false,
- };
-
- it('should invoke the action right away', async () => {
- const events = waitForCSSLoaded(mockedCallback);
- await events;
-
- expect(mockedCallback).toHaveBeenCalledTimes(1);
- });
- });
-
describe('with startup css enabled', () => {
- gon.features = {
- startupCss: true,
- };
-
it('should dispatch CSSLoaded when the assets are cached or already loaded', async () => {
setFixtures(`
<link href="one.css" data-startupcss="loaded">
diff --git a/spec/frontend/merge_request/components/status_box_spec.js b/spec/frontend/merge_request/components/status_box_spec.js
new file mode 100644
index 00000000000..b1f0219643a
--- /dev/null
+++ b/spec/frontend/merge_request/components/status_box_spec.js
@@ -0,0 +1,99 @@
+import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import { GlSprintf } from '@gitlab/ui';
+import StatusBox from '~/merge_request/components/status_box.vue';
+import mrEventHub from '~/merge_request/eventhub';
+
+let wrapper;
+
+function factory(propsData) {
+ wrapper = shallowMount(StatusBox, { propsData, stubs: { GlSprintf } });
+}
+
+const testCases = [
+ {
+ name: 'Open',
+ state: 'opened',
+ class: 'status-box-open',
+ icon: 'issue-open-m',
+ },
+ {
+ name: 'Closed',
+ state: 'closed',
+ class: 'status-box-mr-closed',
+ icon: 'close',
+ },
+ {
+ name: 'Merged',
+ state: 'merged',
+ class: 'status-box-mr-merged',
+ icon: 'git-merge',
+ },
+];
+
+describe('Merge request status box component', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ testCases.forEach((testCase) => {
+ describe(`when merge request is ${testCase.name}`, () => {
+ it('renders human readable test', () => {
+ factory({
+ initialState: testCase.state,
+ initialIsReverted: false,
+ });
+
+ expect(wrapper.text()).toContain(testCase.name);
+ });
+
+ it('sets css class', () => {
+ factory({
+ initialState: testCase.state,
+ initialIsReverted: false,
+ });
+
+ expect(wrapper.classes()).toContain(testCase.class);
+ });
+
+ it('renders icon', () => {
+ factory({
+ initialState: testCase.state,
+ initialIsReverted: false,
+ });
+
+ expect(wrapper.find('[data-testid="status-icon"]').props('name')).toBe(testCase.icon);
+ });
+ });
+ });
+
+ describe('when merge request is reverted', () => {
+ it('renders a link to the reverted merge request', () => {
+ factory({
+ initialState: 'merged',
+ initialIsReverted: true,
+ initialRevertedPath: 'http://test.com',
+ });
+
+ expect(wrapper.find('[data-testid="reverted-link"]').attributes('href')).toBe(
+ 'http://test.com',
+ );
+ });
+ });
+
+ it('updates with eventhub event', async () => {
+ factory({
+ initialState: 'opened',
+ initialIsReverted: false,
+ });
+
+ expect(wrapper.text()).toContain('Open');
+
+ mrEventHub.$emit('mr.state.updated', { state: 'closed', reverted: false });
+
+ await nextTick();
+
+ expect(wrapper.text()).toContain('Closed');
+ });
+});
diff --git a/spec/helpers/ci/triggers_helper_spec.rb b/spec/helpers/ci/triggers_helper_spec.rb
new file mode 100644
index 00000000000..5e43dbfdd5c
--- /dev/null
+++ b/spec/helpers/ci/triggers_helper_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::TriggersHelper do
+ let(:project_id) { 1 }
+
+ describe '.builds_trigger_url' do
+ subject { helper.builds_trigger_url(project_id, ref: ref) }
+
+ context 'with no ref' do
+ let(:ref) { nil }
+
+ specify { expect(subject).to eq "#{Settings.gitlab.url}/api/v4/projects/1/trigger/pipeline" }
+ end
+
+ context 'with ref' do
+ let(:ref) { 'master' }
+
+ specify { expect(subject).to eq "#{Settings.gitlab.url}/api/v4/projects/1/ref/master/trigger/pipeline" }
+ end
+ end
+
+ describe '.service_trigger_url' do
+ subject { helper.service_trigger_url(service) }
+
+ let(:service) { double(project_id: 1, to_param: 'param') }
+
+ specify { expect(subject).to eq "#{Settings.gitlab.url}/api/v4/projects/1/services/param/trigger" }
+ end
+end
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index a994b4b92a6..b603325cdb8 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -224,4 +224,41 @@ RSpec.describe ExpandVariables do
end
end
end
+
+ describe '#possible_var_reference?' do
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty value": {
+ value: '',
+ result: false
+ },
+ "normal value": {
+ value: 'some value',
+ result: false
+ },
+ "simple expansions": {
+ value: 'key$variable',
+ result: true
+ },
+ "complex expansions": {
+ value: 'key${variable}${variable2}',
+ result: true
+ },
+ "complex expansions for Windows": {
+ value: 'key%variable%%variable2%',
+ result: true
+ }
+ }
+ end
+
+ with_them do
+ subject { ExpandVariables.possible_var_reference?(value) }
+
+ it { is_expected.to eq(result) }
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb b/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb
new file mode 100644
index 00000000000..8e74935e127
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_finding_uuid_for_vulnerability_feedback_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::PopulateFindingUuidForVulnerabilityFeedback, schema: 20201211090634 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:scanners) { table(:vulnerability_scanners) }
+ let(:identifiers) { table(:vulnerability_identifiers) }
+ let(:findings) { table(:vulnerability_occurrences) }
+ let(:vulnerability_feedback) { table(:vulnerability_feedback) }
+
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
+ let(:user) { users.create!(username: 'john.doe', projects_limit: 5) }
+ let(:scanner) { scanners.create!(project_id: project.id, external_id: 'foo', name: 'bar') }
+ let(:identifier) { identifiers.create!(project_id: project.id, fingerprint: 'foo', external_type: 'bar', external_id: 'zoo', name: 'baz') }
+ let(:sast_report) { 0 }
+ let(:dependency_scanning_report) { 1 }
+ let(:dast_report) { 3 }
+ let(:secret_detection_report) { 4 }
+ let(:project_fingerprint) { Digest::SHA1.hexdigest(SecureRandom.uuid) }
+ let(:location_fingerprint_1) { Digest::SHA1.hexdigest(SecureRandom.uuid) }
+ let(:location_fingerprint_2) { Digest::SHA1.hexdigest(SecureRandom.uuid) }
+ let(:location_fingerprint_3) { Digest::SHA1.hexdigest(SecureRandom.uuid) }
+ let(:finding_1) { finding_creator.call(sast_report, location_fingerprint_1) }
+ let(:finding_2) { finding_creator.call(dast_report, location_fingerprint_2) }
+ let(:finding_3) { finding_creator.call(secret_detection_report, location_fingerprint_3) }
+ let(:uuid_1_components) { ['sast', identifier.fingerprint, location_fingerprint_1, project.id].join('-') }
+ let(:uuid_2_components) { ['dast', identifier.fingerprint, location_fingerprint_2, project.id].join('-') }
+ let(:uuid_3_components) { ['secret_detection', identifier.fingerprint, location_fingerprint_3, project.id].join('-') }
+ let(:expected_uuid_1) { Gitlab::UUID.v5(uuid_1_components) }
+ let(:expected_uuid_2) { Gitlab::UUID.v5(uuid_2_components) }
+ let(:expected_uuid_3) { Gitlab::UUID.v5(uuid_3_components) }
+ let(:finding_creator) do
+ -> (report_type, location_fingerprint) do
+ findings.create!(
+ project_id: project.id,
+ primary_identifier_id: identifier.id,
+ scanner_id: scanner.id,
+ report_type: report_type,
+ uuid: SecureRandom.uuid,
+ name: 'Foo',
+ location_fingerprint: Gitlab::Database::ShaAttribute.serialize(location_fingerprint),
+ project_fingerprint: Gitlab::Database::ShaAttribute.serialize(project_fingerprint),
+ metadata_version: '1',
+ severity: 0,
+ confidence: 5,
+ raw_metadata: '{}'
+ )
+ end
+ end
+
+ let(:feedback_creator) do
+ -> (category, project_fingerprint) do
+ vulnerability_feedback.create!(
+ project_id: project.id,
+ author_id: user.id,
+ feedback_type: 0,
+ category: category,
+ project_fingerprint: project_fingerprint
+ )
+ end
+ end
+
+ let!(:feedback_1) { feedback_creator.call(finding_1.report_type, project_fingerprint) }
+ let!(:feedback_2) { feedback_creator.call(finding_2.report_type, project_fingerprint) }
+ let!(:feedback_3) { feedback_creator.call(finding_3.report_type, project_fingerprint) }
+ let!(:feedback_4) { feedback_creator.call(finding_1.report_type, 'foo') }
+ let!(:feedback_5) { feedback_creator.call(dependency_scanning_report, project_fingerprint) }
+
+ subject(:populate_finding_uuids) { described_class.new.perform(feedback_1.id, feedback_5.id) }
+
+ before do
+ allow(Gitlab::BackgroundMigration::Logger).to receive(:info)
+ end
+
+ describe '#perform' do
+ it 'updates the `finding_uuid` attributes of the feedback records' do
+ expect { populate_finding_uuids }.to change { feedback_1.reload.finding_uuid }.from(nil).to(expected_uuid_1)
+ .and change { feedback_2.reload.finding_uuid }.from(nil).to(expected_uuid_2)
+ .and change { feedback_3.reload.finding_uuid }.from(nil).to(expected_uuid_3)
+ .and not_change { feedback_4.reload.finding_uuid }
+ .and not_change { feedback_5.reload.finding_uuid }
+
+ expect(Gitlab::BackgroundMigration::Logger).to have_received(:info).once
+ end
+
+ it 'preloads the finding and identifier records to prevent N+1 queries' do
+ # Load feedback records(1), load findings(2), load identifiers(3) and finally update feedback records one by one(6)
+ expect { populate_finding_uuids }.not_to exceed_query_limit(6)
+ end
+
+ context 'when setting the `finding_uuid` attribute of a feedback record fails' do
+ let(:expected_error) { RuntimeError.new }
+
+ before do
+ allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
+
+ allow_next_found_instance_of(described_class::VulnerabilityFeedback) do |feedback|
+ allow(feedback).to receive(:update_column).and_raise(expected_error)
+ end
+ end
+
+ it 'captures the errors and does not crash entirely' do
+ expect { populate_finding_uuids }.not_to raise_error
+
+ expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception).with(expected_error).exactly(3).times
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb b/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb
new file mode 100644
index 00000000000..d85bf29f77f
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection/sorted_spec.rb
@@ -0,0 +1,251 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Variables::Collection::Sorted do
+ describe '#errors' do
+ context 'when FF :variable_inside_variable is disabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: false)
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: []
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ },
+ "complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'key${variable}' }
+ ]
+ },
+ "complex expansions with missing variable for Windows": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'key%variable%%variable2%' }
+ ]
+ },
+ "out-of-order variable reference": {
+ variables: [
+ { key: 'variable2', value: 'key${variable}' },
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "array with cyclic dependency": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ }
+ }
+ end
+
+ with_them do
+ subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables) }
+
+ it 'does not report error' do
+ expect(subject.errors).to eq(nil)
+ end
+
+ it 'valid? reports true' do
+ expect(subject.valid?).to eq(true)
+ end
+ end
+ end
+ end
+
+ context 'when FF :variable_inside_variable is enabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: true)
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: [],
+ validation_result: nil
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ validation_result: nil
+ },
+ "cyclic dependency": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ validation_result: 'circular variable reference detected: ["variable", "variable2", "variable3"]'
+ }
+ }
+ end
+
+ with_them do
+ subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables) }
+
+ it 'errors matches expected validation result' do
+ expect(subject.errors).to eq(validation_result)
+ end
+
+ it 'valid? matches expected validation result' do
+ expect(subject.valid?).to eq(validation_result.nil?)
+ end
+ end
+ end
+ end
+ end
+
+ describe '#sort' do
+ context 'when FF :variable_inside_variable is disabled' do
+ before do
+ stub_feature_flags(variable_inside_variable: false)
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: []
+ },
+ "simple expansions": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ },
+ "complex expansion": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'key${variable}' }
+ ]
+ },
+ "complex expansions with missing variable for Windows": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'key%variable%%variable2%' }
+ ]
+ },
+ "out-of-order variable reference": {
+ variables: [
+ { key: 'variable2', value: 'key${variable}' },
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "array with cyclic dependency": {
+ variables: [
+ { key: 'variable', value: '$variable2' },
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ]
+ }
+ }
+ end
+
+ with_them do
+ subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables) }
+
+ it 'does not expand variables' do
+ expect(subject.sort).to eq(variables)
+ end
+ end
+ end
+ end
+
+ context 'when FF :variable_inside_variable is enabled' do
+ before do
+ stub_licensed_features(group_saml_group_sync: true)
+ stub_feature_flags(saml_group_links: true)
+ stub_feature_flags(variable_inside_variable: true)
+ end
+
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "empty array": {
+ variables: [],
+ result: []
+ },
+ "simple expansions, no reordering needed": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' },
+ { key: 'variable3', value: 'key$variable$variable2' }
+ ],
+ result: %w[variable variable2 variable3]
+ },
+ "complex expansion, reordering needed": {
+ variables: [
+ { key: 'variable2', value: 'key${variable}' },
+ { key: 'variable', value: 'value' }
+ ],
+ result: %w[variable variable2]
+ },
+ "unused variables": {
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable4', value: 'key$variable$variable3' },
+ { key: 'variable2', value: 'result2' },
+ { key: 'variable3', value: 'result3' }
+ ],
+ result: %w[variable variable3 variable4 variable2]
+ },
+ "missing variable": {
+ variables: [
+ { key: 'variable2', value: 'key$variable' }
+ ],
+ result: %w[variable2]
+ },
+ "complex expansions with missing variable": {
+ variables: [
+ { key: 'variable4', value: 'key${variable}${variable2}${variable3}' },
+ { key: 'variable', value: 'value' },
+ { key: 'variable3', value: 'value3' }
+ ],
+ result: %w[variable variable3 variable4]
+ },
+ "cyclic dependency causes original array to be returned": {
+ variables: [
+ { key: 'variable2', value: '$variable3' },
+ { key: 'variable3', value: 'key$variable$variable2' },
+ { key: 'variable', value: '$variable2' }
+ ],
+ result: %w[variable2 variable3 variable]
+ }
+ }
+ end
+
+ with_them do
+ subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables) }
+
+ it 'sort returns correctly sorted variables' do
+ expect(subject.sort.map { |var| var[:key] }).to eq(result)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb
index 4cee7a86411..777c94035d4 100644
--- a/spec/lib/gitlab/git_access_snippet_spec.rb
+++ b/spec/lib/gitlab/git_access_snippet_spec.rb
@@ -29,8 +29,17 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:actor) { build(:deploy_key) }
it 'does not allow push and pull access' do
- expect { push_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:authentication_mechanism])
- expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:authentication_mechanism])
+ expect { push_access_check }.to raise_forbidden(:authentication_mechanism)
+ expect { pull_access_check }.to raise_forbidden(:authentication_mechanism)
+ end
+ end
+
+ describe 'when snippet repository is read-only' do
+ it 'does not allow push and allows pull access' do
+ allow(snippet).to receive(:repository_read_only?).and_return(true)
+
+ expect { push_access_check }.to raise_forbidden(:read_only)
+ expect { pull_access_check }.not_to raise_error
end
end
@@ -58,7 +67,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:snippet) { nil }
it 'blocks access with "not found"' do
- expect { pull_access_check }.to raise_snippet_not_found
+ expect { pull_access_check }.to raise_not_found(:snippet_not_found)
end
end
@@ -66,7 +75,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:snippet) { build_stubbed(:personal_snippet) }
it 'blocks access with "not found"' do
- expect { pull_access_check }.to raise_snippet_not_found
+ expect { pull_access_check }.to raise_not_found(:no_repo)
end
end
end
@@ -81,8 +90,8 @@ RSpec.describe Gitlab::GitAccessSnippet do
it 'blocks access when the user did not accept terms' do
message = /must accept the Terms of Service in order to perform this action/
- expect { push_access_check }.to raise_forbidden(message)
- expect { pull_access_check }.to raise_forbidden(message)
+ expect { push_access_check }.to raise_forbidden_with_message(message)
+ expect { pull_access_check }.to raise_forbidden_with_message(message)
end
it 'allows access when the user accepted the terms' do
@@ -149,8 +158,8 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:membership) { membership }
it 'respects accessibility' do
- expect { push_access_check }.to raise_snippet_not_found
- expect { pull_access_check }.to raise_snippet_not_found
+ expect { push_access_check }.to raise_not_found(:project_not_found)
+ expect { pull_access_check }.to raise_not_found(:project_not_found)
end
end
end
@@ -273,7 +282,7 @@ RSpec.describe Gitlab::GitAccessSnippet do
allow(check).to receive(:validate!).and_raise(Gitlab::GitAccess::ForbiddenError, 'foo')
end
- expect { push_access_check }.to raise_forbidden('foo')
+ expect { push_access_check }.to raise_forbidden_with_message('foo')
end
it 'sets the file count limit from Snippet class' do
@@ -424,15 +433,15 @@ RSpec.describe Gitlab::GitAccessSnippet do
private
- def raise_snippet_not_found
- raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:snippet_not_found])
+ def raise_not_found(message_key)
+ raise_error(described_class::NotFoundError, described_class.error_message(message_key))
end
- def raise_project_not_found
- raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found])
+ def raise_forbidden(message_key)
+ raise_error(Gitlab::GitAccess::ForbiddenError, described_class.error_message(message_key))
end
- def raise_forbidden(message)
+ def raise_forbidden_with_message(message)
raise_error(Gitlab::GitAccess::ForbiddenError, message)
end
end
diff --git a/spec/migrations/schedule_populate_finding_uuid_for_vulnerability_feedback_spec.rb b/spec/migrations/schedule_populate_finding_uuid_for_vulnerability_feedback_spec.rb
new file mode 100644
index 00000000000..d8bdefd5546
--- /dev/null
+++ b/spec/migrations/schedule_populate_finding_uuid_for_vulnerability_feedback_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe SchedulePopulateFindingUuidForVulnerabilityFeedback do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+ let(:vulnerability_feedback) { table(:vulnerability_feedback) }
+
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') }
+ let(:user) { users.create!(username: 'john.doe', projects_limit: 1) }
+
+ let(:common_feedback_params) { { feedback_type: 0, category: 0, project_id: project.id, author_id: user.id } }
+ let!(:feedback_1) { vulnerability_feedback.create!(**common_feedback_params, project_fingerprint: 'foo') }
+ let!(:feedback_2) { vulnerability_feedback.create!(**common_feedback_params, project_fingerprint: 'bar') }
+ let!(:feedback_3) { vulnerability_feedback.create!(**common_feedback_params, project_fingerprint: 'zoo', finding_uuid: SecureRandom.uuid) }
+
+ around do |example|
+ freeze_time { Sidekiq::Testing.fake! { example.run } }
+ end
+
+ before do
+ stub_const("#{described_class.name}::BATCH_SIZE", 1)
+ end
+
+ it 'schedules the background jobs', :aggregate_failures do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to be(3)
+ expect(described_class::MIGRATION_CLASS).to be_scheduled_delayed_migration(2.minutes, feedback_1.id, feedback_1.id)
+ expect(described_class::MIGRATION_CLASS).to be_scheduled_delayed_migration(4.minutes, feedback_2.id, feedback_2.id)
+ expect(described_class::MIGRATION_CLASS).to be_scheduled_delayed_migration(6.minutes, feedback_3.id, feedback_3.id)
+ end
+end
diff --git a/spec/models/commit_with_pipeline_spec.rb b/spec/models/ci/commit_with_pipeline_spec.rb
index c4b6deebae0..4dd288bde62 100644
--- a/spec/models/commit_with_pipeline_spec.rb
+++ b/spec/models/ci/commit_with_pipeline_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe CommitWithPipeline do
+RSpec.describe Ci::CommitWithPipeline do
let(:project) { create(:project, :public, :repository) }
let(:commit) { described_class.new(project.commit) }
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index d042b318d02..4f47a22b07c 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -155,6 +155,12 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do
expect(Todo.where(attributes).count).to eq 1
end
+
+ it 'invalidates counter cache for reviewers', :use_clean_rails_memory_store_caching do
+ expect { merge_request }
+ .to change { user2.review_requested_open_merge_requests_count }
+ .by(1)
+ end
end
context 'when head pipelines already exist for merge request source branch', :sidekiq_inline do
diff --git a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb
index 7292b6793f8..28decb4011d 100644
--- a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb
@@ -63,6 +63,16 @@ RSpec.shared_examples 'rebase quick action' do
expect(page).not_to have_content 'Scheduled a rebase'
end
end
+
+ context 'when there are conflicts in the merge request' do
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, source_branch: 'conflict-missing-side', target_branch: 'conflict-start', merge_status: :cannot_be_merged) }
+
+ it 'does not rebase the MR' do
+ add_note("/rebase")
+
+ expect(page).to have_content 'This merge request cannot be rebased while there are conflicts.'
+ end
+ end
end
context 'when the current user cannot rebase the MR' do
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index d8748873f64..15fdfaaaa65 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe 'layouts/_head' do
render
- expect(rendered).to match('<link rel="stylesheet" media="all" href="/stylesheets/highlight/themes/solarised-light.css" />')
+ expect(rendered).to match('<link rel="stylesheet" media="print" href="/stylesheets/highlight/themes/solarised-light.css" />')
end
context 'when an asset_host is set and snowplow url is set' do
diff --git a/yarn.lock b/yarn.lock
index df422680d02..9e2fbe129f3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -876,10 +876,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
-"@gitlab/ui@25.7.2":
- version "25.7.2"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.7.2.tgz#50271f99818645f9bc7ef675c08f9bf1bae54293"
- integrity sha512-hgM9JHesIqnDujjJ16BHFQWYwzWd9php8xElhkGUdKWGV+PVw9TVdYcKfkIAb3TXtb9551uXN0Jv6wH5DRJyXw==
+"@gitlab/ui@25.7.3":
+ version "25.7.3"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-25.7.3.tgz#abc5caa585c307f38e01a7f061d9da0bc238acd0"
+ integrity sha512-Ia+Pqmy4tQ5oztxXSjViM6YMt13MGyrQ6dqOn3zsiKG9RLh9kSBMtCif9jA0dDT17P+HMIdBYxYc6rTRjbfC0w==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"