kintsugi 0.4.3 → 0.5.3

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.
@@ -5,6 +5,7 @@
5
5
  require "xcodeproj"
6
6
 
7
7
  require_relative "utils"
8
+ require_relative "error"
8
9
  require_relative "xcodeproj_extensions"
9
10
 
10
11
  module Kintsugi
@@ -28,26 +29,28 @@ module Kintsugi
28
29
  puts "Warning: Main group doesn't exist, ignoring changes to it."
29
30
  else
30
31
  apply_change_to_component(project.root_object, "mainGroup",
31
- change["rootObject"]["mainGroup"])
32
+ change["rootObject"]["mainGroup"], "rootObject")
32
33
  end
33
34
  end
34
35
 
35
36
  unless change["rootObject"]["projectReferences"].nil?
36
37
  apply_change_to_component(project.root_object, "projectReferences",
37
- change["rootObject"]["projectReferences"])
38
+ change["rootObject"]["projectReferences"], "rootObject")
38
39
  end
39
40
 
40
41
  apply_change_to_component(project, "rootObject",
41
42
  change["rootObject"].reject { |key|
42
43
  %w[mainGroup projectReferences].include?(key)
43
- })
44
+ }, "")
44
45
  end
45
46
 
46
47
  private
47
48
 
48
- def apply_change_to_component(parent_component, change_name, change)
49
+ def apply_change_to_component(parent_component, change_name, change, parent_change_path)
49
50
  return if change_name == "displayName"
50
51
 
52
+ change_path = parent_change_path.empty? ? change_name : "#{parent_change_path}/#{change_name}"
53
+
51
54
  attribute_name = attribute_name_from_change_name(change_name)
52
55
  if simple_attribute?(parent_component, attribute_name)
53
56
  apply_change_to_simple_attribute(parent_component, attribute_name, change)
@@ -61,7 +64,7 @@ module Kintsugi
61
64
  component = child_component(parent_component, change_name)
62
65
 
63
66
  if component.nil?
64
- add_missing_component_if_valid(parent_component, change_name, change)
67
+ add_missing_component_if_valid(parent_component, change_name, change, change_path)
65
68
  return
66
69
  end
67
70
  end
@@ -75,11 +78,12 @@ module Kintsugi
75
78
 
76
79
  (change[:added] || []).each do |added_change|
77
80
  is_object_list = component.is_a?(Xcodeproj::Project::ObjectList)
78
- add_child_to_component(is_object_list ? parent_component : component, added_change)
81
+ add_child_to_component(is_object_list ? parent_component : component, added_change,
82
+ change_path)
79
83
  end
80
84
 
81
85
  subchanges_of_change(change).each do |subchange_name, subchange|
82
- apply_change_to_component(component, subchange_name, subchange)
86
+ apply_change_to_component(component, subchange_name, subchange, change_path)
83
87
  end
84
88
  end
85
89
 
@@ -92,16 +96,16 @@ module Kintsugi
92
96
  end
93
97
 
94
98
  def attribute_name_from_change_name(change_name)
95
- if change_name == "fileEncoding"
99
+ if %w[fileEncoding repositoryURL].include?(change_name)
96
100
  change_name.to_sym
97
101
  else
98
102
  Xcodeproj::Project::Object::CaseConverter.convert_to_ruby(change_name)
99
103
  end
100
104
  end
101
105
 
102
- def add_missing_component_if_valid(parent_component, change_name, change)
106
+ def add_missing_component_if_valid(parent_component, change_name, change, change_path)
103
107
  if change[:added] && change.compact.count == 1
104
- add_child_to_component(parent_component, change[:added])
108
+ add_child_to_component(parent_component, change[:added], change_path)
105
109
  return
106
110
  end
107
111
 
@@ -111,10 +115,7 @@ module Kintsugi
111
115
 
112
116
  def replace_component_with_new_type(parent_component, name_in_parent_component, change)
113
117
  old_component = parent_component.send(name_in_parent_component)
