Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f306c94b31
commit
d10e03ba6f
16 changed files with 687 additions and 133 deletions
24
Gemfile.lock
24
Gemfile.lock
|
@ -9,7 +9,6 @@ GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
RedCloth (4.3.2)
|
RedCloth (4.3.2)
|
||||||
abstract_type (0.0.7)
|
|
||||||
acme-client (2.0.6)
|
acme-client (2.0.6)
|
||||||
faraday (>= 0.17, < 2.0.0)
|
faraday (>= 0.17, < 2.0.0)
|
||||||
actioncable (6.1.3.2)
|
actioncable (6.1.3.2)
|
||||||
|
@ -76,9 +75,6 @@ GEM
|
||||||
zeitwerk (~> 2.3)
|
zeitwerk (~> 2.3)
|
||||||
acts-as-taggable-on (7.0.0)
|
acts-as-taggable-on (7.0.0)
|
||||||
activerecord (>= 5.0, < 6.2)
|
activerecord (>= 5.0, < 6.2)
|
||||||
adamantium (0.2.0)
|
|
||||||
ice_nine (~> 0.11.0)
|
|
||||||
memoizable (~> 0.4.0)
|
|
||||||
addressable (2.8.0)
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
aes_key_wrap (1.1.0)
|
aes_key_wrap (1.1.0)
|
||||||
|
@ -205,9 +201,6 @@ GEM
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commonmarker (0.21.0)
|
commonmarker (0.21.0)
|
||||||
ruby-enum (~> 0.5)
|
ruby-enum (~> 0.5)
|
||||||
concord (0.1.5)
|
|
||||||
adamantium (~> 0.2.0)
|
|
||||||
equalizer (~> 0.0.9)
|
|
||||||
concurrent-ruby (1.1.9)
|
concurrent-ruby (1.1.9)
|
||||||
connection_pool (2.2.2)
|
connection_pool (2.2.2)
|
||||||
contracts (0.11.0)
|
contracts (0.11.0)
|
||||||
|
@ -336,7 +329,6 @@ GEM
|
||||||
launchy (~> 2.1)
|
launchy (~> 2.1)
|
||||||
mail (~> 2.7)
|
mail (~> 2.7)
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
equalizer (0.0.11)
|
|
||||||
erubi (1.9.0)
|
erubi (1.9.0)
|
||||||
escape_utils (1.2.1)
|
escape_utils (1.2.1)
|
||||||
et-orbi (1.2.1)
|
et-orbi (1.2.1)
|
||||||
|
@ -647,7 +639,6 @@ GEM
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
i18n_data (0.8.0)
|
i18n_data (0.8.0)
|
||||||
icalendar (2.4.1)
|
icalendar (2.4.1)
|
||||||
ice_nine (0.11.2)
|
|
||||||
invisible_captcha (1.1.0)
|
invisible_captcha (1.1.0)
|
||||||
rails (>= 4.2)
|
rails (>= 4.2)
|
||||||
ipaddress (0.8.3)
|
ipaddress (0.8.3)
|
||||||
|
@ -748,8 +739,6 @@ GEM
|
||||||
actionpack (>= 2.3)
|
actionpack (>= 2.3)
|
||||||
activerecord (>= 2.3)
|
activerecord (>= 2.3)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
memoizable (0.4.2)
|
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
|
||||||
memory_profiler (0.9.14)
|
memory_profiler (0.9.14)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mime-types (3.3.1)
|
mime-types (3.3.1)
|
||||||
|
@ -935,7 +924,6 @@ GEM
|
||||||
coderay
|
coderay
|
||||||
parser
|
parser
|
||||||
unparser
|
unparser
|
||||||
procto (0.0.3)
|
|
||||||
prometheus-client-mmap (0.12.0)
|
prometheus-client-mmap (0.12.0)
|
||||||
pry (0.13.1)
|
pry (0.13.1)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
|
@ -1086,7 +1074,7 @@ GEM
|
||||||
rspec-mocks (3.10.2)
|
rspec-mocks (3.10.2)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.10.0)
|
rspec-support (~> 3.10.0)
|
||||||
rspec-parameterized (0.4.2)
|
rspec-parameterized (0.5.0)
|
||||||
binding_ninja (>= 0.2.3)
|
binding_ninja (>= 0.2.3)
|
||||||
parser
|
parser
|
||||||
proc_to_ast
|
proc_to_ast
|
||||||
|
@ -1279,7 +1267,6 @@ GEM
|
||||||
eventmachine (~> 1.0, >= 1.0.4)
|
eventmachine (~> 1.0, >= 1.0.4)
|
||||||
rack (>= 1, < 3)
|
rack (>= 1, < 3)
|
||||||
thor (1.1.0)
|
thor (1.1.0)
|
||||||
thread_safe (0.3.6)
|
|
||||||
thrift (0.14.0)
|
thrift (0.14.0)
|
||||||
tilt (2.0.10)
|
tilt (2.0.10)
|
||||||
timecop (0.9.1)
|
timecop (0.9.1)
|
||||||
|
@ -1332,14 +1319,9 @@ GEM
|
||||||
uniform_notifier (1.13.0)
|
uniform_notifier (1.13.0)
|
||||||
unleash (0.1.5)
|
unleash (0.1.5)
|
||||||
murmurhash3 (~> 0.1.6)
|
murmurhash3 (~> 0.1.6)
|
||||||
unparser (0.4.7)
|
unparser (0.6.0)
|
||||||
abstract_type (~> 0.0.7)
|
|
||||||
adamantium (~> 0.2.0)
|
|
||||||
concord (~> 0.1.5)
|
|
||||||
diff-lcs (~> 1.3)
|
diff-lcs (~> 1.3)
|
||||||
equalizer (~> 0.0.9)
|
parser (>= 3.0.0)
|
||||||
parser (>= 2.6.5)
|
|
||||||
procto (~> 0.0.2)
|
|
||||||
uri_template (0.7.0)
|
uri_template (0.7.0)
|
||||||
valid_email (0.1.3)
|
valid_email (0.1.3)
|
||||||
activemodel
|
activemodel
|
||||||
|
|
|
@ -903,7 +903,7 @@
|
||||||
:feature_category: :importers
|
:feature_category: :importers
|
||||||
:has_external_dependencies: true
|
:has_external_dependencies: true
|
||||||
:urgency: :low
|
:urgency: :low
|
||||||
:resource_boundary: :unknown
|
:resource_boundary: :cpu
|
||||||
:weight: 1
|
:weight: 1
|
||||||
:idempotent:
|
:idempotent:
|
||||||
:tags:
|
:tags:
|
||||||
|
@ -913,7 +913,7 @@
|
||||||
:feature_category: :importers
|
:feature_category: :importers
|
||||||
:has_external_dependencies: true
|
:has_external_dependencies: true
|
||||||
:urgency: :low
|
:urgency: :low
|
||||||
:resource_boundary: :unknown
|
:resource_boundary: :cpu
|
||||||
:weight: 1
|
:weight: 1
|
||||||
:idempotent:
|
:idempotent:
|
||||||
:tags:
|
:tags:
|
||||||
|
|
|
@ -6,6 +6,7 @@ module Gitlab
|
||||||
include ObjectImporter
|
include ObjectImporter
|
||||||
|
|
||||||
tags :exclude_from_kubernetes
|
tags :exclude_from_kubernetes
|
||||||
|
worker_resource_boundary :cpu
|
||||||
|
|
||||||
def representation_class
|
def representation_class
|
||||||
Gitlab::GithubImport::Representation::PullRequest
|
Gitlab::GithubImport::Representation::PullRequest
|
||||||
|
|
|
@ -6,6 +6,7 @@ module Gitlab
|
||||||
include ObjectImporter
|
include ObjectImporter
|
||||||
|
|
||||||
tags :exclude_from_kubernetes
|
tags :exclude_from_kubernetes
|
||||||
|
worker_resource_boundary :cpu
|
||||||
|
|
||||||
def representation_class
|
def representation_class
|
||||||
Gitlab::GithubImport::Representation::PullRequestReview
|
Gitlab::GithubImport::Representation::PullRequestReview
|
||||||
|
|
|
@ -972,11 +972,16 @@ range of inputs, might look like this:
|
||||||
describe "#==" do
|
describe "#==" do
|
||||||
using RSpec::Parameterized::TableSyntax
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
|
let(:one) { 1 }
|
||||||
|
let(:two) { 2 }
|
||||||
|
|
||||||
where(:a, :b, :result) do
|
where(:a, :b, :result) do
|
||||||
1 | 1 | true
|
1 | 1 | true
|
||||||
1 | 2 | false
|
1 | 2 | false
|
||||||
true | true | true
|
true | true | true
|
||||||
true | false | false
|
true | false | false
|
||||||
|
ref(:one) | ref(:one) | true # let variables must be referenced using `ref`
|
||||||
|
ref(:one) | ref(:two) | false
|
||||||
end
|
end
|
||||||
|
|
||||||
with_them do
|
with_them do
|
||||||
|
|
|
@ -83,6 +83,9 @@ The reported licenses might be incomplete or inaccurate.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
WARNING:
|
||||||
|
License Compliance Scanning does not support run-time installation of compilers and interpreters.
|
||||||
|
|
||||||
To run a License Compliance scanning job, you need GitLab Runner with the
|
To run a License Compliance scanning job, you need GitLab Runner with the
|
||||||
[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
|
[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
|
||||||
|
|
||||||
|
|
|
@ -8,23 +8,22 @@ type: reference
|
||||||
# Code Owners **(PREMIUM)**
|
# Code Owners **(PREMIUM)**
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6916) in GitLab 11.3.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6916) in GitLab 11.3.
|
||||||
> - Code Owners for Merge Request approvals was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4418) in GitLab Premium 11.9.
|
> - Code Owners for merge request approvals was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4418) in GitLab Premium 11.9.
|
||||||
> - Moved to GitLab Premium in 13.9.
|
> - Moved to GitLab Premium in 13.9.
|
||||||
|
|
||||||
Code Owners define who owns specific files or paths in a repository.
|
Code Owners define who owns specific files or folders in a repository.
|
||||||
You can require that Code Owners approve a merge request before it's merged.
|
|
||||||
|
|
||||||
Code Owners help you determine who should review or approve merge requests.
|
- The users you define as Code Owners are displayed in the UI when you browse directories.
|
||||||
If you have a question about a file or feature, Code Owners
|
- You can set your merge requests so they must be approved by Code Owners before merge.
|
||||||
can help you find someone who knows the answer.
|
- You can protect a branch and allow only Code Owners to approve changes to the branch.
|
||||||
|
|
||||||
If you don't want to use Code Owners for approvals, you can
|
If you don't want to use Code Owners for approvals, you can
|
||||||
[configure rules](merge_requests/approvals/rules.md) instead.
|
[configure rules](merge_requests/approvals/rules.md) instead.
|
||||||
|
|
||||||
## Set up Code Owners
|
## Set up Code Owners
|
||||||
|
|
||||||
You can specify users or [shared groups](members/share_project_with_groups.md)
|
You can use Code Owners to specify users or [shared groups](members/share_project_with_groups.md)
|
||||||
that are responsible for specific files and directories in a repository.
|
that are responsible for specific files and folders in a repository.
|
||||||
|
|
||||||
To set up Code Owners:
|
To set up Code Owners:
|
||||||
|
|
||||||
|
@ -38,22 +37,28 @@ To set up Code Owners:
|
||||||
1. In the file, enter text that follows one of these patterns:
|
1. In the file, enter text that follows one of these patterns:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
# A member as Code Owner of a file
|
# Code Owners for a file
|
||||||
filename @username
|
filename @username1 @username2
|
||||||
|
|
||||||
# A member as Code Owner of a directory
|
# Code Owners for a directory
|
||||||
directory @username
|
foldername @username1 @username2
|
||||||
|
|
||||||
# All group members as Code Owners of a file
|
# All group members as Code Owners for a file
|
||||||
filename @groupname
|
filename @groupname
|
||||||
|
|
||||||
# All group members as Code Owners of a directory
|
# All group members as Code Owners for a folder
|
||||||
directory @groupname
|
foldername @groupname
|
||||||
```
|
```
|
||||||
|
|
||||||
The Code Owners are displayed in the UI by the files or directory they apply to.
|
The Code Owners are now displayed in the UI.
|
||||||
These owners apply to this branch only. When you add new files to the repository,
|
|
||||||
you should update the `CODEOWNERS` file.
|
Next steps:
|
||||||
|
|
||||||
|
- [Add Code Owners as merge request approvers](merge_requests/approvals/rules.md#code-owners-as-eligible-approvers).
|
||||||
|
- Set up [Code Owner approval on a protected branch](protected_branches.md#require-code-owner-approval-on-a-protected-branch).
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
The Code Owners apply to the current branch only.
|
||||||
|
|
||||||
## When a file matches multiple `CODEOWNERS` entries
|
## When a file matches multiple `CODEOWNERS` entries
|
||||||
|
|
||||||
|
@ -71,42 +76,6 @@ README.md @user1
|
||||||
|
|
||||||
The user that would show for `README.md` would be `@user2`.
|
The user that would show for `README.md` would be `@user2`.
|
||||||
|
|
||||||
## Approvals by Code Owners
|
|
||||||
|
|
||||||
After you've added Code Owners to a project, you can configure it to
|
|
||||||
be used for merge request approvals:
|
|
||||||
|
|
||||||
- As [merge request eligible approvers](merge_requests/approvals/rules.md#code-owners-as-eligible-approvers).
|
|
||||||
- As required approvers for [protected branches](protected_branches.md#require-code-owner-approval-on-a-protected-branch). **(PREMIUM)**
|
|
||||||
|
|
||||||
Developer or higher [permissions](../permissions.md) are required to
|
|
||||||
approve a merge request.
|
|
||||||
|
|
||||||
After it's set, Code Owners are displayed in merge request widgets:
|
|
||||||
|
|
||||||
![MR widget - Code Owners](img/code_owners_mr_widget_v12_4.png)
|
|
||||||
|
|
||||||
While you can use the `CODEOWNERS` file in addition to Merge Request
|
|
||||||
[Approval Rules](merge_requests/approvals/rules.md),
|
|
||||||
you can also use it as the sole driver of merge request approvals
|
|
||||||
without using [Approval Rules](merge_requests/approvals/rules.md):
|
|
||||||
|
|
||||||
1. Create the file in one of the three locations specified above.
|
|
||||||
1. Set the code owners as required approvers for
|
|
||||||
[protected branches](protected_branches.md#require-code-owner-approval-on-a-protected-branch).
|
|
||||||
1. Use [the syntax of Code Owners files](code_owners.md)
|
|
||||||
to specify the actual owners and granular permissions.
|
|
||||||
|
|
||||||
Using Code Owners in conjunction with [protected branches](protected_branches.md#require-code-owner-approval-on-a-protected-branch)
|
|
||||||
prevents any user who is not specified in the `CODEOWNERS` file from pushing
|
|
||||||
changes for the specified files/paths, except those included in the
|
|
||||||
**Allowed to push** column. This allows for a more inclusive push strategy, as
|
|
||||||
administrators don't have to restrict developers from pushing directly to the
|
|
||||||
protected branch, but can restrict pushing to certain files where a review by
|
|
||||||
Code Owners is required.
|
|
||||||
|
|
||||||
[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35097) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5, users and groups who are allowed to push to protected branches do not require a merge request to merge their feature branches. Thus, they can skip merge request approval rules, Code Owners included.
|
|
||||||
|
|
||||||
## Groups as Code Owners
|
## Groups as Code Owners
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53182) in GitLab 12.1.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53182) in GitLab 12.1.
|
||||||
|
@ -154,7 +123,7 @@ file.md @group-x/subgroup-y
|
||||||
file.md @group-x @group-x/subgroup-y
|
file.md @group-x @group-x/subgroup-y
|
||||||
```
|
```
|
||||||
|
|
||||||
### Code Owners Sections **(PREMIUM)**
|
### Code Owners sections **(PREMIUM)**
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12137) in GitLab Premium 13.2 behind a feature flag, enabled by default.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12137) in GitLab Premium 13.2 behind a feature flag, enabled by default.
|
||||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42389) in GitLab 13.4.
|
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42389) in GitLab 13.4.
|
||||||
|
@ -213,18 +182,18 @@ this example, entries defined under the sections "Documentation" and
|
||||||
"DOCUMENTATION" would be combined into one, using the case of the first instance
|
"DOCUMENTATION" would be combined into one, using the case of the first instance
|
||||||
of the section encountered in the file.
|
of the section encountered in the file.
|
||||||
|
|
||||||
When assigned to a section, each code owner rule displayed in merge requests
|
When assigned to a section, each Code Owner rule displayed in merge requests
|
||||||
widget is sorted under a "section" label. In the screenshot below, we can see
|
widget is sorted under a "section" label. In the screenshot below, we can see
|
||||||
the rules for "Groups" and "Documentation" sections:
|
the rules for "Groups" and "Documentation" sections:
|
||||||
|
|
||||||
![MR widget - Sectional Code Owners](img/sectional_code_owners_v13.2.png)
|
![MR widget - Sectional Code Owners](img/sectional_code_owners_v13.2.png)
|
||||||
|
|
||||||
#### Optional Code Owners Sections **(PREMIUM)**
|
#### Optional Code Owners sections **(PREMIUM)**
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232995) in GitLab Premium 13.8 behind a feature flag, enabled by default.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232995) in GitLab Premium 13.8 behind a feature flag, enabled by default.
|
||||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53227) in GitLab 13.9.
|
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53227) in GitLab 13.9.
|
||||||
|
|
||||||
To make a certain section optional, add a code owners section prepended with the
|
To make a certain section optional, add a Code Owners section prepended with the
|
||||||
caret `^` character. Approvals from owners listed in the section are **not** required. For example:
|
caret `^` character. Approvals from owners listed in the section are **not** required. For example:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
|
@ -238,13 +207,13 @@ caret `^` character. Approvals from owners listed in the section are **not** req
|
||||||
*.go @root
|
*.go @root
|
||||||
```
|
```
|
||||||
|
|
||||||
The optional code owners section displays in merge requests under the **Approval Rules** area:
|
The optional Code Owners section displays in merge requests under the **Approval Rules** area:
|
||||||
|
|
||||||
![MR widget - Optional Code Owners Sections](img/optional_code_owners_sections_v13_8.png)
|
![MR widget - Optional Code Owners sections](img/optional_code_owners_sections_v13_8.png)
|
||||||
|
|
||||||
If a section is duplicated in the file, and one of them is marked as optional and the other isn't, the requirement prevails.
|
If a section is duplicated in the file, and one of them is marked as optional and the other isn't, the requirement prevails.
|
||||||
|
|
||||||
For example, the code owners of the "Documentation" section below is still required to approve merge requests:
|
For example, the Code Owners of the "Documentation" section below is still required to approve merge requests:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
[Documentation]
|
[Documentation]
|
||||||
|
@ -260,9 +229,9 @@ For example, the code owners of the "Documentation" section below is still requi
|
||||||
*.txt @root
|
*.txt @root
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional sections in the code owners file are treated as optional only
|
Optional sections in the `CODEOWNERS` file are treated as optional only
|
||||||
when changes are submitted by using merge requests. If a change is submitted directly
|
when changes are submitted by using merge requests. If a change is submitted directly
|
||||||
to the protected branch, approval from code owners is still required, even if the
|
to the protected branch, approval from Code Owners is still required, even if the
|
||||||
section is marked as optional. We plan to change this behavior in a
|
section is marked as optional. We plan to change this behavior in a
|
||||||
[future release](https://gitlab.com/gitlab-org/gitlab/-/issues/297638),
|
[future release](https://gitlab.com/gitlab-org/gitlab/-/issues/297638),
|
||||||
and allow direct pushes to the protected branch for sections marked as optional.
|
and allow direct pushes to the protected branch for sections marked as optional.
|
||||||
|
@ -270,7 +239,7 @@ and allow direct pushes to the protected branch for sections marked as optional.
|
||||||
## Example `CODEOWNERS` file
|
## Example `CODEOWNERS` file
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
# This is an example of a code owners file
|
# This is an example of a CODEOWNERS file
|
||||||
# lines starting with a `#` will be ignored.
|
# lines starting with a `#` will be ignored.
|
||||||
|
|
||||||
# app/ @commented-rule
|
# app/ @commented-rule
|
||||||
|
@ -291,7 +260,7 @@ and allow direct pushes to the protected branch for sections marked as optional.
|
||||||
|
|
||||||
# Multiple codeowners can be specified, separated by spaces or tabs
|
# Multiple codeowners can be specified, separated by spaces or tabs
|
||||||
# In the following case the CODEOWNERS file from the root of the repo
|
# In the following case the CODEOWNERS file from the root of the repo
|
||||||
# has 3 code owners (@multiple @code @owners)
|
# has 3 Code Owners (@multiple @code @owners)
|
||||||
CODEOWNERS @multiple @code @owners
|
CODEOWNERS @multiple @code @owners
|
||||||
|
|
||||||
# Both usernames or email addresses can be used to match
|
# Both usernames or email addresses can be used to match
|
||||||
|
@ -304,11 +273,11 @@ LICENSE @legal this_does_not_match janedoe@gitlab.com
|
||||||
# them as owners for a file
|
# them as owners for a file
|
||||||
README @group @group/with-nested/subgroup
|
README @group @group/with-nested/subgroup
|
||||||
|
|
||||||
# Ending a path in a `/` will specify the code owners for every file
|
# Ending a path in a `/` will specify the Code Owners for every file
|
||||||
# nested in that directory, on any level
|
# nested in that directory, on any level
|
||||||
/docs/ @all-docs
|
/docs/ @all-docs
|
||||||
|
|
||||||
# Ending a path in `/*` will specify code owners for every file in
|
# Ending a path in `/*` will specify Code Owners for every file in
|
||||||
# that directory, but not nested deeper. This will match
|
# that directory, but not nested deeper. This will match
|
||||||
# `docs/index.md` but not `docs/projects/index.md`
|
# `docs/index.md` but not `docs/projects/index.md`
|
||||||
/docs/* @root-docs
|
/docs/* @root-docs
|
||||||
|
@ -321,7 +290,7 @@ lib/ @lib-owner
|
||||||
# repository
|
# repository
|
||||||
/config/ @config-owner
|
/config/ @config-owner
|
||||||
|
|
||||||
# If the path contains spaces, these need to be escaped like this:
|
# If the path contains spaces, escape them like this:
|
||||||
path\ with\ spaces/ @space-owner
|
path\ with\ spaces/ @space-owner
|
||||||
|
|
||||||
# Code Owners section:
|
# Code Owners section:
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
|
@ -179,8 +179,7 @@ When enabled, members who are can push to this branch can also force push.
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13251) in GitLab Premium 12.4.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13251) in GitLab Premium 12.4.
|
||||||
> - [In](https://gitlab.com/gitlab-org/gitlab/-/issues/35097) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5 and later, users and groups who can push to protected branches do not have to use a merge request to merge their feature branches. This means they can skip merge request approval rules.
|
> - [In](https://gitlab.com/gitlab-org/gitlab/-/issues/35097) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5 and later, users and groups who can push to protected branches do not have to use a merge request to merge their feature branches. This means they can skip merge request approval rules.
|
||||||
|
|
||||||
You can require at least one approval by a [Code Owner](code_owners.md) to a file changed by the
|
For a protected branch, you can require at least one approval by a [Code Owner](code_owners.md).
|
||||||
merge request.
|
|
||||||
|
|
||||||
To protect a new branch and enable Code Owner's approval:
|
To protect a new branch and enable Code Owner's approval:
|
||||||
|
|
||||||
|
@ -201,6 +200,16 @@ When enabled, all merge requests for these branches require approval
|
||||||
by a Code Owner per matched rule before they can be merged.
|
by a Code Owner per matched rule before they can be merged.
|
||||||
Additionally, direct pushes to the protected branch are denied if a rule is matched.
|
Additionally, direct pushes to the protected branch are denied if a rule is matched.
|
||||||
|
|
||||||
|
Any user who is not specified in the `CODEOWNERS` file cannot push
|
||||||
|
changes for the specified files or paths, unless they are specifically allowed to.
|
||||||
|
You don't have to restrict developers from pushing directly to the
|
||||||
|
protected branch. Instead, you can restrict pushing to certain files where a review by
|
||||||
|
Code Owners is required.
|
||||||
|
|
||||||
|
In [GitLab Premium 13.5 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/35097), users and groups
|
||||||
|
who are allowed to push to protected branches do not need a merge request to merge their feature branches.
|
||||||
|
Thus, they can skip merge request approval rules, Code Owners included.
|
||||||
|
|
||||||
## Run pipelines on protected branches
|
## Run pipelines on protected branches
|
||||||
|
|
||||||
The permission to merge or push to protected branches defines
|
The permission to merge or push to protected branches defines
|
||||||
|
|
|
@ -14,11 +14,11 @@ module QA
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attribute :id
|
attributes :id,
|
||||||
attribute :iid
|
:iid,
|
||||||
attribute :assignee_ids
|
:assignee_ids,
|
||||||
attribute :labels
|
:labels,
|
||||||
attribute :title
|
:title
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@assignee_ids = []
|
@assignee_ids = []
|
||||||
|
@ -41,13 +41,21 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_get_path
|
def api_get_path
|
||||||
"/projects/#{project.id}/issues/#{id}"
|
"/projects/#{project.id}/issues/#{iid}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_post_path
|
def api_post_path
|
||||||
"/projects/#{project.id}/issues"
|
"/projects/#{project.id}/issues"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def api_put_path
|
||||||
|
"/projects/#{project.id}/issues/#{iid}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def api_comments_path
|
||||||
|
"#{api_get_path}/notes"
|
||||||
|
end
|
||||||
|
|
||||||
def api_post_body
|
def api_post_body
|
||||||
{
|
{
|
||||||
assignee_ids: assignee_ids,
|
assignee_ids: assignee_ids,
|
||||||
|
@ -59,20 +67,28 @@ module QA
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_put_path
|
|
||||||
"/projects/#{project.id}/issues/#{iid}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_issue_assignees(assignee_ids:)
|
def set_issue_assignees(assignee_ids:)
|
||||||
put_body = { assignee_ids: assignee_ids }
|
put_body = { assignee_ids: assignee_ids }
|
||||||
response = put Runtime::API::Request.new(api_client, api_put_path).url, put_body
|
response = put Runtime::API::Request.new(api_client, api_put_path).url, put_body
|
||||||
|
|
||||||
unless response.code == HTTP_STATUS_OK
|
unless response.code == HTTP_STATUS_OK
|
||||||
raise ResourceUpdateFailedError, "Could not update issue assignees to #{assignee_ids}. Request returned (#{response.code}): `#{response}`."
|
raise(
|
||||||
|
ResourceUpdateFailedError,
|
||||||
|
"Could not update issue assignees to #{assignee_ids}. Request returned (#{response.code}): `#{response}`."
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
QA::Runtime::Logger.debug("Successfully updated issue assignees to #{assignee_ids}")
|
QA::Runtime::Logger.debug("Successfully updated issue assignees to #{assignee_ids}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get issue comments
|
||||||
|
#
|
||||||
|
# @return [Array]
|
||||||
|
def comments(auto_paginate: false)
|
||||||
|
return parse_body(api_get_from(api_comments_path)) unless auto_paginate
|
||||||
|
|
||||||
|
auto_paginated_response(Runtime::API::Request.new(api_client, api_comments_path, per_page: '100').url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -160,9 +160,10 @@ module QA
|
||||||
# Get MR comments
|
# Get MR comments
|
||||||
#
|
#
|
||||||
# @return [Array]
|
# @return [Array]
|
||||||
def comments
|
def comments(auto_paginate: false)
|
||||||
response = get(Runtime::API::Request.new(api_client, api_comments_path).url)
|
return parse_body(api_get_from(api_comments_path)) unless auto_paginate
|
||||||
parse_body(response)
|
|
||||||
|
auto_paginated_response(Runtime::API::Request.new(api_client, api_comments_path, per_page: '100').url)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -32,7 +32,7 @@ module QA
|
||||||
end
|
end
|
||||||
|
|
||||||
attribute :path_with_namespace do
|
attribute :path_with_namespace do
|
||||||
"#{sandbox_path}#{group.path}/#{name}" if group
|
"#{group.full_path}/#{name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
alias_method :full_path, :path_with_namespace
|
alias_method :full_path, :path_with_namespace
|
||||||
|
@ -268,14 +268,16 @@ module QA
|
||||||
result[:import_status]
|
result[:import_status]
|
||||||
end
|
end
|
||||||
|
|
||||||
def commits
|
def commits(auto_paginate: false)
|
||||||
response = get(request_url(api_commits_path))
|
return parse_body(api_get_from(api_commits_path)) unless auto_paginate
|
||||||
parse_body(response)
|
|
||||||
|
auto_paginated_response(request_url(api_commits_path, per_page: '100'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_requests
|
def merge_requests(auto_paginate: false)
|
||||||
response = get(request_url(api_merge_requests_path))
|
return parse_body(api_get_from(api_merge_requests_path)) unless auto_paginate
|
||||||
parse_body(response)
|
|
||||||
|
auto_paginated_response(request_url(api_merge_requests_path, per_page: '100'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge_request_with_title(title)
|
def merge_request_with_title(title)
|
||||||
|
@ -299,9 +301,10 @@ module QA
|
||||||
parse_body(response)
|
parse_body(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
def repository_branches
|
def repository_branches(auto_paginate: false)
|
||||||
response = get(request_url(api_repository_branches_path))
|
return parse_body(api_get_from(api_repository_branches_path)) unless auto_paginate
|
||||||
parse_body(response)
|
|
||||||
|
auto_paginated_response(request_url(api_repository_branches_path, per_page: '100'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def repository_tags
|
def repository_tags
|
||||||
|
@ -324,19 +327,22 @@ module QA
|
||||||
parse_body(response)
|
parse_body(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
def issues
|
def issues(auto_paginate: false)
|
||||||
response = get(request_url(api_issues_path))
|
return parse_body(api_get_from(api_issues_path)) unless auto_paginate
|
||||||
parse_body(response)
|
|
||||||
|
auto_paginated_response(request_url(api_issues_path, per_page: '100'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def labels
|
def labels(auto_paginate: false)
|
||||||
response = get(request_url(api_labels_path))
|
return parse_body(api_get_from(api_labels_path)) unless auto_paginate
|
||||||
parse_body(response)
|
|
||||||
|
auto_paginated_response(request_url(api_labels_path, per_page: '100'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def milestones
|
def milestones(auto_paginate: false)
|
||||||
response = get(request_url(api_milestones_path))
|
return parse_body(api_get_from(api_milestones_path)) unless auto_paginate
|
||||||
parse_body(response)
|
|
||||||
|
auto_paginated_response(request_url(api_milestones_path, per_page: '100'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def wikis
|
def wikis
|
||||||
|
|
|
@ -0,0 +1,286 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'octokit'
|
||||||
|
require 'parallel'
|
||||||
|
|
||||||
|
# rubocop:disable Rails/Pluck
|
||||||
|
module QA
|
||||||
|
# Only executes in custom job/pipeline
|
||||||
|
RSpec.describe 'Manage', :github, :requires_admin, only: { job: 'large-github-import' } do
|
||||||
|
describe 'Project import' do
|
||||||
|
let(:api_client) { Runtime::API::Client.as_admin }
|
||||||
|
let(:group) do
|
||||||
|
Resource::Group.fabricate_via_api! do |resource|
|
||||||
|
resource.api_client = api_client
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:user) do
|
||||||
|
Resource::User.fabricate_via_api! do |resource|
|
||||||
|
resource.api_client = api_client
|
||||||
|
resource.hard_delete_on_api_removal = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:differ) { RSpec::Support::Differ.new(color: true) }
|
||||||
|
let(:github_repo) { 'allure-framework/allure-ruby' }
|
||||||
|
|
||||||
|
let(:github_client) do
|
||||||
|
Octokit.middleware = Faraday::RackBuilder.new do |builder|
|
||||||
|
builder.response(:logger, Runtime::Logger.logger, headers: false, bodies: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
Octokit::Client.new(access_token: Runtime::Env.github_access_token, auto_paginate: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:gh_branches) { github_client.branches(github_repo).map(&:name) }
|
||||||
|
let(:gh_commits) { github_client.commits(github_repo).map(&:sha) }
|
||||||
|
let(:gh_repo) { github_client.repository(github_repo) }
|
||||||
|
let(:gh_labels) { github_client.labels(github_repo) }
|
||||||
|
let(:gh_milestones) { github_client.list_milestones(github_repo, state: 'all') }
|
||||||
|
|
||||||
|
let(:gh_all_issues) do
|
||||||
|
github_client.list_issues(github_repo, state: 'all')
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:gh_prs) do
|
||||||
|
gh_all_issues.select(&:pull_request).each_with_object({}) do |pr, hash|
|
||||||
|
hash[pr.title] = {
|
||||||
|
body: pr.body || '',
|
||||||
|
comments: [*gh_pr_comments[pr.html_url], *gh_issue_comments[pr.html_url]].compact.sort
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:gh_issues) do
|
||||||
|
gh_all_issues.reject(&:pull_request).each_with_object({}) do |issue, hash|
|
||||||
|
hash[issue.title] = {
|
||||||
|
body: issue.body || '',
|
||||||
|
comments: gh_issue_comments[issue.html_url]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:gh_issue_comments) do
|
||||||
|
github_client.issues_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
|
||||||
|
hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:gh_pr_comments) do
|
||||||
|
github_client.pull_requests_comments(github_repo).each_with_object(Hash.new { |h, k| h[k] = [] }) do |c, hash|
|
||||||
|
hash[c.html_url.gsub(/\#\S+/, "")] << c.body # use base html url as key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:imported_project) do
|
||||||
|
Resource::ProjectImportedFromGithub.fabricate_via_api! do |project|
|
||||||
|
project.add_name_uuid = false
|
||||||
|
project.name = 'imported-project'
|
||||||
|
project.group = group
|
||||||
|
project.github_personal_access_token = Runtime::Env.github_access_token
|
||||||
|
project.github_repository_path = github_repo
|
||||||
|
project.api_client = api_client
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'imports large Github repo via api' do
|
||||||
|
imported_project # import the project
|
||||||
|
fetch_github_objects # fetch all objects right after import has started
|
||||||
|
|
||||||
|
expect { imported_project.reload!.import_status }.to eventually_eq('finished').within(
|
||||||
|
duration: 3600,
|
||||||
|
interval: 30
|
||||||
|
)
|
||||||
|
|
||||||
|
aggregate_failures do
|
||||||
|
verify_repository_import
|
||||||
|
verify_merge_requests_import
|
||||||
|
verify_issues_import
|
||||||
|
verify_labels_import
|
||||||
|
verify_milestones_import
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Persist all objects from repository being imported
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def fetch_github_objects
|
||||||
|
Runtime::Logger.debug("Fetching objects for github repo: '#{github_repo}'")
|
||||||
|
|
||||||
|
gh_repo
|
||||||
|
gh_branches
|
||||||
|
gh_commits
|
||||||
|
gh_prs
|
||||||
|
gh_issues
|
||||||
|
gh_labels
|
||||||
|
gh_milestones
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verify repository imported correctly
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def verify_repository_import
|
||||||
|
branches = imported_project.repository_branches(auto_paginate: true).map { |b| b[:name] }
|
||||||
|
commits = imported_project.commits(auto_paginate: true).map { |c| c[:id] }
|
||||||
|
|
||||||
|
expect(imported_project.description).to eq(gh_repo.description)
|
||||||
|
# check via include, importer creates more branches
|
||||||
|
# https://gitlab.com/gitlab-org/gitlab/-/issues/332711
|
||||||
|
expect(branches).to include(*gh_branches)
|
||||||
|
expect(commits).to match_array(gh_commits)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verify imported merge requests and mr issues
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def verify_merge_requests_import
|
||||||
|
verify_mrs_or_issues('mrs')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verify imported issues and issue comments
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def verify_issues_import
|
||||||
|
verify_mrs_or_issues('issues')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verify imported labels
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def verify_labels_import
|
||||||
|
labels = imported_project.labels(auto_paginate: true).map { |label| label.slice(:name, :color) }
|
||||||
|
actual_labels = gh_labels.map { |label| { name: label.name, color: "##{label.color}" } }
|
||||||
|
|
||||||
|
expect(labels.length).to eq(actual_labels.length)
|
||||||
|
expect(labels).to match_array(actual_labels)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verify milestones import
|
||||||
|
#
|
||||||
|
# @return [void]
|
||||||
|
def verify_milestones_import
|
||||||
|
milestones = imported_project.milestones(auto_paginate: true).map { |ms| ms.slice(:title, :description) }
|
||||||
|
actual_milestones = gh_milestones.map { |ms| { title: ms.title, description: ms.description } }
|
||||||
|
|
||||||
|
expect(milestones.length).to eq(actual_milestones.length)
|
||||||
|
expect(milestones).to match_array(actual_milestones)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Verify imported mrs or issues
|
||||||
|
#
|
||||||
|
# @param [String] type verification object, 'mrs' or 'issues'
|
||||||
|
# @return [void]
|
||||||
|
def verify_mrs_or_issues(type)
|
||||||
|
msg = ->(title) { "expected #{type} with title '#{title}' to have" }
|
||||||
|
expected = type == 'mrs' ? mrs : gl_issues
|
||||||
|
actual = type == 'mrs' ? gh_prs : gh_issues
|
||||||
|
|
||||||
|
expect(expected.keys).to match_array(actual.keys)
|
||||||
|
actual.each do |title, actual_item|
|
||||||
|
expected_item = expected[title]
|
||||||
|
|
||||||
|
expect(expected_item).to be_truthy, "#{msg.call(title)} been imported"
|
||||||
|
next unless expected_item
|
||||||
|
|
||||||
|
expect(expected_item[:body]).to(
|
||||||
|
include(actual_item[:body]),
|
||||||
|
"#{msg.call(title)} same description. #{diff(expected_item[:body], actual_item[:body])}"
|
||||||
|
)
|
||||||
|
expect(expected_item[:comments].length).to(
|
||||||
|
eq(actual_item[:comments].length),
|
||||||
|
"#{msg.call(title)} same amount of comments"
|
||||||
|
)
|
||||||
|
expect(expected_item[:comments]).to match_array(actual_item[:comments])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Imported project merge requests
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def mrs
|
||||||
|
@mrs ||= begin
|
||||||
|
imported_mrs = imported_project.merge_requests(auto_paginate: true)
|
||||||
|
# fetch comments in parallel since we need to do it for each mr separately
|
||||||
|
mrs_hashes = Parallel.map(imported_mrs, in_processes: 5) do |mr|
|
||||||
|
resource = Resource::MergeRequest.init do |resource|
|
||||||
|
resource.project = imported_project
|
||||||
|
resource.iid = mr[:iid]
|
||||||
|
resource.api_client = api_client
|
||||||
|
end
|
||||||
|
|
||||||
|
{
|
||||||
|
title: mr[:title],
|
||||||
|
body: mr[:description],
|
||||||
|
comments: resource.comments(auto_paginate: true)
|
||||||
|
# remove system notes
|
||||||
|
.reject { |c| c[:system] || c[:body].match?(/^(\*\*Review:\*\*)|(\*Merged by:).*/) }
|
||||||
|
.map { |c| sanitize(c[:body]) }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
mrs_hashes.each_with_object({}) do |mr, hash|
|
||||||
|
hash[mr[:title]] = {
|
||||||
|
body: mr[:body],
|
||||||
|
comments: mr[:comments]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Imported project issues
|
||||||
|
#
|
||||||
|
# @return [Hash]
|
||||||
|
def gl_issues
|
||||||
|
@gl_issues ||= begin
|
||||||
|
imported_issues = imported_project.issues(auto_paginate: true)
|
||||||
|
# fetch comments in parallel since we need to do it for each mr separately
|
||||||
|
issue_hashes = Parallel.map(imported_issues, in_processes: 5) do |issue|
|
||||||
|
resource = Resource::Issue.init do |issue_resource|
|
||||||
|
issue_resource.project = imported_project
|
||||||
|
issue_resource.iid = issue[:iid]
|
||||||
|
issue_resource.api_client = api_client
|
||||||
|
end
|
||||||
|
|
||||||
|
{
|
||||||
|
title: issue[:title],
|
||||||
|
body: issue[:description],
|
||||||
|
comments: resource.comments(auto_paginate: true).map { |c| sanitize(c[:body]) }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
issue_hashes.each_with_object({}) do |issue, hash|
|
||||||
|
hash[issue[:title]] = {
|
||||||
|
body: issue[:body],
|
||||||
|
comments: issue[:comments]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Remove added prefixes by importer
|
||||||
|
#
|
||||||
|
# @param [String] body
|
||||||
|
# @return [String]
|
||||||
|
def sanitize(body)
|
||||||
|
body.gsub(/\*Created by: \S+\*\n\n/, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Diff of 2 objects
|
||||||
|
#
|
||||||
|
# @param [Object] actual
|
||||||
|
# @param [Object] expected
|
||||||
|
# @return [String]
|
||||||
|
def diff(actual, expected)
|
||||||
|
"diff:\n#{differ.diff(actual, expected)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# rubocop:enable Rails/Pluck
|
|
@ -79,11 +79,20 @@ module QA
|
||||||
error.response
|
error.response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def auto_paginated_response(url)
|
||||||
|
pages = []
|
||||||
|
with_paginated_response_body(url) { |response| pages << response }
|
||||||
|
|
||||||
|
pages.flatten
|
||||||
|
end
|
||||||
|
|
||||||
def with_paginated_response_body(url)
|
def with_paginated_response_body(url)
|
||||||
loop do
|
loop do
|
||||||
response = get(url)
|
response = get(url)
|
||||||
|
page, pages = response.headers.values_at(:x_page, :x_total_pages)
|
||||||
|
api_endpoint = url.match(%r{v4/(\S+)\?})[1]
|
||||||
|
|
||||||
QA::Runtime::Logger.debug("Fetching page #{response.headers[:x_page]} of #{response.headers[:x_total_pages]}...")
|
QA::Runtime::Logger.debug("Fetching page (#{page}/#{pages}) for '#{api_endpoint}' ...") unless pages.to_i <= 1
|
||||||
|
|
||||||
yield parse_body(response)
|
yield parse_body(response)
|
||||||
|
|
||||||
|
@ -96,7 +105,7 @@ module QA
|
||||||
|
|
||||||
def pagination_links(response)
|
def pagination_links(response)
|
||||||
response.headers[:link].split(',').map do |link|
|
response.headers[:link].split(',').map do |link|
|
||||||
match = link.match(/\<(?<url>.*)\>\; rel=\"(?<rel>\w+)\"/)
|
match = link.match(/<(?<url>.*)>; rel="(?<rel>\w+)"/)
|
||||||
break nil unless match
|
break nil unless match
|
||||||
|
|
||||||
{ url: match[:url], rel: match[:rel] }
|
{ url: match[:url], rel: match[:rel] }
|
||||||
|
|
|
@ -24,6 +24,7 @@ module Matchers
|
||||||
chain(:within) do |options = {}|
|
chain(:within) do |options = {}|
|
||||||
@duration = options[:duration]
|
@duration = options[:duration]
|
||||||
@attempts = options[:attempts]
|
@attempts = options[:attempts]
|
||||||
|
@interval = options[:interval]
|
||||||
end
|
end
|
||||||
|
|
||||||
def supports_block_expectations?
|
def supports_block_expectations?
|
||||||
|
@ -55,7 +56,7 @@ module Matchers
|
||||||
QA::Support::Retrier.retry_until(
|
QA::Support::Retrier.retry_until(
|
||||||
max_attempts: @attempts,
|
max_attempts: @attempts,
|
||||||
max_duration: @duration,
|
max_duration: @duration,
|
||||||
sleep_interval: 0.5
|
sleep_interval: @interval || 0.5
|
||||||
) do
|
) do
|
||||||
public_send(expectation_name, actual)
|
public_send(expectation_name, actual)
|
||||||
rescue RSpec::Expectations::ExpectationNotMetError, QA::Resource::ApiFabricator::ResourceNotFoundError
|
rescue RSpec::Expectations::ExpectationNotMetError, QA::Resource::ApiFabricator::ResourceNotFoundError
|
||||||
|
|
|
@ -13,6 +13,22 @@ RSpec.describe 'File blob', :js do
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_file(file_name, content)
|
||||||
|
project.add_maintainer(project.creator)
|
||||||
|
|
||||||
|
Files::CreateService.new(
|
||||||
|
project,
|
||||||
|
project.creator,
|
||||||
|
start_branch: 'master',
|
||||||
|
branch_name: 'master',
|
||||||
|
commit_message: "Add #{file_name}",
|
||||||
|
file_path: file_name,
|
||||||
|
file_content: <<-SPEC.strip_heredoc
|
||||||
|
#{content}
|
||||||
|
SPEC
|
||||||
|
).execute
|
||||||
|
end
|
||||||
|
|
||||||
context 'Ruby file' do
|
context 'Ruby file' do
|
||||||
before do
|
before do
|
||||||
visit_blob('files/ruby/popen.rb')
|
visit_blob('files/ruby/popen.rb')
|
||||||
|
@ -785,6 +801,255 @@ RSpec.describe 'File blob', :js do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'CONTRIBUTING.md' do
|
||||||
|
before do
|
||||||
|
file_name = 'CONTRIBUTING.md'
|
||||||
|
|
||||||
|
create_file(file_name, '## Contribution guidelines')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("After you've reviewed these contribution guidelines, you'll be all set to contribute to this project.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'CHANGELOG.md' do
|
||||||
|
before do
|
||||||
|
file_name = 'CHANGELOG.md'
|
||||||
|
|
||||||
|
create_file(file_name, '## Changelog for v1.0.0')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("To find the state of this project's repository at the time of any of these versions, check out the tags.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Cargo.toml' do
|
||||||
|
before do
|
||||||
|
file_name = 'Cargo.toml'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
[package]
|
||||||
|
name = "hello_world" # the name of the package
|
||||||
|
version = "0.1.0" # the current version, obeying semver
|
||||||
|
authors = ["Alice <a@example.com>", "Bob <b@example.com>"]
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using Cargo.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Cartfile' do
|
||||||
|
before do
|
||||||
|
file_name = 'Cartfile'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
gitlab "Alamofire/Alamofire" == 4.9.0
|
||||||
|
gitlab "Alamofire/AlamofireImage" ~> 3.4
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using Carthage.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'composer.json' do
|
||||||
|
before do
|
||||||
|
file_name = 'composer.json'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
{
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using Composer.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Gemfile' do
|
||||||
|
before do
|
||||||
|
file_name = 'Gemfile'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
# Gems here
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using Bundler.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Godeps.json' do
|
||||||
|
before do
|
||||||
|
file_name = 'Godeps.json'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
{
|
||||||
|
"GoVersion": "go1.6"
|
||||||
|
}
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using godep.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'go.mod' do
|
||||||
|
before do
|
||||||
|
file_name = 'go.mod'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
module example.com/mymodule
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using Go Modules.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'package.json' do
|
||||||
|
before do
|
||||||
|
file_name = 'package.json'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
{
|
||||||
|
"name": "my-awesome-package",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using npm.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'podfile' do
|
||||||
|
before do
|
||||||
|
file_name = 'podfile'
|
||||||
|
|
||||||
|
create_file(file_name, 'platform :ios, "8.0"')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using CocoaPods.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'test.podspec' do
|
||||||
|
before do
|
||||||
|
file_name = 'test.podspec'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
Pod::Spec.new do |s|
|
||||||
|
s.name = "TensorFlowLiteC"
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using CocoaPods.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'JSON.podspec.json' do
|
||||||
|
before do
|
||||||
|
file_name = 'JSON.podspec.json'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
{
|
||||||
|
"name": "JSON"
|
||||||
|
}
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using CocoaPods.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'requirements.txt' do
|
||||||
|
before do
|
||||||
|
file_name = 'requirements.txt'
|
||||||
|
|
||||||
|
create_file(file_name, 'Project requirements')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using pip.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'yarn.lock' do
|
||||||
|
before do
|
||||||
|
file_name = 'yarn.lock'
|
||||||
|
|
||||||
|
create_file(file_name, '
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
')
|
||||||
|
visit_blob(file_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an auxiliary viewer' do
|
||||||
|
aggregate_failures do
|
||||||
|
expect(page).to have_content("This project manages its dependencies using Yarn.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'realtime pipelines' do
|
context 'realtime pipelines' do
|
||||||
|
|
Loading…
Reference in a new issue