ast-merge-git 7.1.6 → 7.1.8
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
- checksums.yaml.gz.sig +0 -0
- data/exe/ast-merge-git +51 -11
- data/lib/ast/merge/git/benchmark_adapter.rb +172 -0
- data/lib/ast/merge/git/local_benchmark.rb +885 -68
- data/lib/ast/merge/git/version.rb +1 -1
- data/lib/ast/merge/git.rb +2 -0
- data/sig/ast/merge/git.rbs +36 -1
- data.tar.gz.sig +0 -0
- metadata +27 -12
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d73aa6351e792720651576508f7e9e85c3052648de6ba18fdd6889398f60d804
|
|
4
|
+
data.tar.gz: 01d368cc15f274ceabbd49805d52f05db59011304d68756e6176b4b3a6b2d465
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e66bd81ebaff38eb315d2b5d8164d00d48baadb3bf92ce055e60ca8d2b7845b3627a07176886681c3e4f1dca5c473ae782c29efb00af42637dd9e8624cd61a2f
|
|
7
|
+
data.tar.gz: e5c259116fce46cad58a446548d01761b04ee83b8c73f97954d56256cfb87196a4127a423a46885dd9053e3b83b56837b73dd530122176f5da8a96f4a356c96b
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/exe/ast-merge-git
CHANGED
|
@@ -3,31 +3,47 @@
|
|
|
3
3
|
|
|
4
4
|
require 'ast/merge/git'
|
|
5
5
|
|
|
6
|
-
if ARGV.first == 'benchmark-provider-
|
|
6
|
+
if ARGV.first == 'benchmark-provider-session'
|
|
7
|
+
Ast::Merge::Git::BenchmarkAdapter.serve
|
|
8
|
+
exit 0
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
if %w[benchmark-provider-diff benchmark-provider-merge2].include?(ARGV.first)
|
|
7
12
|
require 'json'
|
|
8
13
|
|
|
9
14
|
begin
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
command = ARGV.first
|
|
16
|
+
first_path, second_path, path_name = ARGV.drop(1)
|
|
17
|
+
raise ArgumentError, 'two source paths and path name are required' unless first_path && second_path && path_name
|
|
12
18
|
|
|
19
|
+
operation, role_sources = if command == 'benchmark-provider-diff'
|
|
20
|
+
[:diff2, {
|
|
21
|
+
before_source: File.binread(first_path),
|
|
22
|
+
after_source: File.binread(second_path)
|
|
23
|
+
}]
|
|
24
|
+
else
|
|
25
|
+
[:merge2, {
|
|
26
|
+
incoming_source: File.binread(first_path),
|
|
27
|
+
current_source: File.binread(second_path)
|
|
28
|
+
}]
|
|
29
|
+
end
|
|
13
30
|
require ENV.fetch('AST_MERGE_REQUIRE')
|
|
14
31
|
result = Ast::Merge.dispatch_provider(
|
|
15
|
-
|
|
32
|
+
operation,
|
|
16
33
|
{
|
|
17
34
|
provider_id: ENV.fetch('AST_MERGE_PROVIDER'),
|
|
18
35
|
family: ENV.fetch('AST_MERGE_FAMILY'),
|
|
19
36
|
dialect: ENV.fetch('AST_MERGE_DIALECT'),
|
|
20
37
|
backend: ENV.fetch('AST_MERGE_BACKEND'),
|
|
21
38
|
profile_id: ENV.fetch('AST_MERGE_PROFILE'),
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
path_name: path_name
|
|
39
|
+
path_name: path_name,
|
|
40
|
+
**role_sources
|
|
25
41
|
}
|
|
26
42
|
)
|
|
27
43
|
puts JSON.generate(Ast::Merge.json_ready(result))
|
|
28
44
|
exit(result[:ok] == true ? 0 : 2)
|
|
29
45
|
rescue ArgumentError, Ast::Merge::Error, KeyError, LoadError, SystemCallError => e
|
|
30
|
-
warn "ast-merge-git
|
|
46
|
+
warn "ast-merge-git #{ARGV.first}: #{e.message}"
|
|
31
47
|
exit 2
|
|
32
48
|
end
|
|
33
49
|
end
|
|
@@ -41,17 +57,23 @@ if ARGV.first == 'benchmark'
|
|
|
41
57
|
profile: 'micro',
|
|
42
58
|
changed_paths: [],
|
|
43
59
|
tmp_root: File.expand_path('../tmp/benchmark', __dir__),
|
|
44
|
-
timeout: 30
|
|
60
|
+
timeout: 30,
|
|
61
|
+
iterations: 1,
|
|
62
|
+
workers: 1
|
|
45
63
|
}
|
|
46
64
|
parser = OptionParser.new do |opts|
|
|
47
|
-
opts.banner = 'usage: ast-merge-git benchmark validate|select|run|report [options]'
|
|
65
|
+
opts.banner = 'usage: ast-merge-git benchmark validate|select|run|report|performance [options]'
|
|
48
66
|
opts.on('--corpus PATH') { |value| options[:corpus] = value }
|
|
49
67
|
opts.on('--profile PROFILE') { |value| options[:profile] = value }
|
|
50
68
|
opts.on('--changed-path PATH') { |value| options[:changed_paths] << value }
|
|
51
69
|
opts.on('--driver PATH') { |value| options[:driver] = value }
|
|
70
|
+
opts.on('--adapter-descriptor PATH') { |value| options[:adapter_descriptor] = value }
|
|
71
|
+
opts.on('--git PATH') { |value| options[:git] = value }
|
|
52
72
|
opts.on('--mergiraf PATH') { |value| options[:mergiraf] = value }
|
|
53
73
|
opts.on('--tmp-root PATH') { |value| options[:tmp_root] = value }
|
|
54
74
|
opts.on('--timeout SECONDS', Integer) { |value| options[:timeout] = value }
|
|
75
|
+
opts.on('--iterations COUNT', Integer) { |value| options[:iterations] = value }
|
|
76
|
+
opts.on('--workers COUNT', Integer) { |value| options[:workers] = value }
|
|
55
77
|
end
|
|
56
78
|
|
|
57
79
|
begin
|
|
@@ -72,9 +94,27 @@ if ARGV.first == 'benchmark'
|
|
|
72
94
|
driver_path: driver,
|
|
73
95
|
tmp_root: options[:tmp_root],
|
|
74
96
|
timeout: options[:timeout],
|
|
75
|
-
competitor_paths:
|
|
97
|
+
competitor_paths: {
|
|
98
|
+
'git' => options[:git],
|
|
99
|
+
'mergiraf' => options[:mergiraf]
|
|
100
|
+
}.compact,
|
|
101
|
+
workers: options[:workers],
|
|
102
|
+
adapter_descriptor: options[:adapter_descriptor]
|
|
76
103
|
).run(profile: options[:profile], changed_paths: options[:changed_paths])
|
|
77
104
|
command == 'report' ? Ast::Merge::Git::LocalBenchmarkReport.build(run) : run
|
|
105
|
+
when 'performance'
|
|
106
|
+
driver = options[:driver] || Gem.bin_path('ast-merge-git', 'ast-merge-git')
|
|
107
|
+
Ast::Merge::Git::LocalBenchmarkRunner.new(
|
|
108
|
+
benchmark: benchmark,
|
|
109
|
+
driver_path: driver,
|
|
110
|
+
tmp_root: options[:tmp_root],
|
|
111
|
+
timeout: options[:timeout],
|
|
112
|
+
adapter_descriptor: options[:adapter_descriptor]
|
|
113
|
+
).performance(
|
|
114
|
+
profile: options[:profile],
|
|
115
|
+
changed_paths: options[:changed_paths],
|
|
116
|
+
iterations: options[:iterations]
|
|
117
|
+
)
|
|
78
118
|
else
|
|
79
119
|
warn parser
|
|
80
120
|
exit 2
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Ast
|
|
6
|
+
module Merge
|
|
7
|
+
module Git
|
|
8
|
+
# Versioned JSONL protocol for repeated benchmark operations in one Ruby process.
|
|
9
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/ModuleLength, Metrics/PerceivedComplexity -- protocol validation and dispatch remain explicit
|
|
10
|
+
module BenchmarkAdapter
|
|
11
|
+
REQUEST_SCHEMA = 'structuredmerge.benchmark.adapter-request/v1'
|
|
12
|
+
RESPONSE_SCHEMA = 'structuredmerge.benchmark.adapter-response/v1'
|
|
13
|
+
OPERATIONS = %w[diff2 merge2 merge3].freeze
|
|
14
|
+
SOURCE_ROLES = {
|
|
15
|
+
'diff2' => %w[before after],
|
|
16
|
+
'merge2' => %w[incoming current],
|
|
17
|
+
'merge3' => %w[base ours theirs]
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def serve(input: $stdin, output: $stdout)
|
|
23
|
+
input.each_line do |line|
|
|
24
|
+
next if line.strip.empty?
|
|
25
|
+
|
|
26
|
+
response = execute(JSON.parse(line))
|
|
27
|
+
output.puts(JSON.generate(response))
|
|
28
|
+
output.flush
|
|
29
|
+
rescue JSON::ParserError => e
|
|
30
|
+
output.puts(JSON.generate(error_response(nil, e)))
|
|
31
|
+
output.flush
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def execute(request)
|
|
36
|
+
started = monotonic_nanoseconds
|
|
37
|
+
operation, selector, sources = validate_request(request)
|
|
38
|
+
require selector.fetch('require')
|
|
39
|
+
result = dispatch(operation, selector, sources, request.fetch('path_name'))
|
|
40
|
+
|
|
41
|
+
response_for(request.fetch('request_id'), operation, result, monotonic_nanoseconds - started)
|
|
42
|
+
rescue ArgumentError, Ast::Merge::Error, KeyError, LoadError => e
|
|
43
|
+
error_response(request.is_a?(Hash) ? request['request_id'] : nil, e, started: started)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_request(request)
|
|
47
|
+
raise ArgumentError, 'request must be an object' unless request.is_a?(Hash)
|
|
48
|
+
raise ArgumentError, 'unsupported request schema' unless request['schema_version'] == REQUEST_SCHEMA
|
|
49
|
+
|
|
50
|
+
operation = request.fetch('operation')
|
|
51
|
+
raise ArgumentError, "unsupported operation: #{operation.inspect}" unless OPERATIONS.include?(operation)
|
|
52
|
+
|
|
53
|
+
selector = request.fetch('selector')
|
|
54
|
+
raise ArgumentError, 'selector must be an object' unless selector.is_a?(Hash)
|
|
55
|
+
|
|
56
|
+
%w[provider_id family dialect backend profile require].each { |key| selector.fetch(key) }
|
|
57
|
+
encoded_sources = request.fetch('sources')
|
|
58
|
+
raise ArgumentError, 'sources must be an object' unless encoded_sources.is_a?(Hash)
|
|
59
|
+
|
|
60
|
+
sources = SOURCE_ROLES.fetch(operation).to_h do |role|
|
|
61
|
+
[role, decode_source(encoded_sources.fetch(role))]
|
|
62
|
+
end
|
|
63
|
+
[operation, selector, sources]
|
|
64
|
+
rescue ArgumentError => e
|
|
65
|
+
raise ArgumentError, "invalid base64 source: #{e.message}" if e.message.include?('invalid base64')
|
|
66
|
+
|
|
67
|
+
raise
|
|
68
|
+
end
|
|
69
|
+
private_class_method :validate_request
|
|
70
|
+
|
|
71
|
+
def dispatch(operation, selector, sources, path_name)
|
|
72
|
+
request = {
|
|
73
|
+
provider_id: selector.fetch('provider_id'),
|
|
74
|
+
family: selector.fetch('family'),
|
|
75
|
+
dialect: selector.fetch('dialect'),
|
|
76
|
+
backend: selector.fetch('backend'),
|
|
77
|
+
profile_id: selector.fetch('profile'),
|
|
78
|
+
path_name: path_name
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case operation
|
|
82
|
+
when 'diff2'
|
|
83
|
+
Ast::Merge.dispatch_provider(
|
|
84
|
+
:diff2,
|
|
85
|
+
request.merge(before_source: sources.fetch('before'), after_source: sources.fetch('after'))
|
|
86
|
+
)
|
|
87
|
+
when 'merge2'
|
|
88
|
+
Ast::Merge.dispatch_provider(
|
|
89
|
+
:merge2,
|
|
90
|
+
request.merge(incoming_source: sources.fetch('incoming'), current_source: sources.fetch('current'))
|
|
91
|
+
)
|
|
92
|
+
when 'merge3'
|
|
93
|
+
Ast::Merge::Git.merge3(
|
|
94
|
+
request.merge(
|
|
95
|
+
base_source: sources.fetch('base'),
|
|
96
|
+
ours_source: sources.fetch('ours'),
|
|
97
|
+
theirs_source: sources.fetch('theirs')
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
private_class_method :dispatch
|
|
103
|
+
|
|
104
|
+
def response_for(request_id, operation, result, duration_ns)
|
|
105
|
+
output = operation_output(operation, result)
|
|
106
|
+
{
|
|
107
|
+
'schema_version' => RESPONSE_SCHEMA,
|
|
108
|
+
'request_id' => request_id,
|
|
109
|
+
'process_id' => Process.pid,
|
|
110
|
+
'operation' => operation,
|
|
111
|
+
'status' => operation_status(operation, result),
|
|
112
|
+
'duration_ns' => duration_ns,
|
|
113
|
+
'output_base64' => encode_source(output),
|
|
114
|
+
'result' => Ast::Merge.json_ready(
|
|
115
|
+
result.slice(:ok, :changes, :conflicts, :diagnostics, :verification)
|
|
116
|
+
),
|
|
117
|
+
'stderr' => ''
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
private_class_method :response_for
|
|
121
|
+
|
|
122
|
+
def operation_output(operation, result)
|
|
123
|
+
case operation
|
|
124
|
+
when 'diff2'
|
|
125
|
+
JSON.generate(Ast::Merge.json_ready(result.fetch(:changes, [])))
|
|
126
|
+
when 'merge2'
|
|
127
|
+
result[:ok] ? result.fetch(:output) : ''
|
|
128
|
+
when 'merge3'
|
|
129
|
+
result[:ok] ? result.fetch(:merged_source) : result.fetch(:conflicted_source, '').to_s
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
private_class_method :operation_output
|
|
133
|
+
|
|
134
|
+
def operation_status(operation, result)
|
|
135
|
+
return 0 if result[:ok]
|
|
136
|
+
return 1 if operation == 'merge3' && result.fetch(:conflicts, []).any?
|
|
137
|
+
|
|
138
|
+
2
|
|
139
|
+
end
|
|
140
|
+
private_class_method :operation_status
|
|
141
|
+
|
|
142
|
+
def encode_source(source)
|
|
143
|
+
[source.b].pack('m0')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def decode_source(encoded)
|
|
147
|
+
encoded.unpack1('m0')
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def error_response(request_id, error, started: nil)
|
|
151
|
+
{
|
|
152
|
+
'schema_version' => RESPONSE_SCHEMA,
|
|
153
|
+
'request_id' => request_id,
|
|
154
|
+
'process_id' => Process.pid,
|
|
155
|
+
'status' => 2,
|
|
156
|
+
'duration_ns' => started ? monotonic_nanoseconds - started : 0,
|
|
157
|
+
'output_base64' => '',
|
|
158
|
+
'result' => {},
|
|
159
|
+
'stderr' => error.message
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
private_class_method :error_response
|
|
163
|
+
|
|
164
|
+
def monotonic_nanoseconds
|
|
165
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
|
166
|
+
end
|
|
167
|
+
private_class_method :monotonic_nanoseconds
|
|
168
|
+
end
|
|
169
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/ModuleLength, Metrics/PerceivedComplexity
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|