114
-
115
- new_component = parent_component.project.new(
116
- Module.const_get("Xcodeproj::Project::#{change["isa"][:added]}")
117
- )
118
+ new_component = component_of_new_type(parent_component, change, old_component)
118
119
 
119
120
  copy_attributes_to_new_component(old_component, new_component)
120
121
 
@@ -122,6 +123,27 @@ module Kintsugi
122
123
  new_component
123
124
  end
124
125
 
126
+ def component_of_new_type(parent_component, change, old_component)
127
+ if change["isa"][:added] == "PBXFileReference"
128
+ path = (change["path"] && change["path"][:added]) || old_component.path
129
+ case parent_component
130
+ when Xcodeproj::Project::XCBuildConfiguration
131
+ parent_component.base_configuration_reference = find_file(parent_component.project, path)
132
+ return parent_component.base_configuration_reference
133
+ when Xcodeproj::Project::PBXNativeTarget
134
+ parent_component.product_reference = find_file(parent_component.project, path)
135
+ return parent_component.product_reference
136
+ when Xcodeproj::Project::PBXBuildFile
137
+ parent_component.file_ref = find_file(parent_component.project, path)
138
+ return parent_component.file_ref
139
+ end
140
+ end
141
+
142
+ parent_component.project.new(
143
+ Module.const_get("Xcodeproj::Project::#{change["isa"][:added]}")
144
+ )
145
+ end
146
+
125
147
  def copy_attributes_to_new_component(old_component, new_component)
126
148
  # The change won't describe the attributes that haven't changed, therefore the attributes
127
149
  # are copied to the new component.
@@ -164,15 +186,8 @@ module Kintsugi
164
186
  end
165
187
 
166
188
  def simple_attribute_value_with_change(old_value, change)
167
- new_value = nil
168
-
169
- if change.key?(:removed)
170
- new_value = apply_removal_to_simple_attribute(old_value, change[:removed], change[:added])
171
- end
172
-
173
- if change.key?(:added)
174
- new_value = apply_addition_to_simple_attribute(old_value, change[:added])
175
- end
189
+ type = simple_attribute_type(old_value, change[:removed], change[:added])
190
+ new_value = new_simple_attribute_value(type, old_value, change[:removed], change[:added])
176
191
 
177
192
  subchanges_of_change(change).each do |subchange_name, subchange_value|
178
193
  new_value = new_value || old_value || {}
@@ -183,13 +198,67 @@ module Kintsugi
183
198
  new_value
184
199
  end
185
200
 
186
- def apply_removal_to_simple_attribute(old_value, removed_change, added_change)
187
- case removed_change
188
- when Array
189
- (old_value || []) - removed_change
190
- when Hash
191
- (old_value || {}).reject do |key, value|
192
- if value != removed_change[key] && added_change[key] != value
201
+ def simple_attribute_type(old_value, removed_change, added_change)
202
+ types = [old_value.class, removed_change.class, added_change.class]
203
+
204
+ if types.include?(Hash)
205
+ unless types.to_set.subset?([Hash, NilClass].to_set)
206
+ raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
207
+ "value: '#{old_value}', removed change: '#{removed_change}', added change: " \
208
+ "'#{added_change}'"
209
+ end
210
+ Hash
211
+ elsif types.include?(Array)
212
+ unless types.to_set.subset?([Array, String, NilClass].to_set)
213
+ raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
214
+ "value: '#{old_value}', removed change: '#{removed_change}', added change: " \
215
+ "'#{added_change}'"
216
+ end
217
+ Array
218
+ elsif types.include?(String)
219
+ unless types.to_set.subset?([String, NilClass].to_set)
220
+ raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
221
+ "value: '#{old_value}', removed change: '#{removed_change}', added change: " \
222
+ "'#{added_change}'"
223
+ end
224
+ String
225
+ else
226
+ raise MergeError, "Unsupported types of all of the values. Existing value: " \
227
+ "'#{old_value}', removed change: '#{removed_change}', added change: '#{added_change}'"
228
+ end
229
+ end
230
+
231
+ def new_simple_attribute_value(type, old_value, removed_change, added_change)
232
+ if type == Hash
233
+ new_hash_simple_attribute_value(old_value, removed_change, added_change)
234
+ elsif type == Array
235
+ new_array_simple_attribute_value(old_value, removed_change, added_change)
236
+ elsif type == String
237
+ new_string_simple_attribute_value(old_value, removed_change, added_change)
238
+ else
239
+ raise MergeError, "Unsupported types of all of the values. Existing value: " \
240
+ "'#{old_value}', removed change: '#{removed_change}', added change: '#{added_change}'"
241
+ end
242
+ end
243
+
244
+ def new_hash_simple_attribute_value(old_value, removed_change, added_change)
245
+ return added_change if ((old_value || {}).to_a - (removed_change || {}).to_a).empty?
246
+
247
+ # First apply the added change to see if there are any conflicts with it.
248
+ new_value = (old_value || {}).merge(added_change || {})
249
+
250
+ unless (old_value.to_a - new_value.to_a).empty?
251
+ raise MergeError, "New hash #{change} contains values that conflict with old hash " \
252
+ "#{old_value}"
253
+ end
254
+
255
+ if removed_change.nil?
256
+ return new_value
257
+ end
258
+
259
+ new_value
260
+ .reject do |key, value|
261
+ if value != removed_change[key] && value != (added_change || {})[key]
193
262
  raise MergeError, "Trying to remove value '#{removed_change[key]}' of hash with key " \
194
263
  "'#{key}' but it changed to #{value}. This is considered a conflict that should be " \
195
264
  "resolved manually."
@@ -197,41 +266,31 @@ module Kintsugi
197
266
 
198
267
  removed_change.key?(key)
199
268
  end
200
- when String
201
- if old_value != removed_change && !old_value.nil? && added_change != old_value
202
- raise MergeError, "Trying to remove value '#{removed_change}', but the existing value " \
203
- "is '#{old_value}'. This is considered a conflict that should be resolved manually."
204
- end
269
+ end
205
270
 
206
- nil
207
- when nil
208
- nil
209
- else
210
- raise MergeError, "Unsupported change #{removed_change} of type #{removed_change.class}"
271
+ def new_array_simple_attribute_value(old_value, removed_change, added_change)
272
+ if old_value.is_a?(String)
273
+ old_value = [old_value]
274
+ end
275
+ if removed_change.is_a?(String)
276
+ removed_change = [removed_change]
277
+ end
278
+ if added_change.is_a?(String)
279
+ added_change = [added_change]
211
280
  end
212
- end
213
281
 
214
- def apply_addition_to_simple_attribute(old_value, change)
215
- case change
216
- when Array
217
- (old_value || []) + change
218
- when Hash
219
- old_value ||= {}
220
- new_value = old_value.merge(change)
282
+ return added_change if ((old_value || []) - (removed_change || [])).empty?
221
283
 
222
- unless (old_value.to_a - new_value.to_a).empty?
223
- raise MergeError, "New hash #{change} contains values that conflict with old hash " \
224
- "#{old_value}"
225
- end
284
+ (old_value || []) + (added_change || []) - (removed_change || [])
285
+ end
226
286
 
227
- new_value
228
- when String
229
- change
230
- when nil
231
- nil
232
- else
233
- raise MergeError, "Unsupported change #{change} of type #{change.class}"
287
+ def new_string_simple_attribute_value(old_value, removed_change, added_change)
288
+ if old_value != removed_change && !old_value.nil? && added_change != old_value
289
+ raise MergeError, "Trying to remove value '#{removed_change || "nil"}', but the existing " \
290
+ "value is '#{old_value}'. This is considered a conflict that should be resolved manually."
234
291
  end
292
+
293
+ added_change
235
294
  end
236
295
 
237
296
  def remove_component(component, change)
@@ -260,56 +319,94 @@ module Kintsugi
260
319
  end
261
320
  end
262
321
 
263
- def add_child_to_component(component, change)
322
+ def add_child_to_component(component, change, component_change_path)
323
+ change_path = "#{component_change_path}/#{change["displayName"]}"
324
+
264
325
  if change["ProjectRef"] && change["ProductGroup"]
