kintsugi 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +8 -0
- data/.github/workflows/tests.yml +1 -0
- data/.rubocop.yml +3 -0
- data/bin/kintsugi +2 -30
- data/kintsugi.gemspec +2 -1
- data/lib/kintsugi/apply_change_to_project.rb +382 -135
- data/lib/kintsugi/cli.rb +8 -0
- data/lib/kintsugi/merge.rb +146 -0
- data/lib/kintsugi/settings.rb +18 -0
- data/lib/kintsugi/version.rb +1 -1
- data/lib/kintsugi/xcodeproj_extensions.rb +84 -0
- data/lib/kintsugi.rb +29 -148
- data/spec/kintsugi_apply_change_to_project_spec.rb +346 -111
- data/spec/kintsugi_integration_spec.rb +148 -0
- metadata +23 -5
@@ -6,8 +6,21 @@ require "xcodeproj"
|
|
6
6
|
|
7
7
|
require_relative "utils"
|
8
8
|
require_relative "error"
|
9
|
+
require_relative "settings"
|
9
10
|
require_relative "xcodeproj_extensions"
|
10
11
|
|
12
|
+
class Array
|
13
|
+
# Converts an array of arrays of size 2 into a multimap, mapping the first element of each
|
14
|
+
# subarray to an array of the last elements it appears with in the same subarray.
|
15
|
+
def to_multi_h
|
16
|
+
raise ArgumentError, "Not all elements are arrays of size 2" unless all? do |arr|
|
17
|
+
arr.is_a?(Array) && arr.count == 2
|
18
|
+
end
|
19
|
+
|
20
|
+
group_by(&:first).transform_values { |group| group.map(&:last) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
11
24
|
module Kintsugi
|
12
25
|
class << self
|
13
26
|
# Applies the change specified by `change` to `project`.
|
@@ -28,27 +41,172 @@ module Kintsugi
|
|
28
41
|
if project.root_object.main_group.nil?
|
29
42
|
puts "Warning: Main group doesn't exist, ignoring changes to it."
|
30
43
|
else
|
31
|
-
|
32
|
-
change["rootObject"]["mainGroup"])
|
44
|
+
apply_main_group_change(project, change["rootObject"]["mainGroup"])
|
33
45
|
end
|
34
46
|
end
|
35
47
|
|
36
48
|
unless change["rootObject"]["projectReferences"].nil?
|
37
49
|
apply_change_to_component(project.root_object, "projectReferences",
|
38
|
-
change["rootObject"]["projectReferences"])
|
50
|
+
change["rootObject"]["projectReferences"], "rootObject")
|
39
51
|
end
|
40
52
|
|
41
53
|
apply_change_to_component(project, "rootObject",
|
42
54
|
change["rootObject"].reject { |key|
|
43
55
|
%w[mainGroup projectReferences].include?(key)
|
44
|
-
})
|
56
|
+
}, "")
|
45
57
|
end
|
46
58
|
|
47
59
|
private
|
48
60
|
|
49
|
-
def
|
61
|
+
def apply_main_group_change(project, main_group_change)
|
62
|
+
additions, removals, diffs = classify_group_and_file_changes(main_group_change, "")
|
63
|
+
apply_group_additions(project, additions)
|
64
|
+
apply_file_changes(project, additions, removals)
|
65
|
+
apply_group_and_file_diffs(project, diffs)
|
66
|
+
apply_group_removals(project, removals)
|
67
|
+
end
|
68
|
+
|
69
|
+
def classify_group_and_file_changes(change, path)
|
70
|
+
children_changes = change["children"] || {}
|
71
|
+
removals = flatten_change(children_changes[:removed], path)
|
72
|
+
additions = flatten_change(children_changes[:added], path)
|
73
|
+
diffs = [[change, path]]
|
74
|
+
subchanges_of_change(children_changes).each do |key, subchange|
|
75
|
+
sub_additions, sub_removals, sub_diffs =
|
76
|
+
classify_group_and_file_changes(subchange, join_path(path, key))
|
77
|
+
removals += sub_removals
|
78
|
+
additions += sub_additions
|
79
|
+
diffs += sub_diffs
|
80
|
+
end
|
81
|
+
|
82
|
+
[additions, removals, diffs]
|
83
|
+
end
|
84
|
+
|
85
|
+
def flatten_change(change, path)
|
86
|
+
entries = (change || []).map do |child|
|
87
|
+
[child, path]
|
88
|
+
end
|
89
|
+
group_entries = entries.map do |group, _|
|
90
|
+
next if group["children"].nil?
|
91
|
+
|
92
|
+
flatten_change(group["children"], join_path(path, group["displayName"]))
|
93
|
+
end.compact.flatten(1)
|
94
|
+
entries + group_entries
|
95
|
+
end
|
96
|
+
|
97
|
+
def apply_group_additions(project, additions)
|
98
|
+
additions.each do |change, path|
|
99
|
+
next unless %w[PBXGroup PBXVariantGroup].include?(change["isa"])
|
100
|
+
|
101
|
+
group_type = Module.const_get("Xcodeproj::Project::#{change["isa"]}")
|
102
|
+
containing_group = path.empty? ? project.main_group : project[path]
|
103
|
+
|
104
|
+
next if !Settings.allow_duplicates &&
|
105
|
+
!find_group_in_group(containing_group, group_type, change).nil?
|
106
|
+
|
107
|
+
new_group = project.new(group_type)
|
108
|
+
containing_group.children << new_group
|
109
|
+
add_attributes_to_component(new_group, change, path, ignore_keys: ["children"])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def find_group_in_group(group, instance_type, change)
|
114
|
+
group
|
115
|
+
.children
|
116
|
+
.select { |child| child.instance_of?(instance_type) }
|
117
|
+
.find do |child_group|
|
118
|
+
child_group.display_name == change["displayName"] && child_group.path == change["path"]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def apply_file_changes(project, additions, removals)
|
123
|
+
def file_reference_key(change)
|
124
|
+
[change["name"], change["path"], change["sourceTree"]]
|
125
|
+
end
|
126
|
+
|
127
|
+
file_additions = additions.select { |change, _| change["isa"] == "PBXFileReference" }
|
128
|
+
file_removals = removals.select { |change, _| change["isa"] == "PBXFileReference" }
|
129
|
+
|
130
|
+
addition_keys_to_paths = file_additions
|
131
|
+
.map { |change, path| [file_reference_key(change), path] }
|
132
|
+
.to_multi_h
|
133
|
+
removal_keys_to_references = file_removals.to_multi_h.map do |change, paths|
|
134
|
+
references = paths.map do |containing_path|
|
135
|
+
project[join_path(containing_path, change["displayName"])]
|
136
|
+
end
|
137
|
+
|
138
|
+
[file_reference_key(change), references]
|
139
|
+
end.to_h
|
140
|
+
|
141
|
+
file_additions.each do |change, path|
|
142
|
+
containing_group = path.empty? ? project.main_group : project[path]
|
143
|
+
change_key = file_reference_key(change)
|
144
|
+
|
145
|
+
if (removal_keys_to_references[change_key] || []).empty?
|
146
|
+
apply_file_addition(containing_group, change, "rootObject/mainGroup/#{path}")
|
147
|
+
elsif addition_keys_to_paths[change_key].length == 1 &&
|
148
|
+
removal_keys_to_references[change_key].length == 1 &&
|
149
|
+
!removal_keys_to_references[change_key].first.nil?
|
150
|
+
removal_keys_to_references[change_key].first.move(containing_group)
|
151
|
+
else
|
152
|
+
file_path = join_path(path, change["displayName"])
|
153
|
+
raise MergeError,
|
154
|
+
"Cannot deduce whether the file #{file_path} is new, or was moved to its new place"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
file_removals.each do |change, path|
|
159
|
+
next unless addition_keys_to_paths[file_reference_key(change)].nil?
|
160
|
+
|
161
|
+
file_reference = project[join_path(path, change["displayName"])]
|
162
|
+
remove_component(file_reference, change)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def apply_file_addition(containing_group, change, path)
|
167
|
+
return if !Settings.allow_duplicates &&
|
168
|
+
!find_file_in_group(containing_group, Xcodeproj::Project::PBXFileReference,
|
169
|
+
change["path"]).nil?
|
170
|
+
|
171
|
+
file_reference = containing_group.project.new(Xcodeproj::Project::PBXFileReference)
|
172
|
+
containing_group.children << file_reference
|
173
|
+
|
174
|
+
# For some reason, `include_in_index` is set to `1` and `source_tree` to `SDKROOT` by
|
175
|
+
# default.
|
176
|
+
file_reference.include_in_index = nil
|
177
|
+
file_reference.source_tree = nil
|
178
|
+
add_attributes_to_component(file_reference, change, path)
|
179
|
+
end
|
180
|
+
|
181
|
+
def apply_group_and_file_diffs(project, diffs)
|
182
|
+
diffs.each do |change, path|
|
183
|
+
component = project[path]
|
184
|
+
|
185
|
+
next if component.nil?
|
186
|
+
|
187
|
+
change.each do |subchange_name, subchange|
|
188
|
+
next if subchange_name == "children"
|
189
|
+
|
190
|
+
apply_change_to_component(component, subchange_name, subchange, path)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def apply_group_removals(project, removals)
|
196
|
+
removals.each do |change, path|
|
197
|
+
next unless %w[PBXGroup PBXVariantGroup].include?(change["isa"])
|
198
|
+
|
199
|
+
group_path = join_path(path, change["displayName"])
|
200
|
+
|
201
|
+
remove_component(project[group_path], change)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def apply_change_to_component(parent_component, change_name, change, parent_change_path)
|
50
206
|
return if change_name == "displayName"
|
51
207
|
|
208
|
+
change_path = join_path(parent_change_path, change_name)
|
209
|
+
|
52
210
|
attribute_name = attribute_name_from_change_name(change_name)
|
53
211
|
if simple_attribute?(parent_component, attribute_name)
|
54
212
|
apply_change_to_simple_attribute(parent_component, attribute_name, change)
|
@@ -59,28 +217,41 @@ module Kintsugi
|
|
59
217
|
component = replace_component_with_new_type(parent_component, attribute_name, change)
|
60
218
|
change = change_for_component_of_new_type(component, change)
|
61
219
|
else
|
62
|
-
component = child_component(parent_component, change_name)
|
63
|
-
|
64
|
-
if component.nil?
|
65
|
-
add_missing_component_if_valid(parent_component, change_name, change)
|
66
|
-
return
|
67
|
-
end
|
220
|
+
component = child_component(parent_component, change, change_name)
|
68
221
|
end
|
69
222
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
223
|
+
if change[:removed].is_a?(Hash)
|
224
|
+
remove_component(component, change[:removed])
|
225
|
+
elsif change[:removed].is_a?(Array)
|
226
|
+
unless component.nil?
|
227
|
+
(change[:removed]).each do |removed_change|
|
228
|
+
child = child_component_of_object_list(component, removed_change,
|
229
|
+
removed_change["displayName"])
|
230
|
+
remove_component(child, removed_change)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
elsif !change[:removed].nil?
|
234
|
+
raise MergeError, "Unsupported removed change type for #{change[:removed]}"
|
75
235
|
end
|
76
236
|
|
77
|
-
|
78
|
-
|
79
|
-
|
237
|
+
if change[:added].is_a?(Hash)
|
238
|
+
add_child_to_component(parent_component, change[:added], change_path)
|
239
|
+
elsif change[:added].is_a?(Array)
|
240
|
+
(change[:added]).each do |added_change|
|
241
|
+
add_child_to_component(parent_component, added_change, change_path)
|
242
|
+
end
|
243
|
+
elsif !change[:added].nil?
|
244
|
+
raise MergeError, "Unsupported added change type for #{change[:added]}"
|
80
245
|
end
|
81
246
|
|
82
247
|
subchanges_of_change(change).each do |subchange_name, subchange|
|
83
|
-
|
248
|
+
if component.nil?
|
249
|
+
raise MergeError, "Trying to apply changes to a component that doesn't exist at path " \
|
250
|
+
"#{change_path}. It was probably removed in a previous commit. This is considered a " \
|
251
|
+
"conflict that should be resolved manually."
|
252
|
+
end
|
253
|
+
|
254
|
+
apply_change_to_component(component, subchange_name, subchange, change_path)
|
84
255
|
end
|
85
256
|
end
|
86
257
|
|
@@ -100,22 +271,9 @@ module Kintsugi
|
|
100
271
|
end
|
101
272
|
end
|
102
273
|
|
103
|
-
def add_missing_component_if_valid(parent_component, change_name, change)
|
104
|
-
if change[:added] && change.compact.count == 1
|
105
|
-
add_child_to_component(parent_component, change[:added])
|
106
|
-
return
|
107
|
-
end
|
108
|
-
|
109
|
-
puts "Warning: Detected change of an object named '#{change_name}' contained in " \
|
110
|
-
"'#{parent_component}' but the object doesn't exist. Ignoring this change."
|
111
|
-
end
|
112
|
-
|
113
274
|
def replace_component_with_new_type(parent_component, name_in_parent_component, change)
|
114
275
|
old_component = parent_component.send(name_in_parent_component)
|
115
|
-
|
116
|
-
new_component = parent_component.project.new(
|
117
|
-
Module.const_get("Xcodeproj::Project::#{change["isa"][:added]}")
|
118
|
-
)
|
276
|
+
new_component = component_of_new_type(parent_component, change, old_component)
|
119
277
|
|
120
278
|
copy_attributes_to_new_component(old_component, new_component)
|
121
279
|
|
@@ -123,6 +281,27 @@ module Kintsugi
|
|
123
281
|
new_component
|
124
282
|
end
|
125
283
|
|
284
|
+
def component_of_new_type(parent_component, change, old_component)
|
285
|
+
if change["isa"][:added] == "PBXFileReference"
|
286
|
+
path = (change["path"] && change["path"][:added]) || old_component.path
|
287
|
+
case parent_component
|
288
|
+
when Xcodeproj::Project::XCBuildConfiguration
|
289
|
+
parent_component.base_configuration_reference = find_file(parent_component.project, path)
|
290
|
+
return parent_component.base_configuration_reference
|
291
|
+
when Xcodeproj::Project::PBXNativeTarget
|
292
|
+
parent_component.product_reference = find_file(parent_component.project, path)
|
293
|
+
return parent_component.product_reference
|
294
|
+
when Xcodeproj::Project::PBXBuildFile
|
295
|
+
parent_component.file_ref = find_file(parent_component.project, path)
|
296
|
+
return parent_component.file_ref
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
parent_component.project.new(
|
301
|
+
Module.const_get("Xcodeproj::Project::#{change["isa"][:added]}")
|
302
|
+
)
|
303
|
+
end
|
304
|
+
|
126
305
|
def copy_attributes_to_new_component(old_component, new_component)
|
127
306
|
# The change won't describe the attributes that haven't changed, therefore the attributes
|
128
307
|
# are copied to the new component.
|
@@ -143,15 +322,23 @@ module Kintsugi
|
|
143
322
|
end
|
144
323
|
end
|
145
324
|
|
146
|
-
def child_component(component, change_name)
|
325
|
+
def child_component(component, change, change_name)
|
147
326
|
if component.is_a?(Xcodeproj::Project::ObjectList)
|
148
|
-
component
|
327
|
+
child_component_of_object_list(component, change, change_name)
|
149
328
|
else
|
150
329
|
attribute_name = attribute_name_from_change_name(change_name)
|
151
330
|
component.send(attribute_name)
|
152
331
|
end
|
153
332
|
end
|
154
333
|
|
334
|
+
def child_component_of_object_list(component, change, change_name)
|
335
|
+
if change["isa"] == "PBXReferenceProxy"
|
336
|
+
find_reference_proxy_in_component(component, change["remoteRef"])
|
337
|
+
else
|
338
|
+
component.find { |child| child.display_name == change_name }
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
155
342
|
def simple_attribute?(component, attribute_name)
|
156
343
|
return false unless component.respond_to?("simple_attributes")
|
157
344
|
|
@@ -260,7 +447,14 @@ module Kintsugi
|
|
260
447
|
|
261
448
|
return added_change if ((old_value || []) - (removed_change || [])).empty?
|
262
449
|
|
263
|
-
|
450
|
+
new_value = (old_value || []) - (removed_change || [])
|
451
|
+
filtered_added_change = if Settings.allow_duplicates
|
452
|
+
(added_change || [])
|
453
|
+
else
|
454
|
+
(added_change || []).reject { |added| new_value.include?(added) }
|
455
|
+
end
|
456
|
+
|
457
|
+
new_value + filtered_added_change
|
264
458
|
end
|
265
459
|
|
266
460
|
def new_string_simple_attribute_value(old_value, removed_change, added_change)
|
@@ -273,10 +467,13 @@ module Kintsugi
|
|
273
467
|
end
|
274
468
|
|
275
469
|
def remove_component(component, change)
|
470
|
+
return if component.nil?
|
471
|
+
|
276
472
|
if component.to_tree_hash != change
|
277
473
|
raise MergeError, "Trying to remove an object that changed since then. This is " \
|
278
474
|
"considered a conflict that should be resolved manually. Name of the object is: " \
|
279
|
-
"'#{component.display_name}'"
|
475
|
+
"'#{component.display_name}'. Existing component: #{component.to_tree_hash}. " \
|
476
|
+
"Change: #{change}"
|
280
477
|
end
|
281
478
|
|
282
479
|
if change["isa"] == "PBXFileReference"
|
@@ -298,63 +495,65 @@ module Kintsugi
|
|
298
495
|
end
|
299
496
|
end
|
300
497
|
|
301
|
-
def add_child_to_component(component, change)
|
498
|
+
def add_child_to_component(component, change, component_change_path)
|
499
|
+
change_path = join_path(component_change_path, change["displayName"])
|
500
|
+
|
302
501
|
if change["ProjectRef"] && change["ProductGroup"]
|
303
|
-
add_subproject_reference(component, change)
|
502
|
+
add_subproject_reference(component, change, change_path)
|
304
503
|
return
|
305
504
|
end
|
306
505
|
|
307
506
|
case change["isa"]
|
308
507
|
when "PBXNativeTarget"
|
309
|
-
add_target(component, change)
|
508
|
+
add_target(component, change, change_path)
|
310
509
|
when "PBXAggregateTarget"
|
311
|
-
add_aggregate_target(component, change)
|
510
|
+
add_aggregate_target(component, change, change_path)
|
312
511
|
when "PBXFileReference"
|
313
|
-
add_file_reference(component, change)
|
512
|
+
add_file_reference(component, change, change_path)
|
314
513
|
when "PBXGroup"
|
315
|
-
add_group(component, change)
|
514
|
+
add_group(component, change, change_path)
|
316
515
|
when "PBXContainerItemProxy"
|
317
|
-
add_container_item_proxy(component, change)
|
516
|
+
add_container_item_proxy(component, change, change_path)
|
318
517
|
when "PBXTargetDependency"
|
319
|
-
add_target_dependency(component, change)
|
518
|
+
add_target_dependency(component, change, change_path)
|
320
519
|
when "PBXBuildFile"
|
321
|
-
add_build_file(component, change)
|
520
|
+
add_build_file(component, change, change_path)
|
322
521
|
when "XCConfigurationList"
|
323
|
-
add_build_configuration_list(component, change)
|
522
|
+
add_build_configuration_list(component, change, change_path)
|
324
523
|
when "XCBuildConfiguration"
|
325
|
-
add_build_configuration(component, change)
|
524
|
+
add_build_configuration(component, change, change_path)
|
326
525
|
when "PBXHeadersBuildPhase"
|
327
|
-
add_headers_build_phase(component, change)
|
526
|
+
add_headers_build_phase(component, change, change_path)
|
328
527
|
when "PBXSourcesBuildPhase"
|
329
|
-
add_sources_build_phase(component, change)
|
528
|
+
add_sources_build_phase(component, change, change_path)
|
330
529
|
when "PBXCopyFilesBuildPhase"
|
331
|
-
add_copy_files_build_phase(component, change)
|
530
|
+
add_copy_files_build_phase(component, change, change_path)
|
332
531
|
when "PBXShellScriptBuildPhase"
|
333
|
-
add_shell_script_build_phase(component, change)
|
532
|
+
add_shell_script_build_phase(component, change, change_path)
|
334
533
|
when "PBXFrameworksBuildPhase"
|
335
|
-
add_frameworks_build_phase(component, change)
|
534
|
+
add_frameworks_build_phase(component, change, change_path)
|
336
535
|
when "PBXResourcesBuildPhase"
|
337
|
-
add_resources_build_phase(component, change)
|
536
|
+
add_resources_build_phase(component, change, change_path)
|
338
537
|
when "PBXBuildRule"
|
339
|
-
add_build_rule(component, change)
|
538
|
+
add_build_rule(component, change, change_path)
|
340
539
|
when "PBXVariantGroup"
|
341
|
-
add_variant_group(component, change)
|
540
|
+
add_variant_group(component, change, change_path)
|
342
541
|
when "PBXReferenceProxy"
|
343
|
-
add_reference_proxy(component, change)
|
542
|
+
add_reference_proxy(component, change, change_path)
|
344
543
|
when "XCSwiftPackageProductDependency"
|
345
|
-
add_swift_package_product_dependency(component, change)
|
544
|
+
add_swift_package_product_dependency(component, change, change_path)
|
346
545
|
when "XCRemoteSwiftPackageReference"
|
347
|
-
add_remote_swift_package_reference(component, change)
|
546
|
+
add_remote_swift_package_reference(component, change, change_path)
|
348
547
|
else
|
349
548
|
raise MergeError, "Trying to add unsupported component type #{change["isa"]}. Full " \
|
350
549
|
"component change is: #{change}"
|
351
550
|
end
|
352
551
|
end
|
353
552
|
|
354
|
-
def add_remote_swift_package_reference(containing_component, change)
|
553
|
+
def add_remote_swift_package_reference(containing_component, change, change_path)
|
355
554
|
remote_swift_package_reference =
|
356
555
|
containing_component.project.new(Xcodeproj::Project::XCRemoteSwiftPackageReference)
|
357
|
-
add_attributes_to_component(remote_swift_package_reference, change)
|
556
|
+
add_attributes_to_component(remote_swift_package_reference, change, change_path)
|
358
557
|
|
359
558
|
case containing_component
|
360
559
|
when Xcodeproj::Project::XCSwiftPackageProductDependency
|
@@ -367,10 +566,10 @@ module Kintsugi
|
|
367
566
|
end
|
368
567
|
end
|
369
568
|
|
370
|
-
def add_swift_package_product_dependency(containing_component, change)
|
569
|
+
def add_swift_package_product_dependency(containing_component, change, change_path)
|
371
570
|
swift_package_product_dependency =
|
372
571
|
containing_component.project.new(Xcodeproj::Project::XCSwiftPackageProductDependency)
|
373
|
-
add_attributes_to_component(swift_package_product_dependency, change)
|
572
|
+
add_attributes_to_component(swift_package_product_dependency, change, change_path)
|
374
573
|
|
375
574
|
case containing_component
|
376
575
|
when Xcodeproj::Project::PBXBuildFile
|
@@ -383,7 +582,7 @@ module Kintsugi
|
|
383
582
|
end
|
384
583
|
end
|
385
584
|
|
386
|
-
def add_reference_proxy(containing_component, change)
|
585
|
+
def add_reference_proxy(containing_component, change, change_path)
|
387
586
|
case containing_component
|
388
587
|
when Xcodeproj::Project::PBXBuildFile
|
389
588
|
# If there are two file references that refer to the same file, one with a build file and
|
@@ -403,85 +602,96 @@ module Kintsugi
|
|
403
602
|
end
|
404
603
|
containing_component.file_ref = file_reference
|
405
604
|
when Xcodeproj::Project::PBXGroup
|
605
|
+
return if !Settings.allow_duplicates &&
|
606
|
+
!find_file_in_group(containing_component, Xcodeproj::Project::PBXReferenceProxy,
|
607
|
+
change["path"]).nil?
|
608
|
+
|
406
609
|
reference_proxy = containing_component.project.new(Xcodeproj::Project::PBXReferenceProxy)
|
407
610
|
containing_component << reference_proxy
|
408
|
-
add_attributes_to_component(reference_proxy, change)
|
611
|
+
add_attributes_to_component(reference_proxy, change, change_path)
|
409
612
|
else
|
410
613
|
raise MergeError, "Trying to add reference proxy to an unsupported component type " \
|
411
614
|
"#{containing_component.isa}. Change is: #{change}"
|
412
615
|
end
|
413
616
|
end
|
414
617
|
|
415
|
-
def add_variant_group(containing_component, change)
|
618
|
+
def add_variant_group(containing_component, change, change_path)
|
416
619
|
case containing_component
|
417
620
|
when Xcodeproj::Project::PBXBuildFile
|
418
621
|
containing_component.file_ref =
|
419
622
|
find_variant_group(containing_component.project, change["displayName"])
|
420
623
|
when Xcodeproj::Project::PBXGroup, Xcodeproj::Project::PBXVariantGroup
|
421
|
-
|
422
|
-
|
423
|
-
|
624
|
+
if find_group_in_group(containing_component, Xcodeproj::Project::PBXVariantGroup,
|
625
|
+
change).nil?
|
626
|
+
raise "Group should have been added already, so this is most likely a bug in Kintsugi" \
|
627
|
+
"Change is: #{change}. Change path: #{change_path}"
|
628
|
+
end
|
424
629
|
else
|
425
630
|
raise MergeError, "Trying to add variant group to an unsupported component type " \
|
426
631
|
"#{containing_component.isa}. Change is: #{change}"
|
427
632
|
end
|
428
633
|
end
|
429
634
|
|
430
|
-
def add_build_rule(target, change)
|
635
|
+
def add_build_rule(target, change, change_path)
|
431
636
|
build_rule = target.project.new(Xcodeproj::Project::PBXBuildRule)
|
432
637
|
target.build_rules << build_rule
|
433
|
-
add_attributes_to_component(build_rule, change)
|
638
|
+
add_attributes_to_component(build_rule, change, change_path)
|
434
639
|
end
|
435
640
|
|
436
|
-
def add_shell_script_build_phase(target, change)
|
641
|
+
def add_shell_script_build_phase(target, change, change_path)
|
437
642
|
build_phase = target.new_shell_script_build_phase(change["displayName"])
|
438
|
-
add_attributes_to_component(build_phase, change)
|
643
|
+
add_attributes_to_component(build_phase, change, change_path)
|
439
644
|
end
|
440
645
|
|
441
|
-
def add_headers_build_phase(target, change)
|
442
|
-
add_attributes_to_component(target.headers_build_phase, change)
|
646
|
+
def add_headers_build_phase(target, change, change_path)
|
647
|
+
add_attributes_to_component(target.headers_build_phase, change, change_path)
|
443
648
|
end
|
444
649
|
|
445
|
-
def add_sources_build_phase(target, change)
|
446
|
-
add_attributes_to_component(target.source_build_phase, change)
|
650
|
+
def add_sources_build_phase(target, change, change_path)
|
651
|
+
add_attributes_to_component(target.source_build_phase, change, change_path)
|
447
652
|
end
|
448
653
|
|
449
|
-
def add_frameworks_build_phase(target, change)
|
450
|
-
add_attributes_to_component(target.frameworks_build_phase, change)
|
654
|
+
def add_frameworks_build_phase(target, change, change_path)
|
655
|
+
add_attributes_to_component(target.frameworks_build_phase, change, change_path)
|
451
656
|
end
|
452
657
|
|
453
|
-
def add_resources_build_phase(target, change)
|
454
|
-
add_attributes_to_component(target.resources_build_phase, change)
|
658
|
+
def add_resources_build_phase(target, change, change_path)
|
659
|
+
add_attributes_to_component(target.resources_build_phase, change, change_path)
|
455
660
|
end
|
456
661
|
|
457
|
-
def add_copy_files_build_phase(target, change)
|
662
|
+
def add_copy_files_build_phase(target, change, change_path)
|
458
663
|
copy_files_phase_name = change["displayName"] == "CopyFiles" ? nil : change["displayName"]
|
459
664
|
copy_files_phase = target.new_copy_files_build_phase(copy_files_phase_name)
|
460
665
|
|
461
|
-
add_attributes_to_component(copy_files_phase, change)
|
666
|
+
add_attributes_to_component(copy_files_phase, change, change_path)
|
462
667
|
end
|
463
668
|
|
464
|
-
def add_build_configuration_list(target, change)
|
669
|
+
def add_build_configuration_list(target, change, change_path)
|
465
670
|
target.build_configuration_list = target.project.new(Xcodeproj::Project::XCConfigurationList)
|
466
|
-
add_attributes_to_component(target.build_configuration_list, change)
|
671
|
+
add_attributes_to_component(target.build_configuration_list, change, change_path)
|
467
672
|
end
|
468
673
|
|
469
|
-
def add_build_configuration(configuration_list, change)
|
674
|
+
def add_build_configuration(configuration_list, change, change_path)
|
470
675
|
build_configuration = configuration_list.project.new(Xcodeproj::Project::XCBuildConfiguration)
|
471
676
|
configuration_list.build_configurations << build_configuration
|
472
|
-
add_attributes_to_component(build_configuration, change)
|
677
|
+
add_attributes_to_component(build_configuration, change, change_path)
|
473
678
|
end
|
474
679
|
|
475
|
-
def add_build_file(build_phase, change)
|
680
|
+
def add_build_file(build_phase, change, change_path)
|
476
681
|
if change["fileRef"].nil?
|
477
682
|
puts "Warning: Trying to add a build file without any file reference to build phase " \
|
478
683
|
"'#{build_phase}'"
|
479
684
|
return
|
480
685
|
end
|
481
686
|
|
687
|
+
existing_build_file = build_phase.files.find do |build_file|
|
688
|
+
build_file.file_ref.path == change["fileRef"]["path"]
|
689
|
+
end
|
690
|
+
return if !Settings.allow_duplicates && !existing_build_file.nil?
|
691
|
+
|
482
692
|
build_file = build_phase.project.new(Xcodeproj::Project::PBXBuildFile)
|
483
693
|
build_phase.files << build_file
|
484
|
-
add_attributes_to_component(build_file, change)
|
694
|
+
add_attributes_to_component(build_file, change, change_path)
|
485
695
|
end
|
486
696
|
|
487
697
|
def find_variant_group(project, display_name)
|
@@ -490,7 +700,7 @@ module Kintsugi
|
|
490
700
|
end
|
491
701
|
end
|
492
702
|
|
493
|
-
def add_target_dependency(target, change)
|
703
|
+
def add_target_dependency(target, change, change_path)
|
494
704
|
target_dependency = find_target(target.project, change["displayName"])
|
495
705
|
|
496
706
|
if target_dependency
|
@@ -501,14 +711,14 @@ module Kintsugi
|
|
501
711
|
target_dependency = target.project.new(Xcodeproj::Project::PBXTargetDependency)
|
502
712
|
|
503
713
|
target.dependencies << target_dependency
|
504
|
-
add_attributes_to_component(target_dependency, change)
|
714
|
+
add_attributes_to_component(target_dependency, change, change_path)
|
505
715
|
end
|
506
716
|
|
507
717
|
def find_target(project, display_name)
|
508
718
|
project.targets.find { |target| target.display_name == display_name }
|
509
719
|
end
|
510
720
|
|
511
|
-
def add_container_item_proxy(component, change)
|
721
|
+
def add_container_item_proxy(component, change, change_path)
|
512
722
|
container_proxy = component.project.new(Xcodeproj::Project::PBXContainerItemProxy)
|
513
723
|
container_proxy.container_portal = find_containing_project_uuid(component.project, change)
|
514
724
|
|
@@ -521,7 +731,8 @@ module Kintsugi
|
|
521
731
|
raise MergeError, "Trying to add container item proxy to an unsupported component type " \
|
522
732
|
"#{containing_component.isa}. Change is: #{change}"
|
523
733
|
end
|
524
|
-
add_attributes_to_component(container_proxy, change,
|
734
|
+
add_attributes_to_component(container_proxy, change, change_path,
|
735
|
+
ignore_keys: ["containerPortal"])
|
525
736
|
end
|
526
737
|
|
527
738
|
def find_containing_project_uuid(project, container_item_proxy_change)
|
@@ -552,14 +763,20 @@ module Kintsugi
|
|
552
763
|
container_item_proxies.first.container_portal
|
553
764
|
end
|
554
765
|
|
555
|
-
def add_subproject_reference(root_object, project_reference_change)
|
766
|
+
def add_subproject_reference(root_object, project_reference_change, change_path)
|
767
|
+
existing_subproject =
|
768
|
+
root_object.project_references.find do |project_reference|
|
769
|
+
project_reference.project_ref.path == project_reference_change["ProjectRef"]["path"]
|
770
|
+
end
|
771
|
+
return if !Settings.allow_duplicates && !existing_subproject.nil?
|
772
|
+
|
556
773
|
filter_subproject_without_project_references = lambda do |file_reference|
|
557
774
|
root_object.project_references.find do |project_reference|
|
558
775
|
project_reference.project_ref.uuid == file_reference.uuid
|
559
776
|
end.nil?
|
560
777
|
end
|
561
778
|
subproject_reference =
|
562
|
-
find_file(root_object.project, project_reference_change["ProjectRef"],
|
779
|
+
find_file(root_object.project, project_reference_change["ProjectRef"]["path"],
|
563
780
|
file_filter: filter_subproject_without_project_references)
|
564
781
|
|
565
782
|
unless subproject_reference
|
@@ -578,7 +795,7 @@ module Kintsugi
|
|
578
795
|
updated_project_reference_change =
|
579
796
|
change_with_updated_subproject_uuid(project_reference_change, subproject_reference.uuid)
|
580
797
|
add_attributes_to_component(project_reference, updated_project_reference_change,
|
581
|
-
ignore_keys: ["ProjectRef"])
|
798
|
+
change_path, ignore_keys: ["ProjectRef"])
|
582
799
|
end
|
583
800
|
|
584
801
|
def change_with_updated_subproject_uuid(change, subproject_reference_uuid)
|
@@ -590,19 +807,19 @@ module Kintsugi
|
|
590
807
|
new_change
|
591
808
|
end
|
592
809
|
|
593
|
-
def add_target(root_object, change)
|
810
|
+
def add_target(root_object, change, change_path)
|
594
811
|
target = root_object.project.new(Xcodeproj::Project::PBXNativeTarget)
|
595
812
|
root_object.project.targets << target
|
596
|
-
add_attributes_to_component(target, change)
|
813
|
+
add_attributes_to_component(target, change, change_path)
|
597
814
|
end
|
598
815
|
|
599
|
-
def add_aggregate_target(root_object, change)
|
816
|
+
def add_aggregate_target(root_object, change, change_path)
|
600
817
|
target = root_object.project.new(Xcodeproj::Project::PBXAggregateTarget)
|
601
818
|
root_object.project.targets << target
|
602
|
-
add_attributes_to_component(target, change)
|
819
|
+
add_attributes_to_component(target, change, change_path)
|
603
820
|
end
|
604
821
|
|
605
|
-
def add_file_reference(containing_component, change)
|
822
|
+
def add_file_reference(containing_component, change, change_path)
|
606
823
|
# base configuration reference and product reference always reference a file that exists
|
607
824
|
# inside a group, therefore in these cases the file is searched for.
|
608
825
|
# In the case of group and variant group, the file can't exist in another group, therefore a
|
@@ -610,59 +827,74 @@ module Kintsugi
|
|
610
827
|
case containing_component
|
611
828
|
when Xcodeproj::Project::XCBuildConfiguration
|
612
829
|
containing_component.base_configuration_reference =
|
613
|
-
find_file(containing_component.project, change)
|
830
|
+
find_file(containing_component.project, change["path"])
|
614
831
|
when Xcodeproj::Project::PBXNativeTarget
|
615
|
-
containing_component.product_reference =
|
832
|
+
containing_component.product_reference =
|
833
|
+
find_file(containing_component.project, change["path"])
|
616
834
|
when Xcodeproj::Project::PBXBuildFile
|
617
|
-
containing_component.file_ref = find_file(containing_component.project, change)
|
835
|
+
containing_component.file_ref = find_file(containing_component.project, change["path"])
|
618
836
|
when Xcodeproj::Project::PBXGroup, Xcodeproj::Project::PBXVariantGroup
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
file_reference.include_in_index = nil
|
625
|
-
file_reference.source_tree = nil
|
626
|
-
add_attributes_to_component(file_reference, change)
|
837
|
+
if find_file_in_group(containing_component, Xcodeproj::Project::PBXFileReference,
|
838
|
+
change["path"]).nil?
|
839
|
+
raise "File should have been added already, so this is most likely a bug in Kintsugi" \
|
840
|
+
"Change is: #{change}. Change path: #{change_path}"
|
841
|
+
end
|
627
842
|
else
|
628
843
|
raise MergeError, "Trying to add file reference to an unsupported component type " \
|
629
844
|
"#{containing_component.isa}. Change is: #{change}"
|
630
845
|
end
|
631
846
|
end
|
632
847
|
|
633
|
-
def
|
848
|
+
def find_file_in_group(group, instance_type, filepath)
|
849
|
+
group
|
850
|
+
.children
|
851
|
+
.select { |child| child.instance_of?(instance_type) }
|
852
|
+
.find { |file| file.path == filepath }
|
853
|
+
end
|
854
|
+
|
855
|
+
def adding_files_and_groups_allowed?(change_path)
|
856
|
+
change_path.start_with?("rootObject/mainGroup") ||
|
857
|
+
change_path.start_with?("rootObject/projectReferences")
|
858
|
+
end
|
859
|
+
|
860
|
+
def add_group(containing_component, change, change_path)
|
634
861
|
case containing_component
|
635
862
|
when Xcodeproj::Project::ObjectDictionary
|
636
863
|
# It is assumed that an `ObjectDictionary` always represents a project reference.
|
637
864
|
new_group = containing_component[:project_ref].project.new(Xcodeproj::Project::PBXGroup)
|
638
865
|
containing_component[:product_group] = new_group
|
866
|
+
add_attributes_to_component(new_group, change, change_path)
|
639
867
|
when Xcodeproj::Project::PBXGroup
|
640
|
-
|
641
|
-
|
868
|
+
if find_group_in_group(containing_component, Xcodeproj::Project::PBXGroup, change).nil?
|
869
|
+
raise "Group should have been added already, so this is most likely a bug in Kintsugi" \
|
870
|
+
"Change is: #{change}. Change path: #{change_path}"
|
871
|
+
end
|
642
872
|
else
|
643
873
|
raise MergeError, "Trying to add group to an unsupported component type " \
|
644
|
-
"#{containing_component.isa}. Change is: #{change}"
|
874
|
+
"#{containing_component.isa}. Change is: #{change}. Change path: #{change_path}"
|
645
875
|
end
|
646
|
-
|
647
|
-
add_attributes_to_component(new_group, change)
|
648
876
|
end
|
649
877
|
|
650
|
-
def add_attributes_to_component(component, change, ignore_keys: [])
|
878
|
+
def add_attributes_to_component(component, change, change_path, ignore_keys: [])
|
651
879
|
change.each do |change_name, change_value|
|
652
880
|
next if (%w[isa displayName] + ignore_keys).include?(change_name)
|
653
881
|
|
654
882
|
attribute_name = attribute_name_from_change_name(change_name)
|
655
883
|
if simple_attribute?(component, attribute_name)
|
656
|
-
|
884
|
+
simple_attribute_change = {
|
885
|
+
added: change_value,
|
886
|
+
removed: simple_attribute_default_value(component, attribute_name)
|
887
|
+
}
|
888
|
+
apply_change_to_simple_attribute(component, attribute_name, simple_attribute_change)
|
657
889
|
next
|
658
890
|
end
|
659
891
|
|
660
892
|
case change_value
|
661
893
|
when Hash
|
662
|
-
add_child_to_component(component, change_value)
|
894
|
+
add_child_to_component(component, change_value, change_path)
|
663
895
|
when Array
|
664
896
|
change_value.each do |added_attribute_element|
|
665
|
-
add_child_to_component(component, added_attribute_element)
|
897
|
+
add_child_to_component(component, added_attribute_element, change_path)
|
666
898
|
end
|
667
899
|
else
|
668
900
|
raise MergeError, "Trying to add attribute of unsupported type '#{change_value.class}' " \
|
@@ -671,16 +903,20 @@ module Kintsugi
|
|
671
903
|
end
|
672
904
|
end
|
673
905
|
|
674
|
-
def
|
906
|
+
def simple_attribute_default_value(component, attribute_name)
|
907
|
+
component.simple_attributes.find do |attribute|
|
908
|
+
attribute.name == attribute_name
|
909
|
+
end.default_value
|
910
|
+
end
|
911
|
+
|
912
|
+
def find_file(project, path, file_filter: ->(_) { true })
|
675
913
|
file_references = project.files.select do |file_reference|
|
676
|
-
file_reference.path ==
|
914
|
+
file_reference.path == path && file_filter.call(file_reference)
|
677
915
|
end
|
678
916
|
if file_references.length > 1
|
679
|
-
puts "Debug: Found more than one matching file with path "
|
680
|
-
"'#{file_reference_change["path"]}'. Using the first one."
|
917
|
+
puts "Debug: Found more than one matching file with path '#{path}'. Using the first one."
|
681
918
|
elsif file_references.empty?
|
682
|
-
puts "Debug: No file reference found for file with path "
|
683
|
-
"'#{file_reference_change["path"]}'."
|
919
|
+
puts "Debug: No file reference found for file with path '#{path}'."
|
684
920
|
return
|
685
921
|
end
|
686
922
|
|
@@ -689,12 +925,9 @@ module Kintsugi
|
|
689
925
|
|
690
926
|
def find_reference_proxy(project, container_item_proxy_change, reference_filter: ->(_) { true })
|
691
927
|
reference_proxies = project.root_object.project_references.map do |project_ref_and_products|
|
692
|
-
project_ref_and_products[:product_group].children
|
693
|
-
|
694
|
-
|
695
|
-
product.remote_ref.remote_info == container_item_proxy_change["remoteInfo"] &&
|
696
|
-
reference_filter.call(product)
|
697
|
-
end
|
928
|
+
find_reference_proxy_in_component(project_ref_and_products[:product_group].children,
|
929
|
+
container_item_proxy_change,
|
930
|
+
reference_filter: reference_filter)
|
698
931
|
end.compact
|
699
932
|
|
700
933
|
if reference_proxies.length > 1
|
@@ -708,5 +941,19 @@ module Kintsugi
|
|
708
941
|
|
709
942
|
reference_proxies.first
|
710
943
|
end
|
944
|
+
|
945
|
+
def find_reference_proxy_in_component(component, container_item_proxy_change,
|
946
|
+
reference_filter: ->(_) { true })
|
947
|
+
component.find do |product|
|
948
|
+
product.remote_ref.remote_global_id_string ==
|
949
|
+
container_item_proxy_change["remoteGlobalIDString"] &&
|
950
|
+
product.remote_ref.remote_info == container_item_proxy_change["remoteInfo"] &&
|
951
|
+
reference_filter.call(product)
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
def join_path(left, right)
|
956
|
+
left.empty? ? right : "#{left}/#{right}"
|
957
|
+
end
|
711
958
|
end
|
712
959
|
end
|