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
|
# 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 => 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')
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 => 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')
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(group_attributes.attributes, 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(group_hash, parent_group)
original_visibility_level = group_hash['visibility_level'] || Gitlab::VisibilityLevel::PRIVATE
if parent_group && parent_group.visibility_level < original_visibility_level
Gitlab::VisibilityLevel.closest_allowed_level(parent_group.visibility_level)
else
original_visibility_level
end
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
|