kintsugi 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d48ba29d100ace39635610ad2876a7b6b78b2ff0b7eb56e34b158c962471b0e3
4
- data.tar.gz: 719750e48502f5a4bb3a9c924faf2a2b6b959235555a7545c40262ad80b8ee3a
3
+ metadata.gz: 1d58044d1f7c23e62f290c33defa4d9b3e95c0012b62754997a7c628d96e4358
4
+ data.tar.gz: 6d63f1e0cf56c131fe7222c9d316f4f152310077fa2a9f4e386fba54ab82a340
5
5
  SHA512:
6
- metadata.gz: 26e3adcbe40a0ee5d597cb3bae344f4b124db1de5fb662b5af3d4357d334edc3f46d4367879c33cb26e395b957ca62c2ac824e6d0478fbd3639fe0d71533da13
7
- data.tar.gz: 7a71972eb202112a3e83d6923f42b30e509af7b3a0ee4394d83822a89129fa984da8e6e1422046c5ee5c6e43152cbb876101f42dab04bad0c17516b6ba5d329f
6
+ metadata.gz: 1aa18d3fd3456e1aa44f6497bc3e2f4e4877836e8722887050725012591e43b0086efa490df8eab8f1b63800f900897365a81c87935fb68341b1727e4c432e99
7
+ data.tar.gz: f8aa152a98e35b9620a3c855eb010781a7fb65e843c6323ea4d14fb27ba414416c2e585273f797e0d8698dfae67d9f7a963e626cc2a2f40ca0d6cce0707cfc0d
data/Gemfile CHANGED
@@ -2,3 +2,5 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
  gemspec
5
+ gem "ruby-debug-ide", "0.7.2"
6
+ gem "debase", "0.2.5.beta2"
data/README.md CHANGED
@@ -38,6 +38,27 @@ When there's a `.pbxproj` file with Git conflicts, and a 3-way merge is possible
38
38
 
39
39
  And see the magic happen! :sparkles:
40
40
 
