Commit 91604006 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch 'allow_skipped' into 'master'

Add setting to allow merge on skipped pipeline

Closes #211482

See merge request gitlab-org/gitlab!27490
parents 8e7db68e f1831cf1
...@@ -362,6 +362,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -362,6 +362,7 @@ class ProjectsController < Projects::ApplicationController
def project_params_attributes def project_params_attributes
[ [
:allow_merge_on_skipped_pipeline,
:avatar, :avatar,
:build_allow_git_fetch, :build_allow_git_fetch,
:build_coverage_regex, :build_coverage_regex,
......
...@@ -95,6 +95,8 @@ module Types ...@@ -95,6 +95,8 @@ module Types
description: 'Status of Jira import background job of the project' description: 'Status of Jira import background job of the project'
field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true, field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if merge requests of the project can only be merged with successful jobs' description: 'Indicates if merge requests of the project can only be merged with successful jobs'
field :allow_merge_on_skipped_pipeline, GraphQL::BOOLEAN_TYPE, null: true,
description: 'If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs'
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true, field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if users can request member access to the project' description: 'Indicates if users can request member access to the project'
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true, field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true,
......
...@@ -1169,6 +1169,7 @@ class MergeRequest < ApplicationRecord ...@@ -1169,6 +1169,7 @@ class MergeRequest < ApplicationRecord
def mergeable_ci_state? def mergeable_ci_state?
return true unless project.only_allow_merge_if_pipeline_succeeds? return true unless project.only_allow_merge_if_pipeline_succeeds?
return false unless actual_head_pipeline return false unless actual_head_pipeline
return true if project.allow_merge_on_skipped_pipeline? && actual_head_pipeline.skipped?
actual_head_pipeline.success? actual_head_pipeline.success?
end end
......
...@@ -198,7 +198,7 @@ class Project < ApplicationRecord ...@@ -198,7 +198,7 @@ class Project < ApplicationRecord
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting' has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting' has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
has_one :grafana_integration, inverse_of: :project has_one :grafana_integration, inverse_of: :project
has_one :project_setting, ->(project) { where_or_create_by(project: project) }, inverse_of: :project has_one :project_setting, inverse_of: :project, autosave: true
has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting' has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
# Merge Requests for target project should be removed with it # Merge Requests for target project should be removed with it
...@@ -376,6 +376,7 @@ class Project < ApplicationRecord ...@@ -376,6 +376,7 @@ class Project < ApplicationRecord
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci
delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?, :allow_merge_on_skipped_pipeline=, to: :project_setting
# Validations # Validations
validates :creator, presence: true, on: :create validates :creator, presence: true, on: :create
...@@ -727,6 +728,10 @@ class Project < ApplicationRecord ...@@ -727,6 +728,10 @@ class Project < ApplicationRecord
super super
end end
def project_setting
super.presence || build_project_setting
end
def all_pipelines def all_pipelines
if builds_enabled? if builds_enabled?
super super
......
...@@ -4,10 +4,6 @@ class ProjectSetting < ApplicationRecord ...@@ -4,10 +4,6 @@ class ProjectSetting < ApplicationRecord
belongs_to :project, inverse_of: :project_setting belongs_to :project, inverse_of: :project_setting
self.primary_key = :project_id self.primary_key = :project_id
def self.where_or_create_by(attrs)
where(primary_key => safe_find_or_create_by(attrs))
end
end end
ProjectSetting.prepend_if_ee('EE::ProjectSetting') ProjectSetting.prepend_if_ee('EE::ProjectSetting')
...@@ -13,6 +13,13 @@ ...@@ -13,6 +13,13 @@
help_page_path('ci/merge_request_pipelines/index.md', help_page_path('ci/merge_request_pipelines/index.md',
anchor: 'pipelines-for-merge-requests'), anchor: 'pipelines-for-merge-requests'),
target: '_blank' target: '_blank'
.form-check.mb-2
.gl-pl-6
= form.check_box :allow_merge_on_skipped_pipeline, class: 'form-check-input'
= form.label :allow_merge_on_skipped_pipeline, class: 'form-check-label' do
= s_('ProjectSettings|Skipped pipelines are considered successful')
.text-secondary
= s_('ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline.')
.form-check.mb-2 .form-check.mb-2
= form.check_box :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-input' = form.check_box :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-input'
= form.label :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-label' do = form.label :only_allow_merge_if_all_discussions_are_resolved, class: 'form-check-label' do
......
---
title: Add setting to allow merge on skipped pipeline
merge_request: 27490
author: Mathieu Parent
type: added
# frozen_string_literal: true
class AddAllowMergeOnSkippedPipelineToProjectSettings < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :project_settings, :allow_merge_on_skipped_pipeline, :boolean
end
end
...@@ -5340,6 +5340,7 @@ CREATE TABLE public.project_settings ( ...@@ -5340,6 +5340,7 @@ CREATE TABLE public.project_settings (
updated_at timestamp with time zone NOT NULL, updated_at timestamp with time zone NOT NULL,
push_rule_id bigint, push_rule_id bigint,
show_default_award_emojis boolean DEFAULT true, show_default_award_emojis boolean DEFAULT true,
allow_merge_on_skipped_pipeline boolean,
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)) CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL))
); );
...@@ -13613,6 +13614,7 @@ COPY "schema_migrations" (version) FROM STDIN; ...@@ -13613,6 +13614,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200323134519 20200323134519
20200324093258 20200324093258
20200324115359 20200324115359
20200325094612
20200325104755 20200325104755
20200325104756 20200325104756
20200325104833 20200325104833
......
...@@ -8265,6 +8265,12 @@ type Project { ...@@ -8265,6 +8265,12 @@ type Project {
statuses: [AlertManagementStatus!] statuses: [AlertManagementStatus!]
): AlertManagementAlertConnection ): AlertManagementAlertConnection
"""
If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge
requests of the project can also be merged with skipped jobs
"""
allowMergeOnSkippedPipeline: Boolean
""" """
Indicates the archived status of the project Indicates the archived status of the project
""" """
......
...@@ -24673,6 +24673,20 @@ ...@@ -24673,6 +24673,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "allowMergeOnSkippedPipeline",
"description": "If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "archived", "name": "archived",
"description": "Indicates the archived status of the project", "description": "Indicates the archived status of the project",
...@@ -1252,6 +1252,7 @@ Information about pagination in a connection. ...@@ -1252,6 +1252,7 @@ Information about pagination in a connection.
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `alertManagementAlert` | AlertManagementAlert | A single Alert Management alert of the project | | `alertManagementAlert` | AlertManagementAlert | A single Alert Management alert of the project |
| `alertManagementAlertStatusCounts` | AlertManagementAlertStatusCountsType | Counts of alerts by status for the project | | `alertManagementAlertStatusCounts` | AlertManagementAlertStatusCountsType | Counts of alerts by status for the project |
| `allowMergeOnSkippedPipeline` | Boolean | If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs |
| `archived` | Boolean | Indicates the archived status of the project | | `archived` | Boolean | Indicates the archived status of the project |
| `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically | | `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically |
| `avatarUrl` | String | URL to avatar image file of the project | | `avatarUrl` | String | URL to avatar image file of the project |
......
...@@ -158,6 +158,7 @@ When the user is authenticated and `simple` is not set this returns something li ...@@ -158,6 +158,7 @@ When the user is authenticated and `simple` is not set this returns something li
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -248,6 +249,7 @@ When the user is authenticated and `simple` is not set this returns something li ...@@ -248,6 +249,7 @@ When the user is authenticated and `simple` is not set this returns something li
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -407,6 +409,7 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo ...@@ -407,6 +409,7 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -497,6 +500,7 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo ...@@ -497,6 +500,7 @@ This endpoint supports [keyset pagination](README.md#keyset-based-pagination) fo
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -623,6 +627,7 @@ Example response: ...@@ -623,6 +627,7 @@ Example response:
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -708,6 +713,7 @@ Example response: ...@@ -708,6 +713,7 @@ Example response:
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -861,6 +867,7 @@ GET /projects/:id ...@@ -861,6 +867,7 @@ GET /projects/:id
], ],
"repository_storage": "default", "repository_storage": "default",
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"printing_merge_requests_link_enabled": true, "printing_merge_requests_link_enabled": true,
...@@ -1044,6 +1051,7 @@ POST /projects ...@@ -1044,6 +1051,7 @@ POST /projects
| `import_url` | string | no | URL to import repository from | | `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members | | `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs | | `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `allow_merge_on_skipped_pipeline` | boolean | no | Set whether or not merge requests can be merged with skipped jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `merge_method` | string | no | Set the [merge method](#project-merge-method) used | | `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `autoclose_referenced_issues` | boolean | no | Set whether auto-closing referenced issues on default branch | | `autoclose_referenced_issues` | boolean | no | Set whether auto-closing referenced issues on default branch |
...@@ -1113,6 +1121,7 @@ POST /projects/user/:user_id ...@@ -1113,6 +1121,7 @@ POST /projects/user/:user_id
| `import_url` | string | no | URL to import repository from | | `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members | | `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs | | `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `allow_merge_on_skipped_pipeline` | boolean | no | Set whether or not merge requests can be merged with skipped jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `merge_method` | string | no | Set the [merge method](#project-merge-method) used | | `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `autoclose_referenced_issues` | boolean | no | Set whether auto-closing referenced issues on default branch | | `autoclose_referenced_issues` | boolean | no | Set whether auto-closing referenced issues on default branch |
...@@ -1183,6 +1192,7 @@ PUT /projects/:id ...@@ -1183,6 +1192,7 @@ PUT /projects/:id
| `import_url` | string | no | URL to import repository from | | `import_url` | string | no | URL to import repository from |
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members | | `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs | | `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `allow_merge_on_skipped_pipeline` | boolean | no | Set whether or not merge requests can be merged with skipped jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved | | `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `merge_method` | string | no | Set the [merge method](#project-merge-method) used | | `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `autoclose_referenced_issues` | boolean | no | Set whether auto-closing referenced issues on default branch | | `autoclose_referenced_issues` | boolean | no | Set whether auto-closing referenced issues on default branch |
...@@ -1317,6 +1327,7 @@ Example responses: ...@@ -1317,6 +1327,7 @@ Example responses:
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -1408,6 +1419,7 @@ Example response: ...@@ -1408,6 +1419,7 @@ Example response:
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -1498,6 +1510,7 @@ Example response: ...@@ -1498,6 +1510,7 @@ Example response:
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -1680,6 +1693,7 @@ Example response: ...@@ -1680,6 +1693,7 @@ Example response:
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
...@@ -1789,6 +1803,7 @@ Example response: ...@@ -1789,6 +1803,7 @@ Example response:
"public_jobs": true, "public_jobs": true,
"shared_with_groups": [], "shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false, "only_allow_merge_if_pipeline_succeeds": false,
"allow_merge_on_skipped_pipeline": false,
"only_allow_merge_if_all_discussions_are_resolved": false, "only_allow_merge_if_all_discussions_are_resolved": false,
"remove_source_branch_after_merge": false, "remove_source_branch_after_merge": false,
"request_access_enabled": false, "request_access_enabled": false,
......
...@@ -59,6 +59,19 @@ merge request from the UI, until you make all relevant jobs pass. ...@@ -59,6 +59,19 @@ merge request from the UI, until you make all relevant jobs pass.
![Only allow merge if pipeline succeeds message](img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png) ![Only allow merge if pipeline succeeds message](img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png)
### Skipped pipelines
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211482) in GitLab 13.1.
When the **Pipelines must succeed** checkbox is checked, [skipped pipelines](../../../ci/yaml/README.md#skip-pipeline) prevent
merge requests from being merged. To change this behavior:
1. Navigate to your project's **Settings > General** page.
1. Expand the **Merge requests** section.
1. In the **Merge checks** subsection, ensure **Pipelines must succeed** is checked.
1. In the **Merge checks** subsection, select the **Skipped pipelines are considered successful** checkbox.
1. Press **Save** for the changes to take effect.
### Limitations ### Limitations
When this setting is enabled, a merge request is prevented from being merged if there is no pipeline. This may conflict with some use cases where [`only/except`](../../../ci/yaml/README.md#onlyexcept-advanced) rules are used and they don't generate any pipelines. When this setting is enabled, a merge request is prevented from being merged if there is no pipeline. This may conflict with some use cases where [`only/except`](../../../ci/yaml/README.md#onlyexcept-advanced) rules are used and they don't generate any pipelines.
......
...@@ -93,6 +93,7 @@ module API ...@@ -93,6 +93,7 @@ module API
SharedGroupWithProject.represent(project.project_group_links, options) SharedGroupWithProject.represent(project.project_group_links, options)
end end
expose :only_allow_merge_if_pipeline_succeeds expose :only_allow_merge_if_pipeline_succeeds
expose :allow_merge_on_skipped_pipeline
expose :request_access_enabled expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved expose :only_allow_merge_if_all_discussions_are_resolved
expose :remove_source_branch_after_merge expose :remove_source_branch_after_merge
...@@ -119,6 +120,7 @@ module API ...@@ -119,6 +120,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555 # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555
super(projects_relation).preload(:group) super(projects_relation).preload(:group)
.preload(:ci_cd_settings) .preload(:ci_cd_settings)
.preload(:project_setting)
.preload(:container_expiration_policy) .preload(:container_expiration_policy)
.preload(:auto_devops) .preload(:auto_devops)
.preload(project_group_links: { group: :route }, .preload(project_group_links: { group: :route },
......
...@@ -44,6 +44,7 @@ module API ...@@ -44,6 +44,7 @@ module API
optional :public_builds, type: Boolean, desc: 'Perform public builds' optional :public_builds, type: Boolean, desc: 'Perform public builds'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
optional :allow_merge_on_skipped_pipeline, type: Boolean, desc: 'Allow to merge if pipeline is skipped'
optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved'
optional :tag_list, type: Array[String], desc: 'The list of tags for a project' optional :tag_list, type: Array[String], desc: 'The list of tags for a project'
# TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
...@@ -92,6 +93,7 @@ module API ...@@ -92,6 +93,7 @@ module API
def self.update_params_at_least_one_of def self.update_params_at_least_one_of
[ [
:allow_merge_on_skipped_pipeline,
:autoclose_referenced_issues, :autoclose_referenced_issues,
:auto_devops_enabled, :auto_devops_enabled,
:auto_devops_deploy_strategy, :auto_devops_deploy_strategy,
......
...@@ -17341,6 +17341,9 @@ msgstr "" ...@@ -17341,6 +17341,9 @@ msgstr ""
msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line" msgid "ProjectSettings|Show link to create/view merge request when pushing from the command line"
msgstr "" msgstr ""
msgid "ProjectSettings|Skipped pipelines are considered successful"
msgstr ""
msgid "ProjectSettings|Snippets" msgid "ProjectSettings|Snippets"
msgstr "" msgstr ""
...@@ -17356,6 +17359,9 @@ msgstr "" ...@@ -17356,6 +17359,9 @@ msgstr ""
msgid "ProjectSettings|These checks must pass before merge requests can be merged" msgid "ProjectSettings|These checks must pass before merge requests can be merged"
msgstr "" msgstr ""
msgid "ProjectSettings|This introduces the risk of merging changes that will not pass the pipeline."
msgstr ""
msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin."
msgstr "" msgstr ""
......
...@@ -424,6 +424,7 @@ project: ...@@ -424,6 +424,7 @@ project:
- deploy_tokens - deploy_tokens
- settings - settings
- ci_cd_settings - ci_cd_settings
- project_settings
- import_export_upload - import_export_upload
- repository_languages - repository_languages
- pool_repository - pool_repository
......
...@@ -703,6 +703,8 @@ Badge: ...@@ -703,6 +703,8 @@ Badge:
- type - type
ProjectCiCdSetting: ProjectCiCdSetting:
- group_runners_enabled - group_runners_enabled
ProjectSetting:
- allow_merge_on_skipped_pipeline
ProtectedEnvironment: ProtectedEnvironment:
- id - id
- project_id - project_id
......
...@@ -2517,12 +2517,13 @@ describe MergeRequest do ...@@ -2517,12 +2517,13 @@ describe MergeRequest do
end end
describe '#mergeable_ci_state?' do describe '#mergeable_ci_state?' do
let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
let(:pipeline) { create(:ci_empty_pipeline) } let(:pipeline) { create(:ci_empty_pipeline) }
context 'when it is only allowed to merge when build is green' do
let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) }
subject { build(:merge_request, target_project: project) } subject { build(:merge_request, target_project: project) }
context 'when it is only allowed to merge when build is green' do
context 'and a failed pipeline is associated' do context 'and a failed pipeline is associated' do
before do before do
pipeline.update(status: 'failed', sha: subject.diff_head_sha) pipeline.update(status: 'failed', sha: subject.diff_head_sha)
...@@ -2544,7 +2545,7 @@ describe MergeRequest do ...@@ -2544,7 +2545,7 @@ describe MergeRequest do
context 'and a skipped pipeline is associated' do context 'and a skipped pipeline is associated' do
before do before do
pipeline.update(status: 'skipped', sha: subject.diff_head_sha) pipeline.update(status: 'skipped', sha: subject.diff_head_sha)
allow(subject).to receive(:head_pipeline) { pipeline } allow(subject).to receive(:head_pipeline).and_return(pipeline)
end end
it { expect(subject.mergeable_ci_state?).to be_falsey } it { expect(subject.mergeable_ci_state?).to be_falsey }
...@@ -2552,7 +2553,48 @@ describe MergeRequest do ...@@ -2552,7 +2553,48 @@ describe MergeRequest do
context 'when no pipeline is associated' do context 'when no pipeline is associated' do
before do before do
allow(subject).to receive(:head_pipeline) { nil } allow(subject).to receive(:head_pipeline).and_return(nil)
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
end
context 'when it is only allowed to merge when build is green or skipped' do
let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true, allow_merge_on_skipped_pipeline: true) }
subject { build(:merge_request, target_project: project) }
context 'and a failed pipeline is associated' do
before do
pipeline.update!(status: 'failed', sha: subject.diff_head_sha)
allow(subject).to receive(:head_pipeline).and_return(pipeline)
end
it { expect(subject.mergeable_ci_state?).to be_falsey }
end
context 'and a successful pipeline is associated' do
before do
pipeline.update!(status: 'success', sha: subject.diff_head_sha)
allow(subject).to receive(:head_pipeline).and_return(pipeline)
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'and a skipped pipeline is associated' do
before do
pipeline.update!(status: 'skipped', sha: subject.diff_head_sha)
allow(subject).to receive(:head_pipeline).and_return(pipeline)
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'when no pipeline is associated' do
before do
allow(subject).to receive(:head_pipeline).and_return(nil)
end end
it { expect(subject.mergeable_ci_state?).to be_falsey } it { expect(subject.mergeable_ci_state?).to be_falsey }
...@@ -2560,7 +2602,9 @@ describe MergeRequest do ...@@ -2560,7 +2602,9 @@ describe MergeRequest do
end end
context 'when merges are not restricted to green builds' do context 'when merges are not restricted to green builds' do
subject { build(:merge_request, target_project: create(:project, only_allow_merge_if_pipeline_succeeds: false)) } let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: false) }
subject { build(:merge_request, target_project: project) }
context 'and a failed pipeline is associated' do context 'and a failed pipeline is associated' do
before do before do
...@@ -2578,6 +2622,23 @@ describe MergeRequest do ...@@ -2578,6 +2622,23 @@ describe MergeRequest do
it { expect(subject.mergeable_ci_state?).to be_truthy } it { expect(subject.mergeable_ci_state?).to be_truthy }
end end
context 'and a skipped pipeline is associated' do
before do
pipeline.update!(status: 'skipped', sha: subject.diff_head_sha)
allow(subject).to receive(:head_pipeline).and_return(pipeline)
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
context 'when no pipeline is associated' do
before do
allow(subject).to receive(:head_pipeline).and_return(nil)
end
it { expect(subject.mergeable_ci_state?).to be_truthy }
end
end end
end end
......
...@@ -183,9 +183,9 @@ describe Project do ...@@ -183,9 +183,9 @@ describe Project do
expect(project.pages_metadatum).to be_persisted expect(project.pages_metadatum).to be_persisted
end end
it 'automatically creates a project setting row' do it 'automatically builds a project setting row' do
expect(project.project_setting).to be_an_instance_of(ProjectSetting) expect(project.project_setting).to be_an_instance_of(ProjectSetting)
expect(project.project_setting).to be_persisted expect(project.project_setting).to be_new_record
end end
end end
......
...@@ -764,7 +764,8 @@ describe API::Projects do ...@@ -764,7 +764,8 @@ describe API::Projects do
resolve_outdated_diff_discussions: false, resolve_outdated_diff_discussions: false,
remove_source_branch_after_merge: true, remove_source_branch_after_merge: true,
autoclose_referenced_issues: true, autoclose_referenced_issues: true,
only_allow_merge_if_pipeline_succeeds: false, only_allow_merge_if_pipeline_succeeds: true,
allow_merge_on_skipped_pipeline: true,
request_access_enabled: true, request_access_enabled: true,
only_allow_merge_if_all_discussions_are_resolved: false, only_allow_merge_if_all_discussions_are_resolved: false,
ci_config_path: 'a/custom/path', ci_config_path: 'a/custom/path',
...@@ -912,6 +913,22 @@ describe API::Projects do ...@@ -912,6 +913,22 @@ describe API::Projects do
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
end end
it 'sets a project as not allowing merge when pipeline is skipped' do
project_params = attributes_for(:project, allow_merge_on_skipped_pipeline: false)
post api('/projects', user), params: project_params
expect(json_response['allow_merge_on_skipped_pipeline']).to be_falsey
end
it 'sets a project as allowing merge when pipeline is skipped' do
project_params = attributes_for(:project, allow_merge_on_skipped_pipeline: true)
post api('/projects', user), params: project_params
expect(json_response['allow_merge_on_skipped_pipeline']).to be_truthy
end
it 'sets a project as allowing merge even if discussions are unresolved' do it 'sets a project as allowing merge even if discussions are unresolved' do
project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false) project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
...@@ -1245,16 +1262,36 @@ describe API::Projects do ...@@ -1245,16 +1262,36 @@ describe API::Projects do
it 'sets a project as allowing merge even if build fails' do it 'sets a project as allowing merge even if build fails' do
project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false) project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
post api("/projects/user/#{user.id}", admin), params: project post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
end end
it 'sets a project as allowing merge only if pipeline succeeds' do it 'sets a project as allowing merge only if pipeline succeeds' do
project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true) project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
post api("/projects/user/#{user.id}", admin), params: project post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
end end
it 'sets a project as not allowing merge when pipeline is skipped' do
project = attributes_for(:project, allow_merge_on_skipped_pipeline: false)
post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['allow_merge_on_skipped_pipeline']).to be_falsey
end
it 'sets a project as allowing merge when pipeline is skipped' do
project = attributes_for(:project, allow_merge_on_skipped_pipeline: true)
post api("/projects/user/#{user.id}", admin), params: project
expect(json_response['allow_merge_on_skipped_pipeline']).to be_truthy
end
it 'sets a project as allowing merge even if discussions are unresolved' do it 'sets a project as allowing merge even if discussions are unresolved' do
project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false) project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
...@@ -1394,6 +1431,7 @@ describe API::Projects do ...@@ -1394,6 +1431,7 @@ describe API::Projects do
expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
expect(json_response['allow_merge_on_skipped_pipeline']).to eq(project.allow_merge_on_skipped_pipeline)
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
end end
end end
...@@ -1461,6 +1499,7 @@ describe API::Projects do ...@@ -1461,6 +1499,7 @@ describe API::Projects do
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]).to have_key('expires_at') expect(json_response['shared_with_groups'][0]).to have_key('expires_at')
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
expect(json_response['allow_merge_on_skipped_pipeline']).to eq(project.allow_merge_on_skipped_pipeline)
expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
expect(json_response['ci_default_git_depth']).to eq(project.ci_default_git_depth) expect(json_response['ci_default_git_depth']).to eq(project.ci_default_git_depth)
expect(json_response['merge_method']).to eq(project.merge_method.to_s) expect(json_response['merge_method']).to eq(project.merge_method.to_s)
......
...@@ -43,10 +43,10 @@ describe Projects::CreateService, '#execute' do ...@@ -43,10 +43,10 @@ describe Projects::CreateService, '#execute' do
create_project(user, opts) create_project(user, opts)
end end
it 'creates associated project settings' do it 'builds associated project settings' do
project = create_project(user, opts) project = create_project(user, opts)
expect(project.project_setting).to be_persisted expect(project.project_setting).to be_new_record
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment