kintsugi 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,10 +4,11 @@
4
4
 
5
5
  require "xcodeproj"
6
6
 
7
- require_relative "utils"
7
+ require_relative "conflict_resolver"
8
8
  require_relative "error"
9
9
  require_relative "settings"
10
10
  require_relative "xcodeproj_extensions"
11
+ require_relative "utils"
11
12
 
12
13
  class Array
13
14
  # Converts an array of arrays of size 2 into a multimap, mapping the first element of each
@@ -33,10 +34,18 @@ module Kintsugi
33
34
  # Xcodeproj::Differ#project_diff where its `key_1` and `key_2` parameters have values of
34
35
  # `:added` and `:removed` respectively.
35
36
  #
37
+ # @param [Xcodeproj::Project] change_source_project
38
+ # Project from which `change` to apply was created. Used for providing information about
39
+ # components that are no longer available in `project`.
40
+
36
41
  # @return [void]
37
- def apply_change_to_project(project, change)
42
+ def apply_change_to_project(project, change, change_source_project)
38
43
  return unless change&.key?("rootObject")
39
44
 
45
+ @change_source_project = change_source_project
46
+ @ignored_components_group_paths = []
47
+ @created_components_group_paths = []
48
+
40
49
  # We iterate over the main group and project references first because they might create file
41
50
  # or project references that are referenced in other parts.
42
51
  unless change["rootObject"]["mainGroup"].nil?
@@ -96,12 +105,24 @@ module Kintsugi
96
105
  entries + group_entries
97
106
  end
98
107
 
99
- def apply_group_additions(project, additions)
108
+ def apply_group_additions(project, additions, force_create_containing_group: false)
100
109
  additions.each do |change, path|
101
110
  next unless %w[PBXGroup PBXVariantGroup].include?(change["isa"])
102
111
 
103
112
  group_type = Module.const_get("Xcodeproj::Project::#{change["isa"]}")
104
- containing_group = path.empty? ? project.main_group : project[path]
113
+ containing_group = project.group_or_file_at_path(path)
114
+
115
+ if containing_group.nil?
116
+ display_name = change["displayName"]
117
+ if !force_create_containing_group &&
118
+ !ConflictResolver.create_nonexistent_group_when_adding_subgroup?(path, display_name)
119
+ @ignored_components_group_paths.append(join_path(path, display_name))
120
+ next
121
+ end
122
+
123
+ @created_components_group_paths.append(join_path(path, display_name))
124
+ containing_group = create_nonexistent_groupable_component(project, path)
125
+ end
105
126
 
106
127
  next if !Settings.allow_duplicates &&
107
128
  !find_group_in_group(containing_group, group_type, change).nil?
@@ -121,7 +142,7 @@ module Kintsugi
121
142
  end
122
143
  end
123
144
 
124
- def apply_file_changes(project, additions, removals)
145
+ def apply_file_changes(project, additions, removals, force_create_containing_group: false)
125
146
  def file_reference_key(change)
126
147
  [change["name"], change["path"], change["sourceTree"]]
127
148
  end
@@ -141,9 +162,21 @@ module Kintsugi
141
162
  end.to_h
142
163
 
143
164
  file_additions.each do |change, path|
144
- containing_group = path.empty? ? project.main_group : project[path]
165
+ containing_group = project.group_or_file_at_path(path)
145
166
  change_key = file_reference_key(change)
146
167
 
168
+ if containing_group.nil?
169
+ if !force_create_containing_group &&
170
+ !ConflictResolver.create_nonexistent_group_when_adding_file?(path,
171
+ change["displayName"])
172
+ @ignored_components_group_paths.append(join_path(path, change["displayName"]))
173
+ next
174
+ end
175
+
176
+ @created_components_group_paths.append(join_path(path, change["displayName"]))
177
+ containing_group = create_nonexistent_groupable_component(project, path)
178
+ end
179
+
147
180
  if (removal_keys_to_references[change_key] || []).empty?