41
+ ### Git merge driver
42
+
43
+ To use Kintsugi as a Git merge driver, follow these steps:
44
+
45
+ - Add it as driver to Git config file by running the following:
46
+ ```sh
47
+ 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"
49
+ ```
50
+
51
+ `kintsugi` should be in your `PATH`.
52
+ Run `git config` with `--global` to add this to the global config file.
53
+
54
+ - Add the following line to the `.gitattributes` file at the root of the repository:
55
+
56
+ `*.pbxproj merge=kintsugi`
57
+
58
+ This will instruct Git to use Kintsugi as a merge driver for `.pbxproj` files.
59
+
60
+ See the [official docs](https://git-scm.com/docs/gitattributes) if you want to set this globally.
61
+
41
62
  ## Contribution
42
63
 
43
64
  See our [Contribution guidelines](./CONTRIBUTING.md).
data/bin/kintsugi CHANGED
@@ -4,49 +4,28 @@
4
4
  # Copyright (c) 2020 Lightricks. All rights reserved.
5
5
  # Created by Ben Yohay.
6
6
 
7
- require "json"
8
- require "optparse"
9
-
10
7
  require "kintsugi"
11
- require_relative "../lib/kintsugi/version"
12
-
13
-
14
- def parse_options!(argv)
15
- options_parser = create_options_parser
8
+ require_relative "../lib/kintsugi/cli"
16
9
 
10
+ def parse_options!(command, argv)
17
11
  options = {}
18
- options_parser.parse!(argv, into: options)
19
-
20
- if argv.length != 1
21
- puts "Incorrect number of arguments\n\n"
22
- puts options_parser
23
- exit(1)
24
- end
25
-
12
+ command.option_parser.parse!(argv, into: options)
26
13
  options
27
14
  end
28
15
 
29
- def create_options_parser
30
- OptionParser.new do |opts|
31
- opts.banner = "Kintsugi, version #{Kintsugi::Version::STRING}\nCopyright (c) 2021 " \
32
- "Lightricks\n\nUsage: kintsugi [pbxproj_filepath] [options]"
33
- opts.on("--changes-output-path=PATH", "Path to which changes applied to the project are " \
34
- "written in JSON format. Used for debug purposes.")
35
-
36
- opts.on("-h", "--help", "Prints this help") do
37
- puts opts
38
- exit
39
- end
40
-
41
- opts.on_tail("-v", "--version", "Prints version") do
42
- puts Kintsugi::Version::STRING
43
- exit
44
- end
45
- end
16
+ def name_of_subcommand?(subcommands, argument)
17
+ subcommands.include?(argument)
46
18
  end
47
19
 
48
- options = parse_options!(ARGV)
49
- project_file_path = File.expand_path(ARGV[0])
50
- Kintsugi.resolve_conflicts(project_file_path, options[:"changes-output-path"])
20
+ first_argument = ARGV[0]
21
+ cli = Kintsugi::CLI.new
22
+ command =
23
+ if name_of_subcommand?(cli.subcommands, first_argument)
24
+ ARGV.shift
25
+ cli.subcommands[first_argument]
26
+ else
27
+ cli.root_command
28
+ end
51
29
 
52
- puts "Resolved conflicts successfully"
30
+ options = parse_options!(command, ARGV)
31
+ command.action.call(options, ARGV, command.option_parser)
data/lib/kintsugi.rb CHANGED
@@ -39,33 +39,68 @@ module Kintsugi
39
39
  File.write(changes_output_path, JSON.pretty_generate(change))
40
40
  end
41
41
 
42
- apply_change_to_project(project_in_temp_directory, change)
43
-
44
- project_in_temp_directory.save
45
-
46
- Dir.chdir(File.dirname(project_file_path)) do
47
- `git reset #{project_file_path}`
48
- end
49
- FileUtils.cp(File.join(project_in_temp_directory.path, "project.pbxproj"), project_file_path)
42
+ apply_change_and_copy_to_original_path(project_in_temp_directory, change, project_file_path)
43
+ end
50
44
 
51
- # Some of the metadata in a `pbxproj` file include a part of the name of the directory it's
52
- # inside. The modified project is stored in a temporary directory and then copied to the
53
- # original path, therefore its metadata is incorrect. To fix this, the project at the original
54
- # path is opened and saved.
55
- Xcodeproj::Project.open(File.dirname(project_file_path)).save
45
+ # Merges the changes done between `theirs_project_path` and `base_project_path` to the file at
46
+ # `ours_project_path`. The files may not be at the original path, and therefore the
47
+ # `original_project_path` is required in order for the project metadata to be written properly.
48
+ #
49
+ # @param [String] base_project_path
50
+ # Path to the base version of the project.
51
+ #
52
+ # @param [String] ours_project_path
53
+ # Path to ours version of the project.
54
+ #
55
+ # @param [String] theirs_project_path
56
+ # Path to theirs version of the project.
57
+ #
58
+ # @param [String] original_project_path
59
+ # Path to the original path of the file.
60
+ #
61
+ # @raise [RuntimeError]
62
+ # If there was an error applying the change to the project.
63
+ #
64
+ # @return [void]
65
+ def three_way_merge(base_project_path, ours_project_path, theirs_project_path,
66
+ original_project_path)
67
+ original_directory_name = File.basename(File.dirname(original_project_path))
68
+ base_temporary_project =
69
+ copy_project_to_temporary_path_in_directory_with_name(base_project_path,
70
+ original_directory_name)
71
+ ours_temporary_project =
72
+ copy_project_to_temporary_path_in_directory_with_name(ours_project_path,
73
+ original_directory_name)
74
+ theirs_temporary_project =
75
+ copy_project_to_temporary_path_in_directory_with_name(theirs_project_path,
76
+ original_directory_name)
77
+
78
+ change =
79
+ Xcodeproj::Differ.project_diff(theirs_temporary_project, base_temporary_project,
80
+ :added, :removed)
81
+
82
+ apply_change_and_copy_to_original_path(ours_temporary_project, change, ours_project_path)
56
83
  end
57
84
 
58
85
  private
59
86
 
60
- def validate_project(project_file_path)
61
- if File.extname(project_file_path) != ".pbxproj"
62
- raise ArgumentError, "Wrong file extension, please provide file with extension .pbxproj\""
63
- end
87
+ PROJECT_FILE_NAME = "project.pbxproj"
64
88
 
89
+ def apply_change_and_copy_to_original_path(project, change, original_project_file_path)
90
+ apply_change_to_project(project, change)
91
+ project.save
92
+ FileUtils.cp(File.join(project.path, PROJECT_FILE_NAME), original_project_file_path)
93
+ end
94
+
95
+ def validate_project(project_file_path)
65
96
  unless File.exist?(project_file_path)
66
97
  raise ArgumentError, "File '#{project_file_path}' doesn't exist"
67
98
  end
68
99
 
100
+ if File.extname(project_file_path) != ".pbxproj"
101
+ raise ArgumentError, "Wrong file extension, please provide file with extension .pbxproj\""
102
+ end
103
+
69
104
  Dir.chdir(File.dirname(project_file_path)) do
70
105
  unless file_has_base_ours_and_theirs_versions?(project_file_path)
71
106
  raise ArgumentError, "File '#{project_file_path}' doesn't have conflicts, or a 3-way " \
@@ -74,8 +109,19 @@ module Kintsugi
74
109
  end
75
110
  end
76
111
 
112
+ def copy_project_to_temporary_path_in_directory_with_name(project_file_path, directory_name)
113
+ temp_directory_name = File.join(Dir.mktmpdir, directory_name)
114
+ Dir.mkdir(temp_directory_name)
115
+ temp_project_file_path = File.join(temp_directory_name, PROJECT_FILE_NAME)
116
+ FileUtils.cp(project_file_path, temp_project_file_path)
117
+ Xcodeproj::Project.open(File.dirname(temp_project_file_path))
118
+ end
119
+
77
120
  def open_project_of_current_commit_in_temporary_directory(project_file_path)
78
- temp_project_file_path = File.join(Dir.mktmpdir, "project.pbxproj")
121
+ project_directory_name = File.basename(File.dirname(project_file_path))
122
+ temp_directory_name = File.join(Dir.mktmpdir, project_directory_name)
123
+ Dir.mkdir(temp_directory_name)
124
+ temp_project_file_path = File.join(temp_directory_name, PROJECT_FILE_NAME)
79
125
  Dir.chdir(File.dirname(project_file_path)) do
80
126
  `git show HEAD:./project.pbxproj > #{temp_project_file_path}`
81
127
  end
@@ -99,11 +145,11 @@ module Kintsugi
99
145
 
100
146
  def change_of_conflicting_commit_with_parent(project_file_path)
101
147
  Dir.chdir(File.dirname(project_file_path)) do
102
- conflicting_commit_project_file_path = File.join(Dir.mktmpdir, "project.pbxproj")
103
- `git show :3:./project.pbxproj > #{conflicting_commit_project_file_path}`
148
+ conflicting_commit_project_file_path = File.join(Dir.mktmpdir, PROJECT_FILE_NAME)
149
+ `git show :3:./#{PROJECT_FILE_NAME} > #{conflicting_commit_project_file_path}`
104
150
 
105
- conflicting_commit_parent_project_file_path = File.join(Dir.mktmpdir, "project.pbxproj")
106
- `git show :1:./project.pbxproj > #{conflicting_commit_parent_project_file_path}`
151
+ conflicting_commit_parent_project_file_path = File.join(Dir.mktmpdir, PROJECT_FILE_NAME)
152
+ `git show :1:./#{PROJECT_FILE_NAME} > #{conflicting_commit_parent_project_file_path}`
107
153
 
108
154
  conflicting_commit_project = Xcodeproj::Project.open(
109
155
  File.dirname(conflicting_commit_project_file_path)
@@ -191,14 +191,15 @@ module Kintsugi
191
191
  (old_value || {}).reject do |key, value|
192
192
  if value != change[key]
193
193
  raise "Trying to remove value #{change[key]} of hash with key #{key} but it changed " \
194
- "to #{value}."
194
+ "to #{value}. This is considered a conflict that should be resolved manually."
195
195
  end
196
196
 
197
197
  change.key?(key)
198
198
  end
199
199
  when String
200
- if old_value != change
201
- raise "Value changed from #{old_value} to #{change}."
200
+ if old_value != change && !old_value.nil?
201
+ raise "Trying to remove value #{change}, but the existing value is #{old_value}. This " \
202
+ "is considered a conflict that should be resolved manually."
202
203
  end
203
204
 
204
205
  nil
@@ -0,0 +1,105 @@
1
+ # Copyright (c) 2021 Lightricks. All rights reserved.
2
+ # Created by Ben Yohay.
3
+ # frozen_string_literal: true
4
+
5
+ require "optparse"
6
+
7
+ require_relative "version"
8
+
9
+ module Kintsugi
10
+ # Class resposible for creating the logic of various options for Kintsugi CLI.
11
+ class CLI
12
+ # Subcommands of Kintsugi CLI.
13
+ attr_reader :subcommands
14
+
15
+ # Root command Kintsugi CLI.
16
+ attr_reader :root_command
17
+
18
+ def initialize
19
+ @subcommands = {
20
+ "driver" => create_driver_subcommand
21
+ }.freeze
22
+ @root_command = create_root_command
23
+ end
24
+
25
+ private
26
+
27
+ Command = Struct.new(:option_parser, :action, :description, keyword_init: true)
28
+
29
+ def create_driver_subcommand
30
+ driver_option_parser =
31
+ OptionParser.new do |opts|
32
+ opts.banner = "Usage: kintsugi driver BASE OURS THEIRS ORIGINAL_FILE_PATH\n" \
33
+ "Uses Kintsugi as a Git merge driver. Parameters " \
34
+ "should be the path to base version of the file, path to ours version, path to " \
35
+ "theirs version, and the original file path."
36
+
37
+ opts.on("-h", "--help", "Prints this help") do
38
+ puts opts
39
+ exit
40
+ end
41
+ end
42
+
43
+ driver_action = lambda { |_, arguments, option_parser|
44
+ if arguments.count != 4
45
+ puts "Incorrect number of arguments to 'driver' subcommand\n\n"
46
+ puts option_parser
47
+ exit(1)
48
+ end
49
+ Kintsugi.three_way_merge(arguments[0], arguments[1], arguments[2], arguments[3])
50
+ }
51
+
52
+ Command.new(
53
+ option_parser: driver_option_parser,
54
+ action: driver_action,
55
+ description: "3-way merge compatible with Git merge driver"
56
+ )
57
+ end
58
+
59
+ def create_root_command
60
+ root_option_parser = OptionParser.new do |opts|
61
+ opts.banner = "Kintsugi, version #{Version::STRING}\n" \
62
+ "Copyright (c) 2021 Lightricks\n\n" \
63
+ "Usage: kintsugi <pbxproj_filepath> [options]\n" \
64
+ " kintsugi <subcommand> [options]"
65
+
66
+ opts.separator ""
67
+ opts.on("--changes-output-path=PATH", "Path to which changes applied to the project are " \
68
+ "written in JSON format. Used for debug purposes.")
69
+
70
+ opts.on("-h", "--help", "Prints this help") do
71
+ puts opts
72
+ exit
73
+ end
74
+
75
+ opts.on("-v", "--version", "Prints version") do
76
+ puts Version::STRING
77
+ exit
78
+ end
79
+
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}")
84
+ end
85
+
86
+ root_action = lambda { |options, arguments, option_parser|
87
+ if arguments.count != 1
88
+ puts "Incorrect number of arguments\n\n"
89
+ puts option_parser
90
+ exit(1)
91
+ end
92
+
93
+ project_file_path = File.expand_path(arguments[0])
94
+ Kintsugi.resolve_conflicts(project_file_path, options[:"changes-output-path"])
95
+ puts "Resolved conflicts successfully"
96
+ }
97
+
98
+ Command.new(
99
+ option_parser: root_option_parser,
100
+ action: root_action,
101
+ description: nil
102
+ )
103
+ end
104
+ end
105
+ 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.1.1"
6
+ STRING = "0.2.0"
7
7
  end
8
8
  end
@@ -770,6 +770,25 @@ describe Kintsugi, :apply_change_to_project do
770
770
  expect(base_project).to be_equivalent_to_project(theirs_project)
771
771
  end
772
772
 
773
+ it "removes attribute target changes from a project it was removed from already" do
774
+ base_project.root_object.attributes["TargetAttributes"] =
775
+ {"foo" => {"LastSwiftMigration" => "1140"}}
776
+ base_project.save
777
+
778
+ theirs_project = create_copy_of_project(base_project.path, "theirs")
779
+ theirs_project.root_object.attributes["TargetAttributes"]["foo"] = {}
780
+
781
+ ours_project = create_copy_of_project(base_project.path, "ours")
782
+ ours_project.root_object.attributes["TargetAttributes"]["foo"] = {}
783
+
784
+ changes_to_apply = get_diff(theirs_project, base_project)
785
+
786
+ described_class.apply_change_to_project(ours_project, changes_to_apply)
787
+ ours_project.save
788
+
789
+ expect(ours_project).to be_equivalent_to_project(theirs_project)
790
+ end
791
+
773
792
  it "identifies subproject added in separate times" do
774
793
  framework_filename = "baz"
775
794
 
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.1.1
4
+ version: 0.2.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: 2021-06-29 00:00:00.000000000 Z
11
+ date: 2021-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: xcodeproj
@@ -116,6 +116,7 @@ files:
116
116
  - kintsugi.gemspec
117
117
  - lib/kintsugi.rb
118
118
  - lib/kintsugi/apply_change_to_project.rb
119
+ - lib/kintsugi/cli.rb
119
120
  - lib/kintsugi/utils.rb
120
121
  - lib/kintsugi/version.rb
121
122
  - lib/kintsugi/xcodeproj_extensions.rb