ast-merge-git 7.1.1 → 7.1.4
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/README.md +24 -14
- data/exe/ast-merge-git +149 -0
- data/lib/ast/merge/git/corpus.rb +580 -0
- data/lib/ast/merge/git/local_benchmark.rb +1199 -0
- data/lib/ast/merge/git/version.rb +1 -1
- data/lib/ast/merge/git.rb +196 -343
- data/sig/ast/merge/git.rbs +106 -0
- data.tar.gz.sig +0 -0
- metadata +26 -37
- metadata.gz.sig +3 -2
|
@@ -6,7 +6,7 @@ module Ast
|
|
|
6
6
|
# Version namespace for this gem.
|
|
7
7
|
module Version
|
|
8
8
|
# Current gem version.
|
|
9
|
-
VERSION = '7.1.
|
|
9
|
+
VERSION = '7.1.4'
|
|
10
10
|
end
|
|
11
11
|
# Current gem version exposed at the traditional constant location.
|
|
12
12
|
VERSION = Version::VERSION # Traditional Constant Location
|
data/lib/ast/merge/git.rb
CHANGED
|
@@ -1,412 +1,265 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'ast/merge'
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
require_relative 'git/corpus'
|
|
5
|
+
require_relative 'git/local_benchmark'
|
|
6
6
|
|
|
7
7
|
module Ast
|
|
8
8
|
module Merge
|
|
9
|
+
# Adapts Git merge-driver roles and outcomes to the portable provider API.
|
|
10
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ModuleLength -- Git protocol translation is one cohesive adapter boundary
|
|
9
11
|
module Git
|
|
10
12
|
PACKAGE_NAME = 'ast-merge-git'
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
EXIT_SUCCESS = 0
|
|
14
|
+
EXIT_CONFLICT = 1
|
|
15
|
+
EXIT_ERROR = 2
|
|
16
|
+
CONFLICT_POLICIES = %i[write leave_ours].freeze
|
|
17
|
+
ADAPTER_ERROR_CATEGORIES = %i[
|
|
18
|
+
file_error
|
|
19
|
+
invalid_provider_output
|
|
20
|
+
invalid_provider_result
|
|
21
|
+
].freeze
|
|
13
22
|
|
|
14
23
|
module_function
|
|
15
24
|
|
|
16
25
|
def merge3(request)
|
|
17
26
|
normalized = normalize_request(request)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
response(
|
|
23
|
-
ok: false,
|
|
24
|
-
request: normalized,
|
|
25
|
-
diagnostics: [{
|
|
26
|
-
severity: 'error',
|
|
27
|
-
category: 'unsupported_feature',
|
|
28
|
-
message: 'ast-merge-git currently supports only json merge3.'
|
|
29
|
-
}]
|
|
30
|
-
)
|
|
31
|
-
end
|
|
27
|
+
provider_result = Ast::Merge.dispatch_provider(:merge3, provider_request(normalized))
|
|
28
|
+
adapt_provider_result(provider_result)
|
|
29
|
+
rescue Ast::Merge::ProviderContract::Error, ArgumentError, KeyError => e
|
|
30
|
+
adapt_provider_result(adapter_failure(:invalid_provider_result, e.message))
|
|
32
31
|
end
|
|
33
32
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
path: ''
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if merge.fetch(:conflicts).empty?
|
|
46
|
-
merged_source = "#{JSON.pretty_generate(merge.fetch(:value))}\n"
|
|
47
|
-
return response(
|
|
48
|
-
ok: true,
|
|
49
|
-
request: request,
|
|
50
|
-
merged_source: merged_source,
|
|
51
|
-
change_classifications: merge.fetch(:change_classifications),
|
|
52
|
-
reparse_after_render: reparse_json_source(merged_source),
|
|
53
|
-
formatting_preservation: {
|
|
54
|
-
line_diff_score: 1.0,
|
|
55
|
-
character_diff_score: 1.0
|
|
56
|
-
}
|
|
33
|
+
def merge_files(base_path:, ours_path:, theirs_path:, conflict_policy: :write, **request)
|
|
34
|
+
policy = normalize_conflict_policy(conflict_policy)
|
|
35
|
+
result = merge3(
|
|
36
|
+
request.merge(
|
|
37
|
+
base_source: File.binread(base_path),
|
|
38
|
+
ours_source: File.binread(ours_path),
|
|
39
|
+
theirs_source: File.binread(theirs_path),
|
|
40
|
+
path_name: request[:path_name] || ours_path
|
|
57
41
|
)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
render_conflicted_json(request, merge)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def parse_json_merge3_inputs(request)
|
|
64
|
-
dialect = request[:dialect].to_s.empty? ? 'json' : request[:dialect].to_s
|
|
65
|
-
{
|
|
66
|
-
ok: true,
|
|
67
|
-
base: Json::Merge.json_value_for_source(request.fetch(:base_source), dialect: dialect),
|
|
68
|
-
ours: Json::Merge.json_value_for_source(request.fetch(:ours_source), dialect: dialect),
|
|
69
|
-
theirs: Json::Merge.json_value_for_source(request.fetch(:theirs_source), dialect: dialect)
|
|
70
|
-
}
|
|
71
|
-
rescue Json::Merge::ParseError => e
|
|
72
|
-
source_role = parse_error_role(request, dialect)
|
|
73
|
-
response(
|
|
74
|
-
ok: false,
|
|
75
|
-
request: request,
|
|
76
|
-
diagnostics: [{
|
|
77
|
-
severity: 'error',
|
|
78
|
-
category: 'parse_error',
|
|
79
|
-
message: "#{source_role} parse error: #{e.message}"
|
|
80
|
-
}]
|
|
81
42
|
)
|
|
43
|
+
write_result(result, ours_path, policy)
|
|
44
|
+
rescue SystemCallError => e
|
|
45
|
+
file_failure = adapt_provider_result(adapter_failure(:file_error, e.message))
|
|
46
|
+
file_failure.merge(git: git_report(file_failure, ours_path, policy, output_written: false))
|
|
82
47
|
end
|
|
83
48
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
49
|
+
def run(argv, env: ENV, stderr: $stderr)
|
|
50
|
+
load_provider_requirements(env['AST_MERGE_REQUIRE'])
|
|
51
|
+
request = command_request(argv, env)
|
|
52
|
+
result = merge_files(**request)
|
|
53
|
+
emit_diagnostics(result, stderr)
|
|
54
|
+
result.dig(:git, :exit_code)
|
|
55
|
+
rescue ArgumentError, LoadError => e
|
|
56
|
+
stderr.puts("#{PACKAGE_NAME}: #{e.message}")
|
|
57
|
+
EXIT_ERROR
|
|
91
58
|
end
|
|
92
59
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return merge_success(theirs, change: classify_json_change(path, base, ours, theirs))
|
|
98
|
-
elsif base == theirs
|
|
99
|
-
return merge_success(ours, change: classify_json_change(path, base, ours, theirs))
|
|
100
|
-
end
|
|
60
|
+
def git_exit_code(result)
|
|
61
|
+
return EXIT_SUCCESS if result[:ok]
|
|
62
|
+
return EXIT_ERROR if adapter_error?(result)
|
|
63
|
+
return EXIT_CONFLICT unless result.fetch(:conflicts, []).empty?
|
|
101
64
|
|
|
102
|
-
|
|
103
|
-
return merge_json_objects(base, ours, theirs, path: path)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
conflict = json_conflict(path, base, ours, theirs)
|
|
107
|
-
{
|
|
108
|
-
value: ours.equal?(MISSING) ? theirs : ours,
|
|
109
|
-
conflicts: [conflict],
|
|
110
|
-
change_classifications: [conflict.fetch(:change_classification)]
|
|
111
|
-
}
|
|
65
|
+
EXIT_ERROR
|
|
112
66
|
end
|
|
113
67
|
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
change_classifications = []
|
|
118
|
-
ordered_keys(base, ours, theirs).each do |key|
|
|
119
|
-
child_path = "#{path}/#{key}"
|
|
120
|
-
child = merge_json_values(
|
|
121
|
-
base.fetch(key, MISSING),
|
|
122
|
-
ours.fetch(key, MISSING),
|
|
123
|
-
theirs.fetch(key, MISSING),
|
|
124
|
-
path: child_path
|
|
125
|
-
)
|
|
126
|
-
merged[key] = child.fetch(:value) unless child.fetch(:value).equal?(MISSING)
|
|
127
|
-
conflicts.concat(child.fetch(:conflicts))
|
|
128
|
-
change_classifications.concat(child.fetch(:change_classifications))
|
|
68
|
+
def adapter_error?(result)
|
|
69
|
+
result.fetch(:diagnostics, []).any? do |item|
|
|
70
|
+
ADAPTER_ERROR_CATEGORIES.include?(item[:category].to_s.to_sym)
|
|
129
71
|
end
|
|
130
|
-
|
|
131
|
-
{
|
|
132
|
-
value: merged,
|
|
133
|
-
conflicts: conflicts,
|
|
134
|
-
change_classifications: change_classifications
|
|
135
|
-
}
|
|
136
72
|
end
|
|
73
|
+
private_class_method :adapter_error?
|
|
74
|
+
|
|
75
|
+
def provider_request(request)
|
|
76
|
+
request.slice(
|
|
77
|
+
:provider_id,
|
|
78
|
+
:family,
|
|
79
|
+
:dialect,
|
|
80
|
+
:backend,
|
|
81
|
+
:profile_id,
|
|
82
|
+
:base_source,
|
|
83
|
+
:ours_source,
|
|
84
|
+
:theirs_source,
|
|
85
|
+
:path_name,
|
|
86
|
+
:labels,
|
|
87
|
+
:conflict_marker_size
|
|
88
|
+
).compact
|
|
89
|
+
end
|
|
90
|
+
private_class_method :provider_request
|
|
137
91
|
|
|
138
|
-
def
|
|
139
|
-
|
|
140
|
-
|
|
92
|
+
def adapt_provider_result(result)
|
|
93
|
+
if result[:ok]
|
|
94
|
+
output = result[:output]
|
|
95
|
+
unless output.is_a?(String)
|
|
96
|
+
return invalid_output_result(result, 'Successful provider result is missing String output.')
|
|
97
|
+
end
|
|
141
98
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
99
|
+
result.merge(
|
|
100
|
+
merged_source: output,
|
|
101
|
+
conflicted_source: nil,
|
|
102
|
+
change_classifications: result.fetch(:changes)
|
|
103
|
+
)
|
|
104
|
+
else
|
|
105
|
+
conflicted = result[:conflicted_output]
|
|
106
|
+
if conflicted && !conflicted.is_a?(String)
|
|
107
|
+
return invalid_output_result(result, 'Provider conflicted_output must be a String when present.')
|
|
108
|
+
end
|
|
145
109
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
110
|
+
result.merge(
|
|
111
|
+
merged_source: nil,
|
|
112
|
+
conflicted_source: conflicted,
|
|
113
|
+
change_classifications: result.fetch(:changes)
|
|
114
|
+
)
|
|
115
|
+
end
|
|
152
116
|
end
|
|
117
|
+
private_class_method :adapt_provider_result
|
|
153
118
|
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
119
|
+
def invalid_output_result(result, message)
|
|
120
|
+
result.merge(
|
|
121
|
+
ok: false,
|
|
122
|
+
output: nil,
|
|
123
|
+
merged_source: nil,
|
|
124
|
+
conflicted_source: nil,
|
|
125
|
+
diagnostics: result.fetch(:diagnostics) + [diagnostic(:invalid_provider_output, message)]
|
|
126
|
+
)
|
|
160
127
|
end
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
128
|
+
private_class_method :invalid_output_result
|
|
129
|
+
|
|
130
|
+
def adapter_failure(category, message)
|
|
131
|
+
Ast::Merge::ProviderResult.build(
|
|
132
|
+
operation: :merge3,
|
|
133
|
+
success: false,
|
|
134
|
+
envelope: {
|
|
135
|
+
provider: { adapter: PACKAGE_NAME },
|
|
136
|
+
diagnostics: [diagnostic(category, message)],
|
|
137
|
+
verification: { base_participated: false }
|
|
138
|
+
}
|
|
139
|
+
)
|
|
168
140
|
end
|
|
141
|
+
private_class_method :adapter_failure
|
|
169
142
|
|
|
170
|
-
def
|
|
171
|
-
category =
|
|
172
|
-
if ours.equal?(MISSING) || theirs.equal?(MISSING)
|
|
173
|
-
'delete_edit'
|
|
174
|
-
else
|
|
175
|
-
'edit_edit'
|
|
176
|
-
end
|
|
143
|
+
def diagnostic(category, message)
|
|
177
144
|
{
|
|
178
|
-
|
|
145
|
+
severity: :error,
|
|
179
146
|
category: category,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
base: base,
|
|
183
|
-
ours: ours,
|
|
184
|
-
theirs: theirs,
|
|
185
|
-
change_classification: classify_json_change(path, base, ours, theirs)
|
|
147
|
+
message: message,
|
|
148
|
+
blocking: true
|
|
186
149
|
}
|
|
187
150
|
end
|
|
151
|
+
private_class_method :diagnostic
|
|
188
152
|
|
|
189
|
-
def
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
153
|
+
def write_result(result, ours_path, policy)
|
|
154
|
+
if missing_required_conflict_output?(result, policy)
|
|
155
|
+
result = invalid_output_result(result, 'Conflict write policy requires String conflicted_output.')
|
|
156
|
+
end
|
|
157
|
+
output = writable_output(result, policy)
|
|
158
|
+
File.binwrite(ours_path, output) if output
|
|
159
|
+
result.merge(git: git_report(result, ours_path, policy, output_written: !output.nil?))
|
|
160
|
+
rescue SystemCallError => e
|
|
161
|
+
failed = result.merge(
|
|
198
162
|
ok: false,
|
|
199
|
-
|
|
200
|
-
conflicted_source: conflicted_source,
|
|
201
|
-
conflicts: conflicts.map { |conflict| conflict.slice(:conflict_id, :category, :path, :message) },
|
|
202
|
-
change_classifications: merge.fetch(:change_classifications),
|
|
203
|
-
diagnostics: [{
|
|
204
|
-
severity: 'error',
|
|
205
|
-
category: MERGE_CONFLICT_CATEGORY,
|
|
206
|
-
message: "#{conflicts.length} unresolved JSON merge conflict(s)."
|
|
207
|
-
}],
|
|
208
|
-
owned_regions: full_file ? [] : [owned_json_region(conflicts.first)],
|
|
209
|
-
reparse_after_render: nil,
|
|
210
|
-
render_strategy: full_file ? 'full_file_conflict_markers' : 'owned_region_conflict_markers'
|
|
163
|
+
diagnostics: result.fetch(:diagnostics) + [diagnostic(:file_error, e.message)]
|
|
211
164
|
)
|
|
165
|
+
failed.merge(git: git_report(failed, ours_path, policy, output_written: false))
|
|
212
166
|
end
|
|
167
|
+
private_class_method :write_result
|
|
213
168
|
|
|
214
|
-
def
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
'{',
|
|
220
|
-
"#{'<' * marker_size} ours",
|
|
221
|
-
"#{JSON.generate(key)}:#{JSON.generate(conflict.fetch(:ours))}",
|
|
222
|
-
"#{'|' * marker_size} base",
|
|
223
|
-
"#{JSON.generate(key)}:#{JSON.generate(conflict.fetch(:base))}",
|
|
224
|
-
'=' * marker_size,
|
|
225
|
-
"#{JSON.generate(key)}:#{JSON.generate(conflict.fetch(:theirs))}",
|
|
226
|
-
"#{'>' * marker_size} theirs",
|
|
227
|
-
'}',
|
|
228
|
-
''
|
|
229
|
-
].join("\n")
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def owned_json_region(conflict)
|
|
233
|
-
{
|
|
234
|
-
owner_path: conflict.fetch(:path),
|
|
235
|
-
node_id: "json:key:#{conflict.fetch(:path).split('/').last}",
|
|
236
|
-
region_kind: 'node',
|
|
237
|
-
line_range: { start: 1, end: 1 },
|
|
238
|
-
attached_spans: [],
|
|
239
|
-
backend_id: 'tree-haver',
|
|
240
|
-
parser_identity: 'tree_sitter_language_pack',
|
|
241
|
-
can_replace: true,
|
|
242
|
-
can_line_merge: false,
|
|
243
|
-
requires_reparse: true
|
|
244
|
-
}
|
|
169
|
+
def missing_required_conflict_output?(result, policy)
|
|
170
|
+
policy == :write &&
|
|
171
|
+
!result[:ok] &&
|
|
172
|
+
!result.fetch(:conflicts, []).empty? &&
|
|
173
|
+
!result[:conflicted_source].is_a?(String)
|
|
245
174
|
end
|
|
175
|
+
private_class_method :missing_required_conflict_output?
|
|
246
176
|
|
|
247
|
-
def
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
false
|
|
252
|
-
end
|
|
177
|
+
def writable_output(result, policy)
|
|
178
|
+
return result.fetch(:merged_source) if result[:ok]
|
|
179
|
+
return unless policy == :write
|
|
180
|
+
return if result.fetch(:conflicts, []).empty?
|
|
253
181
|
|
|
254
|
-
|
|
255
|
-
request.transform_keys(&:to_sym)
|
|
182
|
+
result[:conflicted_source]
|
|
256
183
|
end
|
|
184
|
+
private_class_method :writable_output
|
|
257
185
|
|
|
258
|
-
def
|
|
259
|
-
conflicts = []
|
|
260
|
-
merged_comment =
|
|
261
|
-
if ours_comment == theirs_comment
|
|
262
|
-
ours_comment
|
|
263
|
-
elsif base_comment == ours_comment
|
|
264
|
-
theirs_comment
|
|
265
|
-
elsif base_comment == theirs_comment
|
|
266
|
-
ours_comment
|
|
267
|
-
elsif ours_comment.nil?
|
|
268
|
-
conflicts << comment_conflict('delete_edit', owner_path, 'ours deleted a comment that theirs edited')
|
|
269
|
-
theirs_comment
|
|
270
|
-
elsif theirs_comment.nil?
|
|
271
|
-
conflicts << comment_conflict('delete_edit', owner_path, 'theirs deleted a comment that ours edited')
|
|
272
|
-
ours_comment
|
|
273
|
-
else
|
|
274
|
-
conflicts << comment_conflict('edit_edit', owner_path, 'comment changed differently in ours and theirs')
|
|
275
|
-
ours_comment
|
|
276
|
-
end
|
|
277
|
-
|
|
186
|
+
def git_report(result, ours_path, policy, output_written:)
|
|
278
187
|
{
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
188
|
+
exit_code: git_exit_code(result),
|
|
189
|
+
output_written: output_written,
|
|
190
|
+
output_path: ours_path.to_s,
|
|
191
|
+
conflict_policy: policy
|
|
282
192
|
}
|
|
283
193
|
end
|
|
194
|
+
private_class_method :git_report
|
|
284
195
|
|
|
285
|
-
def
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
},
|
|
301
|
-
render_report: {
|
|
302
|
-
strategy: render_strategy || (request[:render_policy].to_s.empty? ? 'canonical' : request[:render_policy].to_s),
|
|
303
|
-
**render_identity(request)
|
|
304
|
-
},
|
|
305
|
-
formatting_preservation: {
|
|
306
|
-
line_diff_score: 0.0,
|
|
307
|
-
character_diff_score: 0.0
|
|
308
|
-
}.merge(formatting_preservation),
|
|
309
|
-
secondary_formatting_metrics: secondary_formatting_metrics || secondary_formatting_metrics_for(ok && merged_source),
|
|
310
|
-
default_driver_evaluation: default_driver_evaluation(
|
|
311
|
-
formatting_preservation: {
|
|
312
|
-
line_diff_score: 0.0,
|
|
313
|
-
character_diff_score: 0.0
|
|
314
|
-
}.merge(formatting_preservation),
|
|
315
|
-
reparse_after_render: reparse_after_render,
|
|
316
|
-
render_strategy: render_strategy || (request[:render_policy].to_s.empty? ? 'canonical' : request[:render_policy].to_s)
|
|
317
|
-
),
|
|
318
|
-
reparse_after_render: reparse_after_render
|
|
319
|
-
}
|
|
196
|
+
def normalize_request(request)
|
|
197
|
+
request.transform_keys(&:to_sym).then do |normalized|
|
|
198
|
+
normalized[:family] ||= normalized.delete(:language)
|
|
199
|
+
normalized[:labels] = normalize_labels(normalized)
|
|
200
|
+
normalized
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
private_class_method :normalize_request
|
|
204
|
+
|
|
205
|
+
def normalize_labels(request)
|
|
206
|
+
labels = request.fetch(:labels, {}).transform_keys(&:to_sym)
|
|
207
|
+
labels[:base] ||= request[:base_label]
|
|
208
|
+
labels[:ours] ||= request[:ours_label]
|
|
209
|
+
labels[:theirs] ||= request[:theirs_label]
|
|
210
|
+
labels.compact
|
|
320
211
|
end
|
|
212
|
+
private_class_method :normalize_labels
|
|
321
213
|
|
|
322
|
-
def
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
reparse_passed = reparse_after_render == true
|
|
326
|
-
no_full_file_rewrite = render_strategy != 'full_file_conflict_markers'
|
|
327
|
-
coherent_conflict_markers = render_strategy != 'full_file_conflict_markers'
|
|
328
|
-
blocking_reasons = []
|
|
329
|
-
blocking_reasons << 'rendered output did not reparse' unless reparse_passed
|
|
330
|
-
blocking_reasons << 'formatting score is below threshold' if score < threshold
|
|
331
|
-
blocking_reasons << 'full-file rewrite or conflict markers were used' unless no_full_file_rewrite
|
|
332
|
-
blocking_reasons << 'conflict marker placement is not syntactically coherent' unless coherent_conflict_markers
|
|
214
|
+
def normalize_conflict_policy(policy)
|
|
215
|
+
normalized = policy.to_s.strip.to_sym
|
|
216
|
+
return normalized if CONFLICT_POLICIES.include?(normalized)
|
|
333
217
|
|
|
334
|
-
{
|
|
335
|
-
status: blocking_reasons.empty? ? 'recommended' : 'not_recommended',
|
|
336
|
-
formatting_threshold: threshold,
|
|
337
|
-
formatting_score: score,
|
|
338
|
-
hard_gates: [
|
|
339
|
-
{ name: 'reparse_after_render', passed: reparse_passed, weighted: false },
|
|
340
|
-
{ name: 'no_full_file_rewrite', passed: no_full_file_rewrite, weighted: false },
|
|
341
|
-
{ name: 'coherent_conflict_marker_placement', passed: coherent_conflict_markers, weighted: false }
|
|
342
|
-
],
|
|
343
|
-
blocking_reasons: blocking_reasons,
|
|
344
|
-
diagnostics: ['default-driver evaluation is advisory unless explicitly required']
|
|
345
|
-
}
|
|
218
|
+
raise ArgumentError, "Unknown conflict policy: #{policy.inspect}"
|
|
346
219
|
end
|
|
220
|
+
private_class_method :normalize_conflict_policy
|
|
347
221
|
|
|
348
|
-
def
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
output_diff_size: 0,
|
|
353
|
-
source_fragment_retention: 1.0,
|
|
354
|
-
weighted: false,
|
|
355
|
-
diagnostics: ['canonical JSON has no trivia-preserving source fragments yet']
|
|
356
|
-
}
|
|
357
|
-
else
|
|
358
|
-
{
|
|
359
|
-
unchanged_line_churn: 0,
|
|
360
|
-
output_diff_size: 0,
|
|
361
|
-
source_fragment_retention: 0.0,
|
|
362
|
-
weighted: false,
|
|
363
|
-
diagnostics: ['unresolved conflict did not produce a merged source-fragment retention measurement']
|
|
364
|
-
}
|
|
222
|
+
def command_request(argv, env)
|
|
223
|
+
unless argv.length.between?(3, 8)
|
|
224
|
+
raise ArgumentError,
|
|
225
|
+
'usage: ast-merge-git BASE OURS THEIRS [PATH [MARKER_SIZE [BASE_LABEL OURS_LABEL THEIRS_LABEL]]]'
|
|
365
226
|
end
|
|
366
|
-
end
|
|
367
227
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
else
|
|
373
|
-
{}
|
|
228
|
+
family = env['AST_MERGE_FAMILY']
|
|
229
|
+
provider_id = env['AST_MERGE_PROVIDER']
|
|
230
|
+
if family.to_s.empty? && provider_id.to_s.empty?
|
|
231
|
+
raise ArgumentError, 'AST_MERGE_FAMILY or AST_MERGE_PROVIDER is required'
|
|
374
232
|
end
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
def render_conflict_source(request, conflicts)
|
|
378
|
-
marker_size = request[:conflict_marker_size].to_i
|
|
379
|
-
marker_size = 7 unless marker_size.positive?
|
|
380
|
-
header = "/* smorg structured conflicts: #{conflicts.length} unresolved */"
|
|
381
|
-
[
|
|
382
|
-
header,
|
|
383
|
-
"#{'<' * marker_size} ours",
|
|
384
|
-
request.fetch(:ours_source),
|
|
385
|
-
"#{'|' * marker_size} base",
|
|
386
|
-
request.fetch(:base_source),
|
|
387
|
-
'=' * marker_size,
|
|
388
|
-
request.fetch(:theirs_source),
|
|
389
|
-
"#{'>' * marker_size} theirs",
|
|
390
|
-
''
|
|
391
|
-
].join("\n")
|
|
392
|
-
end
|
|
393
233
|
|
|
394
|
-
def comment_conflict(category, path, message)
|
|
395
234
|
{
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
235
|
+
base_path: argv.fetch(0),
|
|
236
|
+
ours_path: argv.fetch(1),
|
|
237
|
+
theirs_path: argv.fetch(2),
|
|
238
|
+
path_name: argv[3],
|
|
239
|
+
conflict_marker_size: argv[4],
|
|
240
|
+
labels: { base: argv[5], ours: argv[6], theirs: argv[7] }.compact,
|
|
241
|
+
family: family,
|
|
242
|
+
provider_id: provider_id,
|
|
243
|
+
dialect: env['AST_MERGE_DIALECT'],
|
|
244
|
+
backend: env['AST_MERGE_BACKEND'],
|
|
245
|
+
profile_id: env['AST_MERGE_PROFILE'],
|
|
246
|
+
conflict_policy: env.fetch('AST_MERGE_CONFLICT_POLICY', :write)
|
|
247
|
+
}.compact
|
|
401
248
|
end
|
|
249
|
+
private_class_method :command_request
|
|
402
250
|
|
|
403
|
-
def
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
251
|
+
def load_provider_requirements(value)
|
|
252
|
+
value.to_s.split(',').map(&:strip).reject(&:empty?).each { |require_path| require require_path }
|
|
253
|
+
end
|
|
254
|
+
private_class_method :load_provider_requirements
|
|
407
255
|
|
|
408
|
-
|
|
256
|
+
def emit_diagnostics(result, stderr)
|
|
257
|
+
result.fetch(:diagnostics).each do |item|
|
|
258
|
+
stderr.puts("#{PACKAGE_NAME}: #{item[:category]}: #{item[:message]}")
|
|
259
|
+
end
|
|
409
260
|
end
|
|
261
|
+
private_class_method :emit_diagnostics
|
|
410
262
|
end
|
|
263
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/ModuleLength
|
|
411
264
|
end
|
|
412
265
|
end
|