148
181
  apply_file_addition(containing_group, change, "rootObject/mainGroup/#{path}")
149
182
  elsif addition_keys_to_paths[change_key].length == 1 &&
@@ -182,9 +215,17 @@ module Kintsugi
182
215
 
183
216
  def apply_group_and_file_diffs(project, diffs)
184
217
  diffs.each do |change, path|
185
- component = project[path]
218
+ component = project.group_or_file_at_path(path)
186
219
 
187
- next if component.nil?
220
+ if component.nil? && change&.keys != ["children"]
221
+ unless ConflictResolver.create_nonexistent_component_when_changing_it?(path)
222
+ @ignored_components_group_paths.append(path)
223
+ next
224
+ end
225
+
226
+ @created_components_group_paths.append(path)
227
+ component = create_nonexistent_groupable_component(project, path)
228
+ end
188
229
 
189
230
  change.each do |subchange_name, subchange|
190
231
  next if subchange_name == "children"
@@ -194,6 +235,25 @@ module Kintsugi
194
235
  end
195
236
  end
196
237
 
238
+ def create_nonexistent_groupable_component(project, path)
239
+ source_project_component = @change_source_project.group_or_file_at_path(path)
240
+ component_change = source_project_component.to_tree_hash
241
+ containing_group_path = parent_group_path(path)
242
+
243
+ case source_project_component
244
+ when Xcodeproj::Project::PBXFileReference
245
+ apply_file_changes(project, [[component_change, containing_group_path]], [],
246
+ force_create_containing_group: true)
247
+ when Xcodeproj::Project::PBXGroup
248
+ apply_group_additions(project, [[component_change, containing_group_path]],
249
+ force_create_containing_group: true)
250
+ else
251
+ raise MergeError, "Component should either be a group or a file reference. " \
252
+ "Instead got: #{source_project_component}"
253
+ end
254
+ project.group_or_file_at_path(path)
255
+ end
256
+
197
257
  def apply_group_removals(project, removals)
198
258
  removals.sort_by(&:last).reverse.each do |change, path|
199
259
  next unless %w[PBXGroup PBXVariantGroup].include?(change["isa"])
@@ -224,7 +284,7 @@ module Kintsugi
224
284
  component = replace_component_with_new_type(parent_component, attribute_name, change)
225
285
  change = change_for_component_of_new_type(component, change)
226
286
  else
227
- component = child_component(parent_component, change, change_name)
287
+ component = child_component(parent_component, change_name)
228
288
  end
229
289
 
230
290
  if change[:removed].is_a?(Hash)
@@ -232,8 +292,7 @@ module Kintsugi
232
292
  elsif change[:removed].is_a?(Array)
233
293
  unless component.nil?
234
294
  (change[:removed]).each do |removed_change|
235
- child = child_component_of_object_list(component, removed_change,
236
- removed_change["displayName"])
295
+ child = child_component_of_object_list(component, removed_change["displayName"])
237
296
  remove_component(child, removed_change)
238
297
  end
239
298
  end
@@ -243,6 +302,7 @@ module Kintsugi
243
302
 
244
303
  if change[:added].is_a?(Hash)
245
304
  add_child_to_component(parent_component, change[:added], change_path)
305
+ component = child_component(parent_component, change_name)
246
306
  elsif change[:added].is_a?(Array)
247
307
  (change[:added]).each do |added_change|
248
308
  add_child_to_component(parent_component, added_change, change_path)
@@ -253,15 +313,58 @@ module Kintsugi
253
313
 
254
314
  subchanges_of_change(change).each do |subchange_name, subchange|
255
315
  if component.nil?
256
- raise MergeError, "Trying to apply changes to a component that doesn't exist at path " \
257
- "#{change_path}. It was probably removed in a previous commit. This is considered a " \
258
- "conflict that should be resolved manually."
316
+ component = resolve_nonexistent_component(parent_component, change_path)
317
+ break if component.nil?
259
318
  end
260
319
 
