ast-merge-git 7.1.7 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2943a67a2400053c0f535008977a3aad094685b0fd2c21423694a7e611176a2b
4
- data.tar.gz: a734402394056e68c03fab8a40e23b3a6be091e98f77275921202d15e1915c37
3
+ metadata.gz: d73aa6351e792720651576508f7e9e85c3052648de6ba18fdd6889398f60d804
4
+ data.tar.gz: 01d368cc15f274ceabbd49805d52f05db59011304d68756e6176b4b3a6b2d465
5
5
  SHA512:
6
- metadata.gz: 13a17a48ce7ffed824dd30841e4a1f8f236aa8237b3ca38020db745ac4c12ffbe47ebdbc69ff290725776cafd71d2fa1b2c67c251b87749a7f36a9d06393b060
7
- data.tar.gz: a70e57f231ac0fc014aa0b6e49eccfc14a0cab8aefbd8a3ae9d781da8a72253fa6af584c26edb13d6208e01d8162cc67e8c4413610698a9050edf60ab8fc5014
6
+ metadata.gz: e66bd81ebaff38eb315d2b5d8164d00d48baadb3bf92ce055e60ca8d2b7845b3627a07176886681c3e4f1dca5c473ae782c29efb00af42637dd9e8624cd61a2f
7
+ data.tar.gz: e5c259116fce46cad58a446548d01761b04ee83b8c73f97954d56256cfb87196a4127a423a46885dd9053e3b83b56837b73dd530122176f5da8a96f4a356c96b
checksums.yaml.gz.sig CHANGED
Binary file
data/exe/ast-merge-git CHANGED
@@ -3,6 +3,11 @@
3
3
 
4
4
  require 'ast/merge/git'
5
5
 
6
+ if ARGV.first == 'benchmark-provider-session'
7
+ Ast::Merge::Git::BenchmarkAdapter.serve
8
+ exit 0
9
+ end
10
+
6
11
  if %w[benchmark-provider-diff benchmark-provider-merge2].include?(ARGV.first)
7
12
  require 'json'
8
13
 
@@ -52,17 +57,23 @@ if ARGV.first == 'benchmark'
52
57
  profile: 'micro',
53
58
  changed_paths: [],
54
59
  tmp_root: File.expand_path('../tmp/benchmark', __dir__),
55
- timeout: 30
60
+ timeout: 30,
61
+ iterations: 1,
62
+ workers: 1
56
63
  }
57
64
  parser = OptionParser.new do |opts|
58
- 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]'
59
66
  opts.on('--corpus PATH') { |value| options[:corpus] = value }
60
67
  opts.on('--profile PROFILE') { |value| options[:profile] = value }
61
68
  opts.on('--changed-path PATH') { |value| options[:changed_paths] << value }
62
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 }
63
72
  opts.on('--mergiraf PATH') { |value| options[:mergiraf] = value }
64
73
  opts.on('--tmp-root PATH') { |value| options[:tmp_root] = value }
65
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 }
66
77
  end
67
78
 
68
79
  begin
@@ -83,9 +94,27 @@ if ARGV.first == 'benchmark'
83
94
  driver_path: driver,
84
95
  tmp_root: options[:tmp_root],
85
96
  timeout: options[:timeout],
86
- competitor_paths: options[:mergiraf] ? { 'mergiraf' => options[:mergiraf] } : {}
97
+ competitor_paths: {
98
+ 'git' => options[:git],
99
+ 'mergiraf' => options[:mergiraf]
100
+ }.compact,
101
+ workers: options[:workers],
102
+ adapter_descriptor: options[:adapter_descriptor]
87
103
  ).run(profile: options[:profile], changed_paths: options[:changed_paths])
88
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
+ )
89
118
  else
90
119
  warn parser
91
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