kintsugi 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|