261
320
  apply_change_to_component(component, subchange_name, subchange, change_path)
262
321
  end
263
322
  end
264
323
 
324
+ def resolve_nonexistent_component(parent_component, change_path)
325
+ source_project_component = component_at_path(@change_source_project, change_path)
326
+ group_path = group_path_of_group_based_component(source_project_component)
327
+
328
+ if group_path
329
+ should_create_component = @created_components_group_paths.include?(group_path) ||
330
+ (!@ignored_components_group_paths.include?(group_path) &&
331
+ !ConflictResolver.create_nonexistent_component_when_changing_it?(change_path))
332
+ return unless should_create_component
333
+ elsif !ConflictResolver.create_nonexistent_component_when_changing_it?(change_path)
334
+ return
335
+ end
336
+
337
+ non_object_list_parent =
338
+ if parent_component.is_a?(Xcodeproj::Project::ObjectList)
339
+ parent_component.owner
340
+ else
341
+ parent_component
342
+ end
343
+ add_child_to_component(non_object_list_parent, source_project_component.to_tree_hash,
344
+ change_path)
345
+ component_at_path(non_object_list_parent.project, change_path)
346
+ end
347
+
348
+ def group_path_of_group_based_component(component)
349
+ if component.is_a?(Xcodeproj::Project::PBXBuildFile) && !component.file_ref.nil?
350
+ component.file_ref.hierarchy_path.delete_prefix("/")
351
+ elsif component.is_a?(Xcodeproj::Project::PBXFileReference) ||
352
+ component.is_a?(Xcodeproj::Project::PBXGroup)
353
+ component.hierarchy_path.delete_prefix("/")
354
+ end
355
+ end
356
+
357
+ def component_at_path(project, path)
358
+ current_component = project
359
+ until path["/"].nil?
360
+ change_name = path.split("/")[0]
361
+ current_component = child_component(current_component, change_name)
362
+ path = path.delete_prefix("#{change_name}/")
363
+ end
364
+
365
+ child_component(current_component, path)
366
+ end
367
+
265
368
  def subchanges_of_change(change)
266
369
  if change.key?(:diff)
267
370
  change[:diff]
@@ -329,21 +432,17 @@ module Kintsugi
329
432
  end
330
433
  end
331
434
 
332
- def child_component(component, change, change_name)
435
+ def child_component(component, change_name)
333
436
  if component.is_a?(Xcodeproj::Project::ObjectList)
334
- child_component_of_object_list(component, change, change_name)
437
+ child_component_of_object_list(component, change_name)
335
438
  else
336
439
  attribute_name = attribute_name_from_change_name(change_name)
337
440
  component.send(attribute_name)
338
441
  end
339
442
  end
340
443
 
341
- def child_component_of_object_list(component, change, change_name)
342
- if change["isa"] == "PBXReferenceProxy"
343
- find_reference_proxy_in_component(component, change["remoteRef"])
344
- else
345
- component.find { |child| child.display_name == change_name }
346
- end
444
+ def child_component_of_object_list(component, change_name)
445
+ component.find { |child| child.display_name == change_name }
347
446
  end
348
447
 
349
448
  def simple_attribute?(component, attribute_name)
@@ -354,18 +453,20 @@ module Kintsugi
354
453
 
355
454
  def apply_change_to_simple_attribute(component, attribute_name, change)
356
455
  new_attribute_value =
357
- simple_attribute_value_with_change(component.send(attribute_name), change)
456
+ simple_attribute_value_with_change(component.send(attribute_name), change, attribute_name)
358
457
  component.send("#{attribute_name}=", new_attribute_value)
359
458
  end
360
459
 
361
- def simple_attribute_value_with_change(old_value, change)
460
+ def simple_attribute_value_with_change(old_value, change, attribute_name)
362
461
  type = simple_attribute_type(old_value, change[:removed], change[:added])
363
- new_value = new_simple_attribute_value(type, old_value, change[:removed], change[:added])
462
+ new_value = new_simple_attribute_value(type, old_value, change[:removed], change[:added],
463
+ attribute_name)
364
464
 
