kintsugi 0.4.3 → 0.5.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/Gemfile +5 -0
- data/bin/kintsugi +2 -0
- data/lib/kintsugi/apply_change_to_project.rb +129 -47
- data/lib/kintsugi/version.rb +1 -1
- data/lib/kintsugi/xcodeproj_extensions.rb +38 -0
- data/spec/kintsugi_apply_change_to_project_spec.rb +493 -50
- data/spec/spec_helper.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46711f40795142678185287c0035cb3ad3ebe304b418cf8add570c0e4f6db997
|
4
|
+
data.tar.gz: b2c6fd04f472dca7ca3ca436140b0256264a221228a8ae2c086ff3cef9ac9dc4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b9a65bbd0796ce8e2f0da7fb50f772e753d03875fd79f5cc0816cd3197e049bc656cc14c2f521ab6c1a63b77b34290137d1b103dc5b51bc3e5b5daadac20b7f
|
7
|
+
data.tar.gz: 143a7a9d17799dde48c845570084ec94b7ea00ee482cd6f50773055c71efdca5e8e82360130d43f64989a5a4c5d945b7b938d2458e91b3d745c6e4700932863f
|
data/Gemfile
CHANGED
data/bin/kintsugi
CHANGED
@@ -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
|
@@ -92,7 +93,7 @@ module Kintsugi
|
|
92
93
|
end
|
93
94
|
|
94
95
|
def attribute_name_from_change_name(change_name)
|
95
|
-
if change_name
|
96
|
+
if %w[fileEncoding repositoryURL].include?(change_name)
|
96
97
|
change_name.to_sym
|
97
98
|
else
|
98
99
|
Xcodeproj::Project::Object::CaseConverter.convert_to_ruby(change_name)
|
@@ -164,15 +165,8 @@ module Kintsugi
|
|
164
165
|
end
|
165
166
|
|
166
167
|
def simple_attribute_value_with_change(old_value, change)
|
167
|
-
|
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
|
168
|
+
type = simple_attribute_type(old_value, change[:removed], change[:added])
|
169
|
+
new_value = new_simple_attribute_value(type, old_value, change[:removed], change[:added])
|
176
170
|
|
177
171
|
subchanges_of_change(change).each do |subchange_name, subchange_value|
|
178
172
|
new_value = new_value || old_value || {}
|
@@ -183,13 +177,67 @@ module Kintsugi
|
|
183
177
|
new_value
|
184
178
|
end
|
185
179
|
|
186
|
-
def
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
180
|
+
def simple_attribute_type(old_value, removed_change, added_change)
|
181
|
+
types = [old_value.class, removed_change.class, added_change.class]
|
182
|
+
|
183
|
+
if types.include?(Hash)
|
184
|
+
unless types.to_set.subset?([Hash, NilClass].to_set)
|
185
|
+
raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
|
186
|
+
"value: '#{old_value}', removed change: '#{removed_change}', added change: " \
|
187
|
+
"'#{added_change}'"
|
188
|
+
end
|
189
|
+
Hash
|
190
|
+
elsif types.include?(Array)
|
191
|
+
unless types.to_set.subset?([Array, String, NilClass].to_set)
|
192
|
+
raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
|
193
|
+
"value: '#{old_value}', removed change: '#{removed_change}', added change: " \
|
194
|
+
"'#{added_change}'"
|
195
|
+
end
|
196
|
+
Array
|
197
|
+
elsif types.include?(String)
|
198
|
+
unless types.to_set.subset?([String, NilClass].to_set)
|
199
|
+
raise MergeError, "Cannot apply changes because the types are not compatible. Existing " \
|
200
|
+
"value: '#{old_value}', removed change: '#{removed_change}', added change: " \
|
201
|
+
"'#{added_change}'"
|
202
|
+
end
|
203
|
+
String
|
204
|
+
else
|
205
|
+
raise MergeError, "Unsupported types of all of the values. Existing value: " \
|
206
|
+
"'#{old_value}', removed change: '#{removed_change}', added change: '#{added_change}'"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def new_simple_attribute_value(type, old_value, removed_change, added_change)
|
211
|
+
if type == Hash
|
212
|
+
new_hash_simple_attribute_value(old_value, removed_change, added_change)
|
213
|
+
elsif type == Array
|
214
|
+
new_array_simple_attribute_value(old_value, removed_change, added_change)
|
215
|
+
elsif type == String
|
216
|
+
new_string_simple_attribute_value(old_value, removed_change, added_change)
|
217
|
+
else
|
218
|
+
raise MergeError, "Unsupported types of all of the values. Existing value: " \
|
219
|
+
"'#{old_value}', removed change: '#{removed_change}', added change: '#{added_change}'"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def new_hash_simple_attribute_value(old_value, removed_change, added_change)
|
224
|
+
return added_change if ((old_value || {}).to_a - (removed_change || {}).to_a).empty?
|
225
|
+
|
226
|
+
# First apply the added change to see if there are any conflicts with it.
|
227
|
+
new_value = (old_value || {}).merge(added_change || {})
|
228
|
+
|
229
|
+
unless (old_value.to_a - new_value.to_a).empty?
|
230
|
+
raise MergeError, "New hash #{change} contains values that conflict with old hash " \
|
231
|
+
"#{old_value}"
|
232
|
+
end
|
233
|
+
|
234
|
+
if removed_change.nil?
|
235
|
+
return new_value
|
236
|
+
end
|
237
|
+
|
238
|
+
new_value
|
239
|
+
.reject do |key, value|
|
240
|
+
if value != removed_change[key] && value != (added_change || {})[key]
|
193
241
|
raise MergeError, "Trying to remove value '#{removed_change[key]}' of hash with key " \
|
194
242
|
"'#{key}' but it changed to #{value}. This is considered a conflict that should be " \
|
195
243
|
"resolved manually."
|
@@ -197,41 +245,31 @@ module Kintsugi
|
|
197
245
|
|
198
246
|
removed_change.key?(key)
|
199
247
|
end
|
200
|
-
|
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
|
248
|
+
end
|
205
249
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
250
|
+
def new_array_simple_attribute_value(old_value, removed_change, added_change)
|
251
|
+
if old_value.is_a?(String)
|
252
|
+
old_value = [old_value]
|
253
|
+
end
|
254
|
+
if removed_change.is_a?(String)
|
255
|
+
removed_change = [removed_change]
|
256
|
+
end
|
257
|
+
if added_change.is_a?(String)
|
258
|
+
added_change = [added_change]
|
211
259
|
end
|
212
|
-
end
|
213
260
|
|
214
|
-
|
215
|
-
case change
|
216
|
-
when Array
|
217
|
-
(old_value || []) + change
|
218
|
-
when Hash
|
219
|
-
old_value ||= {}
|
220
|
-
new_value = old_value.merge(change)
|
261
|
+
return added_change if ((old_value || []) - (removed_change || [])).empty?
|
221
262
|
|
222
|
-
|
223
|
-
|
224
|
-
"#{old_value}"
|
225
|
-
end
|
263
|
+
(old_value || []) + (added_change || []) - (removed_change || [])
|
264
|
+
end
|
226
265
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
nil
|
232
|
-
else
|
233
|
-
raise MergeError, "Unsupported change #{change} of type #{change.class}"
|
266
|
+
def new_string_simple_attribute_value(old_value, removed_change, added_change)
|
267
|
+
if old_value != removed_change && !old_value.nil? && added_change != old_value
|
268
|
+
raise MergeError, "Trying to remove value '#{removed_change || "nil"}', but the existing " \
|
269
|
+
"value is '#{old_value}'. This is considered a conflict that should be resolved manually."
|
234
270
|
end
|
271
|
+
|
272
|
+
added_change
|
235
273
|
end
|
236
274
|
|
237
275
|
def remove_component(component, change)
|
@@ -303,12 +341,48 @@ module Kintsugi
|
|
303
341
|
add_variant_group(component, change)
|
304
342
|
when "PBXReferenceProxy"
|
305
343
|
add_reference_proxy(component, change)
|
344
|
+
when "XCSwiftPackageProductDependency"
|
345
|
+
add_swift_package_product_dependency(component, change)
|
346
|
+
when "XCRemoteSwiftPackageReference"
|
347
|
+
add_remote_swift_package_reference(component, change)
|
306
348
|
else
|
307
349
|
raise MergeError, "Trying to add unsupported component type #{change["isa"]}. Full " \
|
308
350
|
"component change is: #{change}"
|
309
351
|
end
|
310
352
|
end
|
311
353
|
|
354
|
+
def add_remote_swift_package_reference(containing_component, change)
|
355
|
+
remote_swift_package_reference =
|
356
|
+
containing_component.project.new(Xcodeproj::Project::XCRemoteSwiftPackageReference)
|
357
|
+
add_attributes_to_component(remote_swift_package_reference, change)
|
358
|
+
|
359
|
+
case containing_component
|
360
|
+
when Xcodeproj::Project::XCSwiftPackageProductDependency
|
361
|
+
containing_component.package = remote_swift_package_reference
|
362
|
+
when Xcodeproj::Project::PBXProject
|
363
|
+
containing_component.package_references << remote_swift_package_reference
|
364
|
+
else
|
365
|
+
raise MergeError, "Trying to add remote swift package reference to an unsupported " \
|
366
|
+
"component type #{containing_component.isa}. Change is: #{change}"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def add_swift_package_product_dependency(containing_component, change)
|
371
|
+
swift_package_product_dependency =
|
372
|
+
containing_component.project.new(Xcodeproj::Project::XCSwiftPackageProductDependency)
|
373
|
+
add_attributes_to_component(swift_package_product_dependency, change)
|
374
|
+
|
375
|
+
case containing_component
|
376
|
+
when Xcodeproj::Project::PBXBuildFile
|
377
|
+
containing_component.product_ref = swift_package_product_dependency
|
378
|
+
when Xcodeproj::Project::PBXNativeTarget
|
379
|
+
containing_component.package_product_dependencies << swift_package_product_dependency
|
380
|
+
else
|
381
|
+
raise MergeError, "Trying to add swift package product dependency to an unsupported " \
|
382
|
+
"component type #{containing_component.isa}. Change is: #{change}"
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
312
386
|
def add_reference_proxy(containing_component, change)
|
313
387
|
case containing_component
|
314
388
|
when Xcodeproj::Project::PBXBuildFile
|
@@ -488,6 +562,12 @@ module Kintsugi
|
|
488
562
|
find_file(root_object.project, project_reference_change["ProjectRef"],
|
489
563
|
file_filter: filter_subproject_without_project_references)
|
490
564
|
|
565
|
+
unless subproject_reference
|
566
|
+
raise MergeError, "No file reference was found for project reference with change " \
|
567
|
+
"#{project_reference_change}. This might mean that the file used to exist in the " \
|
568
|
+
"project the but was removed at some point"
|
569
|
+
end
|
570
|
+
|
491
571
|
attribute =
|
492
572
|
Xcodeproj::Project::PBXProject.references_by_keys_attributes
|
493
573
|
.find { |attrb| attrb.name == :project_references }
|
@@ -539,8 +619,10 @@ module Kintsugi
|
|
539
619
|
file_reference = containing_component.project.new(Xcodeproj::Project::PBXFileReference)
|
540
620
|
containing_component.children << file_reference
|
541
621
|
|
542
|
-
# For some reason, `include_in_index` is set to `1` by
|
622
|
+
# For some reason, `include_in_index` is set to `1` and `source_tree` to `SDKROOT` by
|
623
|
+
# default.
|
543
624
|
file_reference.include_in_index = nil
|
625
|
+
file_reference.source_tree = nil
|
544
626
|
add_attributes_to_component(file_reference, change)
|
545
627
|
else
|
546
628
|
raise MergeError, "Trying to add file reference to an unsupported component type " \
|
data/lib/kintsugi/version.rb
CHANGED
@@ -27,5 +27,43 @@ module Xcodeproj
|
|
27
27
|
self[:project_ref]
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
module Object
|
32
|
+
# Extends `XCBuildConfiguration` to convert array settings (which might be either array or
|
33
|
+
# string) to actual arrays in `to_tree_hash` so diffs are always between arrays. This means
|
34
|
+
# that if the values in both `ours` and `theirs` are different strings, we will know to solve
|
35
|
+
# the conflict into an array containing both strings.
|
36
|
+
# Code was mostly copied from https://github.com/CocoaPods/Xcodeproj/blob/master/lib/xcodeproj/project/object/build_configuration.rb#L211
|
37
|
+
class XCBuildConfiguration
|
38
|
+
@@old_to_tree_hash = instance_method(:to_tree_hash)
|
39
|
+
|
40
|
+
def to_tree_hash
|
41
|
+
@@old_to_tree_hash.bind(self).call.tap do |hash|
|
42
|
+
convert_array_settings_to_arrays(hash['buildSettings'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def convert_array_settings_to_arrays(settings)
|
47
|
+
return unless settings
|
48
|
+
|
49
|
+
array_settings = BuildSettingsArraySettingsByObjectVersion[project.object_version]
|
50
|
+
|
51
|
+
settings.each_key do |key|
|
52
|
+
value = settings[key]
|
53
|
+
next unless value.is_a?(String)
|
54
|
+
|
55
|
+
stripped_key = key.sub(/\[[^\]]+\]$/, '')
|
56
|
+
next unless array_settings.include?(stripped_key)
|
57
|
+
|
58
|
+
array_value = split_string_setting_into_array(value)
|
59
|
+
settings[key] = array_value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def split_string_setting_into_array(string)
|
64
|
+
string.scan(/ *((['"]?).*?[^\\]\2)(?=( |\z))/).map(&:first)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
30
68
|
end
|
31
69
|
end
|
@@ -8,6 +8,7 @@ require "tempfile"
|
|
8
8
|
require "tmpdir"
|
9
9
|
|
10
10
|
require "kintsugi/apply_change_to_project"
|
11
|
+
require "kintsugi/error"
|
11
12
|
|
12
13
|
require_relative "be_equivalent_to_project"
|
13
14
|
|
@@ -50,6 +51,20 @@ describe Kintsugi, :apply_change_to_project do
|
|
50
51
|
expect(base_project).to be_equivalent_to_project(theirs_project)
|
51
52
|
end
|
52
53
|
|
54
|
+
it "adds package reference" do
|
55
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
56
|
+
|
57
|
+
theirs_project.root_object.package_references <<
|
58
|
+
create_remote_swift_package_reference(theirs_project)
|
59
|
+
|
60
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
61
|
+
|
62
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
63
|
+
base_project.save
|
64
|
+
|
65
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
66
|
+
end
|
67
|
+
|
53
68
|
it "adds new subproject" do
|
54
69
|
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
55
70
|
add_new_subproject_to_project(theirs_project, "foo", "foo")
|
@@ -100,6 +115,23 @@ describe Kintsugi, :apply_change_to_project do
|
|
100
115
|
expect(base_project).to be_equivalent_to_project(theirs_project, ignore_keys: ["containerPortal"])
|
101
116
|
end
|
102
117
|
|
118
|
+
it "raises if adding subproject whose file reference isn't found" do
|
119
|
+
ours_project = create_copy_of_project(base_project.path, "ours")
|
120
|
+
|
121
|
+
add_new_subproject_to_project(base_project, "foo", "foo")
|
122
|
+
base_project.save
|
123
|
+
|
124
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
125
|
+
|
126
|
+
base_project.root_object.project_references.pop
|
127
|
+
|
128
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
129
|
+
|
130
|
+
expect {
|
131
|
+
described_class.apply_change_to_project(ours_project, changes_to_apply)
|
132
|
+
}.to raise_error(Kintsugi::MergeError)
|
133
|
+
end
|
134
|
+
|
103
135
|
describe "file related changes" do
|
104
136
|
let(:filepath) { "foo" }
|
105
137
|
|
@@ -370,6 +402,19 @@ describe Kintsugi, :apply_change_to_project do
|
|
370
402
|
expect(base_project).to be_equivalent_to_project(theirs_project)
|
371
403
|
end
|
372
404
|
|
405
|
+
it "adds package product dependency to target" do
|
406
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
407
|
+
theirs_project.targets[0].package_product_dependencies <<
|
408
|
+
create_swift_package_product_dependency(theirs_project)
|
409
|
+
|
410
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
411
|
+
|
412
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
413
|
+
base_project.save
|
414
|
+
|
415
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
416
|
+
end
|
417
|
+
|
373
418
|
it "changes framework from reference proxy to file reference" do
|
374
419
|
framework_filename = "baz"
|
375
420
|
|
@@ -466,9 +511,28 @@ describe Kintsugi, :apply_change_to_project do
|
|
466
511
|
expect(base_project).to be_equivalent_to_project(theirs_project, ignore_keys: ["containerPortal"])
|
467
512
|
end
|
468
513
|
|
514
|
+
it "adds product ref to build file" do
|
515
|
+
base_project.main_group.new_reference("bar")
|
516
|
+
base_project.save
|
517
|
+
|
518
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
519
|
+
|
520
|
+
file_reference = theirs_project.main_group.files.find { |file| file.display_name == "bar" }
|
521
|
+
build_file =
|
522
|
+
theirs_project.targets[0].frameworks_build_phase.add_file_reference(file_reference)
|
523
|
+
build_file.product_ref =
|
524
|
+
create_swift_package_product_dependency(theirs_project)
|
525
|
+
|
526
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
527
|
+
|
528
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
529
|
+
base_project.save
|
530
|
+
|
531
|
+
expect(base_project).to be_equivalent_to_project(theirs_project, ignore_keys: ["containerPortal"])
|
532
|
+
end
|
533
|
+
|
469
534
|
it "adds build file to a file reference that already exist" do
|
470
|
-
|
471
|
-
base_project.targets[0].frameworks_build_phase.add_file_reference(file_reference)
|
535
|
+
base_project.main_group.new_reference("bar")
|
472
536
|
|
473
537
|
base_project.main_group.new_reference("bar")
|
474
538
|
|
@@ -557,69 +621,427 @@ describe Kintsugi, :apply_change_to_project do
|
|
557
621
|
expect(base_project).to be_equivalent_to_project(theirs_project, ignore_keys: ["containerPortal"])
|
558
622
|
end
|
559
623
|
|
560
|
-
|
561
|
-
|
624
|
+
describe "build settings" do
|
625
|
+
it "adds new string build setting" do
|
626
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
562
627
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
628
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
629
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "$(SRCROOT)/../Bar"
|
630
|
+
end
|
631
|
+
|
632
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
633
|
+
|
634
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
635
|
+
base_project.save
|
636
|
+
|
637
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
568
638
|
end
|
569
639
|
|
570
|
-
|
640
|
+
it "adds new array build setting" do
|
641
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
642
|
+
configuration.build_settings = {}
|
643
|
+
end
|
571
644
|
|
572
|
-
|
573
|
-
base_project.save
|
645
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
574
646
|
|
575
|
-
|
576
|
-
|
647
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
648
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = [
|
649
|
+
"$(SRCROOT)/../Foo",
|
650
|
+
"$(SRCROOT)/../Bar"
|
651
|
+
]
|
652
|
+
end
|
577
653
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
654
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
655
|
+
|
656
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
657
|
+
base_project.save
|
658
|
+
|
659
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
583
660
|
end
|
584
661
|
|
585
|
-
|
662
|
+
it "adds new hash build setting" do
|
663
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
586
664
|
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
665
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
666
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = [
|
667
|
+
"$(SRCROOT)/../Foo",
|
668
|
+
"$(SRCROOT)/../Bar"
|
669
|
+
]
|
670
|
+
end
|
671
|
+
|
672
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
673
|
+
|
674
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
675
|
+
base_project.save
|
676
|
+
|
677
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
592
678
|
end
|
593
679
|
|
594
|
-
|
680
|
+
it "adds values to existing array build setting" do
|
681
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
682
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = [
|
683
|
+
"$(SRCROOT)/../Foo"
|
684
|
+
]
|
685
|
+
end
|
595
686
|
|
596
|
-
|
597
|
-
base_project.save
|
687
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
598
688
|
|
599
|
-
|
600
|
-
|
689
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
690
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = [
|
691
|
+
"$(SRCROOT)/../Foo",
|
692
|
+
"$(SRCROOT)/../Bar"
|
693
|
+
]
|
694
|
+
end
|
601
695
|
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
696
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
697
|
+
|
698
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
699
|
+
base_project.save
|
700
|
+
|
701
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
608
702
|
end
|
609
703
|
|
610
|
-
|
611
|
-
|
704
|
+
it "adds array value to an existing string if no removed value" do
|
705
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
612
706
|
|
613
|
-
|
614
|
-
|
707
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
708
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[bar foo]
|
709
|
+
end
|
710
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
711
|
+
|
712
|
+
ours_project = create_copy_of_project(base_project.path, "ours")
|
713
|
+
ours_project.targets[0].build_configurations.each do |configuration|
|
714
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "baz"
|
715
|
+
end
|
716
|
+
ours_project.save
|
717
|
+
|
718
|
+
described_class.apply_change_to_project(ours_project, changes_to_apply)
|
719
|
+
ours_project.save
|
720
|
+
|
721
|
+
expected_project = create_copy_of_project(base_project.path, "expected")
|
722
|
+
expected_project.targets[0].build_configurations.each do |configuration|
|
723
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[bar foo baz]
|
724
|
+
end
|
725
|
+
expect(ours_project).to be_equivalent_to_project(expected_project)
|
615
726
|
end
|
616
727
|
|
617
|
-
|
728
|
+
it "adds string value to existing array value if no removed value" do
|
729
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
618
730
|
|
619
|
-
|
620
|
-
|
731
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
732
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "baz"
|
733
|
+
end
|
734
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
621
735
|
|
622
|
-
|
736
|
+
ours_project = create_copy_of_project(base_project.path, "ours")
|
737
|
+
ours_project.targets[0].build_configurations.each do |configuration|
|
738
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[bar foo]
|
739
|
+
end
|
740
|
+
ours_project.save
|
741
|
+
|
742
|
+
described_class.apply_change_to_project(ours_project, changes_to_apply)
|
743
|
+
ours_project.save
|
744
|
+
|
745
|
+
expected_project = create_copy_of_project(base_project.path, "expected")
|
746
|
+
expected_project.targets[0].build_configurations.each do |configuration|
|
747
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[bar foo baz]
|
748
|
+
end
|
749
|
+
expect(ours_project).to be_equivalent_to_project(expected_project)
|
750
|
+
end
|
751
|
+
|
752
|
+
it "removes array build setting" do
|
753
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
754
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = [
|
755
|
+
"$(SRCROOT)/../Foo",
|
756
|
+
"$(SRCROOT)/../Bar"
|
757
|
+
]
|
758
|
+
end
|
759
|
+
|
760
|
+
base_project.save
|
761
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
762
|
+
|
763
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
764
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = nil
|
765
|
+
end
|
766
|
+
|
767
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
768
|
+
|
769
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
770
|
+
base_project.save
|
771
|
+
|
772
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
773
|
+
end
|
774
|
+
|
775
|
+
it "removes string build setting" do
|
776
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
777
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
778
|
+
end
|
779
|
+
|
780
|
+
base_project.save
|
781
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
782
|
+
|
783
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
784
|
+
configuration.build_settings =
|
785
|
+
configuration.build_settings.reject { |key, _| key == "HEADER_SEARCH_PATHS" }
|
786
|
+
end
|
787
|
+
|
788
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
789
|
+
|
790
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
791
|
+
base_project.save
|
792
|
+
|
793
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
794
|
+
end
|
795
|
+
|
796
|
+
it "removes hash build setting" do
|
797
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
798
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
799
|
+
end
|
800
|
+
|
801
|
+
base_project.save
|
802
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
803
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
804
|
+
configuration.build_settings = nil
|
805
|
+
end
|
806
|
+
|
807
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
808
|
+
|
809
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
810
|
+
base_project.save
|
811
|
+
|
812
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
813
|
+
end
|
814
|
+
|
815
|
+
it "removes hash build setting if removed hash contains the existing hash" do
|
816
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
817
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
818
|
+
configuration.build_settings["foo"] = "baz"
|
819
|
+
end
|
820
|
+
|
821
|
+
base_project.save
|
822
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
823
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
824
|
+
configuration.build_settings = nil
|
825
|
+
end
|
826
|
+
|
827
|
+
ours_project = create_copy_of_project(base_project.path, "theirs")
|
828
|
+
ours_project.targets[0].build_configurations.each do |configuration|
|
829
|
+
configuration.build_settings["foo"] = nil
|
830
|
+
end
|
831
|
+
|
832
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
833
|
+
|
834
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
835
|
+
base_project.save
|
836
|
+
|
837
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
838
|
+
end
|
839
|
+
|
840
|
+
it "removes value if existing is string and removed is array that contains it" do
|
841
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
842
|
+
|
843
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
844
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
845
|
+
end
|
846
|
+
base_project.save
|
847
|
+
|
848
|
+
before_theirs_project = create_copy_of_project(base_project.path, "before_theirs")
|
849
|
+
before_theirs_project.targets[0].build_configurations.each do |configuration|
|
850
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = ["bar"]
|
851
|
+
end
|
852
|
+
|
853
|
+
changes_to_apply = get_diff(theirs_project, before_theirs_project)
|
854
|
+
|
855
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
856
|
+
base_project.save
|
857
|
+
|
858
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
859
|
+
end
|
860
|
+
|
861
|
+
it "removes value if removed value is string and existing is array that contains it" do
|
862
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
863
|
+
|
864
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
865
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = ["bar"]
|
866
|
+
end
|
867
|
+
base_project.save
|
868
|
+
|
869
|
+
before_theirs_project = create_copy_of_project(base_project.path, "before_theirs")
|
870
|
+
before_theirs_project.targets[0].build_configurations.each do |configuration|
|
871
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
872
|
+
end
|
873
|
+
|
874
|
+
changes_to_apply = get_diff(theirs_project, before_theirs_project)
|
875
|
+
|
876
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
877
|
+
base_project.save
|
878
|
+
|
879
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
880
|
+
end
|
881
|
+
|
882
|
+
it "removes value if existing is string and removed is array that contains it among other " \
|
883
|
+
"values" do
|
884
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
885
|
+
|
886
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
887
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
888
|
+
end
|
889
|
+
base_project.save
|
890
|
+
|
891
|
+
before_theirs_project = create_copy_of_project(base_project.path, "before_theirs")
|
892
|
+
before_theirs_project.targets[0].build_configurations.each do |configuration|
|
893
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[bar baz]
|
894
|
+
end
|
895
|
+
|
896
|
+
changes_to_apply = get_diff(theirs_project, before_theirs_project)
|
897
|
+
|
898
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
899
|
+
base_project.save
|
900
|
+
|
901
|
+
expected_project = create_copy_of_project(base_project.path, "expected")
|
902
|
+
expected_project.targets[0].build_configurations.each do |configuration|
|
903
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = nil
|
904
|
+
end
|
905
|
+
expect(base_project).to be_equivalent_to_project(expected_project)
|
906
|
+
end
|
907
|
+
|
908
|
+
it "changes to a single string value if removed is string and existing is array that " \
|
909
|
+
"contains it among another value" do
|
910
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
911
|
+
|
912
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
913
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[bar baz]
|
914
|
+
end
|
915
|
+
base_project.save
|
916
|
+
|
917
|
+
before_theirs_project = create_copy_of_project(base_project.path, "before_theirs")
|
918
|
+
before_theirs_project.targets[0].build_configurations.each do |configuration|
|
919
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
920
|
+
end
|
921
|
+
|
922
|
+
changes_to_apply = get_diff(theirs_project, before_theirs_project)
|
923
|
+
|
924
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
925
|
+
base_project.save
|
926
|
+
|
927
|
+
expected_project = create_copy_of_project(base_project.path, "expected")
|
928
|
+
expected_project.targets[0].build_configurations.each do |configuration|
|
929
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "baz"
|
930
|
+
end
|
931
|
+
expect(base_project).to be_equivalent_to_project(expected_project)
|
932
|
+
end
|
933
|
+
|
934
|
+
it "changes to string value if change contains removal of existing array" do
|
935
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
936
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[bar foo]
|
937
|
+
end
|
938
|
+
|
939
|
+
base_project.save
|
940
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
941
|
+
|
942
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
943
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "baz"
|
944
|
+
end
|
945
|
+
|
946
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
947
|
+
|
948
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
949
|
+
base_project.save
|
950
|
+
|
951
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
952
|
+
end
|
953
|
+
|
954
|
+
it "changes to array value if change contains removal of existing string" do
|
955
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
956
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
957
|
+
end
|
958
|
+
|
959
|
+
base_project.save
|
960
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
961
|
+
|
962
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
963
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[baz foo]
|
964
|
+
end
|
965
|
+
|
966
|
+
changes_to_apply = get_diff(theirs_project, base_project)
|
967
|
+
|
968
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
969
|
+
base_project.save
|
970
|
+
|
971
|
+
expect(base_project).to be_equivalent_to_project(theirs_project)
|
972
|
+
end
|
973
|
+
|
974
|
+
it "changes to array if added value is string and existing is another string and removal is" \
|
975
|
+
"nil for an array build setting" do
|
976
|
+
before_theirs_project = create_copy_of_project(base_project.path, "theirs")
|
977
|
+
|
978
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
979
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
980
|
+
end
|
981
|
+
base_project.save
|
982
|
+
|
983
|
+
theirs_project = create_copy_of_project(base_project.path, "before_theirs")
|
984
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
985
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "baz"
|
986
|
+
end
|
987
|
+
|
988
|
+
expected_project = create_copy_of_project(base_project.path, "expected")
|
989
|
+
expected_project.targets[0].build_configurations.each do |configuration|
|
990
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = %w[bar baz]
|
991
|
+
end
|
992
|
+
|
993
|
+
changes_to_apply = get_diff(theirs_project, before_theirs_project)
|
994
|
+
|
995
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
996
|
+
base_project.save
|
997
|
+
|
998
|
+
expect(base_project).to be_equivalent_to_project(expected_project)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
it "raises if added value is string and existing is another string and removal is nil for a " \
|
1002
|
+
"string build setting" do
|
1003
|
+
before_theirs_project = create_copy_of_project(base_project.path, "theirs")
|
1004
|
+
|
1005
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
1006
|
+
configuration.build_settings["PRODUCT_NAME"] = "bar"
|
1007
|
+
end
|
1008
|
+
base_project.save
|
1009
|
+
|
1010
|
+
theirs_project = create_copy_of_project(base_project.path, "before_theirs")
|
1011
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
1012
|
+
configuration.build_settings["PRODUCT_NAME"] = "baz"
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
changes_to_apply = get_diff(theirs_project, before_theirs_project)
|
1016
|
+
|
1017
|
+
expect {
|
1018
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
1019
|
+
}.to raise_error(Kintsugi::MergeError)
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
it "raises if trying to remove hash entry whose value changed" do
|
1023
|
+
base_project.targets[0].build_configurations.each do |configuration|
|
1024
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "bar"
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
base_project.save
|
1028
|
+
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
1029
|
+
theirs_project.targets[0].build_configurations.each do |configuration|
|
1030
|
+
configuration.build_settings = nil
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
base_project.save
|
1034
|
+
before_theirs_project = create_copy_of_project(base_project.path, "theirs")
|
1035
|
+
before_theirs_project.targets[0].build_configurations.each do |configuration|
|
1036
|
+
configuration.build_settings["HEADER_SEARCH_PATHS"] = "baz"
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
changes_to_apply = get_diff(theirs_project, before_theirs_project)
|
1040
|
+
|
1041
|
+
expect {
|
1042
|
+
described_class.apply_change_to_project(base_project, changes_to_apply)
|
1043
|
+
}.to raise_error(Kintsugi::MergeError)
|
1044
|
+
end
|
623
1045
|
end
|
624
1046
|
|
625
1047
|
it "adds build phases" do
|
@@ -752,7 +1174,7 @@ describe Kintsugi, :apply_change_to_project do
|
|
752
1174
|
base_project.save
|
753
1175
|
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
754
1176
|
|
755
|
-
theirs_project.root_object.known_regions += ["
|
1177
|
+
theirs_project.root_object.known_regions += ["fr"]
|
756
1178
|
|
757
1179
|
changes_to_apply = get_diff(theirs_project, base_project)
|
758
1180
|
|
@@ -763,12 +1185,9 @@ describe Kintsugi, :apply_change_to_project do
|
|
763
1185
|
end
|
764
1186
|
|
765
1187
|
it "removes known regions" do
|
766
|
-
base_project.root_object.known_regions += ["en"]
|
767
|
-
|
768
|
-
base_project.save
|
769
1188
|
theirs_project = create_copy_of_project(base_project.path, "theirs")
|
770
1189
|
|
771
|
-
theirs_project.root_object.known_regions =
|
1190
|
+
theirs_project.root_object.known_regions = nil
|
772
1191
|
|
773
1192
|
changes_to_apply = get_diff(theirs_project, base_project)
|
774
1193
|
|
@@ -934,7 +1353,15 @@ describe Kintsugi, :apply_change_to_project do
|
|
934
1353
|
end
|
935
1354
|
|
936
1355
|
def get_diff(first_project, second_project)
|
937
|
-
Xcodeproj::Differ.project_diff(first_project, second_project, :added, :removed)
|
1356
|
+
diff = Xcodeproj::Differ.project_diff(first_project, second_project, :added, :removed)
|
1357
|
+
|
1358
|
+
diff_without_display_name =
|
1359
|
+
diff.merge("rootObject" => diff["rootObject"].reject { |key, _| key == "displayName" })
|
1360
|
+
if diff_without_display_name == {"rootObject" => {}}
|
1361
|
+
raise "Diff contains no changes. This probably means the test doesn't check anything."
|
1362
|
+
end
|
1363
|
+
|
1364
|
+
diff
|
938
1365
|
end
|
939
1366
|
|
940
1367
|
def add_new_subproject_to_project(project, subproject_name, subproject_product_name)
|
@@ -987,6 +1414,22 @@ describe Kintsugi, :apply_change_to_project do
|
|
987
1414
|
reference_proxy
|
988
1415
|
end
|
989
1416
|
|
1417
|
+
def create_swift_package_product_dependency(project)
|
1418
|
+
product_dependency = project.new(Xcodeproj::Project::XCSwiftPackageProductDependency)
|
1419
|
+
product_dependency.product_name = "foo"
|
1420
|
+
product_dependency.package = create_remote_swift_package_reference(project)
|
1421
|
+
|
1422
|
+
product_dependency
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
def create_remote_swift_package_reference(project)
|
1426
|
+
package_reference = project.new(Xcodeproj::Project::XCRemoteSwiftPackageReference)
|
1427
|
+
package_reference.repositoryURL = "http://foo"
|
1428
|
+
package_reference.requirement = {"foo" => "bar"}
|
1429
|
+
|
1430
|
+
package_reference
|
1431
|
+
end
|
1432
|
+
|
990
1433
|
def make_temp_directory(directory_prefix, directory_extension)
|
991
1434
|
directory_path = Dir.mktmpdir([directory_prefix, directory_extension])
|
992
1435
|
temporary_directories_paths << directory_path
|
data/spec/spec_helper.rb
CHANGED
@@ -44,6 +44,9 @@ RSpec.configure do |config|
|
|
44
44
|
mocks.verify_partial_doubles = true
|
45
45
|
end
|
46
46
|
|
47
|
+
config.filter_run focus: true
|
48
|
+
config.run_all_when_everything_filtered = true
|
49
|
+
|
47
50
|
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
48
51
|
# have no way to turn it off -- the option exists only for backwards
|
49
52
|
# compatibility in RSpec 3). It causes shared context metadata to be
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kintsugi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Yohay
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: xcodeproj
|