1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
# frozen_string_literal: true
module Gitlab
module ImportExport
module Group
class TreeRestorer
include Gitlab::Utils::StrongMemoize
attr_reader :user, :shared, :groups_mapping
def initialize(user:, shared:, group:)
@user = user
@shared = shared
@top_level_group = group
@groups_mapping = {}
end
def restore
group_ids = relation_reader.consume_relation('groups', '_all').map { |value, _idx| Integer(value) }
root_group_id = group_ids.delete_at(0)
process_root(root_group_id)
group_ids.each do |group_id|
process_child(group_id)
end
true
rescue StandardError => e
shared.error(e)
false
end
class GroupAttributes
attr_reader :attributes, :group_id, :id, :path
def initialize(group_id, relation_reader)
@group_id = group_id
@path = "groups/#{group_id}"
@attributes = relation_reader.consume_attributes(@path)
@id = @attributes.delete('id')
unless @id == @group_id
raise ArgumentError, "Invalid group_id for #{group_id}"
end
end
def delete_attribute(name)
attributes.delete(name)
end
def delete_attributes(*names)
names.map(&method(:delete_attribute))
end
end
private_constant :GroupAttributes
private
def process_root(group_id)
group_attributes = GroupAttributes.new(group_id, relation_reader)
# name and path are not imported on the root group to avoid conflict
# with existing groups name and/or path.
group_attributes.delete_attributes('name', 'path')
if @top_level_group.has_parent?
group_attributes.attributes['visibility_level'] = sub_group_visibility_level(
group_attributes.attributes['visibility_level'],
@top_level_group.parent
)
elsif Gitlab::VisibilityLevel.restricted_level?(group_attributes.attributes['visibility_level'])
group_attributes.delete_attribute('visibility_level')
end
restore_group(@top_level_group, group_attributes)
end
def process_child(group_id)
group_attributes = GroupAttributes.new(group_id, relation_reader)
group = create_group(group_attributes)
restore_group(group, group_attributes)
rescue StandardError => e
import_failure_service.log_import_failure(
source: 'process_child',
relation_key: 'group',
exception: e
)
end
def create_group(group_attributes)
parent_id = group_attributes.delete_attribute('parent_id')
name = group_attributes.delete_attribute('name')
path = group_attributes.delete_attribute('path')
visibility_level = group_attributes.delete_attribute('visibility_level')
parent_group = @groups_mapping.fetch(parent_id) { raise(ArgumentError, 'Parent group not found') }
group = ::Groups::CreateService.new(
user,
name: name,
path: path,
parent_id: parent_group.id,
visibility_level: sub_group_visibility_level(visibility_level, parent_group)
).execute
group.validate!
group
end
def restore_group(group, group_attributes)
@groups_mapping[group_attributes.id] = group
Group::GroupRestorer.new(
user: user,
shared: shared,
group: group,
attributes: group_attributes.attributes,
importable_path: group_attributes.path,
relation_reader: relation_reader,
reader: reader
).restore
end
def relation_reader
strong_memoize(:relation_reader) do
ImportExport::Json::NdjsonReader.new(
File.join(shared.export_path, 'tree')
)
end
end
def sub_group_visibility_level(visibility_level, parent_group)
parent_visibility_level = parent_group.visibility_level
original_visibility_level = visibility_level ||
closest_allowed_level(parent_visibility_level)
if parent_visibility_level < original_visibility_level
closest_allowed_level(parent_visibility_level)
else
closest_allowed_level(original_visibility_level)
end
end
def closest_allowed_level(visibility_level)
Gitlab::VisibilityLevel.closest_allowed_level(visibility_level)
end
def reader
strong_memoize(:reader) do
Gitlab::ImportExport::Reader.new(
shared: @shared,
config: Gitlab::ImportExport::Config.new(
config: Gitlab::ImportExport.group_config_file
).to_h
)
end
end
def import_failure_service
Gitlab::ImportExport::ImportFailureService.new(@top_level_group)
end
end
end
end
end
|