365
465
  subchanges_of_change(change).each do |subchange_name, subchange_value|
366
466
  new_value = new_value || old_value || {}
367
467
  new_value[subchange_name] =
368
- simple_attribute_value_with_change(old_value[subchange_name], subchange_value)
468
+ simple_attribute_value_with_change(old_value[subchange_name], subchange_value,
469
+ subchange_name)
369
470
  end
370
471
 
371
472
  new_value
@@ -377,71 +478,87 @@ module Kintsugi
377
478
  if types.include?(Hash)
378
479
  unless types.to_set.subset?([Hash, NilClass].to_set)
379
480
  raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
380
- "value: '#{old_value}', removed change: '#{removed_change}', added change: " \
381
- "'#{added_change}'"
481
+ "value: '#{old_value}', removed change: '#{removed_change}', added " \
482
+ "change: '#{added_change}'"
382
483
  end
383
484
  Hash
384
485
  elsif types.include?(Array)
385
486
  unless types.to_set.subset?([Array, String, NilClass].to_set)
386
487
  raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
387
- "value: '#{old_value}', removed change: '#{removed_change}', added change: " \
388
- "'#{added_change}'"
488
+ "value: '#{old_value}', removed change: '#{removed_change}', added " \
489
+ "change: '#{added_change}'"
389
490
  end
390
491
  Array
391
492
  elsif types.include?(String)
392
493
  unless types.to_set.subset?([String, NilClass].to_set)
393
494
  raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
394
- "value: '#{old_value}', removed change: '#{removed_change}', added change: " \
395
- "'#{added_change}'"
495
+ "value: '#{old_value}', removed change: '#{removed_change}', added " \
496
+ "change: '#{added_change}'"
396
497
  end
397
498
  String
398
499
  else
399
500
  raise MergeError, "Unsupported types of all of the values. Existing value: " \
400
- "'#{old_value}', removed change: '#{removed_change}', added change: '#{added_change}'"
501
+ "'#{old_value}', removed change: '#{removed_change}', added change: " \
502
+ "'#{added_change}'"
401
503
  end
402
504
  end
403
505
 
404
- def new_simple_attribute_value(type, old_value, removed_change, added_change)
506
+ def new_simple_attribute_value(type, old_value, removed_change, added_change, attribute_name)
405
507
  if type == Hash
406
- new_hash_simple_attribute_value(old_value, removed_change, added_change)
508
+ new_hash_simple_attribute_value(old_value, removed_change, added_change, attribute_name)
407
509
  elsif type == Array
408
- new_array_simple_attribute_value(old_value, removed_change, added_change)
510
+ new_array_simple_attribute_value(old_value, removed_change, added_change, attribute_name)
409
511
  elsif type == String
410
- new_string_simple_attribute_value(old_value, removed_change, added_change)
512
+ new_string_simple_attribute_value(old_value, removed_change, added_change, attribute_name)
411
513
  else
412
514
  raise MergeError, "Unsupported types of all of the values. Existing value: " \
413
- "'#{old_value}', removed change: '#{removed_change}', added change: '#{added_change}'"
515
+ "'#{old_value}', removed change: '#{removed_change}', added change: " \
516
+ "'#{added_change}'"
414
517
  end
415
518
  end
416
519
 
417
- def new_hash_simple_attribute_value(old_value, removed_change, added_change)
520
+ def new_hash_simple_attribute_value(old_value, removed_change, added_change, attribute_name)
418
521
  return added_change if ((old_value || {}).to_a - (removed_change || {}).to_a).empty?
419
522
 
420
523
  # First apply the added change to see if there are any conflicts with it.
421
524
  new_value = (old_value || {}).merge(added_change || {})
525
+ conflicting_added_hash_values = (old_value.to_a - new_value.to_a)
422
526
 