265
- add_subproject_reference(component, change)
326
+ add_subproject_reference(component, change, change_path)
266
327
  return
267
328
  end
268
329
 
269
330
  case change["isa"]
270
331
  when "PBXNativeTarget"
271
- add_target(component, change)
332
+ add_target(component, change, change_path)
272
333
  when "PBXAggregateTarget"
273
- add_aggregate_target(component, change)
334
+ add_aggregate_target(component, change, change_path)
274
335
  when "PBXFileReference"
275
- add_file_reference(component, change)
336
+ add_file_reference(component, change, change_path)
276
337
  when "PBXGroup"
277
- add_group(component, change)
338
+ add_group(component, change, change_path)
278
339
  when "PBXContainerItemProxy"
279
- add_container_item_proxy(component, change)
340
+ add_container_item_proxy(component, change, change_path)
280
341
  when "PBXTargetDependency"
281
- add_target_dependency(component, change)
342
+ add_target_dependency(component, change, change_path)
282
343
  when "PBXBuildFile"
283
- add_build_file(component, change)
344
+ add_build_file(component, change, change_path)
284
345
  when "XCConfigurationList"
285
- add_build_configuration_list(component, change)
346
+ add_build_configuration_list(component, change, change_path)
286
347
  when "XCBuildConfiguration"
287
- add_build_configuration(component, change)
348
+ add_build_configuration(component, change, change_path)
288
349
  when "PBXHeadersBuildPhase"
289
- add_headers_build_phase(component, change)
350
+ add_headers_build_phase(component, change, change_path)
290
351
  when "PBXSourcesBuildPhase"
291
- add_sources_build_phase(component, change)
352
+ add_sources_build_phase(component, change, change_path)
292
353
  when "PBXCopyFilesBuildPhase"
293
- add_copy_files_build_phase(component, change)
354
+ add_copy_files_build_phase(component, change, change_path)
294
355
  when "PBXShellScriptBuildPhase"
295
- add_shell_script_build_phase(component, change)
356
+ add_shell_script_build_phase(component, change, change_path)
296
357
  when "PBXFrameworksBuildPhase"
297
- add_frameworks_build_phase(component, change)
358
+ add_frameworks_build_phase(component, change, change_path)
298
359
  when "PBXResourcesBuildPhase"
299
- add_resources_build_phase(component, change)
360
+ add_resources_build_phase(component, change, change_path)
300
361
  when "PBXBuildRule"
301
- add_build_rule(component, change)
362
+ add_build_rule(component, change, change_path)
302
363
  when "PBXVariantGroup"
303
- add_variant_group(component, change)
364
+ add_variant_group(component, change, change_path)
304
365
  when "PBXReferenceProxy"
305
- add_reference_proxy(component, change)
366
+ add_reference_proxy(component, change, change_path)
367
+ when "XCSwiftPackageProductDependency"
368
+ add_swift_package_product_dependency(component, change, change_path)
369
+ when "XCRemoteSwiftPackageReference"
370
+ add_remote_swift_package_reference(component, change, change_path)
306
371
  else
307
372
  raise MergeError, "Trying to add unsupported component type #{change["isa"]}. Full " \
308
373
  "component change is: #{change}"
309
374
  end
310
375
  end
311
376
 
312
- def add_reference_proxy(containing_component, change)
377
+ def add_remote_swift_package_reference(containing_component, change, change_path)
378
+ remote_swift_package_reference =
379
+ containing_component.project.new(Xcodeproj::Project::XCRemoteSwiftPackageReference)
380
+ add_attributes_to_component(remote_swift_package_reference, change, change_path)
381
+
382
+ case containing_component
383
+ when Xcodeproj::Project::XCSwiftPackageProductDependency
384
+ containing_component.package = remote_swift_package_reference
385
+ when Xcodeproj::Project::PBXProject
386
+ containing_component.package_references << remote_swift_package_reference
387
+ else
388
+ raise MergeError, "Trying to add remote swift package reference to an unsupported " \
389
+ "component type #{containing_component.isa}. Change is: #{change}"
390
+ end
391
+ end
392
+
393
+ def add_swift_package_product_dependency(containing_component, change, change_path)
394
+ swift_package_product_dependency =
395
+ containing_component.project.new(Xcodeproj::Project::XCSwiftPackageProductDependency)
396
+ add_attributes_to_component(swift_package_product_dependency, change, change_path)
397
+
398
+ case containing_component
399
+ when Xcodeproj::Project::PBXBuildFile
400
+ containing_component.product_ref = swift_package_product_dependency
401
+ when Xcodeproj::Project::PBXNativeTarget
402
+ containing_component.package_product_dependencies << swift_package_product_dependency
403
+ else
404
+ raise MergeError, "Trying to add swift package product dependency to an unsupported " \
405
+ "component type #{containing_component.isa}. Change is: #{change}"
406
+ end
407
+ end
408
+
409
+ def add_reference_proxy(containing_component, change, change_path)
313
410
  case containing_component
314
411
  when Xcodeproj::Project::PBXBuildFile
315
412
  # If there are two file references that refer to the same file, one with a build file and
@@ -331,74 +428,78 @@ module Kintsugi
331
428
  when Xcodeproj::Project::PBXGroup
332
429
  reference_proxy = containing_component.project.new(Xcodeproj::Project::PBXReferenceProxy)
333
430
  containing_component << reference_proxy
334
- add_attributes_to_component(reference_proxy, change)
431
+ add_attributes_to_component(reference_proxy, change, change_path)
335
432
  else
336
433
  raise MergeError, "Trying to add reference proxy to an unsupported component type " \
337
434
  "#{containing_component.isa}. Change is: #{change}"
338
435
  end
339
436
  end
340
437
 
341
- def add_variant_group(containing_component, change)
438
+ def add_variant_group(containing_component, change, change_path)
342
439
  case containing_component
343
440
  when Xcodeproj::Project::PBXBuildFile
344
441
  containing_component.file_ref =
345
442
  find_variant_group(containing_component.project, change["displayName"])
346
443
  when Xcodeproj::Project::PBXGroup, Xcodeproj::Project::PBXVariantGroup
444
+ unless adding_files_and_groups_allowed?(change_path)
445
+ return
446
+ end
447
+
347
448
  variant_group = containing_component.project.new(Xcodeproj::Project::PBXVariantGroup)
348
449
  containing_component.children << variant_group
349
- add_attributes_to_component(variant_group, change)
450
+ add_attributes_to_component(variant_group, change, change_path)
350
451
  else
351
452
  raise MergeError, "Trying to add variant group to an unsupported component type " \
352
453
  "#{containing_component.isa}. Change is: #{change}"
353
454
  end
354
455
  end
355
456
 
356
- def add_build_rule(target, change)
457
+ def add_build_rule(target, change, change_path)
357
458
  build_rule = target.project.new(Xcodeproj::Project::PBXBuildRule)
358
459
  target.build_rules << build_rule
359
- add_attributes_to_component(build_rule, change)
460
+ add_attributes_to_component(build_rule, change, change_path)
360
461
  end
361
462
 
362
- def add_shell_script_build_phase(target, change)
463
+ def add_shell_script_build_phase(target, change, change_path)
363
464
  build_phase = target.new_shell_script_build_phase(change["displayName"])
364
- add_attributes_to_component(build_phase, change)
465
+ add_attributes_to_component(build_phase, change, change_path)
365
466
  end
366
467
 
367
- def add_headers_build_phase(target, change)
368
- add_attributes_to_component(target.headers_build_phase, change)
468
+ def add_headers_build_phase(target, change, change_path)
469
+ add_attributes_to_component(target.headers_build_phase, change, change_path)
369
470
  end
370
471
 
371
- def add_sources_build_phase(target, change)
372
- add_attributes_to_component(target.source_build_phase, change)
472
+ def add_sources_build_phase(target, change, change_path)
473
+ add_attributes_to_component(target.source_build_phase, change, change_path)
373
474
  end
374
475
 
375
- def add_frameworks_build_phase(target, change)
376
- add_attributes_to_component(target.frameworks_build_phase, change)
476
+ def add_frameworks_build_phase(target, change, change_path)
477
+ add_attributes_to_component(target.frameworks_build_phase, change, change_path)
377
478
  end
378
479
 
379
- def add_resources_build_phase(target, change)
380
- add_attributes_to_component(target.resources_build_phase, change)
480
+ def add_resources_build_phase(target, change, change_path)
481
+ add_attributes_to_component(target.resources_build_phase, change, change_path)
381
482
  end
382
483
 
383
- def add_copy_files_build_phase(target, change)
484
+ def add_copy_files_build_phase(target, change, change_path)
384
485
  copy_files_phase_name = change["displayName"] == "CopyFiles" ? nil : change["displayName"]
385
486
  copy_files_phase = target.new_copy_files_build_phase(copy_files_phase_name)
386
487
 
387
- add_attributes_to_component(copy_files_phase, change)
488
+ add_attributes_to_component(copy_files_phase, change, change_path)
388
489
  end
389
490
 
390
- def add_build_configuration_list(target, change)
491
+ def add_build_configuration_list(target, change, change_path)
391
492
  target.build_configuration_list = target.project.new(Xcodeproj::Project::XCConfigurationList)
392
- add_attributes_to_component(target.build_configuration_list, change)
493
+ add_attributes_to_component(target.build_configuration_list, change, change_path)
393
494
  end
394
495
 
395
- def add_build_configuration(configuration_list, change)
496
+ def add_build_configuration(configuration_list, change, change_path)
396
497
  build_configuration = configuration_list.project.new(Xcodeproj::Project::XCBuildConfiguration)
397
498
  configuration_list.build_configurations << build_configuration
398
- add_attributes_to_component(build_configuration, change)
499
+ add_attributes_to_component(build_configuration, change, change_path)
399
500
  end
400
501
 
401
- def add_build_file(build_phase, change)
502
+ def add_build_file(build_phase, change, change_path)
402
503
  if change["fileRef"].nil?
403
504
  puts "Warning: Trying to add a build file without any file reference to build phase " \
404
505
  "'#{build_phase}'"
@@ -407,7 +508,7 @@ module Kintsugi
407
508
 
408
509
  build_file = build_phase.project.new(Xcodeproj::Project::PBXBuildFile)
409
510
  build_phase.files << build_file
410
- add_attributes_to_component(build_file, change)
511
+ add_attributes_to_component(build_file, change, change_path)
411
512
  end
412
513
 
413
514
  def find_variant_group(project, display_name)
@@ -416,7 +517,7 @@ module Kintsugi
416
517
  end
417
518
  end
418
519
 
419
- def add_target_dependency(target, change)
520
+ def add_target_dependency(target, change, change_path)
420
521
  target_dependency = find_target(target.project, change["displayName"])
421
522
 
422
523
  if target_dependency
@@ -427,14 +528,14 @@ module Kintsugi
427
528
  target_dependency = target.project.new(Xcodeproj::Project::PBXTargetDependency)
428
529
 
429
530
  target.dependencies << target_dependency
430
- add_attributes_to_component(target_dependency, change)
531
+ add_attributes_to_component(target_dependency, change, change_path)
431
532
  end
432
533
 
433
534
  def find_target(project, display_name)
434
535
  project.targets.find { |target| target.display_name == display_name }
435
536
  end
436
537
 
437
- def add_container_item_proxy(component, change)
538
+ def add_container_item_proxy(component, change, change_path)
438
539
  container_proxy = component.project.new(Xcodeproj::Project::PBXContainerItemProxy)
439
540
  container_proxy.container_portal = find_containing_project_uuid(component.project, change)
440
541
 
@@ -447,7 +548,8 @@ module Kintsugi
447
548
  raise MergeError, "Trying to add container item proxy to an unsupported component type " \
448
549
  "#{containing_component.isa}. Change is: #{change}"
449
550
  end
450
- add_attributes_to_component(container_proxy, change, ignore_keys: ["containerPortal"])
551
+ add_attributes_to_component(container_proxy, change, change_path,
552
+ ignore_keys: ["containerPortal"])
451
553
  end
