Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-13 03:10:09 +00:00
parent 2023b1313d
commit ed01bf9b25
30 changed files with 750 additions and 340 deletions

View file

@ -23,10 +23,10 @@ module Todos
return if user_has_reporter_access?
remove_confidential_resource_todos
remove_group_todos
if entity.private?
remove_project_todos
remove_group_todos
else
enqueue_private_features_worker
end
@ -68,7 +68,7 @@ module Todos
return unless entity.is_a?(Namespace)
Todo
.for_group(non_authorized_non_public_groups)
.for_group(unauthorized_private_groups)
.for_user(user)
.delete_all
end
@ -104,16 +104,13 @@ module Todos
GroupsFinder.new(user, min_access_level: Gitlab::Access::REPORTER).execute.select(:id)
end
# since the entity is a private group, we can assume all subgroups are also
# private. We can therefore limit GroupsFinder with `all_available: false`.
# Otherwise it tries to include all public groups. This generates an expensive
# SQL queries: https://gitlab.com/gitlab-org/gitlab/-/issues/325133
# rubocop: disable CodeReuse/ActiveRecord
def non_authorized_non_public_groups
def unauthorized_private_groups
return [] unless entity.is_a?(Namespace)
return [] unless entity.private?
entity.self_and_descendants.select(:id)
groups = entity.self_and_descendants.private_only
groups.select(:id)
.id_not_in(GroupsFinder.new(user, all_available: false).execute.select(:id).reorder(nil))
end
# rubocop: enable CodeReuse/ActiveRecord

View file

@ -0,0 +1,8 @@
---
name: active_record_transactions_tracking
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67918
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338306
milestone: '14.2'
type: ops
group: group::pipeline execution
default_enabled: false

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
return unless Gitlab.com? || Gitlab.dev_or_test_env?
def feature_flags_available?
# When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
active_db_connection = ActiveRecord::Base.connection.active? rescue false
active_db_connection && Feature::FlipperFeature.table_exists?
rescue ActiveRecord::NoDatabaseError
false
end
Gitlab::Application.configure do
if feature_flags_available? && ::Feature.enabled?(:active_record_transactions_tracking, type: :ops, default_enabled: :yaml)
Gitlab::Database::Transaction::Observer.register!
end
end

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
if ENV['ACTIVE_RECORD_DISABLE_TRANSACTION_METRICS_PATCHES'].blank?
Gitlab::Database.install_transaction_metrics_patches!
end
return unless Gitlab.com? || Gitlab.dev_or_test_env?
if ENV['ACTIVE_RECORD_DISABLE_TRANSACTION_CONTEXT_PATCHES'].blank?
Gitlab::Database.install_transaction_context_patches!
end

View file

@ -1,3 +0,0 @@
# frozen_string_literal: true
Gitlab::Database.install_monkey_patches

View file

