Commit 1e3a3ddf authored by Nourdin el Bacha's avatar Nourdin el Bacha

Adds API feature configuring default mr target

Closes https://gitlab.com/gitlab-org/gitlab/-/issues/343909

Changelog: added
parent 69a0cc02
...@@ -436,6 +436,7 @@ class Project < ApplicationRecord ...@@ -436,6 +436,7 @@ class Project < ApplicationRecord
prefix: :import, to: :import_state, allow_nil: true prefix: :import, to: :import_state, allow_nil: true
delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting
delegate :squash_option, :squash_option=, to: :project_setting delegate :squash_option, :squash_option=, to: :project_setting
delegate :mr_default_target_self, :mr_default_target_self=, to: :project_setting
delegate :previous_default_branch, :previous_default_branch=, to: :project_setting delegate :previous_default_branch, :previous_default_branch=, to: :project_setting
delegate :no_import?, to: :import_state, allow_nil: true delegate :no_import?, to: :import_state, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
......
...@@ -22,6 +22,16 @@ class ProjectSetting < ApplicationRecord ...@@ -22,6 +22,16 @@ class ProjectSetting < ApplicationRecord
def squash_readonly? def squash_readonly?
%w[always never].include?(squash_option) %w[always never].include?(squash_option)
end end
validate :validates_mr_default_target_self
private
def validates_mr_default_target_self
if mr_default_target_self_changed? && !project.forked?
errors.add :mr_default_target_self, _('This setting is allowed for forked projects only')
end
end
end end
ProjectSetting.prepend_mod ProjectSetting.prepend_mod
...@@ -69,6 +69,8 @@ module Projects ...@@ -69,6 +69,8 @@ module Projects
new_params[:avatar] = @project.avatar new_params[:avatar] = @project.avatar
end end
new_params[:mr_default_target_self] = target_mr_default_target_self unless target_mr_default_target_self.nil?
new_params.merge!(@project.object_pool_params) new_params.merge!(@project.object_pool_params)
new_params new_params
...@@ -127,5 +129,9 @@ module Projects ...@@ -127,5 +129,9 @@ module Projects
Gitlab::VisibilityLevel.closest_allowed_level(target_level) Gitlab::VisibilityLevel.closest_allowed_level(target_level)
end end
def target_mr_default_target_self
@target_mr_default_target_self ||= params[:mr_default_target_self]
end
end end
end end
...@@ -1049,8 +1049,10 @@ The `web_url` and `avatar_url` attributes on `namespace` were ...@@ -1049,8 +1049,10 @@ The `web_url` and `avatar_url` attributes on `namespace` were
[introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/27427) [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/27427)
in GitLab 11.11. in GitLab 11.11.
If the project is a fork, and you provide a valid token to authenticate, the If the project is a fork, the `forked_from_project` field appears in the response.
`forked_from_project` field appears in the response. For this field, if the upstream project is private, a valid token for authentication must be provided.
The field `mr_default_target_self` appears as well. If this value is `false`, then all merge requests
will target the upstream project by default.
```json ```json
{ {
...@@ -1058,6 +1060,7 @@ If the project is a fork, and you provide a valid token to authenticate, the ...@@ -1058,6 +1060,7 @@ If the project is a fork, and you provide a valid token to authenticate, the
... ...
"mr_default_target_self": false,
"forked_from_project":{ "forked_from_project":{
"id":13083, "id":13083,
"description":"GitLab Community Edition", "description":"GitLab Community Edition",
...@@ -1448,6 +1451,7 @@ Supported attributes: ...@@ -1448,6 +1451,7 @@ Supported attributes:
| `issues_template` **(PREMIUM)** | string | **{dotted-circle}** No | Default description for Issues. Description is parsed with GitLab Flavored Markdown. See [Templates for issues and merge requests](#templates-for-issues-and-merge-requests). | | `issues_template` **(PREMIUM)** | string | **{dotted-circle}** No | Default description for Issues. Description is parsed with GitLab Flavored Markdown. See [Templates for issues and merge requests](#templates-for-issues-and-merge-requests). |
| `merge_requests_template` **(PREMIUM)** | string | **{dotted-circle}** No | Default description for Merge Requests. Description is parsed with GitLab Flavored Markdown. See [Templates for issues and merge requests](#templates-for-issues-and-merge-requests). | | `merge_requests_template` **(PREMIUM)** | string | **{dotted-circle}** No | Default description for Merge Requests. Description is parsed with GitLab Flavored Markdown. See [Templates for issues and merge requests](#templates-for-issues-and-merge-requests). |
| `keep_latest_artifact` | boolean | **{dotted-circle}** No | Disable or enable the ability to keep the latest artifact for this project. | | `keep_latest_artifact` | boolean | **{dotted-circle}** No | Disable or enable the ability to keep the latest artifact for this project. |
| `mr_default_target_self` | boolean | **{dotted-circle}** No | For forked projects, target merge requests to this project. If `false`, the target will be the upstream project. |
## Fork project ## Fork project
...@@ -1471,6 +1475,7 @@ POST /projects/:id/fork ...@@ -1471,6 +1475,7 @@ POST /projects/:id/fork
| `path` | string | **{dotted-circle}** No | The path assigned to the resultant project after forking. | | `path` | string | **{dotted-circle}** No | The path assigned to the resultant project after forking. |
| `description` | string | **{dotted-circle}** No | The description assigned to the resultant project after forking. | | `description` | string | **{dotted-circle}** No | The description assigned to the resultant project after forking. |
| `visibility` | string | **{dotted-circle}** No | The [visibility level](#project-visibility-level) assigned to the resultant project after forking. | | `visibility` | string | **{dotted-circle}** No | The [visibility level](#project-visibility-level) assigned to the resultant project after forking. |
| `mr_default_target_self` | boolean | **{dotted-circle}** No | For forked projects, target merge requests to this project. If `false`, the target will be the upstream project. |
## List Forks of a project ## List Forks of a project
......
...@@ -82,6 +82,8 @@ module API ...@@ -82,6 +82,8 @@ module API
expose :forked_from_project, using: Entities::BasicProjectDetails, if: ->(project, options) do expose :forked_from_project, using: Entities::BasicProjectDetails, if: ->(project, options) do
project.forked? && Ability.allowed?(options[:current_user], :read_project, project.forked_from_project) project.forked? && Ability.allowed?(options[:current_user], :read_project, project.forked_from_project)
end end
expose :mr_default_target_self, if: -> (project) { project.forked? }
expose :import_status expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project| expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
......
...@@ -71,6 +71,7 @@ module API ...@@ -71,6 +71,7 @@ module API
optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins' optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
optional :packages_enabled, type: Boolean, desc: 'Enable project packages feature' optional :packages_enabled, type: Boolean, desc: 'Enable project packages feature'
optional :squash_option, type: String, values: %w(never always default_on default_off), desc: 'Squash default for project. One of `never`, `always`, `default_on`, or `default_off`.' optional :squash_option, type: String, values: %w(never always default_on default_off), desc: 'Squash default for project. One of `never`, `always`, `default_on`, or `default_off`.'
optional :mr_default_target_self, Boolean, desc: 'Merge requests of this forked project targets itself by default'
end end
params :optional_project_params_ee do params :optional_project_params_ee do
...@@ -169,6 +170,7 @@ module API ...@@ -169,6 +170,7 @@ module API
:packages_enabled, :packages_enabled,
:service_desk_enabled, :service_desk_enabled,
:keep_latest_artifact, :keep_latest_artifact,
:mr_default_target_self,
# TODO: remove in API v5, replaced by *_access_level # TODO: remove in API v5, replaced by *_access_level
:issues_enabled, :issues_enabled,
......
...@@ -363,6 +363,7 @@ module API ...@@ -363,6 +363,7 @@ module API
optional :name, type: String, desc: 'The name that will be assigned to the fork' optional :name, type: String, desc: 'The name that will be assigned to the fork'
optional :description, type: String, desc: 'The description that will be assigned to the fork' optional :description, type: String, desc: 'The description that will be assigned to the fork'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the fork' optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the fork'
optional :mr_default_target_self, Boolean, desc: 'Merge requests of this forked project targets itself by default'
end end
post ':id/fork', feature_category: :source_code_management do post ':id/fork', feature_category: :source_code_management do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20759') Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20759')
......
...@@ -36289,6 +36289,9 @@ msgstr "" ...@@ -36289,6 +36289,9 @@ msgstr ""
msgid "This setting can be overridden in each project." msgid "This setting can be overridden in each project."
msgstr "" msgstr ""
msgid "This setting is allowed for forked projects only"
msgstr ""
msgid "This subscription is for" msgid "This subscription is for"
msgstr "" msgstr ""
......
...@@ -3681,6 +3681,46 @@ RSpec.describe API::Projects do ...@@ -3681,6 +3681,46 @@ RSpec.describe API::Projects do
expect { subject }.to change { project.reload.keep_latest_artifact }.to(true) expect { subject }.to change { project.reload.keep_latest_artifact }.to(true)
end end
end end
context 'attribute mr_default_target_self' do
let_it_be(:source_project) { create(:project, :public) }
let(:forked_project) { fork_project(source_project, user) }
it 'is by default set to false' do
expect(source_project.mr_default_target_self).to be false
expect(forked_project.mr_default_target_self).to be false
end
describe 'for a non-forked project' do
before_all do
source_project.add_maintainer(user)
end
it 'is not exposed' do
get api("/projects/#{source_project.id}", user)
expect(json_response).not_to include('mr_default_target_self')
end
it 'is not possible to update' do
put api("/projects/#{source_project.id}", user), params: { mr_default_target_self: true }
source_project.reload
expect(source_project.mr_default_target_self).to be false
expect(response).to have_gitlab_http_status(:bad_request)
end
end
describe 'for a forked project' do
it 'updates to true' do
put api("/projects/#{forked_project.id}", user), params: { mr_default_target_self: true }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['mr_default_target_self']).to eq(true)
end
end
end
end end
describe 'POST /projects/:id/archive' do describe 'POST /projects/:id/archive' do
...@@ -4190,7 +4230,13 @@ RSpec.describe API::Projects do ...@@ -4190,7 +4230,13 @@ RSpec.describe API::Projects do
end end
it 'accepts custom parameters for the target project' do it 'accepts custom parameters for the target project' do
post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project', description: 'A description', visibility: 'private' } post api("/projects/#{project.id}/fork", user2),
params: {
name: 'My Random Project',
description: 'A description',
visibility: 'private',
mr_default_target_self: true
}
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
expect(json_response['name']).to eq('My Random Project') expect(json_response['name']).to eq('My Random Project')
...@@ -4201,6 +4247,7 @@ RSpec.describe API::Projects do ...@@ -4201,6 +4247,7 @@ RSpec.describe API::Projects do
expect(json_response['description']).to eq('A description') expect(json_response['description']).to eq('A description')
expect(json_response['visibility']).to eq('private') expect(json_response['visibility']).to eq('private')
expect(json_response['import_status']).to eq('scheduled') expect(json_response['import_status']).to eq('scheduled')
expect(json_response['mr_default_target_self']).to eq(true)
expect(json_response).to include("import_error") expect(json_response).to include("import_error")
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