452
554
 
453
555
  def find_containing_project_uuid(project, container_item_proxy_change)
@@ -478,16 +580,22 @@ module Kintsugi
478
580
  container_item_proxies.first.container_portal
479
581
  end
480
582
 
481
- def add_subproject_reference(root_object, project_reference_change)
583
+ def add_subproject_reference(root_object, project_reference_change, change_path)
482
584
  filter_subproject_without_project_references = lambda do |file_reference|
483
585
  root_object.project_references.find do |project_reference|
484
586
  project_reference.project_ref.uuid == file_reference.uuid
485
587
  end.nil?
486
588
  end
487
589
  subproject_reference =
488
- find_file(root_object.project, project_reference_change["ProjectRef"],
590
+ find_file(root_object.project, project_reference_change["ProjectRef"]["path"],
489
591
  file_filter: filter_subproject_without_project_references)
490
592
 
593
+ unless subproject_reference
594
+ raise MergeError, "No file reference was found for project reference with change " \
595
+ "#{project_reference_change}. This might mean that the file used to exist in the " \
596
+ "project the but was removed at some point"
597
+ end
598
+
491
599
  attribute =
492
600
  Xcodeproj::Project::PBXProject.references_by_keys_attributes
493
601
  .find { |attrb| attrb.name == :project_references }
@@ -498,7 +606,7 @@ module Kintsugi
498
606
  updated_project_reference_change =
499
607
  change_with_updated_subproject_uuid(project_reference_change, subproject_reference.uuid)
500
608
  add_attributes_to_component(project_reference, updated_project_reference_change,
501
- ignore_keys: ["ProjectRef"])
609
+ change_path, ignore_keys: ["ProjectRef"])
502
610
  end
503
611
 
504
612
  def change_with_updated_subproject_uuid(change, subproject_reference_uuid)
@@ -510,19 +618,19 @@ module Kintsugi
510
618
  new_change
511
619
  end
512
620
 
513
- def add_target(root_object, change)
621
+ def add_target(root_object, change, change_path)
514
622
  target = root_object.project.new(Xcodeproj::Project::PBXNativeTarget)
515
623
  root_object.project.targets << target
516
- add_attributes_to_component(target, change)
624
+ add_attributes_to_component(target, change, change_path)
517
625
  end
518
626
 
519
- def add_aggregate_target(root_object, change)
627
+ def add_aggregate_target(root_object, change, change_path)
520
628
  target = root_object.project.new(Xcodeproj::Project::PBXAggregateTarget)
521
629
  root_object.project.targets << target
522
- add_attributes_to_component(target, change)
630
+ add_attributes_to_component(target, change, change_path)
523
631
  end
524
632
 
525
- def add_file_reference(containing_component, change)
633
+ def add_file_reference(containing_component, change, change_path)
526
634
  # base configuration reference and product reference always reference a file that exists
527
635
  # inside a group, therefore in these cases the file is searched for.
528
636
  # In the case of group and variant group, the file can't exist in another group, therefore a
@@ -530,25 +638,41 @@ module Kintsugi
530
638
  case containing_component
531
639
  when Xcodeproj::Project::XCBuildConfiguration
532
640
  containing_component.base_configuration_reference =
533
- find_file(containing_component.project, change)
641
+ find_file(containing_component.project, change["path"])
534
642
  when Xcodeproj::Project::PBXNativeTarget
535
- containing_component.product_reference = find_file(containing_component.project, change)
643
+ containing_component.product_reference =
644
+ find_file(containing_component.project, change["path"])
536
645
  when Xcodeproj::Project::PBXBuildFile
537
- containing_component.file_ref = find_file(containing_component.project, change)
646
+ containing_component.file_ref = find_file(containing_component.project, change["path"])
538
647
  when Xcodeproj::Project::PBXGroup, Xcodeproj::Project::PBXVariantGroup
648
+ unless adding_files_and_groups_allowed?(change_path)
649
+ return
650
+ end
651
+
539
652
  file_reference = containing_component.project.new(Xcodeproj::Project::PBXFileReference)