423
- unless (old_value.to_a - new_value.to_a).empty?
424
- raise MergeError, "New hash #{change} contains values that conflict with old hash " \
425
- "#{old_value}"
527
+ unless conflicting_added_hash_values.empty?
528
+ override_values = ConflictResolver.override_values_when_keys_already_exist_in_hash?(
529
+ attribute_name, old_value, added_change
530
+ )
531
+ new_value = override_values ? old_value.merge(added_change) : added_change.merge(old_value)
426
532
  end
427
533
 
428
534
  if removed_change.nil?
429
535
  return new_value
430
536
  end
431
537
 
538
+ conflicting_removal_hash_values = new_value.select do |key, value|
539
+ value != removed_change[key] && value != (added_change || {})[key]
540
+ end
541
+
542
+ unless conflicting_removal_hash_values.empty?
543
+ expected_values =
544
+ removed_change.select { |key, value| conflicting_removal_hash_values.key?(key) }
545
+ should_remove_conflicting_values =
546
+ ConflictResolver.remove_entries_when_unexpected_values_in_hash?(
547
+ attribute_name, expected_values, conflicting_removal_hash_values
548
+ )
549
+ end
550
+
432
551
  new_value
433
552
  .reject do |key, value|
434
- if value != removed_change[key] && value != (added_change || {})[key]
435
- raise MergeError, "Trying to remove value '#{removed_change[key]}' of hash with key " \
436
- "'#{key}' but it changed to #{value}. This is considered a conflict that should be " \
437
- "resolved manually."
553
+ if conflicting_removal_hash_values.key?(key)
554
+ next should_remove_conflicting_values
438
555
  end
439
556
 
440
557
  removed_change.key?(key)
441
558
  end
442
559
  end
443
560
 
444
- def new_array_simple_attribute_value(old_value, removed_change, added_change)
561
+ def new_array_simple_attribute_value(old_value, removed_change, added_change, attribute_name)
445
562
  if old_value.is_a?(String)
446
563
  old_value = [old_value]
447
564
  end
@@ -464,10 +581,12 @@ module Kintsugi
464
581
  new_value + filtered_added_change
465
582
  end
466
583
 
467
- def new_string_simple_attribute_value(old_value, removed_change, added_change)
584
+ def new_string_simple_attribute_value(old_value, removed_change, added_change, attribute_name)
468
585
  if old_value != removed_change && !old_value.nil? && added_change != old_value
469
- raise MergeError, "Trying to remove value '#{removed_change || "nil"}', but the existing " \
470
- "value is '#{old_value}'. This is considered a conflict that should be resolved manually."
586
+ use_added_change = ConflictResolver.set_value_to_string_when_unxpected_value?(
587
+ attribute_name, added_change, removed_change, old_value
588
+ )
589
+ return use_added_change ? added_change : old_value
471
590
  end
472
591
 
473
592
  added_change
@@ -476,26 +595,25 @@ module Kintsugi
476
595
  def remove_component(component, change)
477
596
  return if component.nil?
478
597
 
479
- if component.to_tree_hash != change
480
- raise MergeError, "Trying to remove an object that changed since then. This is " \
481
- "considered a conflict that should be resolved manually. Name of the object is: " \
482
- "'#{component.display_name}'. Existing component: #{component.to_tree_hash}. " \
483
- "Change: #{change}"
598
+ if component.to_tree_hash != change &&
599
+ !ConflictResolver.remove_component_when_unexpected_hash?(component, change)
600
+ return
484
601
  end
485
602
 
486
- if change["isa"] == "PBXFileReference"
487
- remove_build_files_of_file_reference(component, change)
603
+ if change["isa"] == "PBXFileReference" || change["isa"] == "PBXReferenceProxy" ||
604
+ change["isa"] == "PBXGroup" || change["isa"] == "PBXVariantGroup"
605
+ remove_build_files_of_file_reference(component)
488
606
  end
489
607
 
490
608
  component.remove_from_project
491
609
  end
492
610
 
