Commit 1a66a293 authored by Pavel Shutsin's avatar Pavel Shutsin

Merge branch 'add-feature-api-configure-default-mr-target-for-forks' into 'master'

Adds API feature configuring default mr target

See merge request gitlab-org/gitlab!77169
parents 4270e89d 1e3a3ddf
......@@ -436,6 +436,7 @@ class Project < ApplicationRecord
prefix: :import, to: :import_state, allow_nil: true
delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, 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 :no_import?, to: :import_state, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true
......
......@@ -22,6 +22,16 @@ class ProjectSetting < ApplicationRecord
def squash_readonly?
%w[always never].include?(squash_option)
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
ProjectSetting.prepend_mod
......@@ -69,6 +69,8 @@ module Projects
new_params[:avatar] = @project.avatar
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
......@@ -127,5 +129,9 @@ module Projects
Gitlab::VisibilityLevel.closest_allowed_level(target_level)
end
def target_mr_default_target_self
@target_mr_default_target_self ||= params[:mr_default_target_self]
end
end
end
......@@ -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)
in GitLab 11.11.
If the project is a fork, and you provide a valid token to authenticate, the
`forked_from_project` field appears in the response.
If the project is a fork, the `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
{
......@@ -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":{
"id":13083,
"description":"GitLab Community Edition",
......@@ -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). |
| `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. |
| `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
......@@ -1471,6 +1475,7 @@ POST /projects/:id/fork
| `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. |
| `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
......
......@@ -82,6 +82,8 @@ module API
expose :forked_from_project, using: Entities::BasicProjectDetails, if: ->(project, options) do
project.forked? && Ability.allowed?(options[:current_user], :read_project, project.forked_from_project)
end
expose :mr_default_target_self, if: -> (project) { project.forked? }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
......
......@@ -71,6 +71,7 @@ module API
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 :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
params :optional_project_params_ee do
......@@ -169,6 +170,7 @@ module API
:packages_enabled,
:service_desk_enabled,
:keep_latest_artifact,
:mr_default_target_self,
# TODO: remove in API v5, replaced by *_access_level
:issues_enabled,
......
......@@ -363,6 +363,7 @@ module API
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 :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
post ':id/fork', feature_category: :source_code_management do
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20759')
......
......@@ -36578,6 +36578,9 @@ msgstr ""
msgid "This setting can be overridden in each project."
msgstr ""
msgid "This setting is allowed for forked projects only"
msgstr ""
msgid "This subscription is for"
msgstr ""
......
......@@ -3681,6 +3681,46 @@ RSpec.describe API::Projects do
expect { subject }.to change { project.reload.keep_latest_artifact }.to(true)
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
describe 'POST /projects/:id/archive' do
......@@ -4190,7 +4230,13 @@ RSpec.describe API::Projects do
end
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(json_response['name']).to eq('My Random Project')
......@@ -4201,6 +4247,7 @@ RSpec.describe API::Projects do
expect(json_response['description']).to eq('A description')
expect(json_response['visibility']).to eq('private')
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")
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