@ -223,6 +223,28 @@ on the Gitaly server matches the one on Gitaly client. If it doesn't match,
update the secrets file on the Gitaly server to match the Gitaly client, then
[reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure).
### Repository pushes fail with a `deny updating a hidden ref` error
Due to [a change](https://gitlab.com/gitlab-org/gitaly/-/merge_requests/3426)
introduced in GitLab 13.12, Gitaly has read-only, internal GitLab references that users are not
permitted to update. If you attempt to update internal references with `git push --mirror`, Git
returns the rejection error, `deny updating a hidden ref`.
The following references are read-only:
- refs/environments/
- refs/keep-around/
- refs/merge-requests/
- refs/pipelines/
To mirror-push branches and tags only, and avoid attempting to mirror-push protected refs, run:
```shell
git push origin +refs/heads/*:refs/heads/* +refs/tags/*:refs/tags/*
```
Any other namespaces that the admin wants to push can be included there as well via additional patterns.
### Command line tools cannot connect to Gitaly
gRPC cannot reach your Gitaly server if:

View file

@ -6,12 +6,12 @@ type: reference
last_update: 2019-07-03
---
# Merge Trains **(PREMIUM)**
# Merge trains **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9186) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.0.
> - [Squash and merge](../../user/project/merge_requests/squash_and_merge.md) support [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13001) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.6.
For more information about why you might want to use Merge Trains, read [How merge trains keep your master green](https://about.gitlab.com/blog/2020/01/30/all-aboard-merge-trains/).
For more information about why you might want to use merge trains, read [How merge trains keep your master green](https://about.gitlab.com/blog/2020/01/30/all-aboard-merge-trains/).
When [pipelines for merged results](pipelines_for_merged_results.md) are
enabled, the pipeline jobs run as if the changes from your source branch have already
@ -63,7 +63,7 @@ Read more about [how merge trains keep your master green](https://about.gitlab.c
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
Watch this video for a demonstration on [how parallel execution
of Merge Trains can prevent commits from breaking the default
of merge trains can prevent commits from breaking the default
branch](https://www.youtube.com/watch?v=D4qCqXgZkHQ).
## Prerequisites
@ -83,11 +83,13 @@ To enable merge trains for your project:
1. If you are on a self-managed GitLab instance, ensure the [feature flag](#merge-trains-feature-flag) is set correctly.
1. [Configure your CI/CD configuration file](merge_request_pipelines.md#configure-pipelines-for-merge-requests)
so that the pipeline or individual jobs run for merge requests.
1. Visit your project's **Settings > General** and expand **Merge requests**.
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > General**.
1. Expand **Merge requests**.
1. In the **Merge method** section, verify that **Merge commit** is selected.
You cannot use **Merge commit with semi-linear history** or **Fast-forward merge** with merge trains.
1. In the **Merge options** section, select **Enable merged results pipelines.** (if not already selected) and **Enable merge trains.**
1. Click **Save changes**
1. In the **Merge options** section, select **Enable merged results pipelines** (if not already selected) and **Enable merge trains**.
1. Select **Save changes**.
In GitLab 13.5 and earlier, there is only one checkbox, named
**Enable merge trains and pipelines for merged results**.
@ -102,7 +104,7 @@ unresolved state or your pipelines may be dropped.
To start a merge train:
1. Visit a merge request.
1. Click the **Start merge train** button.
1. Select **Start merge train**.
![Start merge train](img/merge_train_start_v12_0.png)
@ -113,7 +115,7 @@ Other merge requests can now be added to the train.
To add a merge request to a merge train:
1. Visit a merge request.
1. Click the **Add to merge train** button.
1. Select **Add to merge train**.
If pipelines are already running for the merge request, you cannot add the merge request
to the train. Instead, you can schedule to add the merge request to a merge train **when the latest
@ -124,7 +126,7 @@ pipeline succeeds**.
## Remove a merge request from a merge train
1. Visit a merge request.
1. Click the **Remove from merge train** button.
1. Select **Remove from merge train**.
![Cancel merge train](img/merge_train_cancel_v12_0.png)
@ -161,23 +163,24 @@ In these cases, the reason for dropping the merge request is in the **system not
To check the reason:
1. Open the merge request that was dropped from the merge train.
1. Open the **Discussion** tab.
1. Select the **Discussion** tab.
1. Find a system note that includes either:
- The text **... removed this merge request from the merge train because ...**
- **... removed this merge request from the merge train because ...**
- **... aborted this merge request from the merge train because ...**
The reason is given in the text after the **because ...** phrase.
![Merge Train Failure](img/merge_train_failure.png)
The reason is given in the text after the **because ...** phrase.
![Merge train failure](img/merge_train_failure.png)
### Merge When Pipeline Succeeds cannot be chosen
[Merge When Pipeline Succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md)
is currently unavailable when Merge Trains are enabled.
is currently unavailable when merge trains are enabled.
See [the related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/12267)
for more information.
### Merge Train Pipeline cannot be retried
### Merge train pipeline cannot be retried
When a pipeline for merge trains fails the merge request is dropped from the train and the pipeline can't be retried.
Pipelines for merge trains run on the merged result of the changes in the merge request and
@ -189,7 +192,7 @@ again, which triggers a new pipeline.
### Unable to add to merge train with message "The pipeline for this merge request failed."
Sometimes the **Start/Add to Merge Train** button is not available and the merge request says,
Sometimes the **Start/Add to merge train** button is not available and the merge request says,
"The pipeline for this merge request failed. Please retry the job or push a new commit to fix the failure."
This issue occurs when [**Pipelines must succeed**](../../user/project/merge_requests/merge_when_pipeline_succeeds.md#only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds)
@ -197,19 +200,19 @@ is enabled in **Settings > General > Merge requests**. This option requires that
run a new successful pipeline before you can re-add a merge request to a merge train.
Merge trains ensure that each pipeline has succeeded before a merge happens, so
you can clear the **Pipelines must succeed** check box and keep
**Enable merge trains and pipelines for merged results** (merge trains) enabled.
you can clear the **Pipelines must succeed** checkbox and keep
**Enable merge trains and pipelines for merged results** (merge trains) selected.
If you want to keep the **Pipelines must succeed** option enabled along with Merge
Trains, create a new pipeline for merged results when this error occurs:
If you want to keep the **Pipelines must succeed** option selected along with merge
trains, create a new pipeline for merged results when this error occurs:
1. Go to the **Pipelines** tab and click **Run pipeline**.
1. Click **Start/Add to merge train when pipeline succeeds**.
1. On the **Pipelines** tab, select **Run pipeline**.
1. Select **Start/Add to merge train when pipeline succeeds**.
See [the related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/35135)
for more information.
### Merge Trains feature flag **(PREMIUM SELF)**
### Merge trains feature flag **(PREMIUM SELF)**
In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/244831),
you can [enable or disable merge trains in the project settings](#enable-merge-trains).
@ -227,7 +230,7 @@ Feature.enable(:disable_merge_trains)
```
After you enable this feature flag, all existing merge trains are cancelled and
the **Start/Add to Merge Train** button no longer appears in merge requests.
the **Start/Add to merge train** button no longer appears in merge requests.
To disable the feature flag, and enable merge trains again:

View file

@ -304,8 +304,8 @@ Prerequisites:
To trigger the pipeline when the upstream project is rebuilt:
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > CI/CD** page.
1. Expand the **Pipeline subscriptions** section.
1. On the left sidebar, select **Settings > CI/CD**.
1. Expand **Pipeline subscriptions**.
1. Enter the project you want to subscribe to, in the format `<namespace>/<project>`.
For example, if the project is `https://gitlab.com/gitlab-org/gitlab`, use `gitlab-org/gitlab`.
1. Select **Subscribe**.

View file

@ -10,7 +10,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10075) in GitLab Ultimate 12.0.
Use the dependency list to review your project's dependencies and key
details about those dependencies, including their known vulnerabilities. It is a collection of dependencies in your project, including existing and new findings. To see the dependency list, go to your project and select **Security & Compliance > Dependency List**.
details about those dependencies, including their known vulnerabilities. It is a collection of dependencies in your project, including existing and new findings.
To see the dependency list, go to your project and select **Security & Compliance > Dependency List**.
This information is sometimes referred to as a Software Bill of Materials or SBoM / BOM.
The dependency list only shows the results of the last successful pipeline to run on the default branch. This is why we recommend not changing the default behavior of allowing the secure jobs to fail.
@ -71,4 +74,12 @@ If the [License Compliance](../../compliance/license_compliance/index.md) CI job
## Downloading the dependency list
You can download your project's full list of dependencies and their details in
`JSON` format by selecting the download button.
`JSON` format.
### In the UI
You can download your projects list of dependencies and their details in JSON format by selecting the **Export** button. Note that the dependency list only shows the results of the last successful pipeline to run on the default branch.
### Using the API
You can download your projects list of dependencies [using the API](../../../api/dependencies.md#list-project-dependencies). Note this only provides the dependencies identified by the gemnasium family of analyzers and [not any other of the GitLab dependency analyzers](../dependency_scanning/analyzers.md).

View file

@ -146,6 +146,7 @@ as shown in the following table:
| [Access to Security Dashboard](../../application_security/security_dashboard/index.md) | **{dotted-circle}** | **{check-circle}** |
| [Configure SAST in the UI](#configure-sast-in-the-ui) | **{dotted-circle}** | **{check-circle}** |
| [Customize SAST Rulesets](#customize-rulesets) | **{dotted-circle}** | **{check-circle}** |
| [False Positive Detection](#false-positive-detection) | **{dotted-circle}** | **{check-circle}** |
## Contribute your scanner
@ -355,6 +356,12 @@ To create a custom ruleset:
value = "gosec-config.json"
```
### False Positive Detection **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/292686) in GitLab 14.2.
Vulnerabilities that have been detected and are false positives will be flagged as false positives in the security dashboard.
### Using CI/CD variables to pass credentials for private repositories
Some analyzers require downloading the project's dependencies in order to

View file

@ -100,6 +100,39 @@ The following table lists project permissions available for each role:
| [Merge requests](project/merge_requests/index.md):<br>Manage or accept | | | ✓ | ✓ | ✓ |
| [Merge requests](project/merge_requests/index.md):<br>Manage merge approval rules (project settings) | | | | ✓ | ✓ |
| [Merge requests](project/merge_requests/index.md):<br>Delete | | | | | ✓ |
| [Projects](project/index.md):<br>Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>Reposition comments on images (posted by any user) | ✓ (*10*) | ✓ (*10*) | ✓ (*10*) | ✓ | ✓ |
| [Projects](project/index.md):<br>View Insights **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>View Requirements **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>View [time tracking](project/time_tracking.md) reports | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>View [wiki](project/wiki/index.md) pages | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>Create [snippets](snippets.md) | | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>Manage labels | | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>View project statistics | | ✓ | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>Create, edit, delete [milestones](project/milestones/index.md). | | | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>Create, edit [wiki](project/wiki/index.md) pages | | | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>Enable Review Apps | | | ✓ | ✓ | ✓ |
| [Projects](project/index.md):<br>View project [Audit Events](../administration/audit_events.md) | | | ✓ (*11*) | ✓ | ✓ |
| [Projects](project/index.md):<br>Add deploy keys | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Add new team members | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Change [project features visibility](../public_access/public_access.md) level | | | | ✓ (14) | ✓ |
| [Projects](project/index.md):<br>Delete [wiki](project/wiki/index.md) pages | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Edit comments (posted by any user) | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Edit project badges | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Edit project settings | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Export project | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Manage [project access tokens](project/settings/project_access_tokens.md) **(FREE SELF)** **(PREMIUM SAAS)** (*12*) | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Manage [Project Operations](../operations/index.md) | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Share (invite) projects with groups | | | | ✓ (*8*) | ✓ (*8*) |
| [Projects](project/index.md):<br>View 2FA status of members | | | | ✓ | ✓ |
| [Projects](project/index.md):<br>Administer project compliance frameworks | | | | | ✓ |
| [Projects](project/index.md):<br>Archive project | | | | | ✓ |
| [Projects](project/index.md):<br>Change project visibility level | | | | | ✓ |
| [Projects](project/index.md):<br>Delete project | | | | | ✓ |
| [Projects](project/index.md):<br>Disable notification emails | | | | | ✓ |
| [Projects](project/index.md):<br>Rename project | | | | | ✓ |
| [Projects](project/index.md):<br>Transfer project to another namespace | | | | | ✓ |
| [Security dashboard](application_security/security_dashboard/index.md):<br>View Security reports **(ULTIMATE)** | ✓ (*3*) | ✓ | ✓ | ✓ | ✓ |
| [Security dashboard](application_security/security_dashboard/index.md):<br>Create issue from vulnerability finding **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| [Security dashboard](application_security/security_dashboard/index.md):<br>Create vulnerability from vulnerability finding **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
@ -110,27 +143,18 @@ The following table lists project permissions available for each role:
| [Security dashboard](application_security/security_dashboard/index.md):<br>Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| [Security dashboard](application_security/security_dashboard/index.md):<br>View vulnerability **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| [Security dashboard](application_security/security_dashboard/index.md):<br>View vulnerability findings in [dependency list](application_security/dependency_list/index.md) **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Download project | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
| Manage user-starred metrics dashboards (*7*) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| Reposition comments on images (posted by any user)| ✓ (*10*)| ✓ (*10*) | ✓ (*10*) | ✓ | ✓ |
| View [Releases](project/releases/index.md) | ✓ (*6*) | ✓ | ✓ | ✓ | ✓ |
| View a time tracking report | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View allowed and denied licenses **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control) | ✓ | ✓ | ✓ | ✓ | ✓ |
| View Insights **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View License Compliance reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View requirements **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View wiki pages | ✓ | ✓ | ✓ | ✓ | ✓ |
| Archive [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ |
| Archive/reopen requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Create new [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ |
| Create/edit requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
| Import/export requirements **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
| Move [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ |
| Pull [packages](packages/index.md) | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| Reopen [test case](../ci/test_cases/index.md) | | ✓ | ✓ | ✓ | ✓ |
@ -138,16 +162,12 @@ The following table lists project permissions available for each role:
| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
| View License list **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
| View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ |
| View project statistics | | ✓ | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Create and edit wiki pages | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ (*5*) | ✓ | ✓ |
| Create/edit/delete [releases](project/releases/index.md)| | | ✓ (*13*) | ✓ (*13*) | ✓ (*13*) |
| Create/edit/delete a Cleanup policy | | | ✓ | ✓ | ✓ |
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
| Enable Review Apps | | | ✓ | ✓ | ✓ |
| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Manage Feature Flags **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Publish [packages](packages/index.md) | | | ✓ | ✓ | ✓ |
@ -158,41 +178,21 @@ The following table lists project permissions available for each role:
| Rewrite/remove Git tags | | | ✓ | ✓ | ✓ |
| Update a container registry | | | ✓ | ✓ | ✓ |
| View Pods logs | | | ✓ | ✓ | ✓ |
| View project Audit Events | | | ✓ (*11*) | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Configure project hooks | | | | ✓ | ✓ |
| Change project features visibility level | | | | ✓ (14) | ✓ |
| Delete [packages](packages/index.md) | | | | ✓ | ✓ |
| Delete wiki pages | | | | ✓ | ✓ |
| Edit comments (posted by any user) | | | | ✓ | ✓ |
| Edit project badges | | | | ✓ | ✓ |
| Edit project settings | | | | ✓ | ✓ |
| Enable/disable branch protection | | | | ✓ | ✓ |
| Enable/disable tag protections | | | | ✓ | ✓ |
| Export project | | | | ✓ | ✓ |
| Manage [project access tokens](project/settings/project_access_tokens.md) **(FREE SELF)** **(PREMIUM SAAS)** (*12*) | | | | ✓ | ✓ |
| Manage [push rules](../push_rules/push_rules.md) | | | | ✓ | ✓ |
| Manage clusters | | | | ✓ | ✓ |
| Manage Error Tracking | | | | ✓ | ✓ |
| Manage GitLab Pages | | | | ✓ | ✓ |
| Manage GitLab Pages domains and certificates | | | | ✓ | ✓ |
| Manage license policy **(ULTIMATE)** | | | | ✓ | ✓ |
| Manage Project Operations | | | | ✓ | ✓ |
| Manage Terraform state | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
| Remove GitLab Pages | | | | ✓ | ✓ |
| Share (invite) projects with groups | | | | ✓ (*8*) | ✓ (*8*)|
| Turn on/off protected branch push for developers | | | | ✓ | ✓ |
| View 2FA status of members | | | | ✓ | ✓ |
| Administer project compliance frameworks | | | | | ✓ |
| Archive project | | | | | ✓ |
| Change project visibility level | | | | | ✓ |
| Delete project | | | | | ✓ |
| Disable notification emails | | | | | ✓ |
| Remove fork relationship | | | | | ✓ |
| Rename project | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Force push to protected branches (*4*) | | | | | |
| Remove protected branches (*4*) | | | | | |

View file

@ -177,11 +177,6 @@ module Gitlab
'unknown'
end
# Monkeypatch rails with upgraded database observability
def self.install_monkey_patches
ActiveRecord::Base.prepend(ActiveRecordBaseTransactionMetrics)
end
def self.read_only?
false
end
@ -190,6 +185,18 @@ module Gitlab
!read_only?
end
# Monkeypatch rails with upgraded database observability
def self.install_transaction_metrics_patches!
ActiveRecord::Base.prepend(ActiveRecordBaseTransactionMetrics)
end
def self.install_transaction_context_patches!
ActiveRecord::ConnectionAdapters::TransactionManager
.prepend(TransactionManagerContext)
ActiveRecord::ConnectionAdapters::RealTransaction
.prepend(RealTransactionContext)
end
# MonkeyPatch for ActiveRecord::Base for adding observability
module ActiveRecordBaseTransactionMetrics
extend ActiveSupport::Concern
@ -204,6 +211,32 @@ module Gitlab
end
end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
module TransactionManagerContext
def transaction_context
@stack.first.try(:gitlab_transaction_context)
end
end
module RealTransactionContext
def gitlab_transaction_context
@gitlab_transaction_context ||= ::Gitlab::Database::Transaction::Context.new
end
def commit
gitlab_transaction_context.commit
super
end
def rollback
gitlab_transaction_context.rollback
super
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
end

View file

@ -127,9 +127,17 @@ module Gitlab
# recognize the connection, this method returns the primary role
# directly. In future, we may need to check for other sources.
def self.db_role_for_connection(connection)
return ROLE_PRIMARY if !enable? || proxy.blank?
return ROLE_UNKNOWN unless connection
proxy.load_balancer.db_role_for_connection(connection)
# The connection proxy does not have a role assigned
# as this is dependent on a execution context
return ROLE_UNKNOWN if connection.is_a?(ConnectionProxy)
if connection.pool.db_config.name.ends_with?(LoadBalancer::REPLICA_SUFFIX)
ROLE_REPLICA
else
ROLE_PRIMARY
end
end
end
end

View file

@ -8,13 +8,11 @@ module Gitlab
# hosts - The list of secondary hosts to add.
def initialize(hosts = [])
@hosts = hosts.shuffle
@pools = Set.new
@index = 0
@mutex = Mutex.new
@hosts_gauge = Gitlab::Metrics.gauge(:db_load_balancing_hosts, 'Current number of load balancing hosts')
set_metrics!
update_pools
end
def hosts
@ -35,15 +33,10 @@ module Gitlab
@mutex.synchronize { @hosts.map { |host| [host.host, host.port] } }
end
def manage_pool?(pool)
@pools.include?(pool)
end
def hosts=(hosts)
@mutex.synchronize do
@hosts = hosts
unsafe_shuffle
update_pools
end
set_metrics!
@ -89,10 +82,6 @@ module Gitlab
def set_metrics!
@hosts_gauge.set({}, @hosts.length)
end
def update_pools
@pools = Set.new(@hosts.map(&:pool))
end
end
end
end

View file

@ -10,14 +10,14 @@ module Gitlab
class LoadBalancer
CACHE_KEY = :gitlab_load_balancer_host
REPLICA_SUFFIX = '_replica'
attr_reader :host_list
# hosts - The hostnames/addresses of the additional databases.
def initialize(hosts = [], model = ActiveRecord::Base)
@model = model
@host_list = HostList.new(hosts.map { |addr| Host.new(addr, self) })
@connection_db_roles = {}.compare_by_identity
@connection_db_roles_count = {}.compare_by_identity
end
def disconnect!(timeout: 120)
@ -29,7 +29,6 @@ module Gitlab
# If no secondaries were available this method will use the primary
# instead.
def read(&block)
connection = nil
conflict_retried = 0
while host
@ -37,12 +36,8 @@ module Gitlab
begin
connection = host.connection
track_connection_role(connection, ROLE_REPLICA)
return yield connection
rescue StandardError => error
untrack_connection_role(connection)
if serialization_failure?(error)
# This error can occur when a query conflicts. See
# https://www.postgresql.org/docs/current/static/hot-standby.html#HOT-STANDBY-CONFLICT
@ -85,8 +80,6 @@ module Gitlab
)
read_write(&block)
ensure
untrack_connection_role(connection)
end
# Yields a connection that can be used for both reads and writes.
@ -97,21 +90,8 @@ module Gitlab
# a few times.
retry_with_backoff do
connection = pool.connection
track_connection_role(connection, ROLE_PRIMARY)
yield connection
end
ensure
untrack_connection_role(connection)
end
# Recognize the role (primary/replica) of the database this connection
# is connecting to. If the connection is not issued by this load
# balancer, return nil
def db_role_for_connection(connection)
return @connection_db_roles[connection] if @connection_db_roles[connection]
return ROLE_REPLICA if @host_list.manage_pool?(connection.pool)
return ROLE_PRIMARY if connection.pool == pool
end
# Returns a host to use for queries.
@ -222,7 +202,7 @@ module Gitlab
replica_db_config = ActiveRecord::DatabaseConfigurations::HashConfig.new(
db_config.env_name,
db_config.name + "_replica",
db_config.name + REPLICA_SUFFIX,
env_config
)
@ -250,22 +230,6 @@ module Gitlab
host.enable_query_cache! unless host.query_cache_enabled
end
def track_connection_role(connection, role)
@connection_db_roles[connection] = role
@connection_db_roles_count[connection] ||= 0
@connection_db_roles_count[connection] += 1
end
def untrack_connection_role(connection)
return if connection.blank? || @connection_db_roles_count[connection].blank?
@connection_db_roles_count[connection] -= 1
if @connection_db_roles_count[connection] <= 0
@connection_db_roles.delete(connection)
@connection_db_roles_count.delete(connection)
end
end
def request_cache
base = RequestStore[:gitlab_load_balancer] ||= {}
base[pool] ||= {}

View file

@ -0,0 +1,125 @@
# frozen_string_literal: true
module Gitlab
module Database
module Transaction
class Context
attr_reader :context
LOG_DEPTH_THRESHOLD = 8
LOG_SAVEPOINTS_THRESHOLD = 32
LOG_DURATION_S_THRESHOLD = 300
LOG_THROTTLE_DURATION = 1
def initialize
@context = {}
end
def set_start_time
@context[:start_time] = current_timestamp
end
def increment_savepoints
@context[:savepoints] = @context[:savepoints].to_i + 1
end
def increment_rollbacks
@context[:rollbacks] = @context[:rollbacks].to_i + 1
end
def increment_releases
@context[:releases] = @context[:releases].to_i + 1
end
def set_depth(depth)
@context[:depth] = [@context[:depth].to_i, depth].max
end
def track_sql(sql)
(@context[:queries] ||= []).push(sql)
end
def duration
return unless @context[:start_time].present?
current_timestamp - @context[:start_time]
end
def depth_threshold_exceeded?
@context[:depth].to_i > LOG_DEPTH_THRESHOLD
end
def savepoints_threshold_exceeded?
@context[:savepoints].to_i > LOG_SAVEPOINTS_THRESHOLD
end
def duration_threshold_exceeded?
duration.to_i > LOG_DURATION_S_THRESHOLD
end
def log_savepoints?
depth_threshold_exceeded? || savepoints_threshold_exceeded?
end
def log_duration?
duration_threshold_exceeded?
end
def should_log?
!logged_already? && (log_savepoints? || log_duration?)
end
def commit
log(:commit)
end
def rollback
log(:rollback)
end
private
def queries
@context[:queries].to_a.join("\n")
end
def current_timestamp
::Gitlab::Metrics::System.monotonic_time
end
def logged_already?
return false if @context[:last_log_timestamp].nil?
(current_timestamp - @context[:last_log_timestamp].to_i) < LOG_THROTTLE_DURATION
end
def set_last_log_timestamp
@context[:last_log_timestamp] = current_timestamp
end
def log(operation)
return unless should_log?
set_last_log_timestamp
attributes = {
class: self.class.name,
result: operation,
duration_s: duration,
depth: @context[:depth].to_i,
savepoints_count: @context[:savepoints].to_i,
rollbacks_count: @context[:rollbacks].to_i,
releases_count: @context[:releases].to_i,
sql: queries
}
application_info(attributes)
end
def application_info(attributes)
Gitlab::AppJsonLogger.info(attributes)
end
end
end
end
end

View file

@ -0,0 +1,66 @@
# frozen_string_literal: true
module Gitlab
module Database
module Transaction
class Observer
INSTRUMENTED_STATEMENTS = %w[BEGIN SAVEPOINT ROLLBACK RELEASE].freeze
LONGEST_COMMAND_LENGTH = 'ROLLBACK TO SAVEPOINT'.length
START_COMMENT = '/*'
END_COMMENT = '*/'
def self.instrument_transactions(cmd, event)
connection = event.payload[:connection]
manager = connection&.transaction_manager
return unless manager.respond_to?(:transaction_context)
context = manager.transaction_context
return if context.nil?
if cmd.start_with?('BEGIN')
context.set_start_time
context.set_depth(0)
context.track_sql(event.payload[:sql])
elsif cmd.start_with?('SAVEPOINT ')
context.set_depth(manager.open_transactions)
context.increment_savepoints
elsif cmd.start_with?('ROLLBACK TO SAVEPOINT')
context.increment_rollbacks
elsif cmd.start_with?('RELEASE SAVEPOINT ')
context.increment_releases
end
end
def self.register!
ActiveSupport::Notifications.subscribe('sql.active_record') do |event|
sql = event.payload.dig(:sql).to_s
cmd = extract_sql_command(sql)
if cmd.start_with?(*INSTRUMENTED_STATEMENTS)
self.instrument_transactions(cmd, event)
end
end
end
def self.extract_sql_command(sql)
return sql unless sql.start_with?(START_COMMENT)
index = sql.index(END_COMMENT)
return sql unless index
# /* comment */ SELECT
#
# We offset using a position of the end comment + 1 character to
# accomodate a space between Marginalia comment and a SQL statement.
offset = index + END_COMMENT.length + 1
# Avoid duplicating the entire string. This isn't optimized to
# strip extra spaces, but we assume that this doesn't happen
# for performance reasons.
sql[offset..offset + LONGEST_COMMAND_LENGTH]
end
end
end
end
end

View file

@ -5,6 +5,7 @@ require 'gitlab/email/handler/reply_processing'
# handles note/reply creation emails with these formats:
# incoming+1234567890abcdef1234567890abcdef@incoming.gitlab.com
# Quoted material is _not_ stripped but appended as a `details` section
module Gitlab
module Email
module Handler
@ -24,7 +25,7 @@ module Gitlab
validate_permission!(:create_note)
raise NoteableNotFoundError unless noteable
raise EmptyEmailError if message.blank?
raise EmptyEmailError if note_message.blank?
verify_record!(
record: create_note,
@ -47,7 +48,13 @@ module Gitlab
end
def create_note
sent_notification.create_reply(message)
sent_notification.create_reply(note_message)
end
def note_message
return message unless sent_notification.noteable_type == "Issue"
message_with_appended_reply
end
end
end

View file

@ -35,13 +35,20 @@ module Gitlab
@message_with_reply ||= process_message(trim_reply: false)
end
def process_message(**kwargs)
message = ReplyParser.new(mail, **kwargs).execute.strip
message_with_attachments = add_attachments(message)
def message_with_appended_reply
@message_with_appended_reply ||= process_message(append_reply: true)
end
# Support bot is specifically forbidden
# from using slash commands.
strip_quick_actions(message_with_attachments)
def process_message(**kwargs)
message, stripped_text = ReplyParser.new(mail, **kwargs).execute
message = message.strip
message_with_attachments = add_attachments(message)
# Support bot is specifically forbidden from using slash commands.
message = strip_quick_actions(message_with_attachments)
return message unless kwargs[:append_reply]
append_reply(message, stripped_text)
end
def add_attachments(reply)
@ -92,10 +99,22 @@ module Gitlab
def strip_quick_actions(content)
return content unless author.support_bot?
command_definitions = ::QuickActions::InterpretService.command_definitions
extractor = ::Gitlab::QuickActions::Extractor.new(command_definitions)
quick_actions_extractor.redact_commands(content)
end
extractor.redact_commands(content)
def quick_actions_extractor
command_definitions = ::QuickActions::InterpretService.command_definitions
::Gitlab::QuickActions::Extractor.new(command_definitions)
end
def append_reply(message, reply)
return message if message.blank? || reply.blank?
# Do not append if message only contains slash commands
body, _commands = quick_actions_extractor.extract_commands(message)
return message if body.empty?
message + "\n\n<details><summary>...</summary>\n\n#{reply}\n\n</details>"
end
end
end

View file

@ -6,20 +6,17 @@ module Gitlab
class ReplyParser
attr_accessor :message
def initialize(message, trim_reply: true)
def initialize(message, trim_reply: true, append_reply: false)
@message = message
@trim_reply = trim_reply
@append_reply = append_reply
end
def execute
body = select_body(message)
encoding = body.encoding
if @trim_reply
body = EmailReplyTrimmer.trim(body)
end
body, stripped_text = EmailReplyTrimmer.trim(body, @append_reply) if @trim_reply
return '' unless body
# not using /\s+$/ here because that deletes empty lines
@ -30,7 +27,10 @@ module Gitlab
# so we detect it manually here.
return "" if body.lines.all? { |l| l.strip.empty? || l.start_with?('>') }
body.force_encoding(encoding).encode("UTF-8")
encoded_body = body.force_encoding(encoding).encode("UTF-8")
return encoded_body unless @append_reply
[encoded_body, stripped_text.force_encoding(encoding).encode("UTF-8")]
end
private

View file

@ -12,6 +12,7 @@ module Gitlab
included do
scope :public_only, -> { where(visibility_level: PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: [PUBLIC, INTERNAL] ) }
scope :private_only, -> { where(visibility_level: PRIVATE) }
scope :non_public_only, -> { where.not(visibility_level: PUBLIC) }
scope :public_to_user, -> (user = nil) do

View file

@ -0,0 +1,23 @@
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
Mime-Version: 1.0
Content-Type: text/plain;
charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
X-Sieve: CMU Sieve 2.2
X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
13 Jun 2013 14:03:48 -0700 (PDT)
X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
--
> quote line 1
> quote line 2
> quote line 3

View file

@ -56,44 +56,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::HostList do
end
end
describe '#manage_pool?' do
context 'when the testing pool belongs to one host of the host list' do
it 'returns true' do
pool = host_list.hosts.first.pool
expect(host_list.manage_pool?(pool)).to be(true)
end
end
context 'when the testing pool belongs to a former host of the host list' do
it 'returns false' do
pool = host_list.hosts.first.pool
host_list.hosts = [
Gitlab::Database::LoadBalancing::Host.new('foo', load_balancer)
]
expect(host_list.manage_pool?(pool)).to be(false)
end
end
context 'when the testing pool belongs to a new host of the host list' do
it 'returns true' do
host = Gitlab::Database::LoadBalancing::Host.new('foo', load_balancer)
host_list.hosts = [host]
expect(host_list.manage_pool?(host.pool)).to be(true)
end
end
context 'when the testing pool does not have any relation with the host list' do
it 'returns false' do
host = Gitlab::Database::LoadBalancing::Host.new('foo', load_balancer)
expect(host_list.manage_pool?(host.pool)).to be(false)
end
end
end
describe '#hosts' do
it 'returns a copy of the host' do
first = host_list.hosts

View file

@ -137,126 +137,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
end
end
describe '#db_role_for_connection' do
context 'when the load balancer creates the connection with #read' do
it 'returns :replica' do
role = nil
lb.read do |connection|
role = lb.db_role_for_connection(connection)
end
expect(role).to be(:replica)
end
end
context 'when the load balancer uses nested #read' do
it 'returns :replica' do
roles = []
lb.read do |connection_1|
lb.read do |connection_2|
roles << lb.db_role_for_connection(connection_2)
end
roles << lb.db_role_for_connection(connection_1)
end
expect(roles).to eq([:replica, :replica])
end
end
context 'when the load balancer creates the connection with #read_write' do
it 'returns :primary' do
role = nil
lb.read_write do |connection|
role = lb.db_role_for_connection(connection)
end
expect(role).to be(:primary)
end
end
context 'when the load balancer uses nested #read_write' do
it 'returns :primary' do
roles = []
lb.read_write do |connection_1|
lb.read_write do |connection_2|
roles << lb.db_role_for_connection(connection_2)
end
roles << lb.db_role_for_connection(connection_1)
end
expect(roles).to eq([:primary, :primary])
end
end
context 'when the load balancer falls back the connection creation to primary' do
it 'returns :primary' do
allow(lb).to receive(:serialization_failure?).and_return(true)
role = nil
raised = 7 # 2 hosts = 6 retries
lb.read do |connection|
if raised > 0
raised -= 1
raise
end
role = lb.db_role_for_connection(connection)
end
expect(role).to be(:primary)
end
end
context 'when the load balancer uses replica after recovery from a failure' do
it 'returns :replica' do
allow(lb).to receive(:connection_error?).and_return(true)
role = nil
raised = false
lb.read do |connection|
unless raised
raised = true
raise
end
role = lb.db_role_for_connection(connection)
end
expect(role).to be(:replica)
end
end
context 'when the connection comes from a pool managed by the host list' do
it 'returns :replica' do
connection = double(:connection)
allow(connection).to receive(:pool).and_return(lb.host_list.hosts.first.pool)
expect(lb.db_role_for_connection(connection)).to be(:replica)
end
end
context 'when the connection comes from the primary pool' do
it 'returns :primary' do
connection = double(:connection)
allow(connection).to receive(:pool).and_return(lb.send(:pool))
expect(lb.db_role_for_connection(connection)).to be(:primary)
end
end
context 'when the connection does not come from any known pool' do
it 'returns nil' do
connection = double(:connection)
pool = double(:connection_pool)
allow(connection).to receive(:pool).and_return(pool)
expect(lb.db_role_for_connection(connection)).to be(nil)
end
end
end
describe '#host' do
it 'returns the secondary host to use' do
expect(lb.host).to be_an_instance_of(Gitlab::Database::LoadBalancing::Host)

View file

@ -296,55 +296,37 @@ RSpec.describe Gitlab::Database::LoadBalancing do
end
describe '.db_role_for_connection' do
let(:connection) { double(:conneciton) }
context 'when the load balancing is not configured' do
before do
allow(described_class).to receive(:enable?).and_return(false)
end
let(:connection) { ActiveRecord::Base.connection }
it 'returns primary' do
expect(described_class.db_role_for_connection(connection)).to be(:primary)
expect(described_class.db_role_for_connection(connection)).to eq(:primary)
end
end
context 'when the load balancing is configured' do
let(:proxy) { described_class::ConnectionProxy.new(%w(foo)) }
let(:load_balancer) { described_class::LoadBalancer.new(%w(foo)) }
let(:db_host) { ActiveRecord::Base.connection_pool.db_config.host }
let(:proxy) { described_class::ConnectionProxy.new([db_host]) }
before do
allow(described_class).to receive(:enable?).and_return(true)
allow(described_class).to receive(:proxy).and_return(proxy)
allow(proxy).to receive(:load_balancer).and_return(load_balancer)
context 'when a proxy connection is used' do
it 'returns :unknown' do
expect(described_class.db_role_for_connection(proxy)).to eq(:unknown)
end
end
context 'when the load balancer returns :replica' do
context 'when a read connection is used' do
it 'returns :replica' do
allow(load_balancer).to receive(:db_role_for_connection).and_return(:replica)
expect(described_class.db_role_for_connection(connection)).to be(:replica)
expect(load_balancer).to have_received(:db_role_for_connection).with(connection)
proxy.load_balancer.read do |connection|
expect(described_class.db_role_for_connection(connection)).to eq(:replica)
end
end
end
context 'when the load balancer returns :primary' do
context 'when a read_write connection is used' do
it 'returns :primary' do
allow(load_balancer).to receive(:db_role_for_connection).and_return(:primary)
expect(described_class.db_role_for_connection(connection)).to be(:primary)
expect(load_balancer).to have_received(:db_role_for_connection).with(connection)
end
end
context 'when the load balancer returns nil' do
it 'returns nil' do
allow(load_balancer).to receive(:db_role_for_connection).and_return(nil)
expect(described_class.db_role_for_connection(connection)).to be(nil)
expect(load_balancer).to have_received(:db_role_for_connection).with(connection)
proxy.load_balancer.read_write do |connection|
expect(described_class.db_role_for_connection(connection)).to eq(:primary)
end
end
end
end

View file

@ -0,0 +1,144 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::Transaction::Context do
subject { described_class.new }
let(:data) { subject.context }
before do
stub_const("#{described_class}::LOG_THROTTLE", 100)
end
describe '#set_start_time' do
before do
subject.set_start_time
end
it 'sets start_time' do
expect(data).to have_key(:start_time)
end
end
describe '#increment_savepoints' do
before do
2.times { subject.increment_savepoints }
end
it { expect(data[:savepoints]).to eq(2) }
end
describe '#increment_rollbacks' do
before do
3.times { subject.increment_rollbacks }
end
it { expect(data[:rollbacks]).to eq(3) }
end
describe '#increment_releases' do
before do
4.times { subject.increment_releases }
end
it { expect(data[:releases]).to eq(4) }
end
describe '#set_depth' do
before do
subject.set_depth(2)
end
it { expect(data[:depth]).to eq(2) }
end
describe '#track_sql' do
before do
subject.track_sql('SELECT 1')
subject.track_sql('SELECT * FROM users')
end
it { expect(data[:queries]).to eq(['SELECT 1', 'SELECT * FROM users']) }
end
describe '#duration' do
before do
subject.set_start_time
end
it { expect(subject.duration).to be >= 0 }
end
context 'when depth is low' do
it 'does not log data upon COMMIT' do
expect(subject).not_to receive(:application_info)
subject.commit
end
it 'does not log data upon ROLLBACK' do
expect(subject).not_to receive(:application_info)
subject.rollback
end
it '#should_log? returns false' do
expect(subject.should_log?).to be false
end
end
shared_examples 'logs transaction data' do
it 'logs once upon COMMIT' do
expect(subject).to receive(:application_info).and_call_original
2.times { subject.commit }
end
it 'logs once upon ROLLBACK' do
expect(subject).to receive(:application_info).once
2.times { subject.rollback }
end
it 'logs again when log throttle duration passes' do
expect(subject).to receive(:application_info).twice.and_call_original
2.times { subject.commit }
data[:last_log_timestamp] -= (described_class::LOG_THROTTLE_DURATION + 1)
subject.commit
end
it '#should_log? returns true' do
expect(subject.should_log?).to be true
end
end
context 'when depth exceeds threshold' do
before do
subject.set_depth(described_class::LOG_DEPTH_THRESHOLD + 1)
end
it_behaves_like 'logs transaction data'
end
context 'when savepoints count exceeds threshold' do
before do
data[:savepoints] = described_class::LOG_SAVEPOINTS_THRESHOLD + 1
end
it_behaves_like 'logs transaction data'
end
context 'when duration exceeds threshold' do
before do
subject.set_start_time
data[:start_time] -= (described_class::LOG_DURATION_S_THRESHOLD + 1)
end
it_behaves_like 'logs transaction data'
end
end

View file

@ -0,0 +1,57 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::Transaction::Observer do
# Use the delete DB strategy so that the test won't be wrapped in a transaction
describe '.instrument_transactions', :delete do
let(:transaction_context) { ActiveRecord::Base.connection.transaction_manager.transaction_context }
let(:context) { transaction_context.context }
around do |example|
# Emulate production environment when SQL comments come first to avoid truncation
Marginalia::Comment.prepend_comment = true
subscriber = described_class.register!
example.run
ActiveSupport::Notifications.unsubscribe(subscriber)
Marginalia::Comment.prepend_comment = false
end
it 'tracks transaction data', :aggregate_failures do
ActiveRecord::Base.transaction do
ActiveRecord::Base.transaction(requires_new: true) do
User.first
expect(transaction_context).to be_a(::Gitlab::Database::Transaction::Context)
expect(context.keys).to match_array(%i(start_time depth savepoints queries))
expect(context[:depth]).to eq(2)
expect(context[:savepoints]).to eq(1)
expect(context[:queries].length).to eq(1)
end
end
expect(context[:depth]).to eq(2)
expect(context[:savepoints]).to eq(1)
expect(context[:releases]).to eq(1)
end
describe '.extract_sql_command' do
using RSpec::Parameterized::TableSyntax
where(:sql, :expected) do
'SELECT 1' | 'SELECT 1'
'/* test comment */ SELECT 1' | 'SELECT 1'
'/* test comment */ ROLLBACK TO SAVEPOINT point1' | 'ROLLBACK TO SAVEPOINT '
'SELECT 1 /* trailing comment */' | 'SELECT 1 /* trailing comment */'
end
with_them do
it do
expect(described_class.extract_sql_command(sql)).to eq(expected)
end
end
end
end
end

View file

@ -110,6 +110,60 @@ RSpec.describe Gitlab::Email::Handler::CreateNoteHandler do
end
end
context 'when email contains reply' do
shared_examples 'no content message' do
context 'when email contains quoted text only' do
let(:email_raw) { fixture_file('emails/no_content_with_quote.eml') }
it 'raises an EmptyEmailError' do
expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError)
end
end
context 'when email contains quoted text and quick commands only' do
let(:email_raw) { fixture_file('emails/commands_only_reply.eml') }
it 'does not create a discussion' do
expect { receiver.execute }.not_to change { noteable.notes.count }
end
end
end
context 'when noteable is not an issue' do
let_it_be(:note) { create(:note_on_merge_request, project: project) }
it_behaves_like 'no content message'
context 'when email contains text, quoted text and quick commands' do
let(:email_raw) { fixture_file('emails/commands_in_reply.eml') }
it 'creates a discussion without appended reply' do
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
new_note = noteable.notes.last
expect(new_note.note).not_to include('<details><summary>...</summary>')
end
end
end
context 'when noteable is an issue' do
let_it_be(:note) { create(:note_on_issue, project: project) }
it_behaves_like 'no content message'
context 'when email contains text, quoted text and quick commands' do
let(:email_raw) { fixture_file('emails/commands_in_reply.eml') }
it 'creates a discussion with appended reply' do
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
new_note = noteable.notes.last
expect(new_note.note).to include('<details><summary>...</summary>')
end
end
end
end
context 'when note is not a discussion' do
let(:note) { create(:note_on_merge_request, project: project) }

View file

@ -228,5 +228,21 @@ RSpec.describe Gitlab::Email::ReplyParser do
BODY
)
end
it "appends trimmed reply when when append_reply option is true" do
body = <<-BODY.strip_heredoc.chomp
The reply by email functionality should be extended to allow creating a new issue by email.
even when the email is forwarded to the project which may include lines that begin with ">"
there should be a quote below this line:
BODY
reply = <<-BODY.strip_heredoc.chomp
> this is a quote
BODY
expect(test_parse_body(fixture_file("emails/valid_new_issue_with_quote.eml"), { append_reply: true }))
.to contain_exactly(body, reply)
end
end
end

View file

@ -640,6 +640,12 @@ RSpec.describe Group do
it { is_expected.to match_array([private_group, internal_group]) }
end
describe 'private_only' do
subject { described_class.private_only.to_a }
it { is_expected.to match_array([private_group]) }
end
describe 'with_onboarding_progress' do
subject { described_class.with_onboarding_progress }