Skip to content
Snippets Groups Projects
Commit e0fa0638 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent f8d15ca6
No related branches found
No related tags found
No related merge requests found
Showing
with 279 additions and 28 deletions
# frozen_string_literal: true
class CleanupClosedByRenameInVulnerabilities < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :vulnerabilities, :closed_by_id, :dismissed_by_id
end
def down
undo_cleanup_concurrent_column_rename :vulnerabilities, :closed_by_id, :dismissed_by_id
end
end
Loading
Loading
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
 
ActiveRecord::Schema.define(version: 2020_02_24_163804) do
ActiveRecord::Schema.define(version: 2020_02_26_162723) do
 
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
Loading
Loading
@@ -878,6 +878,17 @@ ActiveRecord::Schema.define(version: 2020_02_24_163804) do
t.index ["pipeline_id"], name: "index_ci_pipelines_config_on_pipeline_id"
end
 
create_table "ci_refs", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "lock_version", default: 0
t.integer "last_updated_by_pipeline_id"
t.boolean "tag", default: false, null: false
t.string "ref", limit: 255, null: false
t.string "status", limit: 255, null: false
t.index ["last_updated_by_pipeline_id"], name: "index_ci_refs_on_last_updated_by_pipeline_id"
t.index ["project_id", "ref", "tag"], name: "index_ci_refs_on_project_id_and_ref_and_tag", unique: true
end
create_table "ci_resource_groups", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
Loading
Loading
@@ -2845,6 +2856,7 @@ ActiveRecord::Schema.define(version: 2020_02_24_163804) do
t.boolean "issue_due"
t.boolean "new_epic"
t.string "notification_email"
t.boolean "fixed_pipeline"
t.boolean "new_release"
t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type"
t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true
Loading
Loading
@@ -4356,8 +4368,6 @@ ActiveRecord::Schema.define(version: 2020_02_24_163804) do
t.text "description_html"
t.bigint "start_date_sourcing_milestone_id"
t.bigint "due_date_sourcing_milestone_id"
t.bigint "closed_by_id"
t.datetime_with_timezone "closed_at"
t.integer "state", limit: 2, default: 1, null: false
t.integer "severity", limit: 2, null: false
t.boolean "severity_overridden", default: false
Loading
Loading
@@ -4369,9 +4379,11 @@ ActiveRecord::Schema.define(version: 2020_02_24_163804) do
t.integer "cached_markdown_version"
t.bigint "confirmed_by_id"
t.datetime_with_timezone "confirmed_at"
t.datetime_with_timezone "dismissed_at"
t.bigint "dismissed_by_id"
t.index ["author_id"], name: "index_vulnerabilities_on_author_id"
t.index ["closed_by_id"], name: "index_vulnerabilities_on_closed_by_id"
t.index ["confirmed_by_id"], name: "index_vulnerabilities_on_confirmed_by_id"
t.index ["dismissed_by_id"], name: "index_vulnerabilities_on_dismissed_by_id"
t.index ["due_date_sourcing_milestone_id"], name: "index_vulnerabilities_on_due_date_sourcing_milestone_id"
t.index ["epic_id"], name: "index_vulnerabilities_on_epic_id"
t.index ["last_edited_by_id"], name: "index_vulnerabilities_on_last_edited_by_id"
Loading
Loading
@@ -4650,6 +4662,8 @@ ActiveRecord::Schema.define(version: 2020_02_24_163804) do
add_foreign_key "ci_pipelines", "merge_requests", name: "fk_a23be95014", on_delete: :cascade
add_foreign_key "ci_pipelines", "projects", name: "fk_86635dbd80", on_delete: :cascade
add_foreign_key "ci_pipelines_config", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "ci_refs", "ci_pipelines", column: "last_updated_by_pipeline_id", on_delete: :nullify
add_foreign_key "ci_refs", "projects", on_delete: :cascade
add_foreign_key "ci_resource_groups", "projects", name: "fk_774722d144", on_delete: :cascade
add_foreign_key "ci_resources", "ci_builds", column: "build_id", name: "fk_e169a8e3d5", on_delete: :nullify
add_foreign_key "ci_resources", "ci_resource_groups", column: "resource_group_id", on_delete: :cascade
Loading
Loading
@@ -5026,8 +5040,8 @@ ActiveRecord::Schema.define(version: 2020_02_24_163804) do
add_foreign_key "vulnerabilities", "milestones", name: "fk_131d289c65", on_delete: :nullify
add_foreign_key "vulnerabilities", "projects", name: "fk_efb96ab1e2", on_delete: :cascade
add_foreign_key "vulnerabilities", "users", column: "author_id", name: "fk_b1de915a15", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "closed_by_id", name: "fk_cf5c60acbf", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "confirmed_by_id", name: "fk_959d40ad0a", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "dismissed_by_id", name: "fk_725465b774", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "last_edited_by_id", name: "fk_1302949740", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "resolved_by_id", name: "fk_76bc5f5455", on_delete: :nullify
add_foreign_key "vulnerabilities", "users", column: "updated_by_id", name: "fk_7ac31eacb9", on_delete: :nullify
Loading
Loading
Loading
Loading
@@ -1862,6 +1862,12 @@ type Epic implements Noteable {
"""
descendantCounts: EpicDescendantCount
 
"""
Total weight of open and closed descendant epic's issues. Available only when
feature flag unfiltered_epic_aggregates is enabled.
"""
descendantWeightSum: EpicDescendantWeights
"""
Description of the epic
"""
Loading
Loading
@@ -2178,6 +2184,21 @@ type EpicDescendantCount {
openedIssues: Int
}
 
"""
Total weight of open and closed descendant issues
"""
type EpicDescendantWeights {
"""
Total weight of completed (closed) issues in this epic, including epic descendants
"""
closedIssues: Int
"""
Total weight of opened issues in this epic, including epic descendants
"""
openedIssues: Int
}
"""
An edge in a connection.
"""
Loading
Loading
Loading
Loading
@@ -13,6 +13,9 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
Each table below documents a GraphQL type. Types match loosely to models, but not all
fields and methods on a model are available via GraphQL.
 
CAUTION: **Caution:**
Fields that are deprecated are marked with **{warning-solid}**.
## AddAwardEmojiPayload
 
Autogenerated return type of AddAwardEmoji
Loading
Loading
@@ -69,7 +72,7 @@ Represents a project or group board
| `authoredDate` | Time | Timestamp of when the commit was authored |
| `description` | String | Description of the commit message |
| `id` | ID! | ID (global ID) of the commit |
| `latestPipeline` | Pipeline | Latest pipeline of the commit |
| `latestPipeline` **{warning-solid}** | Pipeline | **Deprecated:** Use pipelines |
| `message` | String | Raw commit message |
| `sha` | String! | SHA1 ID of the commit |
| `signatureHtml` | String | Rendered HTML of the commit signature |
Loading
Loading
@@ -294,6 +297,7 @@ Represents an epic.
| `closedAt` | Time | Timestamp of the epic's closure |
| `createdAt` | Time | Timestamp of the epic's creation |
| `descendantCounts` | EpicDescendantCount | Number of open and closed descendant epics and issues |
| `descendantWeightSum` | EpicDescendantWeights | Total weight of open and closed descendant epic's issues. Available only when feature flag unfiltered_epic_aggregates is enabled. |
| `description` | String | Description of the epic |
| `downvotes` | Int! | Number of downvotes the epic has received |
| `dueDate` | Time | Due date of the epic |
Loading
Loading
@@ -334,6 +338,15 @@ Counts of descendent epics.
| `openedEpics` | Int | Number of opened sub-epics |
| `openedIssues` | Int | Number of opened epic issues |
 
## EpicDescendantWeights
Total weight of open and closed descendant issues
| Name | Type | Description |
| --- | ---- | ---------- |
| `closedIssues` | Int | Total weight of completed (closed) issues in this epic, including epic descendants |
| `openedIssues` | Int | Total weight of opened issues in this epic, including epic descendants |
## EpicIssue
 
Relationship between an epic and an issue
Loading
Loading
@@ -347,7 +360,7 @@ Relationship between an epic and an issue
| `description` | String | Description of the issue |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `designCollection` | DesignCollection | Collection of design images associated with this issue |
| `designs` | DesignCollection | Deprecated. Use `designCollection` |
| `designs` **{warning-solid}** | DesignCollection | **Deprecated:** Use designCollection |
| `discussionLocked` | Boolean! | Indicates discussion is locked on the issue |
| `downvotes` | Int! | Number of downvotes the issue has received |
| `dueDate` | Time | Due date of the issue |
Loading
Loading
@@ -417,7 +430,7 @@ Autogenerated return type of EpicTreeReorder
| `enabled` | Boolean! | Indicates whether Grafana integration is enabled |
| `grafanaUrl` | String! | Url for the Grafana host for the Grafana integration |
| `id` | ID! | Internal ID of the Grafana integration |
| `token` | String! | API token for the Grafana integration. Field is permanently masked. |
| `token` **{warning-solid}** | String! | **Deprecated:** Plain text token has been masked for security reasons |
| `updatedAt` | Time! | Timestamp of the issue's last activity |
 
## Group
Loading
Loading
@@ -469,7 +482,7 @@ Autogenerated return type of EpicTreeReorder
| `description` | String | Description of the issue |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `designCollection` | DesignCollection | Collection of design images associated with this issue |
| `designs` | DesignCollection | Deprecated. Use `designCollection` |
| `designs` **{warning-solid}** | DesignCollection | **Deprecated:** Use designCollection |
| `discussionLocked` | Boolean! | Indicates discussion is locked on the issue |
| `downvotes` | Int! | Number of downvotes the issue has received |
| `dueDate` | Time | Due date of the issue |
Loading
Loading
@@ -578,7 +591,7 @@ Autogenerated return type of MarkAsSpamSnippet
| `id` | ID! | ID of the merge request |
| `iid` | String! | Internal ID of the merge request |
| `inProgressMergeCommitSha` | String | Commit SHA of the merge request if merge is in progress |
| `mergeCommitMessage` | String | Deprecated - renamed to defaultMergeCommitMessage |
| `mergeCommitMessage` **{warning-solid}** | String | **Deprecated:** Renamed to defaultMergeCommitMessage |
| `mergeCommitSha` | String | SHA of the merge request commit (set once merged) |
| `mergeError` | String | Error message due to a merge error |
| `mergeOngoing` | Boolean! | Indicates if a merge is currently occurring |
Loading
Loading
Loading
Loading
@@ -30,6 +30,7 @@ If the `custom` level is used, specific email events can be controlled. Availabl
- `reassign_merge_request`
- `merge_merge_request`
- `failed_pipeline`
- `fixed_pipeline`
- `success_pipeline`
- `new_epic` **(ULTIMATE)**
 
Loading
Loading
@@ -83,6 +84,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.
| `reassign_merge_request` | boolean | no | Enable/disable this notification |
| `merge_merge_request` | boolean | no | Enable/disable this notification |
| `failed_pipeline` | boolean | no | Enable/disable this notification |
| `fixed_pipeline` | boolean | no | Enable/disable this notification |
| `success_pipeline` | boolean | no | Enable/disable this notification |
| `new_epic` | boolean | no | Enable/disable this notification ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6626) in 11.3) **(ULTIMATE)** |
 
Loading
Loading
@@ -152,6 +154,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.
| `reassign_merge_request` | boolean | no | Enable/disable this notification |
| `merge_merge_request` | boolean | no | Enable/disable this notification |
| `failed_pipeline` | boolean | no | Enable/disable this notification |
| `fixed_pipeline` | boolean | no | Enable/disable this notification |
| `success_pipeline` | boolean | no | Enable/disable this notification |
| `new_epic` | boolean | no | Enable/disable this notification ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/6626) in 11.3) **(ULTIMATE)** |
 
Loading
Loading
@@ -178,6 +181,7 @@ Example responses:
"reassign_merge_request": false,
"merge_merge_request": false,
"failed_pipeline": false,
"fixed_pipeline": false,
"success_pipeline": false
}
}
Loading
Loading
Loading
Loading
@@ -50,12 +50,12 @@ For indexing Git repository data, GitLab uses an [indexer written in Go](https:/
The way you install the Go indexer depends on your version of GitLab:
 
- For GitLab Omnibus 11.8 and above, see [GitLab Omnibus](#gitlab-omnibus).
- For older versions of GitLab, install the indexer [From Source](#from-source).
- For installations from source or older versions of GitLab Omnibus, install the indexer [From Source](#from-source).
 
### GitLab Omnibus
 
The Go indexer was included in Omnibus GitLab 11.8 as an optional replacement to a
Ruby-based indexer. [Since GitLab v12.3](https://gitlab.com/gitlab-org/gitlab/issues/6481),
Since GitLab 11.8 the Go indexer is included in GitLab Omnibus.
The former Ruby-based indexer was removed in [GitLab 12.3](https://gitlab.com/gitlab-org/gitlab/issues/6481).
 
### From source
 
Loading
Loading
Loading
Loading
@@ -216,7 +216,7 @@ If you're using a custom setup for License Compliance, you're required
to update your CI config accordingly:
 
1. Change the CI template to `License-Scanning.gitlab-ci.yml`.
1. Change the job name to `license_management` (if you mention it in `.gitlab-ci.yml`).
1. Change the job name to `license_scanning` (if you mention it in `.gitlab-ci.yml`).
1. Change the artifact name to `gl-license-scanning-report.json` (if you mention it in `.gitlab-ci.yml`).
 
For example, the following `.gitlab-ci.yml`:
Loading
Loading
Loading
Loading
@@ -178,7 +178,8 @@ In most of the below cases, the notification will be sent to:
| Remove milestone merge request | Subscribers, participants mentioned, and Custom notification level with this event selected |
| New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher |
| Failed pipeline | The author of the pipeline |
| Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set |
| Fixed pipeline | The author of the pipeline |
| Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set. If the pipeline failed previously, a `Fixed pipeline` message will be sent for the first successful pipeline after the failure, then a `Successful pipeline` message for any further successful pipelines. |
| New epic **(ULTIMATE)** | |
| Close epic **(ULTIMATE)** | |
| Reopen epic **(ULTIMATE)** | |
Loading
Loading
Loading
Loading
@@ -18,7 +18,6 @@ module Gitlab
self.table_name = 'epics'
 
belongs_to :author, class_name: "User"
belongs_to :project
belongs_to :group
 
def self.user_mention_model
Loading
Loading
Loading
Loading
@@ -21,7 +21,7 @@ module Gitlab
 
def exec
if creation? || deletion?
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_delete_branch]
raise GitAccess::ForbiddenError, ERROR_MESSAGES[:create_delete_branch]
end
 
# TODO: https://gitlab.com/gitlab-org/gitlab/issues/205628
Loading
Loading
Loading
Loading
@@ -34,8 +34,7 @@ module Gitlab
 
ignore_auto_reply!(mail)
 
mail_key = extract_mail_key(mail)
handler = Handler.for(mail, mail_key)
handler = find_handler(mail)
 
raise UnknownIncomingEmail unless handler
 
Loading
Loading
@@ -46,6 +45,11 @@ module Gitlab
 
private
 
def find_handler(mail)
mail_key = extract_mail_key(mail)
Handler.for(mail, mail_key)
end
def build_mail
Mail::Message.new(@raw)
rescue Encoding::UndefinedConversionError,
Loading
Loading
Loading
Loading
@@ -28,7 +28,7 @@ module Gitlab
# TODO: Investigate if expanding actor/authentication types are needed.
# https://gitlab.com/gitlab-org/gitlab/issues/202190
if actor && !actor.is_a?(User) && !actor.instance_of?(Key)
raise UnauthorizedError, ERROR_MESSAGES[:authentication_mechanism]
raise ForbiddenError, ERROR_MESSAGES[:authentication_mechanism]
end
 
unless Feature.enabled?(:version_snippets, user)
Loading
Loading
@@ -53,7 +53,7 @@ module Gitlab
 
override :check_push_access!
def check_push_access!
raise UnauthorizedError, ERROR_MESSAGES[:update_snippet] unless user
raise ForbiddenError, ERROR_MESSAGES[:update_snippet] unless user
 
check_change_access!
end
Loading
Loading
@@ -74,7 +74,7 @@ module Gitlab
passed = guest_can_download_code? || user_can_download_code?
 
unless passed
raise UnauthorizedError, ERROR_MESSAGES[:read_snippet]
raise ForbiddenError, ERROR_MESSAGES[:read_snippet]
end
end
 
Loading
Loading
@@ -91,7 +91,7 @@ module Gitlab
override :check_change_access!
def check_change_access!
unless user_access.can_do_action?(:update_snippet)
raise UnauthorizedError, ERROR_MESSAGES[:update_snippet]
raise ForbiddenError, ERROR_MESSAGES[:update_snippet]
end
 
changes_list.each do |change|
Loading
Loading
Loading
Loading
@@ -25,6 +25,28 @@ module Gitlab
fields.sort_by { |field| field[:name] }
end
 
def render_field(field)
'| %s | %s | %s |' % [
render_field_name(field),
render_field_type(field[:type][:info]),
render_field_description(field)
]
end
def render_field_name(field)
rendered_name = "`#{field[:name]}`"
rendered_name += ' **{warning-solid}**' if field[:is_deprecated]
rendered_name
end
# Returns the field description. If the field has been deprecated,
# the deprecation reason will be returned in place of the description.
def render_field_description(field)
return field[:description] unless field[:is_deprecated]
"**Deprecated:** #{field[:deprecation_reason]}"
end
# Some fields types are arrays of other types and are displayed
# on docs wrapped in square brackets, for example: [String!].
# This makes GitLab docs renderer thinks they are links so here
Loading
Loading
Loading
Loading
@@ -11,6 +11,9 @@
 
Each table below documents a GraphQL type. Types match loosely to models, but not all
fields and methods on a model are available via GraphQL.
CAUTION: **Caution:**
Fields that are deprecated are marked with **{warning-solid}**.
\
- objects.each do |type|
- unless type[:fields].empty?
Loading
Loading
@@ -22,5 +25,5 @@
~ "| Name | Type | Description |"
~ "| --- | ---- | ---------- |"
- sorted_fields(type[:fields]).each do |field|
= "| `#{field[:name]}` | #{render_field_type(field[:type][:info])} | #{field[:description]} |"
= render_field(field)
\
Loading
Loading
@@ -28,8 +28,9 @@ module Gitlab
config.address.sub(WILDCARD_PLACEHOLDER, "#{key}#{UNSUBSCRIBE_SUFFIX}")
end
 
def key_from_address(address)
regex = address_regex
def key_from_address(address, wildcard_address: nil)
wildcard_address ||= config.address
regex = address_regex(wildcard_address)
return unless regex
 
match = address.match(regex)
Loading
Loading
@@ -55,8 +56,7 @@ module Gitlab
 
private
 
def address_regex
wildcard_address = config.address
def address_regex(wildcard_address)
return unless wildcard_address
 
regex = Regexp.escape(wildcard_address)
Loading
Loading
Loading
Loading
@@ -20,6 +20,7 @@ module Gitlab
chain.add Gitlab::SidekiqMiddleware::AdminMode::Server
chain.add Gitlab::SidekiqStatus::ServerMiddleware
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Server
chain.add Gitlab::SidekiqMiddleware::DuplicateJobs::Server
end
end
 
Loading
Loading
@@ -33,6 +34,7 @@ module Gitlab
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Client # needs to be before the Labkit middleware
chain.add Labkit::Middleware::Sidekiq::Client
chain.add Gitlab::SidekiqMiddleware::AdminMode::Client
chain.add Gitlab::SidekiqMiddleware::DuplicateJobs::Client
end
end
end
Loading
Loading
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
module DuplicateJobs
class Client
def call(worker_class, job, queue, _redis_pool, &block)
DuplicateJob.new(job, queue).schedule(&block)
end
end
end
end
end
# frozen_string_literal: true
require 'digest'
module Gitlab
module SidekiqMiddleware
module DuplicateJobs
# This class defines an identifier of a job in a queue
# The identifier based on a job's class and arguments.
#
# As strategy decides when to keep track of the job in redis and when to
# remove it.
#
# Storing the deduplication key in redis can be done by calling `check!`
# check returns the `jid` of the job if it was scheduled, or the `jid` of
# the duplicate job if it was already scheduled
#
# When new jobs can be scheduled again, the strategy calls `#delete`.
class DuplicateJob
DUPLICATE_KEY_TTL = 6.hours
attr_reader :existing_jid
def initialize(job, queue_name, strategy: :until_executing)
@job = job
@queue_name = queue_name
@strategy = strategy
end
# This will continue the middleware chain if the job should be scheduled
# It will return false if the job needs to be cancelled
def schedule(&block)
Strategies.for(strategy).new(self).schedule(job, &block)
end
# This will continue the server middleware chain if the job should be
# executed.
# It will return false if the job should not be executed.
def perform(&block)
Strategies.for(strategy).new(self).perform(job, &block)
end
# This method will return the jid that was set in redis
def check!
read_jid = nil
Sidekiq.redis do |redis|
redis.multi do |multi|
redis.set(idempotency_key, jid, ex: DUPLICATE_KEY_TTL, nx: true)
read_jid = redis.get(idempotency_key)
end
end
self.existing_jid = read_jid.value
end
def delete!
Sidekiq.redis do |redis|
redis.del(idempotency_key)
end
end
def duplicate?
raise "Call `#check!` first to check for existing duplicates" unless existing_jid
jid != existing_jid
end
private
attr_reader :queue_name, :strategy, :job
attr_writer :existing_jid
def worker_class_name
job['class']
end
def arguments
job['args']
end
def jid
job['jid']
end
def idempotency_key
@idempotency_key ||= "#{namespace}:#{idempotency_hash}"
end
def idempotency_hash
Digest::SHA256.hexdigest(idempotency_string)
end
def namespace
"#{Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE}:duplicate:#{queue_name}"
end
def idempotency_string
"#{worker_class_name}:#{arguments.join('-')}"
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
module DuplicateJobs
class Server
def call(worker, job, queue, &block)
DuplicateJob.new(job, queue).perform(&block)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module SidekiqMiddleware
module DuplicateJobs
module Strategies
UnknownStrategyError = Class.new(StandardError)
STRATEGIES = {
until_executing: UntilExecuting
}.freeze
def self.for(name)
STRATEGIES.fetch(name)
rescue KeyError
raise UnknownStrategyError, "Unknown deduplication strategy #{name}"
end
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment