kintsugi 0.3.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdc0655ad443f7511adec336aef55e40ed0bb5458cf5fd9ae7747294bf40881d
4
- data.tar.gz: 214a8426b955d0c36b2ff51f55b2016ed69861ccc5d99fa2d463567cbc6872fc
3
+ metadata.gz: c4545555bde99b949d5f4a2963ea4e347fc7ea3a91ef22426de9854c5ccf6834
4
+ data.tar.gz: 5d75fff06081dabb27f43fcfc8ee36d732cae2fce4268df42feecabf285250c7
5
5
  SHA512:
6
- metadata.gz: 79d21fb3b3ddcb977a0b09bb5f80d19d6337574cb5b975a342d261bd689b53ff387a4ef2c7f0fb9a33d6698ce0e739b76824515ecf4b869f5858228635d99047
7
- data.tar.gz: 4a902033e2f4236d82f3db8b1fa832ec78e8201b264a32a11599b3c0df3b048bb82e5abee8cb3d3fac7e74ae4d6ff37029ae9cd809fee5ebbe65723c997862e7
6
+ metadata.gz: 8f1aa798183ec84869a155d7d5068c5045105ffa540b5c76bcfd4f7464c5150a36af0dbc6baf7897bf1c8f056ab53eb2cf47dd27aff0d3455391a84de5503469
7
+ data.tar.gz: 2f02d8522127772adf32c654335a08bb923613e3d9ce98ac36529b80a2d399e4dd711d8fc18b7ec8a6d480d3baea7e72bba13f4148363d62ac2abbaad77861a9
data/README.md CHANGED
@@ -40,15 +40,22 @@ And see the magic happen! :sparkles:
40
40
 
41
41
  ### Git merge driver
42
42
 
43
- To use Kintsugi as a Git merge driver, follow these steps:
43
+ You can setup Kintsugi to automatically resolve conflicts that occur in `pbxproj` files when such conflicts occur.
44
44
 
45
- - Add it as driver to Git config file by running the following:
45
+ #### Automatic install
46
+
47
+ Run `kintsugi install-driver`. This will install Kintsugi as a merge driver globally. Note that Kintsugi needs to be in your `PATH`.
48
+
49
+ ❗ Do not install with bundler because the installation might succeed even if Kintsugi is not in `PATH`.
50
+
51
+ #### Manual install
52
+
53
+ - Add Kintsugi as driver to Git config file by running the following:
46
54
  ```sh
47
55
  git config merge.kintsugi.name "Kintsugi driver" # Or any other name you prefer
48
- git config merge.kintsugi.driver "kintsugi driver %O %A %B %P"
56
+ git config merge.kintsugi.driver "<path_to_kintsugi> driver %O %A %B %P"
49
57
  ```
50
58
 
51
- `kintsugi` should be in your `PATH`.
52
59
  Run `git config` with `--global` to add this to the global config file.
53
60
 
54
61
  - Add the following line to the `.gitattributes` file at the root of the repository:
@@ -65,7 +72,11 @@ See our [Contribution guidelines](./CONTRIBUTING.md).
65
72
 
66
73
  ## Alternatives
67
74
 
