Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5699348c82
commit
6031d32432
|
|
@ -96,7 +96,7 @@ class Ability
|
|||
|
||||
# Hook call right before ability check.
|
||||
def before_check(policy, ability, user, subject, opts)
|
||||
# See Support::AbilityCheck.
|
||||
# See Support::AbilityCheck and Support::PermissionsCheck.
|
||||
end
|
||||
|
||||
def policy_for(user, subject = :global)
|
||||
|
|
|
|||
|
|
@ -156,6 +156,9 @@ class MergeRequest < ApplicationRecord
|
|||
# when creating new merge request
|
||||
attr_accessor :can_be_created, :compare_commits, :diff_options, :compare
|
||||
|
||||
# Flag to skip triggering mergeRequestMergeStatusUpdated GraphQL subscription.
|
||||
attr_accessor :skip_merge_status_trigger
|
||||
|
||||
participant :reviewers
|
||||
|
||||
# Keep states definition to be evaluated before the state_machine block to avoid spec failures.
|
||||
|
|
@ -252,6 +255,8 @@ class MergeRequest < ApplicationRecord
|
|||
end
|
||||
|
||||
after_transition any => [:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking, :can_be_merged, :cannot_be_merged] do |merge_request, transition|
|
||||
next if merge_request.skip_merge_status_trigger
|
||||
|
||||
merge_request.run_after_commit do
|
||||
GraphqlTriggers.merge_request_merge_status_updated(merge_request)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -127,16 +127,23 @@ module MergeRequests
|
|||
|
||||
merge_requests_array = merge_requests.to_a + merge_requests_from_forks.to_a
|
||||
filter_merge_requests(merge_requests_array).each do |merge_request|
|
||||
skip_merge_status_trigger = true
|
||||
|
||||
if branch_and_project_match?(merge_request) || @push.force_push?
|
||||
merge_request.reload_diff(current_user)
|
||||
# Clear existing merge error if the push were directed at the
|
||||
# source branch. Clearing the error when the target branch
|
||||
# changes will hide the error from the user.
|
||||
merge_request.merge_error = nil
|
||||
|
||||
# Don't skip trigger since we to update the MR's merge status in real-time
|
||||
# when the push if for the MR's source branch and project.
|
||||
skip_merge_status_trigger = false
|
||||
elsif merge_request.merge_request_diff.includes_any_commits?(push_commit_ids)
|
||||
merge_request.reload_diff(current_user)
|
||||
end
|
||||
|
||||
merge_request.skip_merge_status_trigger = skip_merge_status_trigger
|
||||
merge_request.mark_as_unchecked
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
- group = local_assigns.fetch(:group)
|
||||
|
||||
%li.group-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'group_row_content' } }
|
||||
.avatar-container.rect-avatar.s40.gl-flex-shrink-0
|
||||
= group_icon(group, class: "avatar s40")
|
||||
= render Pajamas::AvatarComponent.new(group, size: 32, alt: '')
|
||||
|
||||
.gl-min-w-0.gl-flex-grow-1
|
||||
.gl-min-w-0.gl-flex-grow-1.gl-ml-3
|
||||
.title
|
||||
= link_to [:admin, group], class: 'group-name', data: { qa_selector: 'group_name_link' } do
|
||||
= group.full_name
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@
|
|||
- c.body do
|
||||
%ul.content-list.content-list-items-padding
|
||||
%li
|
||||
.avatar-container.rect-avatar.s60
|
||||
= group_icon(@group, class: "avatar s60")
|
||||
= render Pajamas::AvatarComponent.new(@group, size: 64, alt: '')
|
||||
%li
|
||||
%span.light= _('Name:')
|
||||
%strong
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
%ul.content-list
|
||||
- @projects.each do |project|
|
||||
%li.project-row.gl-align-items-center{ class: 'gl-display-flex!' }
|
||||
.avatar-container.rect-avatar.s40.gl-flex-shrink-0
|
||||
= project_icon(project, alt: '', class: 'avatar project-avatar s40', width: 40, height: 40)
|
||||
.gl-min-w-0.gl-flex-grow-1
|
||||
= render Pajamas::AvatarComponent.new(project, size: 32, alt: '')
|
||||
.gl-min-w-0.gl-flex-grow-1.gl-ml-3
|
||||
.title
|
||||
= link_to(admin_project_path(project)) do
|
||||
%span.project-full-name
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
- title = topic.title || topic.name
|
||||
|
||||
%li.topic-row.gl-py-3.gl-align-items-center{ class: 'gl-display-flex!', data: { qa_selector: 'topic_row_content' } }
|
||||
.avatar-container.rect-avatar.s40.gl-flex-shrink-0
|
||||
= topic_icon(topic, class: "avatar s40")
|
||||
= render Pajamas::AvatarComponent.new(topic, size: 32, alt: '')
|
||||
|
||||
.gl-min-w-0.gl-flex-grow-1
|
||||
.gl-min-w-0.gl-flex-grow-1.gl-ml-3
|
||||
.title
|
||||
= link_to title, topic_explore_projects_path(topic_name: topic.name)
|
||||
%div
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
- if current_action?(:new) || current_action?(:create)
|
||||
%span.float-left.gl-mr-3
|
||||
\/
|
||||
= text_field_tag 'file_name', params[:file_name], placeholder: "File name", data: { qa_selector: 'file_name_field' },
|
||||
= text_field_tag 'file_name', params[:file_name], placeholder: "Filename", data: { qa_selector: 'file_name_field' },
|
||||
required: true, class: 'form-control gl-form-input new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '')
|
||||
= render 'template_selectors'
|
||||
- if should_suggest_gitlab_ci_yml?
|
||||
|
|
|
|||
|
|
@ -15,18 +15,13 @@ module Gitlab
|
|||
# client - An instance of Gitlab::GithubImport::Client.
|
||||
# project - An instance of Project.
|
||||
def import(client, project)
|
||||
info(project.id, message: "starting importer", importer: 'Importer::CollaboratorsImporter')
|
||||
waiter = Importer::CollaboratorsImporter
|
||||
.new(project, client)
|
||||
.execute
|
||||
info(project.id, message: 'starting importer', importer: 'Importer::CollaboratorsImporter')
|
||||
return skip_to_next_stage(project) unless has_push_access?(client, project.import_source)
|
||||
|
||||
waiter = Importer::CollaboratorsImporter.new(project, client).execute
|
||||
project.import_state.refresh_jid_expiration
|
||||
|
||||
AdvanceStageWorker.perform_async(
|
||||
project.id,
|
||||
{ waiter.key => waiter.jobs_remaining },
|
||||
:pull_requests_merged_by
|
||||
)
|
||||
move_to_next_stage(project, { waiter.key => waiter.jobs_remaining })
|
||||
rescue StandardError => e
|
||||
Gitlab::Import::ImportFailureService.track(
|
||||
project_id: project.id,
|
||||
|
|
@ -41,6 +36,27 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def has_push_access?(client, repo)
|
||||
client.repository(repo).dig(:permissions, :push)
|
||||
end
|
||||
|
||||
def skip_to_next_stage(project)
|
||||
Gitlab::GithubImport::Logger.warn(
|
||||
log_attributes(
|
||||
project.id,
|
||||
message: 'no push access rights to fetch collaborators',
|
||||
importer: 'Importer::CollaboratorsImporter'
|
||||
)
|
||||
)
|
||||
move_to_next_stage(project, {})
|
||||
end
|
||||
|
||||
def move_to_next_stage(project, waiters = {})
|
||||
AdvanceStageWorker.perform_async(
|
||||
project.id, waiters, :pull_requests_merged_by
|
||||
)
|
||||
end
|
||||
|
||||
def abort_on_failure
|
||||
true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Source Code
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Code Owners development guidelines
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219916) in GitLab 15.10.
|
||||
|
||||
This document was created to help contributors understand the code design of
|
||||
[Code Owners](../../user/project/code_owners.md). You should read this
|
||||
document before making changes to the code for this feature.
|
||||
|
||||
This document is intentionally limited to an overview of how the code is
|
||||
designed, as code can change often. To understand how a specific part of the
|
||||
feature works, view the code and the specs. The details here explain how the
|
||||
major components of the Code Owners feature work.
|
||||
|
||||
NOTE:
|
||||
This document should be updated when parts of the codebase referenced in this
|
||||
document are updated, removed, or new parts are added.
|
||||
|
||||
## Business logic
|
||||
|
||||
All of the business logic for code owners is located in the `Gitlab::CodeOwners`
|
||||
namespace. Code Owners is an EE-only feature, so the files only exist in the `./ee` directory.
|
||||
|
||||
- `Gitlab::CodeOwners`: the main module used to interact with the code owner rules.
|
||||
- Defined in `./ee/lib/gitlab/code_owners.rb`.
|
||||
- `Gitlab::CodeOwners::Loader`: finds the correct `CODEOWNER` file and loads the
|
||||
content into a `Gitlab::CodeOwners::File` instance.
|
||||
- Defined in `./ee/lib/gitlab/code_owners/loader.rb`.
|
||||
- `Gitlab::CodeOwners::ReferenceExtractor`: extracts `CODEOWNER` user, group,
|
||||
and email references from texts.
|
||||
- Defined in `./ee/lib/gitlab/code_owners/reference_extractor.rb`.
|
||||
- `Gitlab::CodeOwners::UsersLoader`: the correct `CODEOWNER` file and loads the
|
||||
content into a `Gitlab::CodeOwners::File` instance.
|
||||
- Defined in `./ee/lib/gitlab/code_owners/users_loader.rb`.
|
||||
- `Gitlab::CodeOwners::GroupsLoader`: finds the correct `CODEOWNER` file and loads
|
||||
the content into a `Gitlab::CodeOwners::File` instance.
|
||||
- Defined in `./ee/lib/gitlab/code_owners/groups_loader.rb`.
|
||||
- `Gitlab::CodeOwners::File`: wraps a `CODEOWNERS` file and exposes the data through
|
||||
the class' public methods.
|
||||
- Defined in `./ee/lib/gitlab/code_owners/file.rb`.
|
||||
- `Gitlab::CodeOwners::Entry`: wraps an entry (a pattern and owners line) in a
|
||||
`CODEOWNERS` file and exposes the data through the class' public methods.
|
||||
- Defined in `./ee/lib/gitlab/code_owners/entry.rb`.
|
||||
- `Gitlab::CodeOwners::Validator`: validates no files in the `CODEOWNERS` entries
|
||||
have been changed when a user pushes to a protected branch with `require_code_owner_approval` enabled.
|
||||
- Defined in `./ee/lib/gitlab/code_owners/validator.rb`.
|
||||
|
||||
## Related models
|
||||
|
||||
### `ProtectedBranch`
|
||||
|
||||
The `ProtectedBranch` model is defined in `app/models/protected_branch.rb` and
|
||||
extended in `ee/app/ee/models/protected_branch.rb`. The EE version includes a column
|
||||
named `require_code_owner_approval` which prevents changes from being pushed directly
|
||||
to the branch being protected if the file is listed in `CODEOWNERS`.
|
||||
|
||||
### `ApprovalMergeRequestRule`
|
||||
|
||||
The `ApprovalMergeRequestRule` model is defined in `ee/app/models/approval_merge_request_rule.rb`.
|
||||
The model stores approval rules for a merge request. We use multiple rule types,
|
||||
including a `code_owner` type rule.
|
||||
|
||||
## Controllers and Services
|
||||
|
||||
The following controllers and services below are being used for the approval
|
||||
rules feature to work:
|
||||
|
||||
### `Api::Internal::Base`
|
||||
|
||||
This `/internal/allowed` endpoint is called when pushing to GitLab to ensure the
|
||||
user is allowed to push. The `/internal/allowed` endpoint performs a `Gitlab::Checks::DiffCheck`.
|
||||
In EE, this includes code owner checks.
|
||||
|
||||
Defined in `lib/api/internal/base.rb`.
|
||||
|
||||
### `Repositories::GitHttpController`
|
||||
|
||||
When changes are pushed to GitLab over HTTP, the controller performs an access check
|
||||
to ensure the user is allowed to push. The checks perform a `Gitlab::Checks::DiffCheck`.
|
||||
In EE, this includes Code Owner checks.
|
||||
|
||||
Defined in `app/controllers/repositories/git_http_controller.rb`.
|
||||
|
||||
### `EE::Gitlab::Checks::DiffCheck`
|
||||
|
||||
This module extends the CE `Gitlab::Checks::DiffChecks` class and adds code owner
|
||||
validation. It uses the `Gitlab::CodeOwner::Validator` class to verify users are
|
||||
not pushing files listed in `CODEOWNER` directly to a protected branch while the
|
||||
branch requires code owner approval.
|
||||
|
||||
### `MergeRequests::SyncCodeOwnerApprovalRules`
|
||||
|
||||
This service is defined in `services/merge_requests/sync_code_owner_approval_rules.rb` and used for:
|
||||
|
||||
- Deleting outdated code owner approval rules when new changes are pushed to a merge request.
|
||||
- Creating code owner approval rules for each changed file in a merge request that is also listed in the `CODEOWNER` file.
|
||||
|
||||
## Flow
|
||||
|
||||
These flowcharts should help explain the flow from the controllers down to the
|
||||
models for different features.
|
||||
|
||||
### Push changes to a protected branch with `require_code_owner_approval` enabled
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Api::Internal::Base --> Gitlab::GitAccess
|
||||
Gitlab::GitAccess --> Gitlab::Checks::DiffCheck
|
||||
Gitlab::Checks::DiffCheck --> Gitlab::CodeOwners::Validator
|
||||
Gitlab::CodeOwners::Validator --> ProtectedBranch
|
||||
Gitlab::CodeOwners::Validator --> Gitlab::CodeOwners::Loader
|
||||
Gitlab::CodeOwners::Loader --> Gitlab::CodeOwners::Entry
|
||||
```
|
||||
|
||||
### Sync code owner rules to merge request approval rules
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
EE::ProtectedBranches::CreateService --> MergeRequest::SyncCodeOwnerApprovalRules
|
||||
EE::MergeRequestRefreshService --> MergeRequest::SyncCodeOwnerApprovalRules
|
||||
EE::MergeRequests::ReloadMergeHeadDiffService --> MergeRequest::SyncCodeOwnerApprovalRules
|
||||
EE::MergeRequests::CreateService --> MergeRequests::SyncCodeOwnerApprovalRulesWorker
|
||||
EE::MergeRequests::UpdateService --> MergeRequests::SyncCodeOwnerApprovalRulesWorker
|
||||
MergeRequests::SyncCodeOwnerApprovalRulesWorker --> MergeRequest::SyncCodeOwnerApprovalRules
|
||||
MergeRequest::SyncCodeOwnerApprovalRules --> id1{delete outdated code owner rules}
|
||||
MergeRequest::SyncCodeOwnerApprovalRules --> id2{create rule for each code owner entry}
|
||||
```
|
||||
|
|
@ -969,6 +969,12 @@ Use lowercase for **personal access token**.
|
|||
|
||||
Do not use **please**. For details, see the [Microsoft style guide](https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/please).
|
||||
|
||||
## prerequisites
|
||||
|
||||
Use **prerequisites** when documenting the steps before a task. Do not use **requirements**.
|
||||
|
||||
For more information, see [the task topic type](../topic_types/task.md).
|
||||
|
||||
## press
|
||||
|
||||
Use **press** when talking about keyboard keys. For example:
|
||||
|
|
@ -1031,6 +1037,12 @@ Do not use **Reporter permissions**. A user who is assigned the Reporter role ha
|
|||
|
||||
Use title case for **Repository Mirroring**.
|
||||
|
||||
## requirements
|
||||
|
||||
Use **prerequisites** when documenting the steps before a task. Do not use **requirements**.
|
||||
|
||||
For more information, see [the task topic type](../topic_types/task.md).
|
||||
|
||||
## respectively
|
||||
|
||||
Avoid **respectively** and be more precise instead.
|
||||
|
|
|
|||
|
|
@ -198,3 +198,124 @@ Say, for example, we extract the whole endpoint into a service. The `can?` check
|
|||
- If the finder doesn't accept `current_user`, and therefore doesn't check permissions, then probably no.
|
||||
- If the finder accepts `current_user`, and doesn't check permissions, then it would be a good idea to double check other usages of the finder, and we might consider adding authorization.
|
||||
- If the finder accepts `current_user`, and already checks permissions, then either we need to add our case, or the existing checks are appropriate.
|
||||
|
||||
### Refactoring permissions
|
||||
|
||||
#### Finding existing permissions checks
|
||||
|
||||
As mentioned [above](#where-should-permissions-be-checked), permissions are
|
||||
often checked in multiple locations for a single endpoint or web request. As a
|
||||
result, finding the list of authorization checks that are run for a given endpoint
|
||||
can be challenging.
|
||||
|
||||
To assist with this, you can locally set `GITLAB_DEBUG_POLICIES=true`.
|
||||
|
||||
This outputs information about which abilities are checked in the requests
|
||||
made in any specs that you run. The output also includes the line of code where the
|
||||
authorization check was made. Caller information is especially helpful in cases
|
||||
where there is metaprogramming used because those cases are difficult to find by
|
||||
grepping for ability name strings.
|
||||
|
||||
Example:
|
||||
|
||||
```shell
|
||||
# example spec run
|
||||
|
||||
GITLAB_DEBUG_POLICIES=true bundle exec rspec spec/controllers/groups_controller_spec.rb:162
|
||||
|
||||
# permissions debug output when spec is run; if multiple policy checks are run they will all be in the debug output.
|
||||
|
||||
POLICY CHECK DEBUG -> policy: GlobalPolicy, ability: create_group, called_from: ["/gitlab/app/controllers/application_controller.rb:245:in `can?'", "/gitlab/app/controllers/groups_controller.rb:255:in `authorize_create_group!'"]
|
||||
```
|
||||
|
||||
This flag is meant to help learn more about authorization checks while
|
||||
refactoring and should not remain enabled for any specs on the default branch.
|
||||
|
||||
#### Understanding logic for individual abilities
|
||||
|
||||
References to an ability may appear in a `DeclarativePolicy` class many times
|
||||
and depend on conditions and rules which reference other abilities. As a result,
|
||||
it can be challenging to know exactly which conditions apply to a particular
|
||||
ability.
|
||||
|
||||
`DeclarativePolicy` provides a `ability_map` for each Policy class, which
|
||||
pulls all Rules for an ability into an array.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
> GroupPolicy.ability_map.map.select { |k,v| k == :read_group_member }
|
||||
=> {:read_group_member=>[[:enable, #<Rule can?(:read_group)>], [:prevent, #<Rule ~can_read_group_member>]]}
|
||||
|
||||
> GroupPolicy.ability_map.map.select { |k,v| k == :read_group }
|
||||
=> {:read_group=>
|
||||
[[:enable, #<Rule public_group>],
|
||||
[:enable, #<Rule logged_in_viewable>],
|
||||
[:enable, #<Rule guest>],
|
||||
[:enable, #<Rule admin>],
|
||||
[:enable, #<Rule has_projects>],
|
||||
[:enable, #<Rule read_package_registry_deploy_token>],
|
||||
[:enable, #<Rule write_package_registry_deploy_token>],
|
||||
[:prevent, #<Rule all?(~public_group, ~admin, user_banned_from_group)>],
|
||||
[:enable, #<Rule auditor>],
|
||||
[:prevent, #<Rule needs_new_sso_session>],
|
||||
[:prevent, #<Rule all?(ip_enforcement_prevents_access, ~owner, ~auditor)>]]}
|
||||
```
|
||||
|
||||
`DeclarativePolicy` also provides a `debug` method that can be used to
|
||||
understand the logic tree for a specific object and actor. The output is similar
|
||||
to the list of rules from `ability_map`. But, `DeclarativePolicy` stops
|
||||
evaluating rules once one `prevent`s an ability, so it is possible that
|
||||
not all conditions are called.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
policy = GroupPolicy.new(User.last, Group.last)
|
||||
policy.debug(:read_group)
|
||||
|
||||
- [0] enable when public_group ((@custom_guest_user1 : Group/139))
|
||||
- [0] enable when logged_in_viewable ((@custom_guest_user1 : Group/139))
|
||||
- [0] enable when admin ((@custom_guest_user1 : Group/139))
|
||||
- [0] enable when auditor ((@custom_guest_user1 : Group/139))
|
||||
- [14] prevent when all?(~public_group, ~admin, user_banned_from_group) ((@custom_guest_user1 : Group/139))
|
||||
- [14] prevent when needs_new_sso_session ((@custom_guest_user1 : Group/139))
|
||||
- [16] enable when guest ((@custom_guest_user1 : Group/139))
|
||||
- [16] enable when has_projects ((@custom_guest_user1 : Group/139))
|
||||
- [16] enable when read_package_registry_deploy_token ((@custom_guest_user1 : Group/139))
|
||||
- [16] enable when write_package_registry_deploy_token ((@custom_guest_user1 : Group/139))
|
||||
[21] prevent when all?(ip_enforcement_prevents_access, ~owner, ~auditor) ((@custom_guest_user1 : Group/139))
|
||||
|
||||
=> #<DeclarativePolicy::Runner::State:0x000000015c665050
|
||||
@called_conditions=
|
||||
#<Set: {
|
||||
"/dp/condition/GroupPolicy/public_group/Group:139",
|
||||
"/dp/condition/GroupPolicy/logged_in_viewable/User:83,Group:139",
|
||||
"/dp/condition/BasePolicy/admin/User:83",
|
||||
"/dp/condition/BasePolicy/auditor/User:83",
|
||||
"/dp/condition/GroupPolicy/user_banned_from_group/User:83,Group:139",
|
||||
"/dp/condition/GroupPolicy/needs_new_sso_session/User:83,Group:139",
|
||||
"/dp/condition/GroupPolicy/guest/User:83,Group:139",
|
||||
"/dp/condition/GroupPolicy/has_projects/User:83,Group:139",
|
||||
"/dp/condition/GroupPolicy/read_package_registry_deploy_token/User:83,Group:139",
|
||||
"/dp/condition/GroupPolicy/write_package_registry_deploy_token/User:83,Group:139"}>,
|
||||
@enabled=false,
|
||||
@prevented=true>
|
||||
```
|
||||
|
||||
#### Testing that individual policies are equivalent
|
||||
|
||||
You can use the `'equivalent project policy abilities'` shared example to ensure
|
||||
that 2 project policy abilities are equivalent for all project visibility levels
|
||||
and access levels.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
context 'when refactoring read_pipeline_schedule and read_pipeline' do
|
||||
let(:old_policy) { :read_pipeline_schedule }
|
||||
let(:new_policy) { :read_pipeline }
|
||||
|
||||
it_behaves_like 'equivalent policies'
|
||||
end
|
||||
```
|
||||
|
|
|
|||
|
|
@ -149,19 +149,19 @@ Depending on your version of GitLab, the Chain of Custody report is either sent
|
|||
|
||||
#### Generate commit-specific Chain of Custody report
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267629) in GitLab 13.6.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267629) in GitLab 13.6.
|
||||
> - Support for including all commits instead of only merge commits [added](https://gitlab.com/gitlab-org/gitlab/-/issues/393446) in GitLab 15.10.
|
||||
|
||||
You can generate a commit-specific Chain of Custody report for a given merge commit SHA. This report provides only the
|
||||
details for the provided merge commit SHA. Issue [393446](https://gitlab.com/gitlab-org/gitlab/-/issues/393446) proposes
|
||||
to extend the commit SHA filtering to work with all commits instead of only merge commits.
|
||||
You can generate a commit-specific Chain of Custody report for a given commit SHA. This report provides only the
|
||||
details for the provided commit SHA.
|
||||
|
||||
To generate a commit-specific Chain of Custody report:
|
||||
|
||||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Security and Compliance > Compliance report**.
|
||||
1. At the top of the compliance report, to the right of **List of all merge commits**, select the down arrow
|
||||
1. At the top of the compliance report, to the right of **List of all commits**, select the down arrow
|
||||
(**{chevron-lg-down}**).
|
||||
1. Enter the merge commit SHA, and then select **Export commit custody report**.
|
||||
1. Enter the commit SHA, and then select **Export commit custody report**.
|
||||
|
||||
Depending on your version of GitLab, the Chain of Custody report is either sent through email or available for download.
|
||||
|
||||
|
|
|
|||
|
|
@ -284,6 +284,8 @@ GitHub Enterprise Cloud has
|
|||
[custom repository roles](https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/about-custom-repository-roles).
|
||||
These roles aren't supported and cause partial imports.
|
||||
|
||||
To import GitHub collaborators, you must have at least the Write role on the GitHub project. Otherwise collaborators import is skipped.
|
||||
|
||||
## Import from GitHub Enterprise on an internal network
|
||||
|
||||
If your GitHub Enterprise instance is on a internal network that is inaccessible to the internet, you can use a reverse proxy
|
||||
|
|
|
|||
|
|
@ -26,11 +26,32 @@ for any change you commit through the Web Editor.
|
|||
To create a text file in the Web Editor:
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
|
||||
1. From the project dashboard or repository, next to the branch name,
|
||||
select the plus icon (**{plus}**).
|
||||
1. From the dropdown list, select **New file**.
|
||||
1. Complete the fields. To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected.
|
||||
1. Complete the fields.
|
||||
1. To create a merge request with the new file, ensure the **Start a new merge request with these changes** checkbox is selected, if you had chosen a **Target branch** other than the [default branch (such as `main`)](../../../user/project/repository/branches/default.md).
|
||||
1. Select **Commit changes**.
|
||||
|
||||
### Create a file from a template
|
||||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Repository > Files**.
|
||||
1. Next to the project name, select the plus icon (**{plus}**) to display a
|
||||
dropdown list, then select **New file** from the list.
|
||||
1. For **Filename**, provide one of the filenames that GitLab provides a template for:
|
||||
- `.gitignore`
|
||||
- `.gitlab-ci.yml`
|
||||
- `LICENSE`
|
||||
- `Dockerfile`
|
||||
1. Select **Apply a template**, then select the template you want to apply.
|
||||
1. Make your changes to the file.
|
||||
1. Provide a **Commit message**.
|
||||
1. Enter a **Target branch** to merge into. To create a new merge request with
|
||||
your changes, enter a branch name that is not your repository's
|
||||
[default branch](../../../user/project/repository/branches/default.md),
|
||||
1. Select **Commit changes** to add the commit to your branch.
|
||||
|
||||
## Edit a file
|
||||
|
||||
To edit a text file in the Web Editor:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module Gitlab
|
|||
def load(string)
|
||||
return unless string
|
||||
|
||||
object = YAML.safe_load(string, [Symbol])
|
||||
object = YAML.safe_load(string, permitted_classes: [Symbol])
|
||||
|
||||
object.map do |variable|
|
||||
variable.symbolize_keys.tap do |variable|
|
||||
|
|
|
|||
|
|
@ -26847,9 +26847,6 @@ msgstr ""
|
|||
msgid "Merge blocked: pipeline must succeed. It's waiting for a manual job to continue."
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge commit SHA"
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge commit message"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -100,8 +100,8 @@ RSpec.describe Gitlab::Ci::Lint, feature_category: :pipeline_composition do
|
|||
end
|
||||
|
||||
it 'sets merged_config' do
|
||||
root_config = YAML.safe_load(content, [Symbol])
|
||||
included_config = YAML.safe_load(included_content, [Symbol])
|
||||
root_config = YAML.safe_load(content, permitted_classes: [Symbol])
|
||||
included_config = YAML.safe_load(included_content, permitted_classes: [Symbol])
|
||||
expected_config = included_config.merge(root_config).except(:include).deep_stringify_keys
|
||||
|
||||
expect(subject.merged_yaml).to eq(expected_config.to_yaml)
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
it 'returns expanded yaml config' do
|
||||
expanded_config = YAML.safe_load(config_metadata[:merged_yaml], [Symbol])
|
||||
included_config = YAML.safe_load(included_yml, [Symbol])
|
||||
expanded_config = YAML.safe_load(config_metadata[:merged_yaml], permitted_classes: [Symbol])
|
||||
included_config = YAML.safe_load(included_yml, permitted_classes: [Symbol])
|
||||
|
||||
expect(expanded_config).to include(*included_config.keys)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ RSpec.describe Gitlab::ImportExport::Config, feature_category: :importers do
|
|||
EOF
|
||||
end
|
||||
|
||||
let(:config_hash) { YAML.safe_load(config, [Symbol]) }
|
||||
let(:config_hash) { YAML.safe_load(config, permitted_classes: [Symbol]) }
|
||||
|
||||
before do
|
||||
allow_any_instance_of(described_class).to receive(:parse_yaml) do
|
||||
|
|
|
|||
|
|
@ -4321,6 +4321,14 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
|
|||
transition!
|
||||
end
|
||||
|
||||
context 'when skip_merge_status_trigger is set to true' do
|
||||
before do
|
||||
subject.skip_merge_status_trigger = true
|
||||
end
|
||||
|
||||
it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription'
|
||||
end
|
||||
|
||||
context 'when transaction is not committed' do
|
||||
it_behaves_like 'transition not triggering mergeRequestMergeStatusUpdated GraphQL subscription' do
|
||||
def transition!
|
||||
|
|
|
|||
|
|
@ -245,8 +245,8 @@ RSpec.describe API::Lint, feature_category: :pipeline_composition do
|
|||
it 'passes validation' do
|
||||
ci_lint
|
||||
|
||||
included_config = YAML.safe_load(included_content, [Symbol])
|
||||
root_config = YAML.safe_load(yaml_content, [Symbol])
|
||||
included_config = YAML.safe_load(included_content, permitted_classes: [Symbol])
|
||||
root_config = YAML.safe_load(yaml_content, permitted_classes: [Symbol])
|
||||
expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
|
@ -535,8 +535,8 @@ RSpec.describe API::Lint, feature_category: :pipeline_composition do
|
|||
it 'passes validation' do
|
||||
ci_lint
|
||||
|
||||
included_config = YAML.safe_load(included_content, [Symbol])
|
||||
root_config = YAML.safe_load(yaml_content, [Symbol])
|
||||
included_config = YAML.safe_load(included_content, permitted_classes: [Symbol])
|
||||
root_config = YAML.safe_load(yaml_content, permitted_classes: [Symbol])
|
||||
expected_yaml = included_config.merge(root_config).except(:include).deep_stringify_keys.to_yaml
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
|
|
|||
|
|
@ -109,6 +109,14 @@ RSpec.describe MergeRequests::RefreshService, feature_category: :code_review_wor
|
|||
expect(@fork_build_failed_todo).to be_done
|
||||
end
|
||||
|
||||
it 'triggers mergeRequestMergeStatusUpdated GraphQL subscription conditionally' do
|
||||
expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@merge_request)
|
||||
expect(GraphqlTriggers).to receive(:merge_request_merge_status_updated).with(@another_merge_request)
|
||||
expect(GraphqlTriggers).not_to receive(:merge_request_merge_status_updated).with(@fork_merge_request)
|
||||
|
||||
refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
|
||||
end
|
||||
|
||||
context 'when a merge error exists' do
|
||||
let(:error_message) { 'This is a merge error' }
|
||||
|
||||
|
|
|
|||
|
|
@ -546,6 +546,7 @@ end
|
|||
# Disabled because it's causing N+1 queries.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/396352.
|
||||
# Support::AbilityCheck.inject(Ability.singleton_class)
|
||||
Support::PermissionsCheck.inject(Ability.singleton_class)
|
||||
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Support
|
||||
module PermissionsCheck
|
||||
def self.inject(mod)
|
||||
mod.prepend PermissionsExtension if Gitlab::Utils.to_boolean(ENV['GITLAB_DEBUG_POLICIES'])
|
||||
end
|
||||
|
||||
module PermissionsExtension
|
||||
def before_check(policy, ability, _user, _subject, _opts)
|
||||
puts(
|
||||
"POLICY CHECK DEBUG -> " \
|
||||
"policy: #{policy.class.name}, ability: #{ability}, called_from: #{caller_locations(2, 5)}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -401,3 +401,24 @@ RSpec.shared_examples 'package access with repository disabled' do
|
|||
|
||||
it { is_expected.to be_allowed(:read_package) }
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'equivalent project policy abilities' do
|
||||
where(:project_visibility, :user_role_on_project) do
|
||||
project_visibilities = [:public, :internal, :private]
|
||||
user_role_on_project = [:anonymous, :non_member, :guest, :reporter, :developer, :maintainer, :owner, :admin]
|
||||
project_visibilities.product(user_role_on_project)
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'evaluates the same' do
|
||||
project = public_send("#{project_visibility}_project")
|
||||
current_user = public_send(user_role_on_project)
|
||||
enable_admin_mode!(current_user) if user_role_on_project == :admin
|
||||
policy = ProjectPolicy.new(current_user, project)
|
||||
old_permissions = policy.allowed?(old_policy)
|
||||
new_permissions = policy.allowed?(new_policy)
|
||||
|
||||
expect(old_permissions).to eq new_permissions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,40 +11,63 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportCollaboratorsWorker, feature_c
|
|||
let(:client) { instance_double(Gitlab::GithubImport::Client) }
|
||||
|
||||
describe '#import' do
|
||||
it 'imports all the pull requests' do
|
||||
waiter = Gitlab::JobWaiter.new(2, '123')
|
||||
let(:push_rights_granted) { true }
|
||||
|
||||
expect(Gitlab::GithubImport::Importer::CollaboratorsImporter)
|
||||
.to receive(:new)
|
||||
.with(project, client)
|
||||
.and_return(importer)
|
||||
expect(importer).to receive(:execute).and_return(waiter)
|
||||
|
||||
expect(import_state).to receive(:refresh_jid_expiration)
|
||||
|
||||
expect(Gitlab::GithubImport::AdvanceStageWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(project.id, { '123' => 2 }, :pull_requests_merged_by)
|
||||
|
||||
worker.import(client, project)
|
||||
before do
|
||||
allow(client).to receive(:repository).with(project.import_source)
|
||||
.and_return({ permissions: { push: push_rights_granted } })
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
exception = StandardError.new('_some_error_')
|
||||
context 'when user has push access for this repo' do
|
||||
it 'imports all the pull requests' do
|
||||
waiter = Gitlab::JobWaiter.new(2, '123')
|
||||
|
||||
expect_next_instance_of(Gitlab::GithubImport::Importer::CollaboratorsImporter) do |importer|
|
||||
expect(importer).to receive(:execute).and_raise(exception)
|
||||
expect(Gitlab::GithubImport::Importer::CollaboratorsImporter)
|
||||
.to receive(:new)
|
||||
.with(project, client)
|
||||
.and_return(importer)
|
||||
expect(importer).to receive(:execute).and_return(waiter)
|
||||
|
||||
expect(import_state).to receive(:refresh_jid_expiration)
|
||||
|
||||
expect(Gitlab::GithubImport::AdvanceStageWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(project.id, { '123' => 2 }, :pull_requests_merged_by)
|
||||
|
||||
worker.import(client, project)
|
||||
end
|
||||
end
|
||||
expect(Gitlab::Import::ImportFailureService).to receive(:track)
|
||||
.with(
|
||||
project_id: project.id,
|
||||
exception: exception,
|
||||
error_source: described_class.name,
|
||||
fail_import: true,
|
||||
metrics: true
|
||||
).and_call_original
|
||||
|
||||
expect { worker.import(client, project) }.to raise_error(StandardError)
|
||||
context 'when user do not have push access for this repo' do
|
||||
let(:push_rights_granted) { false }
|
||||
|
||||
it 'skips stage' do
|
||||
expect(Gitlab::GithubImport::Importer::CollaboratorsImporter).not_to receive(:new)
|
||||
|
||||
expect(Gitlab::GithubImport::AdvanceStageWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(project.id, {}, :pull_requests_merged_by)
|
||||
|
||||
worker.import(client, project)
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
exception = StandardError.new('_some_error_')
|
||||
|
||||
expect_next_instance_of(Gitlab::GithubImport::Importer::CollaboratorsImporter) do |importer|
|
||||
expect(importer).to receive(:execute).and_raise(exception)
|
||||
end
|
||||
expect(Gitlab::Import::ImportFailureService).to receive(:track)
|
||||
.with(
|
||||
project_id: project.id,
|
||||
exception: exception,
|
||||
error_source: described_class.name,
|
||||
fail_import: true,
|
||||
metrics: true
|
||||
).and_call_original
|
||||
|
||||
expect { worker.import(client, project) }.to raise_error(StandardError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ module Tooling
|
|||
def parse(yaml_files)
|
||||
Array(yaml_files).each do |yaml_file|
|
||||
data = File.read(yaml_file)
|
||||
metadata, example_groups = data.split("---\n").reject(&:empty?).map { |yml| YAML.safe_load(yml, [Symbol]) }
|
||||
metadata, example_groups = data.split("---\n").reject(&:empty?).map do |yml|
|
||||
YAML.safe_load(yml, permitted_classes: [Symbol])
|
||||
end
|
||||
|
||||
if example_groups.nil?
|
||||
puts "No examples in #{yaml_file}! Metadata: #{metadata}"
|
||||
|
|
|
|||
Loading…
Reference in New Issue