kintsugi 0.1.1 → 0.2.0

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