493
- def remove_build_files_of_file_reference(file_reference, change)
611
+ def remove_build_files_of_file_reference(file_reference)
494
612
  # Since the build file's display name depends on the file reference, removing the file
495
613
  # reference before removing it will change the build file's display name which will not be
496
614
  # detected when trying to remove the build file. Therefore, the build files that depend on
497
615
  # the file reference are removed prior to removing the file reference.
498
- file_reference.build_files.each do |build_file|
616
+ file_reference.referrers.grep(Xcodeproj::Project::PBXBuildFile).each do |build_file|
499
617
  build_file.referrers.each do |referrer|
500
618
  referrer.remove_build_file(build_file)
501
619
  end
@@ -553,7 +671,7 @@ module Kintsugi
553
671
  add_remote_swift_package_reference(component, change, change_path)
554
672
  else
555
673
  raise MergeError, "Trying to add unsupported component type #{change["isa"]}. Full " \
556
- "component change is: #{change}"
674
+ "component change is: #{change}"
557
675
  end
558
676
  end
559
677
 
@@ -569,7 +687,7 @@ module Kintsugi
569
687
  containing_component.package_references << remote_swift_package_reference
570
688
  else
571
689
  raise MergeError, "Trying to add remote swift package reference to an unsupported " \
572
- "component type #{containing_component.isa}. Change is: #{change}"
690
+ "component type #{containing_component.isa}. Change is: #{change}"
573
691
  end
574
692
  end
575
693
 
@@ -585,7 +703,7 @@ module Kintsugi
585
703
  containing_component.package_product_dependencies << swift_package_product_dependency
586
704
  else
587
705
  raise MergeError, "Trying to add swift package product dependency to an unsupported " \
588
- "component type #{containing_component.isa}. Change is: #{change}"
706
+ "component type #{containing_component.isa}. Change is: #{change}"
589
707
  end
590
708
  end
591
709
 
@@ -602,10 +720,10 @@ module Kintsugi
602
720
  end.nil?
603
721
  end
604
722
  file_reference =