540
653
  containing_component.children << file_reference
541
654
 
542
- # For some reason, `include_in_index` is set to `1` by default.
655
+ # For some reason, `include_in_index` is set to `1` and `source_tree` to `SDKROOT` by
656
+ # default.
543
657
  file_reference.include_in_index = nil
544
- add_attributes_to_component(file_reference, change)
658
+ file_reference.source_tree = nil
659
+ add_attributes_to_component(file_reference, change, change_path)
545
660
  else
546
661
  raise MergeError, "Trying to add file reference to an unsupported component type " \
547
662
  "#{containing_component.isa}. Change is: #{change}"
548
663
  end
549
664
  end
550
665
 
551
- def add_group(containing_component, change)
666
+ def adding_files_and_groups_allowed?(change_path)
667
+ change_path.start_with?("rootObject/mainGroup") ||
668
+ change_path.start_with?("rootObject/projectReferences")
669
+ end
670
+
671
+ def add_group(containing_component, change, change_path)
672
+ unless adding_files_and_groups_allowed?(change_path)
673
+ return
674
+ end
675
+
552
676
  case containing_component
553
677
  when Xcodeproj::Project::ObjectDictionary
554
678
  # It is assumed that an `ObjectDictionary` always represents a project reference.
@@ -562,25 +686,29 @@ module Kintsugi
562
686
  "#{containing_component.isa}. Change is: #{change}"
563
687
  end
564
688
 
565
- add_attributes_to_component(new_group, change)
689
+ add_attributes_to_component(new_group, change, change_path)
566
690
  end
567
691
 
568
- def add_attributes_to_component(component, change, ignore_keys: [])
692
+ def add_attributes_to_component(component, change, change_path, ignore_keys: [])
569
693
  change.each do |change_name, change_value|
570
694
  next if (%w[isa displayName] + ignore_keys).include?(change_name)
571
695
 
572
696
  attribute_name = attribute_name_from_change_name(change_name)
573
697
  if simple_attribute?(component, attribute_name)
574
- apply_change_to_simple_attribute(component, attribute_name, {added: change_value})
698
+ simple_attribute_change = {
699
+ added: change_value,
700
+ removed: simple_attribute_default_value(component, attribute_name)
701
+ }
702
+ apply_change_to_simple_attribute(component, attribute_name, simple_attribute_change)
575
703
  next
576
704
  end
577
705
 
578
706
  case change_value
579
707
  when Hash
580
- add_child_to_component(component, change_value)
708
+ add_child_to_component(component, change_value, change_path)
581
709
  when Array
582
710
  change_value.each do |added_attribute_element|
583
- add_child_to_component(component, added_attribute_element)
711
+ add_child_to_component(component, added_attribute_element, change_path)
584
712
  end
585
713
  else
586
714
  raise MergeError, "Trying to add attribute of unsupported type '#{change_value.class}' " \
@@ -589,16 +717,20 @@ module Kintsugi
589
717
  end
590
718
  end
591
719
 
592
- def find_file(project, file_reference_change, file_filter: ->(_) { true })
720
+ def simple_attribute_default_value(component, attribute_name)
721
+ component.simple_attributes.find do |attribute|
722
+ attribute.name == attribute_name
723
+ end.default_value
724
+ end
725
+
726
+ def find_file(project, path, file_filter: ->(_) { true })
593
727
  file_references = project.files.select do |file_reference|
594
- file_reference.path == file_reference_change["path"] && file_filter.call(file_reference)
728
+ file_reference.path == path && file_filter.call(file_reference)
595
729
  end
596
730
  if file_references.length > 1
597
- puts "Debug: Found more than one matching file with path " \
598
- "'#{file_reference_change["path"]}'. Using the first one."
731
+ puts "Debug: Found more than one matching file with path '#{path}'. Using the first one."
599
732
  elsif file_references.empty?
600
- puts "Debug: No file reference found for file with path " \
601
- "'#{file_reference_change["path"]}'."
733
+ puts "Debug: No file reference found for file with path '#{path}'."
602
734
  return
603
735
  end
604
736