68
- - [XcodeGen](https://github.com/yonaskolb/XcodeGen): You can commit this JSON file into Git instead of the `.pbxproj` file. Then resolving conflicts is much easier.
75
+ All of the alternatives below allow you to generate your Xcode projects based on a spec or manifest. You commit these files to git, and can even remove the `.xcodeproj` files from git.
76
+
77
+ - [XcodeGen](https://github.com/yonaskolb/XcodeGen)
78
+ - [Tuist](https://github.com/tuist)
79
+ - [Xcake](https://github.com/igor-makarov/xcake)
69
80
 
70
81
  ## Copyright
71
82
 
data/bin/kintsugi CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  require "kintsugi"
8
8
  require_relative "../lib/kintsugi/cli"
9
+ require_relative "../lib/kintsugi/error"
9
10
 
10
11
  def parse_options!(command, argv)
11
12
  options = {}
@@ -28,4 +29,11 @@ command =
28
29
  end
29
30
 
30
31
  options = parse_options!(command, ARGV)
31
- command.action.call(options, ARGV, command.option_parser)
32
+
33
+ begin
34
+ command.action.call(options, ARGV)
35
+ rescue ArgumentError => e
36
+ puts "#{e.class}: #{e}"
37
+ rescue Kintsugi::MergeError => e
38
+ puts e
39
+ end
@@ -190,24 +190,24 @@ module Kintsugi
190
190
  when Hash
191
191
  (old_value || {}).reject do |key, value|
192
192
  if value != removed_change[key] && added_change[key] != value
193
- raise "Trying to remove value '#{removed_change[key]}' of hash with key '#{key}' but " \
194
- "it changed to #{value}. This is considered a conflict that should be resolved " \
195
- "manually."
193
+ raise MergeError, "Trying to remove value '#{removed_change[key]}' of hash with key " \
194
+ "'#{key}' but it changed to #{value}. This is considered a conflict that should be " \
195
+ "resolved manually."
196
196
  end
197
197
 
198
198
  removed_change.key?(key)
199
199
  end
200
200
  when String
201
201
  if old_value != removed_change && !old_value.nil? && added_change != old_value
202
- raise "Trying to remove value '#{removed_change}', but the existing value is " \
203
- "'#{old_value}'. This is considered a conflict that should be resolved manually."
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
204
  end
205
205
 
206
206
  nil
207
207
  when nil
208
208
  nil
209
209
  else
210
- raise "Unsupported change #{removed_change} of type #{removed_change.class}"
210
+ raise MergeError, "Unsupported change #{removed_change} of type #{removed_change.class}"
211
211
  end
212
212
  end
213
213
 
@@ -220,7 +220,8 @@ module Kintsugi
220
220
  new_value = old_value.merge(change)
221
221
 
222
222
  unless (old_value.to_a - new_value.to_a).empty?
223
- raise "New hash #{change} contains values that conflict with old hash #{old_value}"
223
+ raise MergeError, "New hash #{change} contains values that conflict with old hash " \
224
+ "#{old_value}"
224
225
  end
225
226
 
226
227
  new_value
@@ -229,14 +230,15 @@ module Kintsugi
229
230
  when nil
230
231
  nil
231
232
  else
232
- raise "Unsupported change #{change} of type #{change.class}"
233
+ raise MergeError, "Unsupported change #{change} of type #{change.class}"
233
234
  end
234
235
  end
235
236
 
236
237
  def remove_component(component, change)
237
238
  if component.to_tree_hash != change
238
- raise "Trying to remove an object that changed since then. This is considered a conflict " \
239
- "that should be resolved manually. Name of the object is: '#{component.display_name}'"
239
+ raise MergeError, "Trying to remove an object that changed since then. This is " \
240
+ "considered a conflict that should be resolved manually. Name of the object is: " \
241
+ "'#{component.display_name}'"
240
242
  end
241
243
 
242
244
  if change["isa"] == "PBXFileReference"
@@ -302,8 +304,8 @@ module Kintsugi
302
304
  when "PBXReferenceProxy"
303
305
  add_reference_proxy(component, change)
304
306
  else
305
- raise "Trying to add unsupported component type #{change["isa"]}. Full component change " \
306
- "is: #{change}"
307
+ raise MergeError, "Trying to add unsupported component type #{change["isa"]}. Full " \
308
+ "component change is: #{change}"
307
309
  end
308
310
  end
309
311
 
@@ -331,7 +333,7 @@ module Kintsugi
331
333
  containing_component << reference_proxy
332
334
  add_attributes_to_component(reference_proxy, change)
333
335
  else
334
- raise "Trying to add reference proxy to an unsupported component type " \
336
+ raise MergeError, "Trying to add reference proxy to an unsupported component type " \
335
337
  "#{containing_component.isa}. Change is: #{change}"
336
338
  end
337
339
  end
@@ -346,7 +348,7 @@ module Kintsugi
346
348
  containing_component.children << variant_group
347
349
  add_attributes_to_component(variant_group, change)
348
350
  else
349
- raise "Trying to add variant group to an unsupported component type " \
351
+ raise MergeError, "Trying to add variant group to an unsupported component type " \
350
352
  "#{containing_component.isa}. Change is: #{change}"
351
353
  end
352
354
  end
@@ -442,7 +444,7 @@ module Kintsugi
442
444
  when "PBXReferenceProxy"
443
445
  component.remote_ref = container_proxy
444
446
  else
445
- raise "Trying to add container item proxy to an unsupported component type " \
447
+ raise MergeError, "Trying to add container item proxy to an unsupported component type " \
446
448
  "#{containing_component.isa}. Change is: #{change}"
447
449
  end
448
450
  add_attributes_to_component(container_proxy, change, ignore_keys: ["containerPortal"])
@@ -541,7 +543,7 @@ module Kintsugi
541
543
  file_reference.include_in_index = nil
542
544
  add_attributes_to_component(file_reference, change)
543
545
  else
544
- raise "Trying to add file reference to an unsupported component type " \
546
+ raise MergeError, "Trying to add file reference to an unsupported component type " \
545
547
  "#{containing_component.isa}. Change is: #{change}"
546
548
  end
547
549
  end
@@ -556,8 +558,8 @@ module Kintsugi
556
558
  new_group = containing_component.project.new(Xcodeproj::Project::PBXGroup)
557
559
  containing_component.children << new_group
558
560
  else
559
- raise "Trying to add group to an unsupported component type #{containing_component.isa}. " \
560
- "Change is: #{change}"
561
+ raise MergeError, "Trying to add group to an unsupported component type " \
562
+ "#{containing_component.isa}. Change is: #{change}"
561
563
  end
562
564
 
563
565
  add_attributes_to_component(new_group, change)
@@ -581,8 +583,8 @@ module Kintsugi
581
583
  add_child_to_component(component, added_attribute_element)
582
584
  end
583
585
  else
584
- raise "Trying to add attribute of unsupported type '#{change_value.class}' to " \
585
- "object #{component}. Attribute name is '#{change_name}'"
586
+ raise MergeError, "Trying to add attribute of unsupported type '#{change_value.class}' " \
587
+ "to object #{component}. Attribute name is '#{change_name}'"
586
588
  end
587
589
  end
588
590
  end
data/lib/kintsugi/cli.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  # Created by Ben Yohay.
3
3
  # frozen_string_literal: true
4
4
 
5
+ require "fileutils"
5
6
  require "optparse"
6
7
 
7
8
  require_relative "version"
@@ -12,12 +13,14 @@ module Kintsugi
12
13
  # Subcommands of Kintsugi CLI.
13
14
  attr_reader :subcommands
14
15
 
15
- # Root command Kintsugi CLI.
16
+ # Root command of Kintsugi CLI.
16
17
  attr_reader :root_command
17
18
 
18
19
  def initialize
19
20
  @subcommands = {
20
- "driver" => create_driver_subcommand
21
+ "driver" => create_driver_subcommand,
22
+ "install-driver" => create_install_driver_subcommand,
23
+ "uninstall-driver" => create_uninstall_driver_subcommand
21
24
  }.freeze
22
25
  @root_command = create_root_command
23
26
  end
@@ -27,7 +30,7 @@ module Kintsugi
27
30
  Command = Struct.new(:option_parser, :action, :description, keyword_init: true)
28
31
 
29
32
  def create_driver_subcommand
30
- driver_option_parser =
33
+ option_parser =
31
34
  OptionParser.new do |opts|
32
35
  opts.banner = "Usage: kintsugi driver BASE OURS THEIRS ORIGINAL_FILE_PATH\n" \
33
36
  "Uses Kintsugi as a Git merge driver. Parameters " \
@@ -40,7 +43,7 @@ module Kintsugi
40
43
  end
41
44
  end
42
45
 
43
- driver_action = lambda { |_, arguments, option_parser|
46
+ driver_action = lambda { |_, arguments|
44
47
  if arguments.count != 4
45
48
  puts "Incorrect number of arguments to 'driver' subcommand\n\n"
46
49
  puts option_parser
@@ -50,14 +53,115 @@ module Kintsugi
50
53
  }
51
54
 
52
55
  Command.new(
53
- option_parser: driver_option_parser,
56
+ option_parser: option_parser,
54
57
  action: driver_action,
55
58
  description: "3-way merge compatible with Git merge driver"
56
59
  )
57
60
  end
58
61
 
62
+ def create_install_driver_subcommand
63
+ option_parser =
64
+ OptionParser.new do |opts|
65
+ opts.banner = "Usage: kintsugi install-driver\n" \
66
+ "Installs Kintsugi as a Git merge driver globally. "
67
+
68
+ opts.on("-h", "--help", "Prints this help") do
69
+ puts opts
70
+ exit
71
+ end
72
+ end
73
+
74
+ action = lambda { |_, arguments|
75
+ if arguments.count != 0
76
+ puts "Incorrect number of arguments to 'install-driver' subcommand\n\n"
77
+ puts option_parser
78
+ exit(1)
79
+ end
80
+
81
+ if `which kintsugi`.chomp.empty?
82
+ puts "Can only install Kintsugi globally if Kintsugi is in your PATH"
83
+ exit(1)
84
+ end
85
+
86
+ install_kintsugi_driver_globally
87
+ puts "Done! 🪄"
88
+ }
89
+
90
+ Command.new(
91
+ option_parser: option_parser,
92
+ action: action,
93
+ description: "Installs Kintsugi as a Git merge driver globally"
94
+ )
95
+ end
96
+
97
+ def install_kintsugi_driver_globally
98
+ `git config --global merge.kintsugi.name "Kintsugi driver"`
99
+ `git config --global merge.kintsugi.driver "kintsugi driver %O %A %B %P"`
100
+
101
+ attributes_file_path = global_attributes_file_path
102
+ FileUtils.mkdir_p(File.dirname(attributes_file_path))
103
+
104
+ merge_using_kintsugi_line = "'*.pbxproj merge=kintsugi'"
105
+ `grep -sqxF #{merge_using_kintsugi_line} "#{attributes_file_path}" \
106
+ || echo #{merge_using_kintsugi_line} >> "#{attributes_file_path}"`
107
+ end
108
+
109
+ def global_attributes_file_path
110
+ # The logic to decide the path to the global attributes file is described at:
111
+ # https://git-scm.com/docs/gitattributes.
112
+ config_attributes_file_path = `git config --global core.attributesfile`
113
+ return config_attributes_file_path unless config_attributes_file_path.empty?
114
+
115
+ if ENV["XDG_CONFIG_HOME"].nil? || ENV["XDG_CONFIG_HOME"].empty?
116
+ File.join(ENV["HOME"], ".config/git/attributes")
117
+ else
118
+ File.join(ENV["XDG_CONFIG_HOME"], "git/attributes")
119
+ end
120
+ end
121
+
122
+ def create_uninstall_driver_subcommand
123
+ option_parser =
124
+ OptionParser.new do |opts|
125
+ opts.banner = "Usage: kintsugi uninstall-driver\n" \
126
+ "Uninstalls Kintsugi as a Git merge driver that was previously installed globally."
127
+
128
+ opts.on("-h", "--help", "Prints this help") do
129
+ puts opts
130
+ exit
131
+ end
132
+ end
133
+
134
+ action = lambda { |_, arguments|
135
+ if arguments.count != 0
136
+ puts "Incorrect number of arguments to 'uninstall-driver' subcommand\n\n"
137
+ puts option_parser
138
+ exit(1)
139
+ end
140
+
141
+ uninstall_kintsugi_driver_globally
142
+ puts "Done!"
143
+ }
144
+
145
+ Command.new(
146
+ option_parser: option_parser,
147
+ action: action,
148
+ description: "Uninstalls Kintsugi as a Git merge driver that was previously installed " \
149
+ "globally."
150
+ )
151
+ end
152
+
153
+ def uninstall_kintsugi_driver_globally
154
+ `git config --global --unset merge.kintsugi.name`
155
+ `git config --global --unset merge.kintsugi.driver`
156
+
157
+ attributes_file_path = global_attributes_file_path
158
+ return unless File.exist?(attributes_file_path)
159
+
160
+ `sed -i '' '/\*.pbxproj\ merge=kintsugi/d' "#{attributes_file_path}"`
161
+ end
162
+
59
163
  def create_root_command
60
- root_option_parser = OptionParser.new do |opts|
164
+ option_parser = OptionParser.new do |opts|
61
165
  opts.banner = "Kintsugi, version #{Version::STRING}\n" \
62
166
  "Copyright (c) 2021 Lightricks\n\n" \
63
167
  "Usage: kintsugi <pbxproj_filepath> [options]\n" \
@@ -77,13 +181,10 @@ module Kintsugi
77
181
  exit
78
182
  end
79
183
 
80
- subcommands_descriptions = @subcommands.map do |command_name, command|
81
- " #{command_name}: #{command.description}"
82
- end.join("\n")
83
- opts.on_tail("\nSUBCOMMANDS\n#{subcommands_descriptions}")
184
+ opts.on_tail("\nSUBCOMMANDS\n#{subcommands_descriptions(subcommands)}")
84
185
  end
85
186
 
86
- root_action = lambda { |options, arguments, option_parser|
187
+ root_action = lambda { |options, arguments|
87
188
  if arguments.count != 1
88
189
  puts "Incorrect number of arguments\n\n"
89
190
  puts option_parser
@@ -96,10 +197,18 @@ module Kintsugi
96
197
  }
97
198
 
98
199
  Command.new(
99
- option_parser: root_option_parser,
200
+ option_parser: option_parser,
100
201
  action: root_action,
101
202
  description: nil
102
203
  )
103
204
  end
205
+
206
+ def subcommands_descriptions(subcommands)
207
+ longest_subcommand_length = subcommands.keys.map(&:length).max + 4
208
+ format_string = " %-#{longest_subcommand_length}s%s"
209
+ subcommands.map do |command_name, command|
210
+ format(format_string, "#{command_name}:", command.description)
211
+ end.join("\n")
212
+ end
104
213
  end
105
214
  end
@@ -0,0 +1,9 @@
1
+ # Copyright (c) 2021 Lightricks. All rights reserved.
2
+ # Created by Ben Yohay.
3
+ # frozen_string_literal: true
4
+
5
+ module Kintsugi
6
+ # Raised when an error occurred while Kintsugi tried to resolve conflicts.
7
+ class MergeError < RuntimeError
8
+ end
9
+ end
@@ -3,6 +3,6 @@
3
3
  module Kintsugi
4
4
  # This module holds the Kintsugi version information.
5
5
  module Version
6
- STRING = "0.3.1"
6
+ STRING = "0.4.3"
7
7
  end
8
8
  end
data/lib/kintsugi.rb CHANGED
@@ -9,6 +9,7 @@ require "xcodeproj"
9
9
 
10
10
  require_relative "kintsugi/xcodeproj_extensions"
11
11
  require_relative "kintsugi/apply_change_to_project"
12
+ require_relative "kintsugi/error"
12
13
 
13
14
  module Kintsugi
14
15
  class << self
@@ -21,11 +22,11 @@ module Kintsugi
21
22
  # Path to where the changes to apply to the project are written in JSON format.
22
23
  #
23
24
  # @raise [ArgumentError]
24
- # If the file extension is not `pbxproj` or the file doesn't exist
25
+ # If the file extension is not `pbxproj`, or the file doesn't exist, or if no rebase,
26
+ # cherry-pick, or merge is in progress
25
27
  #
26
- # @raise [RuntimeError]
27
- # If no rebase, cherry-pick, or merge is in progress, or the project file couldn't be
28
- # opened, or there was an error applying the change to the project.
28
+ # @raise [MergeError]
29
+ # If there was an error applying the change to the project.
29
30
  #
30
31
  # @return [void]
31
32
  def resolve_conflicts(project_file_path, changes_output_path)
@@ -59,7 +60,7 @@ module Kintsugi
59
60
  # @param [String] original_project_path
60
61
  # Path to the original path of the file.
61
62
  #
62
- # @raise [RuntimeError]
63
+ # @raise [MergeError]
63
64
  # If there was an error applying the change to the project.
64
65
  #
65
66
  # @return [void]
@@ -104,8 +105,8 @@ module Kintsugi
104
105
 
105
106
  Dir.chdir(File.dirname(project_file_path)) do
106
107
  unless file_has_base_ours_and_theirs_versions?(project_file_path)
107
- raise ArgumentError, "File '#{project_file_path}' doesn't have conflicts, or a 3-way " \
108
- "merge is not possible."
108
+ raise ArgumentError, "File '#{project_file_path}' doesn't have conflicts, " \
109
+ "or a 3-way merge is not possible."
109
110
  end
110
111
  end
111
112
  end
@@ -124,7 +125,7 @@ module Kintsugi
124
125
  Dir.mkdir(temp_directory_name)
125
126
  temp_project_file_path = File.join(temp_directory_name, PROJECT_FILE_NAME)
126
127
  Dir.chdir(File.dirname(project_file_path)) do
127
- `git show HEAD:./project.pbxproj > #{temp_project_file_path}`
128
+ `git show HEAD:./project.pbxproj > "#{temp_project_file_path}"`
128
129
  end
129
130
  Xcodeproj::Project.open(File.dirname(temp_project_file_path))
130
131
  end
@@ -138,7 +139,7 @@ module Kintsugi
138
139
  def file_has_version_in_stage_numbers?(file_path, stage_numbers)
139
140
  file_absolute_path = File.absolute_path(file_path)
140
141
  actual_stage_numbers =
141
- `git ls-files -u -- #{file_absolute_path}`.split("\n").map do |git_file_status|
142
+ `git ls-files -u -- "#{file_absolute_path}"`.split("\n").map do |git_file_status|
142
143
  git_file_status.split[2]
143
144
  end
144
145
  (stage_numbers - actual_stage_numbers.map(&:to_i)).empty?
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.3.1
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Yohay
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-30 00:00:00.000000000 Z
11
+ date: 2022-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xcodeproj
@@ -123,6 +123,7 @@ files:
123
123
  - lib/kintsugi.rb
124
124
  - lib/kintsugi/apply_change_to_project.rb
125
125
  - lib/kintsugi/cli.rb
126
+ - lib/kintsugi/error.rb
126
127
  - lib/kintsugi/utils.rb
127
128
  - lib/kintsugi/version.rb
128
129
  - lib/kintsugi/xcodeproj_extensions.rb
@@ -134,7 +135,7 @@ homepage: https://github.com/Lightricks/Kintsugi
134
135
  licenses:
135
136
  - MIT
136
137
  metadata: {}
137
- post_install_message:
138
+ post_install_message:
138
139
  rdoc_options: []
139
140
  require_paths:
140
141
  - lib
@@ -149,9 +150,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
150
  - !ruby/object:Gem::Version
150
151
  version: '0'
151
152
  requirements: []
152
- rubyforge_project:
153
- rubygems_version: 2.7.3
154
- signing_key:
153
+ rubyforge_project:
154
+ rubygems_version: 2.7.6.3
155
+ signing_key:
155
156
  specification_version: 4
156
157
  summary: pbxproj files git conflicts solver
157
158
  test_files: