Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
68b6fd7fb2
commit
81373fe07e
|
@ -47,6 +47,7 @@
|
|||
- rspec_profiling/
|
||||
- tmp/capybara/
|
||||
- tmp/memory_test/
|
||||
- tmp/feature_flags/
|
||||
- log/*.log
|
||||
reports:
|
||||
junit: junit_rspec.xml
|
||||
|
@ -221,11 +222,6 @@ static-analysis:
|
|||
script:
|
||||
- run_timed_command "retry yarn install --frozen-lockfile"
|
||||
- scripts/static-analysis
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
when: always
|
||||
paths:
|
||||
- tmp/feature_flags/
|
||||
|
||||
static-analysis as-if-foss:
|
||||
extends:
|
||||
|
@ -464,9 +460,25 @@ rspec:coverage:
|
|||
rspec:feature-flags:
|
||||
extends:
|
||||
- .coverage-base
|
||||
- .static-analysis:rules:ee-and-foss
|
||||
- .rails:rules:rspec-feature-flags
|
||||
stage: post-test
|
||||
needs: ["static-analysis"]
|
||||
# We cannot use needs since it would mean needing 84 jobs (since most are parallelized)
|
||||
# so we use `dependencies` here.
|
||||
dependencies:
|
||||
- setup-test-env
|
||||
- rspec migration pg12
|
||||
- rspec unit pg12
|
||||
- rspec integration pg12
|
||||
- rspec system pg12
|
||||
- rspec-ee migration pg12
|
||||
- rspec-ee unit pg12
|
||||
- rspec-ee integration pg12
|
||||
- rspec-ee system pg12
|
||||
- rspec-ee unit pg12 geo
|
||||
- rspec-ee integration pg12 geo
|
||||
- rspec-ee system pg12 geo
|
||||
- memory-static
|
||||
- memory-on-boot
|
||||
script:
|
||||
- !reference [.minimal-bundle-install, script]
|
||||
- if [ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]; then
|
||||
|
|
|
@ -932,6 +932,14 @@
|
|||
- <<: *if-merge-request-title-run-all-rspec
|
||||
when: always
|
||||
|
||||
.rails:rules:rspec-feature-flags:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-default-branch-schedule-2-hourly
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request-title-run-all-rspec
|
||||
|
||||
.rails:rules:default-branch-schedule-nightly--code-backstage:
|
||||
rules:
|
||||
- <<: *if-default-branch-schedule-nightly
|
||||
|
|
|
@ -49,9 +49,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
before_action only: :show do
|
||||
real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(:real_time_issue_sidebar, @project)
|
||||
real_time_feature_flag = :real_time_issue_sidebar
|
||||
real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(real_time_feature_flag, @project)
|
||||
|
||||
push_to_gon_attributes(:features, :real_time_issue_sidebar, real_time_enabled)
|
||||
push_to_gon_attributes(:features, real_time_feature_flag, real_time_enabled)
|
||||
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:labels_widget, @project, default_enabled: :yaml)
|
||||
|
|
|
@ -23,6 +23,7 @@ ActiveSupport::Inflector.inflections do |inflect|
|
|||
group_wiki_repository_registry
|
||||
job_artifact_registry
|
||||
lfs_object_registry
|
||||
merge_request_diff_registry
|
||||
package_file_registry
|
||||
pipeline_artifact_registry
|
||||
project_auto_devops
|
||||
|
|
|
@ -68,3 +68,27 @@ end
|
|||
if helper.security_mr? && feature_flag_file_added?
|
||||
fail "Feature flags are discouraged from security merge requests. Read the [security documentation](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/utilities/feature_flags.md) for details."
|
||||
end
|
||||
|
||||
if feature_flag_file_added_or_removed?
|
||||
new_mr_title = helper.mr_title.dup
|
||||
new_mr_title << ' [RUN ALL RSPEC]' unless helper.run_all_rspec_mr?
|
||||
new_mr_title << ' [RUN AS-IF-FOSS]' unless helper.run_as_if_foss_mr?
|
||||
|
||||
changes = {}
|
||||
changes[:add_labels] = FEATURE_FLAG_LABEL unless helper.mr_has_labels?(FEATURE_FLAG_LABEL)
|
||||
|
||||
if new_mr_title != helper.mr_title
|
||||
changes[:title] = new_mr_title
|
||||
else
|
||||
message "You're adding or removing a feature flag, your MR title needs to include `[RUN ALL RSPEC] [RUN AS-IF-FOSS]` (we may have updated it automatically for you and started a new MR pipeline) to ensure everything is covered."
|
||||
end
|
||||
|
||||
if changes.any?
|
||||
gitlab.api.update_merge_request(
|
||||
gitlab.mr_json['project_id'],
|
||||
gitlab.mr_json['iid'],
|
||||
**changes
|
||||
)
|
||||
gitlab.api.post("/projects/#{gitlab.mr_json['project_id']}/merge_requests/#{gitlab.mr_json['iid']}/pipelines")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,8 +9,7 @@ SPECIALIZATIONS = {
|
|||
docs: 'documentation',
|
||||
qa: 'QA',
|
||||
engineering_productivity: 'Engineering Productivity',
|
||||
ci_template: 'ci::templates',
|
||||
feature_flag: 'feature flag'
|
||||
ci_template: 'ci::templates'
|
||||
}.freeze
|
||||
|
||||
labels_to_add = project_helper.changes_by_category.each_with_object([]) do |(category, _changes), memo|
|
||||
|
|
|
@ -229,11 +229,15 @@ configuration option in `gitlab.yml`. These metrics are served from the
|
|||
| `global_search_bulk_cron_queue_size` | Gauge | 12.10 | Number of database records waiting to be synchronized to Elasticsearch | |
|
||||
| `global_search_awaiting_indexing_queue_size` | Gauge | 13.2 | Number of database updates waiting to be synchronized to Elasticsearch while indexing is paused | |
|
||||
| `geo_merge_request_diffs` | Gauge | 13.4 | Number of merge request diffs on primary | `url` |
|
||||
| `geo_merge_request_diffs_checksummed` | Gauge | 13.4 | Number of merge request diffs checksummed on primary | `url` |
|
||||
| `geo_merge_request_diffs_checksum_total` | Gauge | 13.12 | Number of merge request diffs tried to checksum on primary | `url` |
|
||||
| `geo_merge_request_diffs_checksummed` | Gauge | 13.4 | Number of merge request diffs successfully checksummed on primary | `url` |
|
||||
| `geo_merge_request_diffs_checksum_failed` | Gauge | 13.4 | Number of merge request diffs failed to calculate the checksum on primary | `url` |
|
||||
| `geo_merge_request_diffs_synced` | Gauge | 13.4 | Number of syncable merge request diffs synced on secondary | `url` |
|
||||
| `geo_merge_request_diffs_failed` | Gauge | 13.4 | Number of syncable merge request diffs failed to sync on secondary | `url` |
|
||||
| `geo_merge_request_diffs_registry` | Gauge | 13.4 | Number of merge request diffs in the registry | `url` |
|
||||
| `geo_merge_request_diffs_verification_total` | Gauge | 13.12 | Number of merge request diffs verifications tried on secondary | `url` |
|
||||
| `geo_merge_request_diffs_verified` | Gauge | 13.12 | Number of merge request diffs verified on secondary | `url` |
|
||||
| `geo_merge_request_diffs_verification_failed` | Gauge | 13.12 | Number of merge request diffs verifications failed on secondary | `url` |
|
||||
| `geo_snippet_repositories` | Gauge | 13.4 | Number of snippets on primary | `url` |
|
||||
| `geo_snippet_repositories_checksummed` | Gauge | 13.4 | Number of snippets checksummed on primary | `url` |
|
||||
| `geo_snippet_repositories_checksum_failed` | Gauge | 13.4 | Number of snippets failed to calculate the checksum on primary | `url` |
|
||||
|
|
|
@ -8,139 +8,34 @@ description: "Introduction to using Git through the command line."
|
|||
|
||||
# Start using Git on the command line **(FREE)**
|
||||
|
||||
[Git](https://git-scm.com/) is an open-source distributed version control system designed to
|
||||
handle everything from small to very large projects with speed and efficiency. GitLab is built
|
||||
[Git](https://git-scm.com/) is an open-source distributed version control system. GitLab is built
|
||||
on top of Git.
|
||||
|
||||
While GitLab has a powerful user interface from which you can do a great amount of Git operations
|
||||
directly in the browser, the command line is required for advanced tasks.
|
||||
You can do many Git operations directly in GitLab. However, the command line is required for advanced tasks,
|
||||
like fixing complex merge conflicts or rolling back commits.
|
||||
|
||||
For example, if you need to fix complex merge conflicts, rebase branches,
|
||||
or undo and roll back commits, you must use Git from
|
||||
the command line and then push your changes to the remote server.
|
||||
|
||||
This guide helps you get started with Git through the command line and can be a reference
|
||||
for Git commands in the future. If you're only looking for a quick reference of Git commands, you
|
||||
can download the GitLab [Git Cheat Sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf).
|
||||
For a quick reference of Git commands, download a [Git Cheat Sheet](https://about.gitlab.com/images/press/git-cheat-sheet.pdf).
|
||||
|
||||
For more information about the advantages of working with Git and GitLab:
|
||||
|
||||
- <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch the [GitLab Source Code Management Walkthrough](https://www.youtube.com/watch?v=wTQ3aXJswtM) video.
|
||||
- Learn how [GitLab became the backbone of Worldline](https://about.gitlab.com/customers/worldline/)'s development environment.
|
||||
- Learn how [GitLab became the backbone of the Worldline](https://about.gitlab.com/customers/worldline/) development environment.
|
||||
|
||||
NOTE:
|
||||
To help you visualize what you're doing locally, there are
|
||||
[Git GUI apps](https://git-scm.com/download/gui/) you can install.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You don't need a GitLab account to use Git locally, but for the purpose of this guide we
|
||||
recommend registering and signing into your account before starting. Some commands need a
|
||||
connection between the files on your computer and their version on a remote server.
|
||||
|
||||
You must also open a [terminal](#open-a-terminal) and have
|
||||
[Git installed](#install-git) on your computer.
|
||||
|
||||
### Open a terminal
|
||||
|
||||
To execute Git commands on your computer, you must open a terminal (also known as command
|
||||
prompt, command shell, and command line) of your preference. Here are some suggestions:
|
||||
|
||||
- For macOS users:
|
||||
- Built-in: [Terminal](https://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line). Press <kbd>⌘ command</kbd> + <kbd>space</kbd> and type "terminal" to find it.
|
||||
- [iTerm2](https://iterm2.com/), which you can integrate with [zsh](https://git-scm.com/book/id/v2/Appendix-A%3A-Git-in-Other-Environments-Git-in-Zsh) and [oh my zsh](https://ohmyz.sh/) for color highlighting, among other handy features for Git users.
|
||||
- For Windows users:
|
||||
- Built-in: `cmd`. Click the search icon on the bottom navigation bar on Windows and type `cmd` to find it.
|
||||
- [PowerShell](https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/install/installing-windows-powershell?view=powershell-7): a Windows "powered up" shell, from which you can execute a greater number of commands.
|
||||
- Git Bash: it comes built into [Git for Windows](https://gitforwindows.org/).
|
||||
- For Linux users:
|
||||
- Built-in: [Linux Terminal](https://www.howtogeek.com/140679/beginner-geek-how-to-start-using-the-linux-terminal/).
|
||||
|
||||
### Install Git
|
||||
|
||||
Open a terminal and run the following command to check if Git is already installed in your
|
||||
computer:
|
||||
|
||||
```shell
|
||||
git --version
|
||||
```
|
||||
|
||||
If you have Git installed, the output is:
|
||||
|
||||
```shell
|
||||
git version X.Y.Z
|
||||
```
|
||||
|
||||
If your computer doesn't recognize `git` as a command, you must [install Git](../topics/git/how_to_install_git/index.md).
|
||||
After that, run `git --version` again to verify whether it was correctly installed.
|
||||
|
||||
## Configure Git
|
||||
|
||||
To start using Git from your computer, you must enter your credentials (user name and email)
|
||||
to identify you as the author of your work. The user name and email should match the ones you're
|
||||
using on GitLab.
|
||||
|
||||
In your shell, add your user name:
|
||||
|
||||
```shell
|
||||
git config --global user.name "your_username"
|
||||
```
|
||||
|
||||
And your email address:
|
||||
|
||||
```shell
|
||||
git config --global user.email "your_email_address@example.com"
|
||||
```
|
||||
|
||||
To check the configuration, run:
|
||||
|
||||
```shell
|
||||
git config --global --list
|
||||
```
|
||||
|
||||
The `--global` option tells Git to always use this information for anything you do on your system.
|
||||
If you omit `--global` or use `--local`, the configuration is applied only to the current
|
||||
repository.
|
||||
|
||||
You can read more on how Git manages configurations in the
|
||||
[Git configuration documentation](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration).
|
||||
|
||||
## Git authentication methods
|
||||
|
||||
To connect your computer with GitLab, you need to add your credentials to identify yourself.
|
||||
You have two options:
|
||||
|
||||
- Authenticate on a project-by-project basis through HTTPS, and enter your credentials every time
|
||||
you perform an operation between your computer and GitLab.
|
||||
- Authenticate through SSH once and GitLab no longer requests your credentials every time you
|
||||
perform an operation between your computer and GitLab.
|
||||
|
||||
To start the authentication process, we'll [clone](#clone-a-repository) an existing repository
|
||||
to our computer:
|
||||
|
||||
- If you want to use **SSH** to authenticate, follow the instructions on the [SSH documentation](../ssh/README.md)
|
||||
to set it up before cloning.
|
||||
- If you want to use **HTTPS**, GitLab requests your username and password:
|
||||
- If you have 2FA enabled for your account, you must use a [Personal Access Token](../user/profile/personal_access_tokens.md)
|
||||
with **read_repository** or **write_repository** permissions instead of your account's password.
|
||||
- If you don't have 2FA enabled, use your account's password.
|
||||
|
||||
NOTE:
|
||||
Authenticating through SSH is the GitLab recommended method. You can read more about credential storage
|
||||
in the [Git Credentials documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage).
|
||||
To help you visualize what you're doing locally, you can install a
|
||||
[Git GUI app](https://git-scm.com/download/gui/).
|
||||
|
||||
## Git terminology
|
||||
|
||||
If you're familiar with Git terminology, you may want to jump directly
|
||||
into [setting up a repository](#set-up-a-repository).
|
||||
If you're familiar with Git terminology, you might want to skip this section and
|
||||
go directly to [prerequisites](#prerequisites).
|
||||
|
||||
### Repository
|
||||
|
||||
Your files in GitLab live in a **repository**, similar to how you have them in a folder or
|
||||
directory on your computer.
|
||||
In GitLab, files are stored in a **repository**. A repository is similar to how you
|
||||
store files in a folder or directory on your computer.
|
||||
|
||||
- **Remote** repository refers to the files in GitLab.
|
||||
- A **local** copy refers to the files on your computer.
|
||||
- A **remote repository** refers to the files in GitLab.
|
||||
- A **local copy** refers to the files on your computer.
|
||||
|
||||
<!-- vale gitlab.Spelling = NO -->
|
||||
<!-- vale gitlab.SubstitutionWarning = NO -->
|
||||
|
@ -148,129 +43,219 @@ Often, the word "repository" is shortened to "repo".
|
|||
<!-- vale gitlab.Spelling = YES -->
|
||||
<!-- vale gitlab.SubstitutionWarning = YES -->
|
||||
|
||||
A **project** in GitLab is what holds a repository.
|
||||
In GitLab, a repository is contained in a **project**.
|
||||
|
||||
### Fork
|
||||
|
||||
When you want to copy someone else's repository, you [**fork**](../user/project/repository/forking_workflow.md#creating-a-fork)
|
||||
the project. By forking it, you create a copy of the project into your own
|
||||
[namespace](../user/group/#namespaces) to have read and write permissions to modify the project files
|
||||
When you want to contribute to someone else's repository, you make a copy of it.
|
||||
This copy is called a [**fork**](../user/project/repository/forking_workflow.md#creating-a-fork).
|
||||
The process is called "creating a fork."
|
||||
|
||||
When you fork a repo, you create a copy of the project in your own
|
||||
[namespace](../user/group/#namespaces). You then have write permissions to modify the project files
|
||||
and settings.
|
||||
|
||||
For example, if you fork this project, <https://gitlab.com/gitlab-tests/sample-project/> into your namespace,
|
||||
you create your own copy of the repository in your namespace (`https://gitlab.com/your-namespace/sample-project/`).
|
||||
From there, you can clone the repository, work on the files, and (optionally) submit proposed changes back to the
|
||||
For example, you can fork this project, <https://gitlab.com/gitlab-tests/sample-project/>, into your namespace.
|
||||
You now have your own copy of the repository. You can view the namespace in the URL, for example
|
||||
`https://gitlab.com/your-namespace/sample-project/`.
|
||||
Then you can clone the repository to your local machine, work on the files, and submit changes back to the
|
||||
original repository.
|
||||
|
||||
### Difference between download and clone
|
||||
|
||||
To create a copy of a remote repository's files on your computer, you can either
|
||||
**download** or **clone** the repository. If you download it, you cannot sync the repository with the
|
||||
remote version on GitLab.
|
||||
remote repository on GitLab.
|
||||
|
||||
[Cloning](#clone-a-repository) a repository is the same as downloading, except it preserves the Git connection
|
||||
with the remote repository. This allows you to modify the files locally and
|
||||
with the remote repository. You can then modify the files locally and
|
||||
upload the changes to the remote repository on GitLab.
|
||||
|
||||
### Pull and push
|
||||
|
||||
After you save a local copy of a repository and modify the files on your computer, you can upload the
|
||||
changes to GitLab. This is referred to as **pushing** to the remote, as this is achieved by the command
|
||||
changes to GitLab. This is referred to as **pushing** to the remote, because you use the command
|
||||
[`git push`](#send-changes-to-gitlabcom).
|
||||
|
||||
When the remote repository changes, your local copy is behind. You can update your local copy with the new
|
||||
changes in the remote repository.
|
||||
This is referred to as **pulling** from the remote, as this is achieved by the command
|
||||
This is referred to as **pulling** from the remote, because you use the command
|
||||
[`git pull`](#download-the-latest-changes-in-the-project).
|
||||
|
||||
## Set up a repository
|
||||
## Prerequisites
|
||||
|
||||
Git commands will work with any Git repository.
|
||||
To start using GitLab with Git, complete the following tasks:
|
||||
|
||||
For the purposes of this guide, we refer to this example project on GitLab.com:
|
||||
[https://gitlab.com/gitlab-tests/sample-project/](https://gitlab.com/gitlab-tests/sample-project/).
|
||||
Remember to replace the example URLs with the relevant path of your project.
|
||||
- Create and sign in to a GitLab account.
|
||||
- [Open a terminal](#open-a-terminal).
|
||||
- [Install Git](#install-git) on your computer.
|
||||
- [Configure Git](#configure-git).
|
||||
- [Choose a repository](#choose-a-repository).
|
||||
|
||||
To get started, choose one of the following:
|
||||
### Open a terminal
|
||||
|
||||
- Use the example project by signing into GitLab.com and [forking](../user/project/repository/forking_workflow.md#creating-a-fork)
|
||||
it into your namespace to make it available under `https://gitlab.com/<your-namespace>/sample-project/`.
|
||||
- Copy an existing GitLab repository onto your computer by [cloning a repository](#clone-a-repository).
|
||||
- Upload an existing folder from your computer to GitLab by [converting a local folder into a Git repository](#convert-a-local-directory-into-a-repository).
|
||||
To execute Git commands on your computer, you must open a terminal (also known as command
|
||||
prompt, command shell, and command line). Here are some options:
|
||||
|
||||
### Clone a repository
|
||||
- For macOS users:
|
||||
- Built-in [Terminal](https://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line). Press <kbd>⌘ command</kbd> + <kbd>space</kbd> and type `terminal`.
|
||||
- [iTerm2](https://iterm2.com/). You can integrate it with [zsh](https://git-scm.com/book/id/v2/Appendix-A%3A-Git-in-Other-Environments-Git-in-Zsh) and [oh my zsh](https://ohmyz.sh/) for color highlighting and other advanced features.
|
||||
- For Windows users:
|
||||
- Built-in command line. On the Windows taskbar, select the search icon and type `cmd`.
|
||||
- [PowerShell](https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/install/installing-windows-powershell?view=powershell-7).
|
||||
- Git Bash. It is built into [Git for Windows](https://gitforwindows.org/).
|
||||
- For Linux users:
|
||||
- Built-in [Linux Terminal](https://ubuntu.com/tutorials/command-line-for-beginners#3-opening-a-terminal).
|
||||
|
||||
To start working locally on an existing remote repository, clone it with the
|
||||
command `git clone <repository path>`. You can either clone it using [HTTPS](#clone-using-https)
|
||||
or [SSH](#clone-using-ssh), according to your preferred [authentication method](#git-authentication-methods).
|
||||
### Install Git
|
||||
|
||||
You can find both paths (HTTPS and SSH) by navigating to your project's landing page
|
||||
and clicking **Clone**. GitLab prompts you with both paths, from which you can copy
|
||||
and paste in your command line. You can also
|
||||
[clone and open directly in Visual Studio Code](../user/project/repository/index.md#clone-and-open-in-apple-xcode).
|
||||
|
||||
For example, with our [sample project](https://gitlab.com/gitlab-tests/sample-project/):
|
||||
|
||||
- To clone through HTTPS, use `https://gitlab.com/gitlab-tests/sample-project.git`.
|
||||
- To clone through SSH, use `git@gitlab.com:gitlab-tests/sample-project.git`.
|
||||
|
||||
To get started, open a terminal window in the directory you wish to add the
|
||||
repository files into, and run one of the `git clone` commands as described below.
|
||||
|
||||
Both commands download a copy of the files in a folder named after the project's
|
||||
name and preserve the connection with the remote repository.
|
||||
You can then navigate to the new directory with `cd sample-project` and start working on it
|
||||
locally.
|
||||
|
||||
#### Clone using HTTPS
|
||||
|
||||
To clone `https://gitlab.com/gitlab-tests/sample-project/` using HTTPS:
|
||||
Determine if Git is already installed on your computer by opening a terminal
|
||||
and running this command:
|
||||
|
||||
```shell
|
||||
git clone https://gitlab.com/gitlab-tests/sample-project.git
|
||||
git --version
|
||||
```
|
||||
|
||||
If Git is installed, the output is:
|
||||
|
||||
```shell
|
||||
git version X.Y.Z
|
||||
```
|
||||
|
||||
If your computer doesn't recognize `git` as a command, you must [install Git](../topics/git/how_to_install_git/index.md).
|
||||
After you install Git, run `git --version` to confirm that it installed correctly.
|
||||
|
||||
### Configure Git
|
||||
|
||||
To start using Git from your computer, you must enter your credentials
|
||||
to identify yourself as the author of your work. The username and email address
|
||||
should match the ones you use in GitLab.
|
||||
|
||||
1. In your shell, add your user name:
|
||||
|
||||
```shell
|
||||
git config --global user.name "your_username"
|
||||
```
|
||||
|
||||
1. Add your email address:
|
||||
|
||||
```shell
|
||||
git config --global user.email "your_email_address@example.com"
|
||||
```
|
||||
|
||||
1. To check the configuration, run:
|
||||
|
||||
```shell
|
||||
git config --global --list
|
||||
```
|
||||
|
||||
The `--global` option tells Git to always use this information for anything you do on your system.
|
||||
If you omit `--global` or use `--local`, the configuration applies only to the current
|
||||
repository.
|
||||
|
||||
You can read more on how Git manages configurations in the
|
||||
[Git configuration documentation](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration).
|
||||
|
||||
### Choose a repository
|
||||
|
||||
Before you begin, choose the repository you want to work in. You can use any project you have permission to
|
||||
access on GitLab.com or any other GitLab instance.
|
||||
|
||||
To use the repository in the examples on this page:
|
||||
|
||||
1. Go to [https://gitlab.com/gitlab-tests/sample-project/](https://gitlab.com/gitlab-tests/sample-project/).
|
||||
1. In the top right, select **Fork**.
|
||||
1. Choose a namespace for your fork.
|
||||
|
||||
The project becomes available at `https://gitlab.com/<your-namespace>/sample-project/`.
|
||||
|
||||
You can [fork](../user/project/repository/forking_workflow.md#creating-a-fork) any project you have access to.
|
||||
|
||||
## Clone a repository
|
||||
|
||||
When you clone a repository, the files from the remote repository are downloaded to your computer,
|
||||
and a connection is created.
|
||||
|
||||
This connection requires you to add credentials. You can either use SSH or HTTPS. SSH is recommended.
|
||||
|
||||
### Clone with SSH
|
||||
|
||||
Clone with SSH when you want to authenticate only one time.
|
||||
|
||||
1. Authenticate with GitLab by following the instructions in the [SSH documentation](../ssh/README.md).
|
||||
1. Go to your project's landing page and select **Clone**. Copy the URL for **Clone with SSH**.
|
||||
1. Open a terminal and go to the directory where you want to clone the files. Git automatically creates a folder with the repository name and downloads the files there.
|
||||
1. Run this command:
|
||||
|
||||
```shell
|
||||
git clone git@gitlab.com:gitlab-tests/sample-project.git
|
||||
```
|
||||
|
||||
1. To view the files, go to the new directory:
|
||||
|
||||
```shell
|
||||
cd sample-project
|
||||
```
|
||||
|
||||
You can also
|
||||
[clone a repository and open it directly in Visual Studio Code](../user/project/repository/index.md#clone-and-open-in-visual-studio-code).
|
||||
|
||||
### Clone with HTTPS
|
||||
|
||||
Clone with HTTPS when you want to authenticate each time you perform an operation
|
||||
between your computer and GitLab.
|
||||
|
||||
1. Go to your project's landing page and select **Clone**. Copy the URL for **Clone with HTTPS**.
|
||||
1. Open a terminal and go to the directory where you want to clone the files.
|
||||
1. Run the following command. Git automatically creates a folder with the repository name and downloads the files there.
|
||||
|
||||
```shell
|
||||
git clone https://gitlab.com/gitlab-tests/sample-project.git
|
||||
```
|
||||
|
||||
1. GitLab requests your username and password:
|
||||
- If you have 2FA enabled for your account, you must use a [Personal Access Token](../user/profile/personal_access_tokens.md)
|
||||
with **read_repository** or **write_repository** permissions instead of your account's password.
|
||||
- If you don't have 2FA enabled, use your account's password.
|
||||
|
||||
1. To view the files, go to the new directory:
|
||||
|
||||
```shell
|
||||
cd sample-project
|
||||
```
|
||||
|
||||
NOTE:
|
||||
On Windows, if you enter your password incorrectly multiple times and GitLab is responding `Access denied`,
|
||||
add your namespace (username or group):
|
||||
On Windows, if you enter your password incorrectly multiple times and an `Access denied` message appears,
|
||||
add your namespace (username or group) to the path:
|
||||
`git clone https://namespace@gitlab.com/gitlab-org/gitlab.git`.
|
||||
|
||||
#### Clone using SSH
|
||||
|
||||
To clone `git@gitlab.com:gitlab-org/gitlab.git` using SSH:
|
||||
|
||||
```shell
|
||||
git clone git@gitlab.com:gitlab-org/gitlab.git
|
||||
```
|
||||
|
||||
### Convert a local directory into a repository
|
||||
|
||||
When you have your files in a local folder and want to convert it into
|
||||
a repository, you must _initialize_ the folder through the `git init`
|
||||
command. This command instructs Git to track that directory as a
|
||||
repository. Open the terminal in the directory you'd like to convert
|
||||
and run:
|
||||
You can initialize a local folder so Git tracks it as a repository.
|
||||
|
||||
```shell
|
||||
git init
|
||||
```
|
||||
1. Open the terminal in the directory you'd like to convert.
|
||||
1. Run this command:
|
||||
|
||||
This command creates a `.git` folder in your directory that contains Git
|
||||
records and configuration files. We advise against editing these files
|
||||
directly.
|
||||
```shell
|
||||
git init
|
||||
```
|
||||
|
||||
Following the steps in the next section, add the [path to your remote repository](#add-a-remote-repository)
|
||||
so that Git can upload your files into the correct project.
|
||||
A `.git` folder is created in your directory. This folder contains Git
|
||||
records and configuration files. You should not edit these files
|
||||
directly.
|
||||
|
||||
#### Add a remote repository
|
||||
1. Add the [path to your remote repository](#add-a-remote)
|
||||
so Git can upload your files into the correct project.
|
||||
|
||||
You add a remote repository to tell Git which remote project in GitLab is tied
|
||||
#### Add a remote
|
||||
|
||||
You add a "remote" to tell Git which remote repository in GitLab is tied
|
||||
to the specific local folder on your computer.
|
||||
The remote tells Git where to push or pull from.
|
||||
|
||||
To add a remote repository to your local copy:
|
||||
To add a remote to your local copy:
|
||||
|
||||
1. In GitLab, [create a new project](../user/project/working_with_projects.md#create-a-project) to hold your files.
|
||||
1. In GitLab, [create a project](../user/project/working_with_projects.md#create-a-project) to hold your files.
|
||||
1. Visit this project's homepage, scroll down to **Push an existing folder**, and copy the command that starts with `git remote add`.
|
||||
1. On your computer, open the terminal in the directory you've initialized, paste the command you copied, and press <kbd>enter</kbd>:
|
||||
|
||||
|
@ -280,6 +265,16 @@ To add a remote repository to your local copy:
|
|||
|
||||
After you've done that, you can [stage your files](#add-and-commit-local-changes) and [upload them to GitLab](#send-changes-to-gitlabcom).
|
||||
|
||||
#### View your remote repositories
|
||||
|
||||
To view your remote repositories, type:
|
||||
|
||||
```shell
|
||||
git remote -v
|
||||
```
|
||||
|
||||
The `-v` flag stands for verbose.
|
||||
|
||||
### Download the latest changes in the project
|
||||
|
||||
To work on an up-to-date copy of the project, you `pull` to get all the changes made by users
|
||||
|
@ -301,16 +296,6 @@ existing branch. You can create additional named remotes and branches as necessa
|
|||
You can learn more on how Git manages remote repositories in the
|
||||
[Git Remote documentation](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes).
|
||||
|
||||
### View your remote repositories
|
||||
|
||||
To view your remote repositories, type:
|
||||
|
||||
```shell
|
||||
git remote -v
|
||||
```
|
||||
|
||||
The `-v` flag stands for verbose.
|
||||
|
||||
## Branches
|
||||
|
||||
A **branch** is a copy of the files in the repository at the time you create the branch.
|
||||
|
@ -487,8 +472,8 @@ To create a copy of a repository in your namespace, you [fork it](../user/projec
|
|||
Changes made to your copy of the repository are not automatically synchronized with the original.
|
||||
To keep the project in sync with the original project, you need to `pull` from the original repository.
|
||||
|
||||
In this case, you [create a link to the remote repository](#add-a-remote-repository).
|
||||
This remote is commonly called the `upstream`.
|
||||
You must [create a link to the remote repository](#add-a-remote) to pull
|
||||
changes from the original repository. It is common to call this remote repository the `upstream`.
|
||||
|
||||
You can now use the `upstream` as a [`<remote>` to `pull` new updates](#download-the-latest-changes-in-the-project)
|
||||
from the original repository, and use the `origin`
|
||||
|
|
|
@ -147,7 +147,7 @@ gosec-sast:
|
|||
# SAST_ANALYZER_IMAGE is an undocumented variable used internally to allow QA to
|
||||
# override the analyzer image with a custom value. This may be subject to change or
|
||||
# breakage across GitLab releases.
|
||||
SAST_ANALYZER_IMAGE_TAG: 2
|
||||
SAST_ANALYZER_IMAGE_TAG: 3
|
||||
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/gosec:$SAST_ANALYZER_IMAGE_TAG"
|
||||
rules:
|
||||
- if: $SAST_DISABLED
|
||||
|
|
|
@ -77,6 +77,8 @@ brakeman:
|
|||
|
||||
gosec:
|
||||
extends: .download_images
|
||||
variables:
|
||||
SECURE_BINARIES_ANALYZER_VERSION: "3"
|
||||
only:
|
||||
variables:
|
||||
- $SECURE_BINARIES_DOWNLOAD_IMAGES == "true" &&
|
||||
|
|
|
@ -32,79 +32,59 @@ module RuboCop
|
|||
|
||||
# Returns true if the given node resides in app/finders or ee/app/finders.
|
||||
def in_finder?(node)
|
||||
in_app_directory?(node, 'finders')
|
||||
in_directory?(node, 'finders')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in app/models or ee/app/models.
|
||||
def in_model?(node)
|
||||
in_app_directory?(node, 'models')
|
||||
in_directory?(node, 'models')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in app/services or ee/app/services.
|
||||
def in_service_class?(node)
|
||||
in_app_directory?(node, 'services')
|
||||
in_directory?(node, 'services')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in app/presenters or
|
||||
# ee/app/presenters.
|
||||
def in_presenter?(node)
|
||||
in_app_directory?(node, 'presenters')
|
||||
in_directory?(node, 'presenters')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in app/serializers or
|
||||
# ee/app/serializers.
|
||||
def in_serializer?(node)
|
||||
in_app_directory?(node, 'serializers')
|
||||
in_directory?(node, 'serializers')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in app/workers or ee/app/workers.
|
||||
def in_worker?(node)
|
||||
in_app_directory?(node, 'workers')
|
||||
in_directory?(node, 'workers')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in app/controllers or
|
||||
# ee/app/controllers.
|
||||
def in_controller?(node)
|
||||
in_app_directory?(node, 'controllers')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in app/graphql/types,
|
||||
# ee/app/graphql/types, or ee/app/graphql/ee/types.
|
||||
def in_graphql_types?(node)
|
||||
in_app_directory?(node, 'graphql/types') || in_app_directory?(node, 'graphql/ee/types')
|
||||
in_directory?(node, 'controllers')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in lib/api or ee/lib/api.
|
||||
def in_api?(node)
|
||||
in_lib_directory?(node, 'api')
|
||||
end
|
||||
|
||||
# Returns true if the given node resides in spec or ee/spec.
|
||||
def in_spec?(node)
|
||||
file_path_for_node(node).start_with?(
|
||||
ce_spec_directory,
|
||||
ee_spec_directory
|
||||
File.join(ce_lib_directory, 'api'),
|
||||
File.join(ee_lib_directory, 'api')
|
||||
)
|
||||
end
|
||||
|
||||
# Returns `true` if the given AST node resides in the given directory,
|
||||
# relative to app and/or ee/app.
|
||||
def in_app_directory?(node, directory)
|
||||
def in_directory?(node, directory)
|
||||
file_path_for_node(node).start_with?(
|
||||
File.join(ce_app_directory, directory),
|
||||
File.join(ee_app_directory, directory)
|
||||
)
|
||||
end
|
||||
|
||||
# Returns `true` if the given AST node resides in the given directory,
|
||||
# relative to lib and/or ee/lib.
|
||||
def in_lib_directory?(node, directory)
|
||||
file_path_for_node(node).start_with?(
|
||||
File.join(ce_lib_directory, directory),
|
||||
File.join(ee_lib_directory, directory)
|
||||
)
|
||||
end
|
||||
|
||||
# Returns the receiver name of a send node.
|
||||
#
|
||||
# For the AST node `(send (const nil? :Foo) ...)` this would return
|
||||
|
@ -169,14 +149,6 @@ module RuboCop
|
|||
File.join(rails_root, 'ee', 'lib')
|
||||
end
|
||||
|
||||
def ce_spec_directory
|
||||
File.join(rails_root, 'spec')
|
||||
end
|
||||
|
||||
def ee_spec_directory
|
||||
File.join(rails_root, 'ee', 'spec')
|
||||
end
|
||||
|
||||
def rails_root
|
||||
File.expand_path('..', __dir__)
|
||||
end
|
||||
|
|
|
@ -1,264 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../code_reuse_helpers'
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gitlab
|
||||
# This cop tracks the usage of feature flags among the codebase.
|
||||
#
|
||||
# The files set in `tmp/feature_flags/*.used` can then be used for verification purpose.
|
||||
#
|
||||
class MarkUsedFeatureFlags < RuboCop::Cop::Cop
|
||||
include RuboCop::CodeReuseHelpers
|
||||
|
||||
FEATURE_METHODS = %i[enabled? disabled?].freeze
|
||||
EXPERIMENTATION_METHODS = %i[active?].freeze
|
||||
EXPERIMENT_METHODS = %i[
|
||||
experiment
|
||||
experiment_enabled?
|
||||
push_frontend_experiment
|
||||
].freeze
|
||||
RUGGED_METHODS = %i[
|
||||
use_rugged?
|
||||
].freeze
|
||||
WORKER_METHODS = %i[
|
||||
data_consistency
|
||||
].freeze
|
||||
GRAPHQL_METHODS = %i[
|
||||
field
|
||||
].freeze
|
||||
SELF_METHODS = %i[
|
||||
push_frontend_feature_flag
|
||||
limit_feature_flag=
|
||||
].freeze + EXPERIMENT_METHODS + RUGGED_METHODS + WORKER_METHODS
|
||||
|
||||
RESTRICT_ON_SEND = FEATURE_METHODS + EXPERIMENTATION_METHODS + GRAPHQL_METHODS + SELF_METHODS
|
||||
|
||||
USAGE_DATA_COUNTERS_EVENTS_YAML_GLOBS = [
|
||||
File.expand_path("../../../config/metrics/aggregates/*.yml", __dir__),
|
||||
File.expand_path("../../../lib/gitlab/usage_data_counters/known_events/*.yml", __dir__)
|
||||
].freeze
|
||||
|
||||
DYNAMIC_FEATURE_FLAGS = [
|
||||
:usage_data_static_site_editor_commits, # https://gitlab.com/gitlab-org/gitlab/-/issues/284082
|
||||
:usage_data_static_site_editor_merge_requests # https://gitlab.com/gitlab-org/gitlab/-/issues/284083
|
||||
].freeze
|
||||
|
||||
# Called before all on_... have been called
|
||||
# When refining this method, always call `super`
|
||||
def on_new_investigation
|
||||
super
|
||||
track_dynamic_feature_flags!
|
||||
track_usage_data_counters_known_events!
|
||||
end
|
||||
|
||||
def on_casgn(node)
|
||||
_, lhs_name, rhs = *node
|
||||
|
||||
save_used_feature_flag(rhs.value) if lhs_name == :FEATURE_FLAG
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
return if in_spec?(node)
|
||||
return unless trackable_flag?(node)
|
||||
|
||||
flag_arg = flag_arg(node)
|
||||
flag_value = flag_value(node)
|
||||
return unless flag_value
|
||||
|
||||
if flag_arg_is_str_or_sym?(node)
|
||||
if caller_is_feature_gitaly?(node)
|
||||
save_used_feature_flag("gitaly_#{flag_value}")
|
||||
else
|
||||
save_used_feature_flag(flag_value)
|
||||
end
|
||||
|
||||
if experiment_method?(node) || experimentation_method?(node)
|
||||
# Additionally, mark experiment-related feature flag as used as well
|
||||
matching_feature_flags = defined_feature_flags.select { |flag| flag == "#{flag_value}_experiment_percentage" }
|
||||
matching_feature_flags.each do |matching_feature_flag|
|
||||
puts_if_ci(node, "The '#{matching_feature_flag}' feature flag tracks the #{flag_value} experiment, which is still in use, so we'll mark it as used.")
|
||||
save_used_feature_flag(matching_feature_flag)
|
||||
end
|
||||
end
|
||||
elsif flag_arg_is_send_type?(node)
|
||||
puts_if_ci(node, "Feature flag is dynamic: '#{flag_value}.")
|
||||
elsif flag_arg_is_dstr_or_dsym?(node)
|
||||
str_prefix = flag_arg.children[0]
|
||||
rest_children = flag_arg.children[1..]
|
||||
|
||||
if rest_children.none? { |child| child.str_type? }
|
||||
matching_feature_flags = defined_feature_flags.select { |flag| flag.start_with?(str_prefix.value) }
|
||||
matching_feature_flags.each do |matching_feature_flag|
|
||||
puts_if_ci(node, "The '#{matching_feature_flag}' feature flag starts with '#{str_prefix.value}', so we'll optimistically mark it as used.")
|
||||
save_used_feature_flag(matching_feature_flag)
|
||||
end
|
||||
else
|
||||
puts_if_ci(node, "Interpolated feature flag name has multiple static string parts, we won't track it.")
|
||||
end
|
||||
else
|
||||
puts_if_ci(node, "Feature flag has an unknown type: #{flag_arg.type}.")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def puts_if_ci(node, text)
|
||||
puts "#{text} (call: `#{node.source}`, source: #{node.location.expression.source_buffer.name})" if ENV['CI']
|
||||
end
|
||||
|
||||
def save_used_feature_flag(feature_flag_name)
|
||||
used_feature_flag_file = File.expand_path("../../../tmp/feature_flags/#{feature_flag_name}.used", __dir__)
|
||||
return if File.exist?(used_feature_flag_file)
|
||||
|
||||
FileUtils.touch(used_feature_flag_file)
|
||||
end
|
||||
|
||||
def class_caller(node)
|
||||
node.children[0]&.const_name.to_s
|
||||
end
|
||||
|
||||
def method_name(node)
|
||||
node.children[1]
|
||||
end
|
||||
|
||||
def flag_arg(node)
|
||||
if worker_method?(node)
|
||||
return unless node.children.size > 3
|
||||
|
||||
node.children[3].each_pair.find do |pair|
|
||||
pair.key.value == :feature_flag
|
||||
end&.value
|
||||
elsif graphql_method?(node)
|
||||
return unless node.children.size > 3
|
||||
|
||||
opts_index = node.children[3].hash_type? ? 3 : 4
|
||||
return unless node.children[opts_index]
|
||||
|
||||
node.children[opts_index].each_pair.find do |pair|
|
||||
pair.key.value == :feature_flag
|
||||
end&.value
|
||||
else
|
||||
arg_index = rugged_method?(node) ? 3 : 2
|
||||
|
||||
node.children[arg_index]
|
||||
end
|
||||
end
|
||||
|
||||
def flag_value(node)
|
||||
flag_arg = flag_arg(node)
|
||||
return unless flag_arg
|
||||
|
||||
if flag_arg.respond_to?(:value)
|
||||
flag_arg.value
|
||||
else
|
||||
flag_arg
|
||||
end.to_s.tr("\n/", ' _')
|
||||
end
|
||||
|
||||
def flag_arg_is_str_or_sym?(node)
|
||||
flag_arg = flag_arg(node)
|
||||
flag_arg.str_type? || flag_arg.sym_type?
|
||||
end
|
||||
|
||||
def flag_arg_is_send_type?(node)
|
||||
flag_arg(node).send_type?
|
||||
end
|
||||
|
||||
def flag_arg_is_dstr_or_dsym?(node)
|
||||
flag = flag_arg(node)
|
||||
(flag.dstr_type? || flag.dsym_type?) && flag.children[0].str_type?
|
||||
end
|
||||
|
||||
def caller_is_feature?(node)
|
||||
class_caller(node) == "Feature"
|
||||
end
|
||||
|
||||
def caller_is_feature_gitaly?(node)
|
||||
class_caller(node) == "Feature::Gitaly"
|
||||
end
|
||||
|
||||
def caller_is_experimentation?(node)
|
||||
class_caller(node) == "Gitlab::Experimentation"
|
||||
end
|
||||
|
||||
def experiment_method?(node)
|
||||
EXPERIMENT_METHODS.include?(method_name(node))
|
||||
end
|
||||
|
||||
def rugged_method?(node)
|
||||
RUGGED_METHODS.include?(method_name(node))
|
||||
end
|
||||
|
||||
def feature_method?(node)
|
||||
FEATURE_METHODS.include?(method_name(node)) && (caller_is_feature?(node) || caller_is_feature_gitaly?(node))
|
||||
end
|
||||
|
||||
def experimentation_method?(node)
|
||||
EXPERIMENTATION_METHODS.include?(method_name(node)) && caller_is_experimentation?(node)
|
||||
end
|
||||
|
||||
def worker_method?(node)
|
||||
WORKER_METHODS.include?(method_name(node))
|
||||
end
|
||||
|
||||
def graphql_method?(node)
|
||||
GRAPHQL_METHODS.include?(method_name(node)) && in_graphql_types?(node)
|
||||
end
|
||||
|
||||
def self_method?(node)
|
||||
SELF_METHODS.include?(method_name(node)) && class_caller(node).empty?
|
||||
end
|
||||
|
||||
def trackable_flag?(node)
|
||||
feature_method?(node) || experimentation_method?(node) || graphql_method?(node) || self_method?(node)
|
||||
end
|
||||
|
||||
# Marking all event's feature flags as used as Gitlab::UsageDataCounters::HLLRedisCounter.track_event{,context}
|
||||
# is mostly used with dynamic event name.
|
||||
def track_dynamic_feature_flags!
|
||||
DYNAMIC_FEATURE_FLAGS.each(&method(:save_used_feature_flag))
|
||||
end
|
||||
|
||||
# Marking all event's feature flags as used as Gitlab::UsageDataCounters::HLLRedisCounter.track_event{,context}
|
||||
# is mostly used with dynamic event name.
|
||||
def track_usage_data_counters_known_events!
|
||||
usage_data_counters_known_event_feature_flags.each(&method(:save_used_feature_flag))
|
||||
end
|
||||
|
||||
def usage_data_counters_known_event_feature_flags
|
||||
USAGE_DATA_COUNTERS_EVENTS_YAML_GLOBS.each_with_object(Set.new) do |glob, memo|
|
||||
Dir.glob(glob).each do |path|
|
||||
YAML.safe_load(File.read(path))&.each do |hash|
|
||||
memo << hash['feature_flag'] if hash['feature_flag']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def defined_feature_flags
|
||||
@defined_feature_flags ||= begin
|
||||
flags_paths = [
|
||||
'config/feature_flags/**/*.yml'
|
||||
]
|
||||
|
||||
# For EE additionally process `ee/` feature flags
|
||||
if File.exist?(File.expand_path('../../../ee/app/models/license.rb', __dir__)) && !%w[true 1].include?(ENV['FOSS_ONLY'].to_s)
|
||||
flags_paths << 'ee/config/feature_flags/**/*.yml'
|
||||
end
|
||||
|
||||
flags_paths.each_with_object([]) do |flags_path, memo|
|
||||
flags_path = File.expand_path("../../../#{flags_path}", __dir__)
|
||||
Dir.glob(flags_path).each do |path|
|
||||
feature_flag_name = File.basename(path, '.yml')
|
||||
|
||||
memo << feature_flag_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,16 +28,6 @@ flags_paths = [
|
|||
# For EE additionally process `ee/` feature flags
|
||||
if File.exist?('ee/app/models/license.rb') && !%w[true 1].include?(ENV['FOSS_ONLY'].to_s)
|
||||
flags_paths << 'ee/config/feature_flags/**/*.yml'
|
||||
|
||||
# Geo feature flags are constructed dynamically and there's no explicit checks in the codebase so we mark all
|
||||
# the replicators' derived feature flags as used.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/blob/54e802e8fe76b6f93656d75ef9b566bf57b60f41/ee/lib/gitlab/geo/replicator.rb#L183-185
|
||||
Dir.glob('ee/app/replicators/geo/*_replicator.rb').each_with_object(Set.new) do |path, memo|
|
||||
replicator_name = File.basename(path, '.rb')
|
||||
feature_flag_name = "geo_#{replicator_name.delete_suffix('_replicator')}_replication"
|
||||
|
||||
FileUtils.touch(File.join('tmp', 'feature_flags', "#{feature_flag_name}.used"))
|
||||
end
|
||||
end
|
||||
|
||||
all_flags = {}
|
||||
|
@ -51,17 +41,7 @@ flags_paths.each do |flags_path|
|
|||
feature_flag_name = File.basename(path, '.yml')
|
||||
|
||||
# TODO: we need a better way of tracking use of Gitaly FF across Gitaly and GitLab
|
||||
if feature_flag_name.start_with?('gitaly_')
|
||||
puts "Skipping the #{feature_flag_name} feature flag since it starts with 'gitaly_'."
|
||||
next
|
||||
end
|
||||
|
||||
# Dynamic feature flag names for redirect to latest CI templates
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63144/diffs#fa2193ace3f6a02f7ef9995ef9bc519eca92c4ee_57_84
|
||||
if feature_flag_name.start_with?('redirect_to_latest_template_')
|
||||
puts "Skipping the #{feature_flag_name} feature flag since it starts with 'redirect_to_latest_template_'."
|
||||
next
|
||||
end
|
||||
next if feature_flag_name.start_with?('gitaly_')
|
||||
|
||||
all_flags[feature_flag_name] = File.exist?(File.join('tmp', 'feature_flags', feature_flag_name + '.used'))
|
||||
end
|
||||
|
|
|
@ -19,6 +19,12 @@ RSpec.describe ApplicationExperiment, :experiment do
|
|||
allow(subject).to receive(:enabled?).and_return(true)
|
||||
end
|
||||
|
||||
it "naively assumes a 1x1 relationship to feature flags for tests" do
|
||||
expect(Feature).to receive(:persist_used!).with('namespaced_stub')
|
||||
|
||||
described_class.new('namespaced/stub')
|
||||
end
|
||||
|
||||
it "doesn't raise an exception without a defined control" do
|
||||
# because we have a default behavior defined
|
||||
|
||||
|
|
|
@ -22,8 +22,3 @@ ActiveSupport::Dependencies.autoload_paths << 'lib'
|
|||
ActiveSupport::Dependencies.autoload_paths << 'ee/lib'
|
||||
|
||||
ActiveSupport::XmlMini.backend = 'Nokogiri'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.filter_run focus: true
|
||||
config.run_all_when_everything_filtered = true
|
||||
end
|
||||
|
|
|
@ -150,31 +150,6 @@ RSpec.describe RuboCop::CodeReuseHelpers do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#in_graphql_types?' do
|
||||
%w[
|
||||
app/graphql/types
|
||||
ee/app/graphql/ee/types
|
||||
ee/app/graphql/types
|
||||
].each do |path|
|
||||
it "returns true for a node in #{path}" do
|
||||
node = build_and_parse_source('10', rails_root_join(path, 'foo.rb'))
|
||||
|
||||
expect(cop.in_graphql_types?(node)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
%w[
|
||||
app/graphql/resolvers
|
||||
app/foo
|
||||
].each do |path|
|
||||
it "returns true for a node in #{path}" do
|
||||
node = build_and_parse_source('10', rails_root_join(path, 'foo.rb'))
|
||||
|
||||
expect(cop.in_graphql_types?(node)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#in_api?' do
|
||||
it 'returns true for a node in the API directory' do
|
||||
node = build_and_parse_source('10', rails_root_join('lib', 'api', 'foo.rb'))
|
||||
|
@ -189,67 +164,25 @@ RSpec.describe RuboCop::CodeReuseHelpers do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#in_spec?' do
|
||||
it 'returns true for a node in the spec directory' do
|
||||
node = build_and_parse_source('10', rails_root_join('spec', 'foo.rb'))
|
||||
|
||||
expect(cop.in_spec?(node)).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns true for a node in the ee/spec directory' do
|
||||
node = build_and_parse_source('10', rails_root_join('ee', 'spec', 'foo.rb'))
|
||||
|
||||
expect(cop.in_spec?(node)).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false for a node outside the spec directory' do
|
||||
node = build_and_parse_source('10', rails_root_join('lib', 'foo.rb'))
|
||||
|
||||
expect(cop.in_spec?(node)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#in_app_directory?' do
|
||||
describe '#in_directory?' do
|
||||
it 'returns true for a directory in the CE app/ directory' do
|
||||
node = build_and_parse_source('10', rails_root_join('app', 'models', 'foo.rb'))
|
||||
|
||||
expect(cop.in_app_directory?(node, 'models')).to eq(true)
|
||||
expect(cop.in_directory?(node, 'models')).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns true for a directory in the EE app/ directory' do
|
||||
node =
|
||||
build_and_parse_source('10', rails_root_join('ee', 'app', 'models', 'foo.rb'))
|
||||
|
||||
expect(cop.in_app_directory?(node, 'models')).to eq(true)
|
||||
expect(cop.in_directory?(node, 'models')).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false for a directory in the lib/ directory' do
|
||||
node =
|
||||
build_and_parse_source('10', rails_root_join('lib', 'models', 'foo.rb'))
|
||||
|
||||
expect(cop.in_app_directory?(node, 'models')).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#in_lib_directory?' do
|
||||
it 'returns true for a directory in the CE lib/ directory' do
|
||||
node = build_and_parse_source('10', rails_root_join('lib', 'models', 'foo.rb'))
|
||||
|
||||
expect(cop.in_lib_directory?(node, 'models')).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns true for a directory in the EE lib/ directory' do
|
||||
node =
|
||||
build_and_parse_source('10', rails_root_join('ee', 'lib', 'models', 'foo.rb'))
|
||||
|
||||
expect(cop.in_lib_directory?(node, 'models')).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false for a directory in the app/ directory' do
|
||||
node =
|
||||
build_and_parse_source('10', rails_root_join('app', 'models', 'foo.rb'))
|
||||
|
||||
expect(cop.in_lib_directory?(node, 'models')).to eq(false)
|
||||
expect(cop.in_directory?(node, 'models')).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'rubocop'
|
||||
require 'rubocop/rspec/support'
|
||||
require_relative '../../../../rubocop/cop/gitlab/mark_used_feature_flags'
|
||||
|
||||
RSpec.describe RuboCop::Cop::Gitlab::MarkUsedFeatureFlags do
|
||||
let(:defined_feature_flags) do
|
||||
%w[a_feature_flag foo_hello foo_world baz_experiment_percentage bar_baz]
|
||||
end
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
before do
|
||||
stub_const("#{described_class}::DYNAMIC_FEATURE_FLAGS", [])
|
||||
allow(cop).to receive(:defined_feature_flags).and_return(defined_feature_flags)
|
||||
allow(cop).to receive(:usage_data_counters_known_event_feature_flags).and_return([])
|
||||
end
|
||||
|
||||
def feature_flag_path(feature_flag_name)
|
||||
File.expand_path("../../../../tmp/feature_flags/#{feature_flag_name}.used", __dir__)
|
||||
end
|
||||
|
||||
shared_examples 'sets flag as used' do |method_call, flags_to_be_set|
|
||||
it 'sets the flag as used' do
|
||||
Array(flags_to_be_set).each do |flag_to_be_set|
|
||||
expect(FileUtils).to receive(:touch).with(feature_flag_path(flag_to_be_set))
|
||||
end
|
||||
|
||||
expect_no_offenses(<<~RUBY)
|
||||
class Foo < ApplicationRecord
|
||||
#{method_call}
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'does not set any flags as used' do |method_call|
|
||||
it 'sets the flag as used' do
|
||||
expect(FileUtils).not_to receive(:touch)
|
||||
|
||||
expect_no_offenses(method_call)
|
||||
end
|
||||
end
|
||||
|
||||
%w[
|
||||
Feature.enabled?
|
||||
Feature.disabled?
|
||||
push_frontend_feature_flag
|
||||
].each do |feature_flag_method|
|
||||
context "#{feature_flag_method} method" do
|
||||
context 'a string feature flag' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo")|, 'foo'
|
||||
end
|
||||
|
||||
context 'a symbol feature flag' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(:foo)|, 'foo'
|
||||
end
|
||||
|
||||
context 'an interpolated string feature flag with a string prefix' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo_\#{bar}")|, %w[foo_hello foo_world]
|
||||
end
|
||||
|
||||
context 'an interpolated symbol feature flag with a string prefix' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(:"foo_\#{bar}")|, %w[foo_hello foo_world]
|
||||
end
|
||||
|
||||
context 'a string with a "/" in it' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}("bar/baz")|, 'bar_baz'
|
||||
end
|
||||
|
||||
context 'an interpolated string feature flag with a string prefix and suffix' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(:"foo_\#{bar}_baz")|
|
||||
end
|
||||
|
||||
context 'a dynamic string feature flag as a variable' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(a_variable, an_arg)|
|
||||
end
|
||||
|
||||
context 'an integer feature flag' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(123)|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%w[
|
||||
Feature::Gitaly.enabled?
|
||||
Feature::Gitaly.disabled?
|
||||
].each do |feature_flag_method|
|
||||
context "#{feature_flag_method} method" do
|
||||
context 'a string feature flag' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo")|, 'gitaly_foo'
|
||||
end
|
||||
|
||||
context 'a symbol feature flag' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(:foo)|, 'gitaly_foo'
|
||||
end
|
||||
|
||||
context 'an interpolated string feature flag with a string prefix' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo_\#{bar}")|, %w[foo_hello foo_world]
|
||||
end
|
||||
|
||||
context 'an interpolated symbol feature flag with a string prefix' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(:"foo_\#{bar}")|, %w[foo_hello foo_world]
|
||||
end
|
||||
|
||||
context 'an interpolated string feature flag with a string prefix and suffix' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(:"foo_\#{bar}_baz")|
|
||||
end
|
||||
|
||||
context 'a dynamic string feature flag as a variable' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(a_variable, an_arg)|
|
||||
end
|
||||
|
||||
context 'an integer feature flag' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(123)|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%w[
|
||||
experiment
|
||||
experiment_enabled?
|
||||
push_frontend_experiment
|
||||
Gitlab::Experimentation.active?
|
||||
].each do |feature_flag_method|
|
||||
context "#{feature_flag_method} method" do
|
||||
context 'a string feature flag' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}("baz")|, %w[baz baz_experiment_percentage]
|
||||
end
|
||||
|
||||
context 'a symbol feature flag' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(:baz)|, %w[baz baz_experiment_percentage]
|
||||
end
|
||||
|
||||
context 'an interpolated string feature flag with a string prefix' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}("foo_\#{bar}")|, %w[foo_hello foo_world]
|
||||
end
|
||||
|
||||
context 'an interpolated symbol feature flag with a string prefix' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(:"foo_\#{bar}")|, %w[foo_hello foo_world]
|
||||
end
|
||||
|
||||
context 'an interpolated string feature flag with a string prefix and suffix' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(:"foo_\#{bar}_baz")|
|
||||
end
|
||||
|
||||
context 'a dynamic string feature flag as a variable' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(a_variable, an_arg)|
|
||||
end
|
||||
|
||||
context 'an integer feature flag' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(123)|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%w[
|
||||
use_rugged?
|
||||
].each do |feature_flag_method|
|
||||
context "#{feature_flag_method} method" do
|
||||
context 'a string feature flag' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(arg, "baz")|, 'baz'
|
||||
end
|
||||
|
||||
context 'a symbol feature flag' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(arg, :baz)|, 'baz'
|
||||
end
|
||||
|
||||
context 'an interpolated string feature flag with a string prefix' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(arg, "foo_\#{bar}")|, %w[foo_hello foo_world]
|
||||
end
|
||||
|
||||
context 'an interpolated symbol feature flag with a string prefix' do
|
||||
include_examples 'sets flag as used', %Q|#{feature_flag_method}(arg, :"foo_\#{bar}")|, %w[foo_hello foo_world]
|
||||
end
|
||||
|
||||
context 'an interpolated string feature flag with a string prefix and suffix' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(arg, :"foo_\#{bar}_baz")|
|
||||
end
|
||||
|
||||
context 'a dynamic string feature flag as a variable' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(a_variable, an_arg)|
|
||||
end
|
||||
|
||||
context 'an integer feature flag' do
|
||||
include_examples 'does not set any flags as used', %Q|#{feature_flag_method}(arg, 123)|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'self.limit_feature_flag = :foo' do
|
||||
include_examples 'sets flag as used', 'self.limit_feature_flag = :foo', 'foo'
|
||||
end
|
||||
|
||||
describe 'FEATURE_FLAG = :foo' do
|
||||
include_examples 'sets flag as used', 'FEATURE_FLAG = :foo', 'foo'
|
||||
end
|
||||
|
||||
describe 'Worker `data_consistency` method' do
|
||||
include_examples 'sets flag as used', 'data_consistency :delayed, feature_flag: :foo', 'foo'
|
||||
include_examples 'does not set any flags as used', 'data_consistency :delayed'
|
||||
end
|
||||
|
||||
describe 'GraphQL `field` method' do
|
||||
before do
|
||||
allow(cop).to receive(:in_graphql_types?).and_return(true)
|
||||
end
|
||||
|
||||
include_examples 'sets flag as used', 'field :runners, Types::Ci::RunnerType.connection_type, null: true, feature_flag: :foo', 'foo'
|
||||
include_examples 'sets flag as used', 'field :runners, null: true, feature_flag: :foo', 'foo'
|
||||
include_examples 'does not set any flags as used', 'field :solution'
|
||||
include_examples 'does not set any flags as used', 'field :runners, Types::Ci::RunnerType.connection_type'
|
||||
include_examples 'does not set any flags as used', 'field :runners, Types::Ci::RunnerType.connection_type, null: true, description: "hello world"'
|
||||
include_examples 'does not set any flags as used', 'field :solution, type: GraphQL::STRING_TYPE, null: true, description: "URL to the vulnerabilitys details page."'
|
||||
end
|
||||
|
||||
describe "tracking of usage data metrics known events happens at the beginning of inspection" do
|
||||
let(:usage_data_counters_known_event_feature_flags) { ['an_event_feature_flag'] }
|
||||
|
||||
before do
|
||||
allow(cop).to receive(:usage_data_counters_known_event_feature_flags).and_return(usage_data_counters_known_event_feature_flags)
|
||||
end
|
||||
|
||||
include_examples 'sets flag as used', "FEATURE_FLAG = :foo", %w[foo an_event_feature_flag]
|
||||
end
|
||||
end
|
|
@ -4,6 +4,16 @@
|
|||
require 'gitlab/experiment/rspec'
|
||||
require_relative 'stub_snowplow'
|
||||
|
||||
# This is a temporary fix until we have a larger discussion around the
|
||||
# challenges raised in https://gitlab.com/gitlab-org/gitlab/-/issues/300104
|
||||
require Rails.root.join('app', 'experiments', 'application_experiment')
|
||||
class ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
|
||||
def initialize(...)
|
||||
super(...)
|
||||
Feature.persist_used!(feature_flag_name)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include StubSnowplow, :experiment
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ module StubExperiments
|
|||
allow(Gitlab::Experimentation).to receive(:active?).and_call_original
|
||||
|
||||
experiments.each do |experiment_key, enabled|
|
||||
Feature.persist_used!("#{experiment_key}#{feature_flag_suffix}")
|
||||
allow(Gitlab::Experimentation).to receive(:active?).with(experiment_key) { enabled }
|
||||
end
|
||||
end
|
||||
|
@ -25,6 +26,7 @@ module StubExperiments
|
|||
allow(Gitlab::Experimentation).to receive(:in_experiment_group?).and_call_original
|
||||
|
||||
experiments.each do |experiment_key, enabled|
|
||||
Feature.persist_used!("#{experiment_key}#{feature_flag_suffix}")
|
||||
allow(Gitlab::Experimentation).to receive(:in_experiment_group?).with(experiment_key, anything) { enabled }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,14 @@
|
|||
module StubbedFeature
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
prepended do
|
||||
cattr_reader(:persist_used) do
|
||||
# persist feature flags in CI
|
||||
# nil: indicates that we do not want to persist used feature flags
|
||||
Gitlab::Utils.to_boolean(ENV['CI']) ? {} : nil
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
# Turn stubbed feature flags on or off.
|
||||
def stub=(stub)
|
||||
|
@ -33,6 +41,8 @@ module StubbedFeature
|
|||
feature_flag = super
|
||||
return feature_flag unless stub?
|
||||
|
||||
persist_used!(args.first)
|
||||
|
||||
# If feature flag is not persisted we mark the feature flag as enabled
|
||||
# We do `m.call` as we want to validate the execution of method arguments
|
||||
# and a feature flag state if it is not persisted
|
||||
|
@ -42,5 +52,17 @@ module StubbedFeature
|
|||
|
||||
feature_flag
|
||||
end
|
||||
|
||||
# This method creates a temporary file in `tmp/feature_flags`
|
||||
# if feature flag was touched during execution
|
||||
def persist_used!(name)
|
||||
return unless persist_used
|
||||
return if persist_used[name]
|
||||
|
||||
persist_used[name] = true
|
||||
FileUtils.touch(
|
||||
Rails.root.join('tmp', 'feature_flags', name.to_s + ".used")
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue