diff options
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 @@ -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" |