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