diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 5f96bdec9e2..c4d393eb16c 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -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 diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index beb46685654..8dd97c1fe69 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -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 diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 6ef981c77bf..848463bc3ec 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -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) diff --git a/config/initializers_before_autoloader/000_inflections.rb b/config/initializers_before_autoloader/000_inflections.rb index de8f79b9a29..39905adf390 100644 --- a/config/initializers_before_autoloader/000_inflections.rb +++ b/config/initializers_before_autoloader/000_inflections.rb @@ -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 diff --git a/danger/feature_flag/Dangerfile b/danger/feature_flag/Dangerfile index ac9ea812ebb..bf2194724fc 100644 --- a/danger/feature_flag/Dangerfile +++ b/danger/feature_flag/Dangerfile @@ -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 diff --git a/danger/specialization_labels/Dangerfile b/danger/specialization_labels/Dangerfile index 35125f20b14..2261fe23e4e 100644 --- a/danger/specialization_labels/Dangerfile +++ b/danger/specialization_labels/Dangerfile @@ -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| diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index 9a168b84869..f61b302882e 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -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` | diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index 7ee9d454df3..f9623586e55 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -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: -  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 ⌘ command + space 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. @@ -148,129 +43,219 @@ Often, the word "repository" is shortened to "repo". -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, 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, , 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//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 ⌘ command + space 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 `. 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//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 enter: @@ -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 [`` to `pull` new updates](#download-the-latest-changes-in-the-project) from the original repository, and use the `origin` diff --git a/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml index f4b40a2d44e..9dc77d52e11 100644 --- a/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml index c71b669c2a4..d410c49b9a4 100644 --- a/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Secure-Binaries.gitlab-ci.yml @@ -77,6 +77,8 @@ brakeman: gosec: extends: .download_images + variables: + SECURE_BINARIES_ANALYZER_VERSION: "3" only: variables: - $SECURE_BINARIES_DOWNLOAD_IMAGES == "true" && diff --git a/rubocop/code_reuse_helpers.rb b/rubocop/code_reuse_helpers.rb index 283c43de227..63019c43943 100644 --- a/rubocop/code_reuse_helpers.rb +++ b/rubocop/code_reuse_helpers.rb @@ -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 diff --git a/rubocop/cop/gitlab/mark_used_feature_flags.rb b/rubocop/cop/gitlab/mark_used_feature_flags.rb deleted file mode 100644 index 3fa9b1a5bce..00000000000 --- a/rubocop/cop/gitlab/mark_used_feature_flags.rb +++ /dev/null @@ -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 diff --git a/scripts/used-feature-flags b/scripts/used-feature-flags index 07c022a4c1a..aebd007dda9 100755 --- a/scripts/used-feature-flags +++ b/scripts/used-feature-flags @@ -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 diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb index 2d2b911749b..22c436e4159 100644 --- a/spec/experiments/application_experiment_spec.rb +++ b/spec/experiments/application_experiment_spec.rb @@ -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 diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb index 469c29cd2e0..2f0bcd318d9 100644 --- a/spec/fast_spec_helper.rb +++ b/spec/fast_spec_helper.rb @@ -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 diff --git a/spec/rubocop/code_reuse_helpers_spec.rb b/spec/rubocop/code_reuse_helpers_spec.rb index 695c152e3db..9337df368e3 100644 --- a/spec/rubocop/code_reuse_helpers_spec.rb +++ b/spec/rubocop/code_reuse_helpers_spec.rb @@ -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 diff --git a/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb b/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb deleted file mode 100644 index 063d9dc7017..00000000000 --- a/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb +++ /dev/null @@ -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 diff --git a/spec/support/gitlab_experiment.rb b/spec/support/gitlab_experiment.rb index 3d099dc689c..b84adf82d29 100644 --- a/spec/support/gitlab_experiment.rb +++ b/spec/support/gitlab_experiment.rb @@ -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 diff --git a/spec/support/helpers/stub_experiments.rb b/spec/support/helpers/stub_experiments.rb index 8995b8f5f7b..408d16a7c08 100644 --- a/spec/support/helpers/stub_experiments.rb +++ b/spec/support/helpers/stub_experiments.rb @@ -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 diff --git a/spec/support/helpers/stubbed_feature.rb b/spec/support/helpers/stubbed_feature.rb index 4113a28182b..67ceb7d9b35 100644 --- a/spec/support/helpers/stubbed_feature.rb +++ b/spec/support/helpers/stubbed_feature.rb @@ -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