Commit 76e9fc7b authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent a210c43e
...@@ -301,7 +301,7 @@ gem 'sentry-raven', '~> 2.9' ...@@ -301,7 +301,7 @@ gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.10.3' gem 'premailer-rails', '~> 1.10.3'
# LabKit: Tracing and Correlation # LabKit: Tracing and Correlation
gem 'gitlab-labkit', '0.10.1' gem 'gitlab-labkit', '0.11.0'
# I18n # I18n
gem 'ruby_parser', '~> 3.8', require: false gem 'ruby_parser', '~> 3.8', require: false
......
...@@ -380,7 +380,7 @@ GEM ...@@ -380,7 +380,7 @@ GEM
github-markup (1.7.0) github-markup (1.7.0)
gitlab-chronic (0.10.5) gitlab-chronic (0.10.5)
numerizer (~> 0.2) numerizer (~> 0.2)
gitlab-labkit (0.10.1) gitlab-labkit (0.11.0)
actionpack (>= 5.0.0, < 6.1.0) actionpack (>= 5.0.0, < 6.1.0)
activesupport (>= 5.0.0, < 6.1.0) activesupport (>= 5.0.0, < 6.1.0)
grpc (~> 1.19) grpc (~> 1.19)
...@@ -1232,7 +1232,7 @@ DEPENDENCIES ...@@ -1232,7 +1232,7 @@ DEPENDENCIES
gitaly (~> 1.86.0) gitaly (~> 1.86.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.10.1) gitlab-labkit (= 0.11.0)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
gitlab-mail_room (~> 0.0.3) gitlab-mail_room (~> 0.0.3)
gitlab-markup (~> 1.7.0) gitlab-markup (~> 1.7.0)
......
<script> <script>
import _ from 'underscore'; import { head, tail } from 'lodash';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -48,8 +48,7 @@ export default { ...@@ -48,8 +48,7 @@ export default {
const projects = [...this.deployKey.deploy_keys_projects]; const projects = [...this.deployKey.deploy_keys_projects];
if (this.projectId !== null) { if (this.projectId !== null) {
const indexOfCurrentProject = _.findIndex( const indexOfCurrentProject = projects.findIndex(
projects,
project => project =>
project && project &&
project.project && project.project &&
...@@ -66,10 +65,10 @@ export default { ...@@ -66,10 +65,10 @@ export default {
return projects; return projects;
}, },
firstProject() { firstProject() {
return _.head(this.projects); return head(this.projects);
}, },
restProjects() { restProjects() {
return _.tail(this.projects); return tail(this.projects);
}, },
restProjectsTooltip() { restProjectsTooltip() {
return sprintf(s__('DeployKeys|Expand %{count} other projects'), { return sprintf(s__('DeployKeys|Expand %{count} other projects'), {
......
...@@ -166,7 +166,6 @@ export default { ...@@ -166,7 +166,6 @@ export default {
:href="lineHref" :href="lineHref"
@click="setHighlightedRow(lineCode)" @click="setHighlightedRow(lineCode)"
> >
{{ lineNumber }}
</a> </a>
<diff-gutter-avatars <diff-gutter-avatars
v-if="shouldShowAvatarsOnGutter" v-if="shouldShowAvatarsOnGutter"
......
...@@ -485,6 +485,10 @@ table.code { ...@@ -485,6 +485,10 @@ table.code {
} }
} }
} }
&:not(.js-unfold-bottom) a::before {
content: attr(data-linenumber);
}
} }
&.line_content { &.line_content {
......
...@@ -26,6 +26,8 @@ class SessionsController < Devise::SessionsController ...@@ -26,6 +26,8 @@ class SessionsController < Devise::SessionsController
before_action :load_recaptcha before_action :load_recaptcha
before_action :frontend_tracking_data, only: [:new] before_action :frontend_tracking_data, only: [:new]
around_action :set_current_context
after_action :log_failed_login, if: :action_new_and_failed_login? after_action :log_failed_login, if: :action_new_and_failed_login?
helper_method :captcha_enabled?, :captcha_on_login_required? helper_method :captcha_enabled?, :captcha_on_login_required?
...@@ -305,6 +307,13 @@ class SessionsController < Devise::SessionsController ...@@ -305,6 +307,13 @@ class SessionsController < Devise::SessionsController
# We want tracking data pushed to the frontend when the user is _in_ the control group # We want tracking data pushed to the frontend when the user is _in_ the control group
frontend_experimentation_tracking_data(:signup_flow, 'start') unless experiment_enabled?(:signup_flow) frontend_experimentation_tracking_data(:signup_flow, 'start') unless experiment_enabled?(:signup_flow)
end end
def set_current_context(&block)
Gitlab::ApplicationContext.with_context(
user: -> { current_user },
caller_id: "#{self.class.name}##{action_name}",
&block)
end
end end
SessionsController.prepend_if_ee('EE::SessionsController') SessionsController.prepend_if_ee('EE::SessionsController')
...@@ -307,7 +307,7 @@ class Snippet < ApplicationRecord ...@@ -307,7 +307,7 @@ class Snippet < ApplicationRecord
end end
class << self class << self
# Searches for snippets with a matching title or file name. # Searches for snippets with a matching title, description or file name.
# #
# This method uses ILIKE on PostgreSQL and LIKE on MySQL. # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
# #
...@@ -315,7 +315,7 @@ class Snippet < ApplicationRecord ...@@ -315,7 +315,7 @@ class Snippet < ApplicationRecord
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def search(query) def search(query)
fuzzy_search(query, [:title, :file_name]) fuzzy_search(query, [:title, :description, :file_name])
end end
# Searches for snippets with matching content. # Searches for snippets with matching content.
......
---
title: Add nginx request aggregations to WAF anomaly service
merge_request: 25273
author:
type: added
---
title: Replace underscore with lodash for ./app/assets/javascripts/deploy_keys
merge_request: 24965
author: Jacopo Beschi @jacopo-beschi
type: changed
---
title: Include snippet description as part of snippet title search (when Elasticsearch is not enabled)
merge_request: 25961
author:
type: added
---
title: Replace line diff number css selector with actual HTML inside MRs
merge_request:
author: Oregand
type: other
---
title: 'Use uncached SQL queries for Geo long-running workers'
merge_request: 26187
author:
type: fixed
---
title: Update Project Import API rate limit
merge_request: 26903
author:
type: other
---
title: Use process-wide memory cache for feature flags
merge_request: 26935
author:
type: performance
...@@ -70,7 +70,11 @@ Example response: ...@@ -70,7 +70,11 @@ Example response:
"updated_at":"2019-07-12T19:45:44.256Z", "updated_at":"2019-07-12T19:45:44.256Z",
"due_date":"2019-08-16T11:00:00.256Z", "due_date":"2019-08-16T11:00:00.256Z",
"start_date":"2019-07-30T12:00:00.256Z", "start_date":"2019-07-30T12:00:00.256Z",
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/1" "web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/1",
"issue_stats": {
"total": 98,
"closed": 76
}
}, },
{ {
"id":52, "id":52,
...@@ -83,7 +87,11 @@ Example response: ...@@ -83,7 +87,11 @@ Example response:
"updated_at":"2019-07-16T14:00:12.256Z", "updated_at":"2019-07-16T14:00:12.256Z",
"due_date":"2019-08-16T11:00:00.256Z", "due_date":"2019-08-16T11:00:00.256Z",
"start_date":"2019-07-30T12:00:00.256Z", "start_date":"2019-07-30T12:00:00.256Z",
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2" "web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2",
"issue_stats": {
"total": 24,
"closed": 21
}
} }
], ],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a", "commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
...@@ -252,7 +260,11 @@ Example response: ...@@ -252,7 +260,11 @@ Example response:
"updated_at":"2019-07-12T19:45:44.256Z", "updated_at":"2019-07-12T19:45:44.256Z",
"due_date":"2019-08-16T11:00:00.256Z", "due_date":"2019-08-16T11:00:00.256Z",
"start_date":"2019-07-30T12:00:00.256Z", "start_date":"2019-07-30T12:00:00.256Z",
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/1" "web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/1",
"issue_stats": {
"total": 98,
"closed": 76
}
}, },
{ {
"id":52, "id":52,
...@@ -265,7 +277,11 @@ Example response: ...@@ -265,7 +277,11 @@ Example response:
"updated_at":"2019-07-16T14:00:12.256Z", "updated_at":"2019-07-16T14:00:12.256Z",
"due_date":"2019-08-16T11:00:00.256Z", "due_date":"2019-08-16T11:00:00.256Z",
"start_date":"2019-07-30T12:00:00.256Z", "start_date":"2019-07-30T12:00:00.256Z",
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2" "web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2",
"issue_stats": {
"total": 24,
"closed": 21
}
} }
], ],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a", "commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
...@@ -374,7 +390,11 @@ Example response: ...@@ -374,7 +390,11 @@ Example response:
"updated_at":"2019-07-12T19:45:44.256Z", "updated_at":"2019-07-12T19:45:44.256Z",
"due_date":"2019-08-16T11:00:00.256Z", "due_date":"2019-08-16T11:00:00.256Z",
"start_date":"2019-07-30T12:00:00.256Z", "start_date":"2019-07-30T12:00:00.256Z",
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/1" "web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/1",
"issue_stats": {
"total": 99,
"closed": 76
}
}, },
{ {
"id":52, "id":52,
...@@ -387,7 +407,11 @@ Example response: ...@@ -387,7 +407,11 @@ Example response:
"updated_at":"2019-07-16T14:00:12.256Z", "updated_at":"2019-07-16T14:00:12.256Z",
"due_date":"2019-08-16T11:00:00.256Z", "due_date":"2019-08-16T11:00:00.256Z",
"start_date":"2019-07-30T12:00:00.256Z", "start_date":"2019-07-30T12:00:00.256Z",
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2" "web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/2",
"issue_stats": {
"total": 24,
"closed": 21
}
} }
], ],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a", "commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
...@@ -495,7 +519,11 @@ Example response: ...@@ -495,7 +519,11 @@ Example response:
"updated_at":"2019-09-01T13:00:00.256Z", "updated_at":"2019-09-01T13:00:00.256Z",
"due_date":"2019-09-20T13:00:00.256Z", "due_date":"2019-09-20T13:00:00.256Z",
"start_date":"2019-09-05T12:00:00.256Z", "start_date":"2019-09-05T12:00:00.256Z",
"web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/3" "web_url":"https://gitlab.example.com/root/awesome-app/-/milestones/3",
"issue_stats": {
"opened": 11,
"closed": 78
}
} }
], ],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a", "commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
......
...@@ -145,6 +145,7 @@ Complementary reads: ...@@ -145,6 +145,7 @@ Complementary reads:
- [Hash indexes](hash_indexes.md) - [Hash indexes](hash_indexes.md)
- [Storing SHA1 hashes as binary](sha1_as_binary.md) - [Storing SHA1 hashes as binary](sha1_as_binary.md)
- [Iterating tables in batches](iterating_tables_in_batches.md) - [Iterating tables in batches](iterating_tables_in_batches.md)
- [Insert into tables in batches](insert_into_tables_in_batches.md)
- [Ordering table columns](ordering_table_columns.md) - [Ordering table columns](ordering_table_columns.md)
- [Verifying database capabilities](verifying_database_capabilities.md) - [Verifying database capabilities](verifying_database_capabilities.md)
- [Database Debugging and Troubleshooting](database_debugging.md) - [Database Debugging and Troubleshooting](database_debugging.md)
......
---
description: "Sometimes it is necessary to store large amounts of records at once, which can be inefficient
when iterating collections and performing individual `save`s. With the arrival of `insert_all`
in Rails 6, which operates at the row level (that is, using `Hash`es), GitLab has added a set
of APIs that make it safe and simple to insert ActiveRecord objects in bulk."
---
# Insert into tables in batches
Sometimes it is necessary to store large amounts of records at once, which can be inefficient
when iterating collections and saving each record individually. With the arrival of
[`insert_all`](https://apidock.com/rails/ActiveRecord/Persistence/ClassMethods/insert_all)
in Rails 6, which operates at the row level (that is, using `Hash` objects), GitLab has added a set
of APIs that make it safe and simple to insert `ActiveRecord` objects in bulk.
## Prepare `ApplicationRecord`s for bulk insertion
In order for a model class to take advantage of the bulk insertion API, it has to include the
`BulkInsertSafe` concern first:
```ruby
class MyModel < ApplicationRecord
# other includes here
# ...
include BulkInsertSafe # include this last
# ...
end
```
The `BulkInsertSafe` concern has two functions:
- It performs checks against your model class to ensure that it does not use ActiveRecord
APIs that are not safe to use with respect to bulk insertions (more on that below).
- It adds a new class method `bulk_insert!`, which you can use to insert many records at once.
## Insert records via `bulk_insert!`
If the target class passes the checks performed by `BulkInsertSafe`, you can proceed to use
the `bulk_insert!` class method as follows:
```ruby
records = [MyModel.new, ...]
MyModel.bulk_insert!(records)
```
### Record validation
The `bulk_insert!` method guarantees that `records` will be inserted transactionally, and
will run validations on each record prior to insertion. If any record fails to validate,
an error is raised and the transaction is rolled back. You can turn off validations via
the `:validate` option:
```ruby
MyModel.bulk_insert!(records, validate: false)
```
### Batch size configuration
In those cases where the number of `records` is above a given threshold, insertions will
occur in multiple batches. The default batch size is defined in
[`BulkInsertSafe::DEFAULT_BATCH_SIZE`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/concerns/bulk_insert_safe.rb).
Assuming a default threshold of 500, inserting 950 records
would result in two batches being written sequentially (of size 500 and 450 respectively.)
You can override the default batch size via the `:batch_size` option:
```ruby
MyModel.bulk_insert!(records, batch_size: 100)
```
Assuming the same number of 950 records, this would result in 10 batches being written instead.
Since this will also affect the number of `INSERT`s that occur, make sure you measure the
performance impact this might have on your code. There is a trade-off between the number of
`INSERT` statements the database has to process and the size and cost of each `INSERT`.
### Requirements for safe bulk insertions
Large parts of ActiveRecord's persistence API are built around the notion of callbacks. Many
of these callbacks fire in response to model life cycle events such as `save` or `create`.
These callbacks cannot be used with bulk insertions, since they are meant to be called for
every instance that is saved or created. Since these events do not fire when
records are inserted in bulk, we currently disallow their use.
The specifics around which callbacks are disallowed are defined in
[`BulkInsertSafe`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/models/concerns/bulk_insert_safe.rb).
Consult the module source code for details. If your class uses any of the blacklisted
functionality, and you `include BulkInsertSafe`, the application will fail with an error.
### `BulkInsertSafe` versus `InsertAll`
Internally, `BulkInsertSafe` is based on `InsertAll`, and you may wonder when to choose
the former over the latter. To help you make the decision,
the key differences between these classes are listed in the table below.
| | Input type | Validates input | Specify batch size | Can bypass callbacks | Transactional |
|--------------- | -------------------- | --------------- | ------------------ | --------------------------------- | ------------- |
| `bulk_insert!` | ActiveRecord objects | Yes (optional) | Yes (optional) | No (prevents unsafe callback use) | Yes |
| `insert_all!` | Attribute hashes | No | No | Yes | Yes |
To summarize, `BulkInsertSafe` moves bulk inserts closer to how ActiveRecord objects
and inserts would normally behave. However, if all you need is to insert raw data in bulk, then
`insert_all` is more efficient.
## Insert `has_many` associations in bulk
A common use case is to save collections of associated relations through the owner side of the relation,
where the owned relation is associated to the owner through the `has_many` class method:
```ruby
owner = OwnerModel.new(owned_relations: array_of_owned_relations)
# saves all `owned_relations` one-by-one
owner.save!
```
This will issue a single `INSERT`, and transaction, for every record in `owned_relations`, which is inefficient if
`array_of_owned_relations` is large. To remedy this, the `BulkInsertableAssociations` concern can be
used to declare that the owner defines associations that are safe for bulk insertion:
```ruby
class OwnerModel < ApplicationRecord
# other includes here
# ...
include BulkInsertableAssociations # include this last
has_many :my_models
end
```
Here `my_models` must be declared `BulkInsertSafe` (as described previously) for bulk insertions
to happen. You can now insert any yet unsaved records as follows:
```ruby
BulkInsertableAssociations.with_bulk_insert do
owner = OwnerModel.new(my_models: array_of_my_model_instances)
# saves `my_models` using a single bulk insert (possibly via multiple batches)
owner.save!
end
```
Note that you can still save relations that are not `BulkInsertSafe` in this block; they will
simply be treated as if you had invoked `save` from outside the block.
## Known limitations
There are a few restrictions to how these APIs can be used:
- Bulk inserts only work for new records; `UPDATE`s or "upserts" are not supported yet.
- `ON CONFLICT` behavior cannot currently be configured; an error will be raised on primary key conflicts.
- `BulkInsertableAssociations` furthermore has the following restrictions:
- only compatible with `has_many` relations.
- does not support `has_many through: ...` relations.
Moreover, input data should either be limited to around 1000 records at most,
or already batched prior to calling bulk insert. The `INSERT` statement will run in a single
transaction, so for large amounts of records it may negatively affect database stability.
--- ---
type: howto type: howto
--- ---
# Create and add your SSH key pair
# Create and add your SSH public key
It is best practice to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols). It is best practice to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
In order to use SSH, you will need to: In order to use SSH, you will need to:
1. [Create an SSH key pair](#creating-your-ssh-key-pair) on your local computer. 1. Create an SSH key pair
1. [Add the key to GitLab](#adding-your-ssh-public-key-to-gitlab). 1. Add your SSH public key to GitLab
## Creating your SSH key pair ## Creating your SSH key pair
......
...@@ -29,10 +29,11 @@ Note the following: ...@@ -29,10 +29,11 @@ Note the following:
- Exports are stored in a temporary [shared directory](../../../development/shared_files.md) - Exports are stored in a temporary [shared directory](../../../development/shared_files.md)
and are deleted every 24 hours by a specific worker. and are deleted every 24 hours by a specific worker.
- Group members are exported as project members, as long as the user has - Group members are exported as project members, as long as the user has
maintainer or admin access to the group where the exported project lives. Import admins should map users by email address. maintainer or admin access to the group where the exported project lives.
- Project members with owner access will be imported as maintainers.
- Using an admin account to import will map users by email address (self-managed only).
Otherwise, a supplementary comment is left to mention that the original author and Otherwise, a supplementary comment is left to mention that the original author and
the MRs, notes, or issues will be owned by the importer. the MRs, notes, or issues will be owned by the importer.
- Project members with owner access will be imported as maintainers.
- If an imported project contains merge requests originating from forks, - If an imported project contains merge requests originating from forks,
then new branches associated with such merge requests will be created then new branches associated with such merge requests will be created
within a project during the import/export. Thus, the number of branches within a project during the import/export. Thus, the number of branches
...@@ -142,4 +143,4 @@ To help avoid abuse, users are rate limited to: ...@@ -142,4 +143,4 @@ To help avoid abuse, users are rate limited to:
| ---------------- | --------------------------- | | ---------------- | --------------------------- |
| Export | 1 project per 5 minutes | | Export | 1 project per 5 minutes |
| Download export | 10 projects per 10 minutes | | Download export | 10 projects per 10 minutes |
| Import | 30 projects per 10 minutes | | Import | 30 projects per 5 minutes |
# frozen_string_literal: true
module API
module Entities
class MilestoneWithStats < Entities::Milestone
expose :issue_stats do
expose :total_issues_count, as: :total
expose :closed_issues_count, as: :closed
end
end
end
end
...@@ -18,7 +18,7 @@ module API ...@@ -18,7 +18,7 @@ module API
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? } expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? }
expose :upcoming_release?, as: :upcoming_release expose :upcoming_release?, as: :upcoming_release
expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? && can_read_milestone? } expose :milestones, using: Entities::MilestoneWithStats, if: -> (release, _) { release.milestones.present? && can_read_milestone? }
expose :commit_path, expose_nil: false expose :commit_path, expose_nil: false
expose :tag_path, expose_nil: false expose :tag_path, expose_nil: false
expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? } expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? }
......
...@@ -134,7 +134,11 @@ class Feature ...@@ -134,7 +134,11 @@ class Feature
end end
def l1_cache_backend def l1_cache_backend
Gitlab::ThreadMemoryCache.cache_backend if Gitlab::Utils.to_boolean(ENV['USE_THREAD_MEMORY_CACHE'])
Gitlab::ThreadMemoryCache.cache_backend
else
Gitlab::ProcessMemoryCache.cache_backend
end
end end
def l2_cache_backend def l2_cache_backend
......
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
project_download_export: { threshold: 10, interval: 10.minutes }, project_download_export: { threshold: 10, interval: 10.minutes },
project_repositories_archive: { threshold: 5, interval: 1.minute }, project_repositories_archive: { threshold: 5, interval: 1.minute },
project_generate_new_export: { threshold: 1, interval: 5.minutes }, project_generate_new_export: { threshold: 1, interval: 5.minutes },
project_import: { threshold: 30, interval: 10.minutes }, project_import: { threshold: 30, interval: 5.minutes },
play_pipeline_schedule: { threshold: 1, interval: 1.minute }, play_pipeline_schedule: { threshold: 1, interval: 1.minute },
show_raw_controller: { threshold: -> { Gitlab::CurrentSettings.current_application_settings.raw_blob_request_limit }, interval: 1.minute } show_raw_controller: { threshold: -> { Gitlab::CurrentSettings.current_application_settings.raw_blob_request_limit }, interval: 1.minute }
}.freeze }.freeze
......
...@@ -84,12 +84,6 @@ module Gitlab ...@@ -84,12 +84,6 @@ module Gitlab
end end
def open_file(params, key) def open_file(params, key)
allowed_paths = [
::FileUploader.root,
Gitlab.config.uploads.storage_path,
File.join(Rails.root, 'public/uploads/tmp')
]
::UploadedFile.from_params(params, key, allowed_paths) ::UploadedFile.from_params(params, key, allowed_paths)
end end
...@@ -106,6 +100,16 @@ module Gitlab ...@@ -106,6 +100,16 @@ module Gitlab
# inside other env keys, here we ensure everything is updated correctly # inside other env keys, here we ensure everything is updated correctly
ActionDispatch::Request.new(@request.env).update_param(key, value) ActionDispatch::Request.new(@request.env).update_param(key, value)
end end
private
def allowed_paths
[
::FileUploader.root,
Gitlab.config.uploads.storage_path,
File.join(Rails.root, 'public/uploads/tmp')
]
end
end end
def initialize(app) def initialize(app)
...@@ -125,3 +129,5 @@ module Gitlab ...@@ -125,3 +129,5 @@ module Gitlab
end end
end end
end end
::Gitlab::Middleware::Multipart::Handler.prepend_if_ee('EE::Gitlab::Middleware::Multipart::Handler')
# frozen_string_literal: true
module Gitlab
class ProcessMemoryCache
# ActiveSupport::Cache::MemoryStore is thread-safe:
# https://github.com/rails/rails/blob/2f1fefe456932a6d7d2b155d27b5315c33f3daa1/activesupport/lib/active_support/cache/memory_store.rb#L19
@cache = ActiveSupport::Cache::MemoryStore.new
def self.cache_backend
@cache
end
end
end
...@@ -495,4 +495,45 @@ describe SessionsController do ...@@ -495,4 +495,45 @@ describe SessionsController do
expect(session[:failed_login_attempts]).to eq(1) expect(session[:failed_login_attempts]).to eq(1)
end end
end end
describe '#set_current_context' do
before do
set_devise_mapping(context: @request)
end
context 'when signed in' do
let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
it 'sets the username and caller_id in the context' do
expect(controller).to receive(:destroy).and_wrap_original do |m, *args|
expect(Labkit::Context.current.to_h)
.to include('meta.user' => user.username,
'meta.caller_id' => 'SessionsController#destroy')
m.call(*args)
end
delete :destroy
end
end
context 'when not signed in' do
it 'sets the caller_id in the context' do
expect(controller).to receive(:new).and_wrap_original do |m, *args|
expect(Labkit::Context.current.to_h)
.to include('meta.caller_id' => 'SessionsController#new')
expect(Labkit::Context.current.to_h)
.not_to include('meta.user')
m.call(*args)
end
get :new
end
end
end
end end
{
"type": "object",
"properties" : {
"id": { "type": "integer" },
"iid": { "type": "integer" },
"project_id": { "type": ["integer", "null"] },
"group_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"description": { "type": ["string", "null"] },
"state": { "type": "string" },
"created_at": { "type": "date" },
"updated_at": { "type": "date" },
"start_date": { "type": "date" },
"due_date": { "type": "date" },
"web_url": { "type": "string" },
"issue_stats": {
"required": ["total", "closed"],
"properties": {
"total": { "type": "integer" },
"closed": { "type": "integer" }
},
"additionalProperties": false
}
},
"required": [
"id", "iid", "title", "description", "state",
"state", "created_at", "updated_at", "start_date", "due_date", "issue_stats"
],
"additionalProperties": false
}
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
}, },
"milestones": { "milestones": {
"type": "array", "type": "array",
"items": { "$ref": "milestone.json" } "items": { "$ref": "milestone_with_stats.json" }
}, },
"commit_path": { "type": "string" }, "commit_path": { "type": "string" },
"tag_path": { "type": "string" }, "tag_path": { "type": "string" },
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
"upcoming_release": { "type": "boolean" }, "upcoming_release": { "type": "boolean" },
"milestones": { "milestones": {
"type": "array", "type": "array",
"items": { "$ref": "../milestone.json" } "items": { "$ref": "../milestone_with_stats.json" }
}, },
"commit_path": { "type": "string" }, "commit_path": { "type": "string" },
"tag_path": { "type": "string" }, "tag_path": { "type": "string" },
......
...@@ -155,10 +155,6 @@ describe('DiffTableCell', () => { ...@@ -155,10 +155,6 @@ describe('DiffTableCell', () => {
}); });
}); });
it('renders the correct line number', () => {
expect(findLineNumber().text()).toEqual(TEST_LINE_NUMBER.toString());
});
it('on click, dispatches setHighlightedRow', () => { it('on click, dispatches setHighlightedRow', () => {
expect(store.dispatch).not.toHaveBeenCalled(); expect(store.dispatch).not.toHaveBeenCalled();
......
...@@ -146,7 +146,15 @@ describe Feature do ...@@ -146,7 +146,15 @@ describe Feature do
expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy expect(described_class.enabled?(:enabled_feature_flag)).to be_truthy
end end
it { expect(described_class.l1_cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) } context 'with USE_THREAD_MEMORY_CACHE defined' do
before do
stub_env('USE_THREAD_MEMORY_CACHE', '1')
end
it { expect(described_class.l1_cache_backend).to eq(Gitlab::ThreadMemoryCache.cache_backend) }
end
it { expect(described_class.l1_cache_backend).to eq(Gitlab::ProcessMemoryCache.cache_backend) }
it { expect(described_class.l2_cache_backend).to eq(Rails.cache) } it { expect(described_class.l2_cache_backend).to eq(Rails.cache) }
it 'caches the status in L1 and L2 caches', it 'caches the status in L1 and L2 caches',
......
...@@ -5,9 +5,7 @@ require 'spec_helper' ...@@ -5,9 +5,7 @@ require 'spec_helper'
require 'tempfile' require 'tempfile'
describe Gitlab::Middleware::Multipart do describe Gitlab::Middleware::Multipart do
let(:app) { double(:app) } include_context 'multipart middleware context'
let(:middleware) { described_class.new(app) }
let(:original_filename) { 'filename' }
shared_examples_for 'multipart upload files' do shared_examples_for 'multipart upload files' do
it 'opens top-level files' do it 'opens top-level files' do
...@@ -82,22 +80,12 @@ describe Gitlab::Middleware::Multipart do ...@@ -82,22 +80,12 @@ describe Gitlab::Middleware::Multipart do
end end
it 'allows files in uploads/tmp directory' do it 'allows files in uploads/tmp directory' do
Dir.mktmpdir do |dir| with_tmp_dir('public/uploads/tmp') do |dir, env|
uploads_dir = File.join(dir, 'public/uploads/tmp') expect(app).to receive(:call) do |env|
FileUtils.mkdir_p(uploads_dir) expect(get_params(env)['file']).to be_a(::UploadedFile)
allow(Rails).to receive(:root).and_return(dir)
allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir'))
Tempfile.open('top-level', uploads_dir) do |tempfile|
env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect(app).to receive(:call) do |env|
expect(get_params(env)['file']).to be_a(::UploadedFile)
end
middleware.call(env)
end end
middleware.call(env)
end end
end end
...@@ -127,22 +115,4 @@ describe Gitlab::Middleware::Multipart do ...@@ -127,22 +115,4 @@ describe Gitlab::Middleware::Multipart do
middleware.call(env) middleware.call(env)
end end
end end
# Rails 5 doesn't combine the GET/POST parameters in
# ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
# https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
def get_params(env)
req = ActionDispatch::Request.new(env)
req.GET.merge(req.POST)
end
def post_env(rewritten_fields, params, secret, issuer)
token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
Rack::MockRequest.env_for(
'/',
method: 'post',
params: params,
described_class::RACK_ENV_KEY => token
)
end
end end
...@@ -149,7 +149,7 @@ describe Snippet do ...@@ -149,7 +149,7 @@ describe Snippet do
end end
describe '.search' do describe '.search' do
let(:snippet) { create(:snippet, title: 'test snippet') } let(:snippet) { create(:snippet, title: 'test snippet', description: 'description') }
it 'returns snippets with a matching title' do it 'returns snippets with a matching title' do
expect(described_class.search(snippet.title)).to eq([snippet]) expect(described_class.search(snippet.title)).to eq([snippet])
...@@ -174,6 +174,10 @@ describe Snippet do ...@@ -174,6 +174,10 @@ describe Snippet do
it 'returns snippets with a matching file name regardless of the casing' do it 'returns snippets with a matching file name regardless of the casing' do
expect(described_class.search(snippet.file_name.upcase)).to eq([snippet]) expect(described_class.search(snippet.file_name.upcase)).to eq([snippet])
end end
it 'returns snippets with a matching description' do
expect(described_class.search(snippet.description)).to eq([snippet])
end
end end
describe '.search_code' do describe '.search_code' do
......
...@@ -359,12 +359,29 @@ describe API::Releases do ...@@ -359,12 +359,29 @@ describe API::Releases do
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
it 'matches schema' do
get api("/projects/#{project.id}/releases/v0.1", non_project_member)
expect(response).to match_response_schema('public_api/v4/release')
end
it 'exposes milestones' do it 'exposes milestones' do
get api("/projects/#{project.id}/releases/v0.1", non_project_member) get api("/projects/#{project.id}/releases/v0.1", non_project_member)
expect(json_response['milestones'].first['title']).to eq(milestone.title) expect(json_response['milestones'].first['title']).to eq(milestone.title)
end end
it 'returns issue stats for milestone' do
create_list(:issue, 2, milestone: milestone, project: project)
create_list(:issue, 3, :closed, milestone: milestone, project: project)
get api("/projects/#{project.id}/releases/v0.1", non_project_member)
issue_stats = json_response['milestones'].first["issue_stats"]
expect(issue_stats["total"]).to eq(5)
expect(issue_stats["closed"]).to eq(3)
end
context 'when project restricts visibility of issues and merge requests' do context 'when project restricts visibility of issues and merge requests' do
let!(:project) { create(:project, :repository, :public, :issues_private, :merge_requests_private) } let!(:project) { create(:project, :repository, :public, :issues_private, :merge_requests_private) }
......
...@@ -193,8 +193,10 @@ RSpec.configure do |config| ...@@ -193,8 +193,10 @@ RSpec.configure do |config|
# expect(Gitlab::Git::KeepAround).to receive(:execute).and_call_original # expect(Gitlab::Git::KeepAround).to receive(:execute).and_call_original
allow(Gitlab::Git::KeepAround).to receive(:execute) allow(Gitlab::Git::KeepAround).to receive(:execute)
# Clear thread cache and Sidekiq queues [Gitlab::ThreadMemoryCache, Gitlab::ProcessMemoryCache].each do |cache|
Gitlab::ThreadMemoryCache.cache_backend.clear cache.cache_backend.clear
end
Sidekiq::Worker.clear_all Sidekiq::Worker.clear_all
# Temporary patch to force admin mode to be active by default in tests when # Temporary patch to force admin mode to be active by default in tests when
......
# frozen_string_literal: true
RSpec.shared_context 'multipart middleware context' do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:original_filename) { 'filename' }
# Rails 5 doesn't combine the GET/POST parameters in
# ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
# https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
def get_params(env)
req = ActionDispatch::Request.new(env)
req.GET.merge(req.POST)
end
def post_env(rewritten_fields, params, secret, issuer)
token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
Rack::MockRequest.env_for(
'/',
method: 'post',
params: params,
described_class::RACK_ENV_KEY => token
)
end
def with_tmp_dir(uploads_sub_dir, storage_path = '')
Dir.mktmpdir do |dir|
upload_dir = File.join(dir, storage_path, uploads_sub_dir)
FileUtils.mkdir_p(upload_dir)
allow(Rails).to receive(:root).and_return(dir)
allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir'))
allow(GitlabUploader).to receive(:root).and_return(File.join(dir, storage_path))
Tempfile.open('top-level', upload_dir) do |tempfile|
env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
yield dir, env
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment