kintsugi 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|