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 +4 -4
- data/Gemfile +2 -0
- data/README.md +21 -0
- data/bin/kintsugi +16 -37
- data/lib/kintsugi.rb +68 -22
- data/lib/kintsugi/apply_change_to_project.rb +4 -3
- data/lib/kintsugi/cli.rb +105 -0
- data/lib/kintsugi/version.rb +1 -1
- data/spec/kintsugi_apply_change_to_project_spec.rb +19 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d58044d1f7c23e62f290c33defa4d9b3e95c0012b62754997a7c628d96e4358
|
4
|
+
data.tar.gz: 6d63f1e0cf56c131fe7222c9d316f4f152310077fa2a9f4e386fba54ab82a340
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1aa18d3fd3456e1aa44f6497bc3e2f4e4877836e8722887050725012591e43b0086efa490df8eab8f1b63800f900897365a81c87935fb68341b1727e4c432e99
|
7
|
+
data.tar.gz: f8aa152a98e35b9620a3c855eb010781a7fb65e843c6323ea4d14fb27ba414416c2e585273f797e0d8698dfae67d9f7a963e626cc2a2f40ca0d6cce0707cfc0d
|
data/Gemfile
CHANGED
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/
|
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
|
-
|
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
|
30
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
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
|
-
|
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,
|
103
|
-
`git show :3
|
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,
|
106
|
-
`git show :1
|
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 "
|
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
|
data/lib/kintsugi/cli.rb
ADDED
@@ -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
|
data/lib/kintsugi/version.rb
CHANGED
@@ -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.
|
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-
|
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
|