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

Add latest changes from gitlab-org/gitlab@master

parent 46b10c0f
No related branches found
No related tags found
No related merge requests found
Showing
with 1024 additions and 43 deletions
# frozen_string_literal: true
 
module ErrorTracking
class DetailedErrorPolicy < BasePolicy
class BasePolicy < ::BasePolicy
delegate { @subject.gitlab_project }
end
end
# frozen_string_literal: true
 
class SentryDetailedErrorPresenter < Gitlab::View::Presenter::Delegated
class SentryErrorPresenter < Gitlab::View::Presenter::Delegated
presents :error
 
FrequencyStruct = Struct.new(:time, :count, keyword_init: true)
 
def first_seen
DateTime.parse(error.first_seen)
end
def last_seen
DateTime.parse(error.last_seen)
end
def project_id
Gitlab::GlobalId.build(model_name: 'Project', id: error.project_id).to_s
end
def frequency
utc_offset = Time.zone_offset('UTC')
 
Loading
Loading
# frozen_string_literal: true
class HamService
attr_accessor :spam_log
def initialize(spam_log)
@spam_log = spam_log
end
def mark_as_ham!
if akismet.submit_ham
spam_log.update_attribute(:submitted_as_ham, true)
else
false
end
end
private
def akismet
user = spam_log.user
@akismet ||= AkismetService.new(
user.name,
user.email,
spam_log.text,
ip_address: spam_log.source_ip,
user_agent: spam_log.user_agent
)
end
end
# frozen_string_literal: true
module Spam
class HamService
attr_accessor :spam_log
def initialize(spam_log)
@spam_log = spam_log
end
def mark_as_ham!
if akismet.submit_ham
spam_log.update_attribute(:submitted_as_ham, true)
else
false
end
end
private
def akismet
user = spam_log.user
@akismet ||= AkismetService.new(
user.name,
user.email,
spam_log.text,
ip_address: spam_log.source_ip,
user_agent: spam_log.user_agent
)
end
end
end
---
title: Add querying of Sentry errors to Graphql
merge_request: 21802
author:
type: added
---
title: refactoring gl_dropdown.js to use ES6 classes instead of constructor functions
merge_request: 20488
author: nuwe1
type: other
---
title: Add license FAQ link to license expired message
merge_request:
author:
type: added
Loading
Loading
@@ -342,16 +342,28 @@ pages:
 
1. [Reconfigure GitLab][reconfigure] for the changes to take effect.
 
### Using a custom Certificate Authority (CA) with Access Control
### Using a custom Certificate Authority (CA)
 
When using certificates issued by a custom CA, Access Control on GitLab Pages may fail to work if the custom CA is not recognized.
When using certificates issued by a custom CA, [Access Control](../../user/project/pages/pages_access_control.md#gitlab-pages-access-control) and
the [online view of HTML job artifacts](../../user/project/pipelines/job_artifacts.md#browsing-artifacts)
will fail to work if the custom CA is not recognized.
 
This usually results in this error:
`Post /oauth/token: x509: certificate signed by unknown authority`.
 
For GitLab Pages Access Control with TLS/SSL certs issued by an internal or custom CA:
For installation from source this can be fixed by installing the custom Certificate
Authority (CA) in the system certificate store.
 
1. Copy the certificate bundle to `/opt/gitlab/embedded/ssl/certs/` in `.pem` format.
For Omnibus, normally this would be fixed by [installing a custom CA in GitLab Omnibus](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
but a [bug](https://gitlab.com/gitlab-org/gitlab/issues/25411) is currently preventing
that method from working. Use the following workaround:
1. Append your GitLab server TLS/SSL certficate to `/opt/gitlab/embedded/ssl/certs/cacert.pem` where `gitlab-domain-example.com` is your GitLab application URL
```bash
printf "\ngitlab-domain-example.com\n===========================\n" | sudo tee --append /opt/gitlab/embedded/ssl/certs/cacert.pem
echo -n | openssl s_client -connect gitlab-domain-example.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee --append /opt/gitlab/embedded/ssl/certs/cacert.pem
```
 
1. [Restart](../restart_gitlab.md) the GitLab Pages Daemon. For GitLab Omnibus instances:
 
Loading
Loading
@@ -359,6 +371,9 @@ For GitLab Pages Access Control with TLS/SSL certs issued by an internal or cust
sudo gitlab-ctl restart gitlab-pages
```
 
CAUTION: **Caution:**
Some GitLab Omnibus upgrades will revert this workaround and you'll need to apply it again.
## Activate verbose logging for daemon
 
Verbose logging was [introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/2533) in
Loading
Loading
Loading
Loading
@@ -5453,6 +5453,11 @@ type Project {
id: ID!
): SentryDetailedError
 
"""
Paginated collection of Sentry errors on the project
"""
sentryErrors: SentryErrorCollection
"""
E-mail address of the service desk.
"""
Loading
Loading
@@ -6054,6 +6059,9 @@ type RootStorageStatistics {
wikiSize: Int!
}
 
"""
A Sentry error.
"""
type SentryDetailedError {
"""
Count of occurrences
Loading
Loading
@@ -6186,6 +6194,186 @@ type SentryDetailedError {
userCount: Int!
}
 
"""
A Sentry error. A simplified version of SentryDetailedError.
"""
type SentryError {
"""
Count of occurrences
"""
count: Int!
"""
Culprit of the error
"""
culprit: String!
"""
External URL of the error
"""
externalUrl: String!
"""
Timestamp when the error was first seen
"""
firstSeen: Time!
"""
Last 24hr stats of the error
"""
frequency: [SentryErrorFrequency!]!
"""
ID (global ID) of the error
"""
id: ID!
"""
Timestamp when the error was last seen
"""
lastSeen: Time!
"""
Sentry metadata message of the error
"""
message: String
"""
ID (Sentry ID) of the error
"""
sentryId: String!
"""
ID of the project (Sentry project)
"""
sentryProjectId: ID!
"""
Name of the project affected by the error
"""
sentryProjectName: String!
"""
Slug of the project affected by the error
"""
sentryProjectSlug: String!
"""
Short ID (Sentry ID) of the error
"""
shortId: String!
"""
Status of the error
"""
status: SentryErrorStatus!
"""
Title of the error
"""
title: String!
"""
Type of the error
"""
type: String!
"""
Count of users affected by the error
"""
userCount: Int!
}
"""
An object containing a collection of Sentry errors, and a detailed error.
"""
type SentryErrorCollection {
"""
Detailed version of a Sentry error on the project
"""
detailedError(
"""
ID of the Sentry issue
"""
id: ID!
): SentryDetailedError
"""
Collection of Sentry Errors
"""
errors(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
Search term for the Sentry error.
"""
searchTerm: String
"""
Attribute to sort on. Options are frequency, first_seen, last_seen. last_seen is default.
"""
sort: String
): SentryErrorConnection
"""
External URL for Sentry
"""
externalUrl: String
}
"""
The connection type for SentryError.
"""
type SentryErrorConnection {
"""
A list of edges.
"""
edges: [SentryErrorEdge]
"""
A list of nodes.
"""
nodes: [SentryError]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type SentryErrorEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: SentryError
}
type SentryErrorFrequency {
"""
Count of errors received since the previously recorded time
Loading
Loading
Loading
Loading
@@ -1433,6 +1433,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sentryErrors",
"description": "Paginated collection of Sentry errors on the project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "SentryErrorCollection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "serviceDeskAddress",
"description": "E-mail address of the service desk.",
Loading
Loading
@@ -16708,7 +16722,7 @@
{
"kind": "OBJECT",
"name": "SentryDetailedError",
"description": null,
"description": "A Sentry error.",
"fields": [
{
"name": "count",
Loading
Loading
@@ -17408,6 +17422,568 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryErrorCollection",
"description": "An object containing a collection of Sentry errors, and a detailed error.",
"fields": [
{
"name": "detailedError",
"description": "Detailed version of a Sentry error on the project",
"args": [
{
"name": "id",
"description": "ID of the Sentry issue",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "SentryDetailedError",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Collection of Sentry Errors",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "searchTerm",
"description": "Search term for the Sentry error.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "sort",
"description": "Attribute to sort on. Options are frequency, first_seen, last_seen. last_seen is default.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "SentryErrorConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "externalUrl",
"description": "External URL for Sentry",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryErrorConnection",
"description": "The connection type for SentryError.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SentryErrorEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SentryError",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryErrorEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "SentryError",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryError",
"description": "A Sentry error. A simplified version of SentryDetailedError.",
"fields": [
{
"name": "count",
"description": "Count of occurrences",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "culprit",
"description": "Culprit of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "externalUrl",
"description": "External URL of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "firstSeen",
"description": "Timestamp when the error was first seen",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "frequency",
"description": "Last 24hr stats of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SentryErrorFrequency",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID (global ID) of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lastSeen",
"description": "Timestamp when the error was last seen",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "message",
"description": "Sentry metadata message of the error",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sentryId",
"description": "ID (Sentry ID) of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sentryProjectId",
"description": "ID of the project (Sentry project)",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sentryProjectName",
"description": "Name of the project affected by the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sentryProjectSlug",
"description": "Slug of the project affected by the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "shortId",
"description": "Short ID (Sentry ID) of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "status",
"description": "Status of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "SentryErrorStatus",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "title",
"description": "Title of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "Type of the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userCount",
"description": "Count of users affected by the error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Metadata",
Loading
Loading
Loading
Loading
@@ -815,6 +815,7 @@ Information about pagination in a connection.
| `repository` | Repository | Git repository of the project |
| `requestAccessEnabled` | Boolean | Indicates if users can request member access to the project |
| `sentryDetailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
| `sentryErrors` | SentryErrorCollection | Paginated collection of Sentry errors on the project |
| `serviceDeskAddress` | String | E-mail address of the service desk. |
| `serviceDeskEnabled` | Boolean | Indicates if the project has service desk enabled. |
| `sharedRunnersEnabled` | Boolean | Indicates if shared runners are enabled on the project |
Loading
Loading
@@ -919,6 +920,8 @@ Autogenerated return type of RemoveAwardEmoji
 
## SentryDetailedError
 
A Sentry error.
| Name | Type | Description |
| --- | ---- | ---------- |
| `count` | Int! | Count of occurrences |
Loading
Loading
@@ -948,6 +951,40 @@ Autogenerated return type of RemoveAwardEmoji
| `type` | String! | Type of the error |
| `userCount` | Int! | Count of users affected by the error |
 
## SentryError
A Sentry error. A simplified version of SentryDetailedError.
| Name | Type | Description |
| --- | ---- | ---------- |
| `count` | Int! | Count of occurrences |
| `culprit` | String! | Culprit of the error |
| `externalUrl` | String! | External URL of the error |
| `firstSeen` | Time! | Timestamp when the error was first seen |
| `frequency` | SentryErrorFrequency! => Array | Last 24hr stats of the error |
| `id` | ID! | ID (global ID) of the error |
| `lastSeen` | Time! | Timestamp when the error was last seen |
| `message` | String | Sentry metadata message of the error |
| `sentryId` | String! | ID (Sentry ID) of the error |
| `sentryProjectId` | ID! | ID of the project (Sentry project) |
| `sentryProjectName` | String! | Name of the project affected by the error |
| `sentryProjectSlug` | String! | Slug of the project affected by the error |
| `shortId` | String! | Short ID (Sentry ID) of the error |
| `status` | SentryErrorStatus! | Status of the error |
| `title` | String! | Title of the error |
| `type` | String! | Type of the error |
| `userCount` | Int! | Count of users affected by the error |
## SentryErrorCollection
An object containing a collection of Sentry errors, and a detailed error.
| Name | Type | Description |
| --- | ---- | ---------- |
| `detailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
| `errors` | SentryErrorConnection | Collection of Sentry Errors |
| `externalUrl` | String | External URL for Sentry |
## SentryErrorFrequency
 
| Name | Type | Description |
Loading
Loading
Loading
Loading
@@ -385,6 +385,21 @@ NOTE: **Note:**
The usage of `perform_enqueued_jobs` is currently useless since our
workers aren't inheriting from `ApplicationJob` / `ActiveJob::Base`.
 
#### DNS
DNS requests are stubbed universally in the test suite
(as of [!22368](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22368)), as DNS can
cause issues depending on the developer's local network. There are RSpec labels
available in `spec/support/dns.rb` which you can apply to tests if you need to
bypass the DNS stubbing, e.g.:
```
it "really connects to Prometheus", :permit_dns do
```
And if you need more specific control, the DNS blocking is implemented in
`spec/support/helpers/dns_helpers.rb` and these methods can be called elsewhere.
#### Filesystem
 
Filesystem data can be roughly split into "repositories", and "everything else".
Loading
Loading
Loading
Loading
@@ -121,7 +121,7 @@ module Banzai
 
def object_link_text(object, matches)
milestone_link = escape_once(super)
reference = object.project&.to_reference(project)
reference = object.project&.to_reference_base(project)
 
if reference.present?
"#{milestone_link} <i>in #{reference}</i>".html_safe
Loading
Loading
Loading
Loading
@@ -104,7 +104,7 @@ module Banzai
def link_to_project(project, link_content: nil)
url = urls.project_url(project, only_path: context[:only_path])
data = data_attribute(project: project.id)
content = link_content || project.to_reference_with_postfix
content = link_content || project.to_reference
 
link_tag(url, data, content, project.name)
end
Loading
Loading
Loading
Loading
@@ -35,7 +35,7 @@ module Gitlab
:user_count
 
def self.declarative_policy_class
'ErrorTracking::DetailedErrorPolicy'
'ErrorTracking::BasePolicy'
end
end
end
Loading
Loading
Loading
Loading
@@ -4,11 +4,16 @@ module Gitlab
module ErrorTracking
class Error
include ActiveModel::Model
include GlobalID::Identification
 
attr_accessor :id, :title, :type, :user_count, :count,
:first_seen, :last_seen, :message, :culprit,
:external_url, :project_id, :project_name, :project_slug,
:short_id, :status, :frequency
def self.declarative_policy_class
'ErrorTracking::BasePolicy'
end
end
end
end
# frozen_string_literal: true
module Gitlab
module ErrorTracking
class ErrorCollection
include GlobalID::Identification
attr_accessor :issues, :external_url, :project
alias_attribute :gitlab_project, :project
def initialize(project:, external_url: nil, issues: [])
@project = project
@external_url = external_url
@issues = issues
end
def self.declarative_policy_class
'ErrorTracking::BasePolicy'
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Graphql
module Extensions
class ExternallyPaginatedArrayExtension < GraphQL::Schema::Field::ConnectionExtension
def resolve(object:, arguments:, context:)
yield(object, arguments)
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module ImportExport
class ProjectTreeLoader
def load(path, dedup_entries: false)
tree_hash = ActiveSupport::JSON.decode(IO.read(path))
if dedup_entries
dedup_tree(tree_hash)
else
tree_hash
end
end
private
# This function removes duplicate entries from the given tree recursively
# by caching nodes it encounters repeatedly. We only consider nodes for
# which there can actually be multiple equivalent instances (e.g. strings,
# hashes and arrays, but not `nil`s, numbers or booleans.)
#
# The algorithm uses a recursive depth-first descent with 3 cases, starting
# with a root node (the tree/hash itself):
# - a node has already been cached; in this case we return it from the cache
# - a node has not been cached yet but should be; descend into its children
# - a node is neither cached nor qualifies for caching; this is a no-op
def dedup_tree(node, nodes_seen = {})
if nodes_seen.key?(node) && distinguishable?(node)
yield nodes_seen[node]
elsif should_dedup?(node)
nodes_seen[node] = node
case node
when Array
node.each_index do |idx|
dedup_tree(node[idx], nodes_seen) do |cached_node|
node[idx] = cached_node
end
end
when Hash
node.each do |k, v|
dedup_tree(v, nodes_seen) do |cached_node|
node[k] = cached_node
end
end
end
else
node
end
end
# We do not need to consider nodes for which there cannot be multiple instances
def should_dedup?(node)
node && !(node.is_a?(Numeric) || node.is_a?(TrueClass) || node.is_a?(FalseClass))
end
# We can only safely de-dup values that are distinguishable. True value objects
# are always distinguishable by nature. Hashes however can represent entities,
# which are identified by ID, not value. We therefore disallow de-duping hashes
# that do not have an `id` field, since we might risk dropping entities that
# have equal attributes yet different identities.
def distinguishable?(node)
if node.is_a?(Hash)
node.key?('id')
else
true
end
end
end
end
end
Loading
Loading
@@ -3,15 +3,17 @@
module Gitlab
module ImportExport
class ProjectTreeRestorer
LARGE_PROJECT_FILE_SIZE_BYTES = 500.megabyte
attr_reader :user
attr_reader :shared
attr_reader :project
 
def initialize(user:, shared:, project:)
@path = File.join(shared.export_path, 'project.json')
@user = user
@shared = shared
@project = project
@tree_loader = ProjectTreeLoader.new
end
 
def restore
Loading
Loading
@@ -36,9 +38,16 @@ module Gitlab
 
private
 
def large_project?(path)
File.size(path) >= LARGE_PROJECT_FILE_SIZE_BYTES
end
def read_tree_hash
json = IO.read(@path)
ActiveSupport::JSON.decode(json)
path = File.join(@shared.export_path, 'project.json')
dedup_entries = large_project?(path) &&
Feature.enabled?(:dedup_project_import_metadata, project.group)
@tree_loader.load(path, dedup_entries: dedup_entries)
rescue => e
Rails.logger.error("Import/Export error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
Loading
Loading
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