605
- find_reference_proxy(containing_component.project, change["remoteRef"],
723
+ find_reference_proxy(containing_component.project, change,
606
724
  reference_filter: filter_references_without_build_files)
607
725
  if file_reference.nil?
608
- file_reference = find_reference_proxy(containing_component.project, change["remoteRef"])
726
+ file_reference = find_reference_proxy(containing_component.project, change)
609
727
  end
610
728
  containing_component.file_ref = file_reference
611
729
  when Xcodeproj::Project::PBXGroup
@@ -618,7 +736,7 @@ module Kintsugi
618
736
  add_attributes_to_component(reference_proxy, change, change_path)
619
737
  else
620
738
  raise MergeError, "Trying to add reference proxy to an unsupported component type " \
621
- "#{containing_component.isa}. Change is: #{change}"
739
+ "#{containing_component.isa}. Change is: #{change}"
622
740
  end
623
741
  end
624
742
 
@@ -627,15 +745,11 @@ module Kintsugi
627
745
  when Xcodeproj::Project::PBXBuildFile
628
746
  containing_component.file_ref =
629
747
  find_variant_group(containing_component.project, change["displayName"])
630
- when Xcodeproj::Project::PBXGroup, Xcodeproj::Project::PBXVariantGroup
631
- if find_group_in_group(containing_component, Xcodeproj::Project::PBXVariantGroup,
632
- change).nil?
633
- raise "Group should have been added already, so this is most likely a bug in Kintsugi" \
634
- "Change is: #{change}. Change path: #{change_path}"
635
- end
748
+ when Xcodeproj::Project::PBXGroup
749
+ # Adding variant groups to groups is handled by another part of the code.
636
750
  else
637
751
  raise MergeError, "Trying to add variant group to an unsupported component type " \
638
- "#{containing_component.isa}. Change is: #{change}"
752
+ "#{containing_component.isa}. Change is: #{change}"
639
753
  end
640
754
  end
641
755
 
@@ -687,7 +801,7 @@ module Kintsugi
687
801
  def add_build_file(build_phase, change, change_path)
688
802
  if change["fileRef"].nil?
689
803
  puts "Warning: Trying to add a build file without any file reference to build phase " \
690
- "'#{build_phase}'"
804
+ "'#{build_phase}'"
691
805
  return
692
806
  end
693
807
 
@@ -736,7 +850,7 @@ module Kintsugi
736
850
  component.remote_ref = container_proxy
737
851
  else
738
852
  raise MergeError, "Trying to add container item proxy to an unsupported component type " \
739
- "#{containing_component.isa}. Change is: #{change}"
853
+ "#{containing_component.isa}. Change is: #{change}"
740
854
  end
741
855
  add_attributes_to_component(container_proxy, change, change_path,
742
856
  ignore_keys: ["containerPortal"])
@@ -760,10 +874,10 @@ module Kintsugi
760
874
 
761
875
  if container_item_proxies.length > 1
762
876
  puts "Debug: Found more than one potential dependency with name " \
763
- "'#{container_item_proxy_change["remoteInfo"]}'. Using the first one."
877
+ "'#{container_item_proxy_change["remoteInfo"]}'. Using the first one."
764
878
  elsif container_item_proxies.empty?
765
879
  puts "Warning: No container portal was found for dependency with name " \
766
- "'#{container_item_proxy_change["remoteInfo"]}'."
880
+ "'#{container_item_proxy_change["remoteInfo"]}'."
767
881
  return
768
882
  end
769
883
 
@@ -788,8 +902,8 @@ module Kintsugi
788
902
 
789
903
  unless subproject_reference
790
904
  raise MergeError, "No file reference was found for project reference with change " \
791
- "#{project_reference_change}. This might mean that the file used to exist in the " \
792
- "project the but was removed at some point"
905
+ "#{project_reference_change}. This might mean that the file used to " \
906
+ "exist in the project the but was removed at some point"
793
907
  end
794
908
 
795
909
  attribute =
@@ -829,8 +943,6 @@ module Kintsugi
829
943
  def add_file_reference(containing_component, change, change_path)
830
944
  # base configuration reference and product reference always reference a file that exists
831
945
  # inside a group, therefore in these cases the file is searched for.
832
- # In the case of group and variant group, the file can't exist in another group, therefore a
833
- # new file reference is always created.
834
946
  case containing_component
835
947
  when Xcodeproj::Project::XCBuildConfiguration
836
948
  containing_component.base_configuration_reference =
@@ -840,15 +952,11 @@ module Kintsugi
840
952
  find_file(containing_component.project, change["path"])
841
953
  when Xcodeproj::Project::PBXBuildFile
842
954
  containing_component.file_ref = find_file(containing_component.project, change["path"])
843
- when Xcodeproj::Project::PBXGroup, Xcodeproj::Project::PBXVariantGroup
844
- if find_file_in_group(containing_component, Xcodeproj::Project::PBXFileReference,
845
- change["path"]).nil?
846
- raise "File should have been added already, so this is most likely a bug in Kintsugi" \
847
- "Change is: #{change}. Change path: #{change_path}"
848
- end
955
+ when Xcodeproj::Project::PBXGroup
956
+ # Adding files to groups is handled by another part of the code.
849
957
  else
850
958
  raise MergeError, "Trying to add file reference to an unsupported component type " \
851
- "#{containing_component.isa}. Change is: #{change}"
959
+ "#{containing_component.isa}. Change is: #{change}"
852
960
  end
853
961
  end
854
962
 
@@ -859,11 +967,6 @@ module Kintsugi
859
967
  .find { |file| file.path == filepath }
860
968
  end
861
969
 
862
- def adding_files_and_groups_allowed?(change_path)
863
- change_path.start_with?("rootObject/mainGroup") ||
864
- change_path.start_with?("rootObject/projectReferences")
865
- end
866
-
867
970
  def add_group(containing_component, change, change_path)
868
971
  case containing_component
869
972
  when Xcodeproj::Project::ObjectDictionary
@@ -872,13 +975,11 @@ module Kintsugi
872
975
  containing_component[:product_group] = new_group
873
976
  add_attributes_to_component(new_group, change, change_path)
874
977
  when Xcodeproj::Project::PBXGroup
875
- if find_group_in_group(containing_component, Xcodeproj::Project::PBXGroup, change).nil?
876
- raise "Group should have been added already, so this is most likely a bug in Kintsugi" \
877
- "Change is: #{change}. Change path: #{change_path}"
878
- end
978
+ # Adding groups to groups is handled by another part of the code.
879
979
  else
880
980
  raise MergeError, "Trying to add group to an unsupported component type " \
881
- "#{containing_component.isa}. Change is: #{change}. Change path: #{change_path}"
981
+ "#{containing_component.isa}. Change is: #{change}. Change path: " \
982
+ "#{change_path}"
882
983
  end
883
984
  end
884
985
 
@@ -905,7 +1006,7 @@ module Kintsugi
905
1006
  end
906
1007
  else
907
1008
  raise MergeError, "Trying to add attribute of unsupported type '#{change_value.class}' " \
908
- "to object #{component}. Attribute name is '#{change_name}'"
1009
+ "to object #{component}. Attribute name is '#{change_name}'"
909
1010
  end
910
1011
  end
911
1012
  end
@@ -923,44 +1024,37 @@ module Kintsugi
923
1024
  if file_references.length > 1
924
1025
  puts "Debug: Found more than one matching file with path '#{path}'. Using the first one."
925
1026
  elsif file_references.empty?
926
- puts "Debug: No file reference found for file with path '#{path}'."
927
1027
  return
928
1028
  end
929
1029
 
930
1030
  file_references.first
931
1031
  end
932
1032
 
933
- def find_reference_proxy(project, container_item_proxy_change, reference_filter: ->(_) { true })
1033
+ def find_reference_proxy(project, change, reference_filter: ->(_) { true })
934
1034
  reference_proxies = project.root_object.project_references.map do |project_ref_and_products|
935
- find_reference_proxy_in_component(project_ref_and_products[:product_group].children,
936
- container_item_proxy_change,
937
- reference_filter: reference_filter)
1035
+ project_ref_and_products[:product_group].children.find do |reference_proxy|
1036
+ reference_proxy.display_name == change["displayName"] &&
1037
+ reference_filter.call(reference_proxy)
1038
+ end
938
1039
  end.compact
939
1040
 
940
1041
  if reference_proxies.length > 1
941
1042
  puts "Debug: Found more than one matching reference proxy with name " \
942
- "'#{container_item_proxy_change["remoteInfo"]}'. Using the first one."
1043
+ "'#{change["remoteInfo"]}'. Using the first one."
943
1044
  elsif reference_proxies.empty?
944
- puts "Warning: No reference proxy was found for name " \
945
- "'#{container_item_proxy_change["remoteInfo"]}'."
1045
+ puts "Warning: No reference proxy was found for name '#{change["remoteInfo"]}'."
946
1046
  return
947
1047
  end
948
1048
 
949
1049
  reference_proxies.first
950
1050
  end
951
1051
 
952
- def find_reference_proxy_in_component(component, container_item_proxy_change,
953
- reference_filter: ->(_) { true })
954
- component.find do |product|
955
- product.remote_ref.remote_global_id_string ==
956
- container_item_proxy_change["remoteGlobalIDString"] &&
957
- product.remote_ref.remote_info == container_item_proxy_change["remoteInfo"] &&
958
- reference_filter.call(product)
959
- end
960
- end
961
-
962
1052
  def join_path(left, right)
963
1053
  left.empty? ? right : "#{left}/#{right}"
964
1054
  end
1055
+
1056
+ def parent_group_path(group_path)
1057
+ group_path[/(.*)\//, 1] || ""
1058
+ end
965
1059
  end
966
1060
  end