ast-merge-git 7.1.6 → 7.1.7

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: 5f77299ac80ed9c1b08218ab290de6784428c7fc691e1693df0f6a074b33a6cd
4
- data.tar.gz: 4b43c57ae0f44ce041cc3db5ae098e0f517666a634f791414d3860893caa404b
3
+ metadata.gz: 2943a67a2400053c0f535008977a3aad094685b0fd2c21423694a7e611176a2b
4
+ data.tar.gz: a734402394056e68c03fab8a40e23b3a6be091e98f77275921202d15e1915c37
5
5
  SHA512:
6
- metadata.gz: 81a80302f650d7f80415a5fa34a2b0e3ceb3bcab3169a8eb15000b592e994e71a9ae9377e47b033e6f3fa50ddde34c8585aa2ba756ccefaf24cd4950ba7de484
7
- data.tar.gz: 7608307153363924e423f9ed7f0021407094e5520c2b919c6e40c4f676f54168c4cb6e4658059b978fc1bb255e944cec52fb66238c18bc834110f14a6c3cf838
6
+ metadata.gz: 13a17a48ce7ffed824dd30841e4a1f8f236aa8237b3ca38020db745ac4c12ffbe47ebdbc69ff290725776cafd71d2fa1b2c67c251b87749a7f36a9d06393b060
7
+ data.tar.gz: a70e57f231ac0fc014aa0b6e49eccfc14a0cab8aefbd8a3ae9d781da8a72253fa6af584c26edb13d6208e01d8162cc67e8c4413610698a9050edf60ab8fc5014
checksums.yaml.gz.sig CHANGED
Binary file
data/exe/ast-merge-git CHANGED
@@ -3,31 +3,42 @@
3
3
 
4
4
  require 'ast/merge/git'
5
5
 
6
- if ARGV.first == 'benchmark-provider-diff'
6
+ if %w[benchmark-provider-diff benchmark-provider-merge2].include?(ARGV.first)
7
7
  require 'json'
8
8
 
9
9
  begin
10
- before_path, after_path, path_name = ARGV.drop(1)
11
- raise ArgumentError, 'before, after, and path name are required' unless before_path && after_path && path_name
10
+ command = ARGV.first
11
+ first_path, second_path, path_name = ARGV.drop(1)
12
+ raise ArgumentError, 'two source paths and path name are required' unless first_path && second_path && path_name
12
13
 
14
+ operation, role_sources = if command == 'benchmark-provider-diff'
15
+ [:diff2, {
16
+ before_source: File.binread(first_path),
17
+ after_source: File.binread(second_path)
18
+ }]
19
+ else
20
+ [:merge2, {
21
+ incoming_source: File.binread(first_path),
22
+ current_source: File.binread(second_path)
23
+ }]
24
+ end
13
25
  require ENV.fetch('AST_MERGE_REQUIRE')
14
26
  result = Ast::Merge.dispatch_provider(
15
- :diff2,
27
+ operation,
16
28
  {
17
29
  provider_id: ENV.fetch('AST_MERGE_PROVIDER'),
18
30
  family: ENV.fetch('AST_MERGE_FAMILY'),
19
31
  dialect: ENV.fetch('AST_MERGE_DIALECT'),
20
32
  backend: ENV.fetch('AST_MERGE_BACKEND'),
21
33
  profile_id: ENV.fetch('AST_MERGE_PROFILE'),
22
- before_source: File.binread(before_path),
23
- after_source: File.binread(after_path),
24
- path_name: path_name
34
+ path_name: path_name,
35
+ **role_sources
25
36
  }
26
37
  )
27
38
  puts JSON.generate(Ast::Merge.json_ready(result))
28
39
  exit(result[:ok] == true ? 0 : 2)
29
40
  rescue ArgumentError, Ast::Merge::Error, KeyError, LoadError, SystemCallError => e
30
- warn "ast-merge-git benchmark-provider-diff: #{e.message}"
41
+ warn "ast-merge-git #{ARGV.first}: #{e.message}"
31
42
  exit 2
32
43
  end
33
44
  end
@@ -15,8 +15,8 @@ module Ast
15
15
  Error = Class.new(StandardError)
16
16
  SCHEMA = 'structuredmerge.benchmark.corpus/v1'
17
17
  CASE_SCHEMA = 'structuredmerge.benchmark/v1'
18
- OPERATIONS = %w[merge3 metamorphic diff].freeze
19
- EXECUTABLE_OPERATIONS = %w[merge3 metamorphic].freeze
18
+ OPERATIONS = %w[merge2 merge3 metamorphic diff].freeze
19
+ EXECUTABLE_OPERATIONS = %w[merge2 merge3 metamorphic].freeze
20
20
  PARTITIONS = %w[sentinel gold metamorphic].freeze
21
21
  EXPECTATIONS = %w[clean conflict error excluded_ambiguous].freeze
22
22
  SEVERITIES = %w[none low high critical].freeze
@@ -34,6 +34,7 @@ module Ast
34
34
  ].freeze
35
35
  SELECTOR_FIELDS = %w[provider_id family dialect backend profile require].freeze
36
36
  INPUT_ROLES = {
37
+ 'merge2' => %w[incoming current],
37
38
  'merge3' => %w[base ours theirs],
38
39
  'metamorphic' => %w[source transformed],
39
40
  'diff' => %w[before after]
@@ -267,7 +268,7 @@ module Ast
267
268
  end
268
269
 
269
270
  def validate_operation!(item, id)
270
- if item['operation'] == 'merge3'
271
+ if %w[merge2 merge3].include?(item['operation'])
271
272
  require_keys(item, %w[expected expected_conflicts], id)
272
273
  expectation = item.dig('expected', 'outcome')
273
274
  error!("#{id}: unsupported expected outcome") unless EXPECTATIONS.include?(expectation)
@@ -433,6 +434,7 @@ module Ast
433
434
  end
434
435
 
435
436
  def execute_case(item)
437
+ return execute_merge2_case(item) if item['operation'] == 'merge2'
436
438
  return execute_merge3_case(item) if item['operation'] == 'merge3'
437
439
  return execute_metamorphic_case(item) if item['operation'] == 'metamorphic'
438
440
 
@@ -496,6 +498,20 @@ module Ast
496
498
  FileUtils.rm_rf(workspace) if workspace
497
499
  end
498
500
 
501
+ def execute_merge2_case(item)
502
+ workspace = @tmp_root.join("#{safe_id(item['id'])}-#{Process.pid}")
503
+ FileUtils.rm_rf(workspace)
504
+ FileUtils.mkdir_p(workspace)
505
+ write_merge2_roles(item, workspace)
506
+ baseline = execute_merge2_baseline(item, workspace)
507
+ candidate = execute_merge2_candidate(item, workspace)
508
+ rerun = execute_merge2_candidate(item, workspace)
509
+ candidate['deterministic_correctness_rerun'] = correctness_record(candidate) == correctness_record(rerun)
510
+ [baseline, candidate]
511
+ ensure
512
+ FileUtils.rm_rf(workspace) if workspace
513
+ end
514
+
499
515
  def execute_metamorphic_case(item)
500
516
  workspace = @tmp_root.join("#{safe_id(item['id'])}-#{Process.pid}")
501
517
  FileUtils.rm_rf(workspace)
@@ -542,6 +558,47 @@ module Ast
542
558
  end
543
559
  end
544
560
 
561
+ def write_merge2_roles(item, workspace)
562
+ %w[incoming current].each do |role|
563
+ workspace.join(role).binwrite(item.dig('inputs', role, 'bytes'))
564
+ end
565
+ end
566
+
567
+ def execute_merge2_baseline(item, workspace)
568
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
569
+ output = workspace.join('incoming').binread
570
+ capture = {
571
+ stdout: '',
572
+ stderr: '',
573
+ status: 0,
574
+ output: output,
575
+ duration_ns: Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - started
576
+ }
577
+ raw_result(item, 'template.overwrite', capture)
578
+ end
579
+
580
+ def execute_merge2_candidate(item, workspace)
581
+ selector = item.fetch('selector')
582
+ capture = timed_capture(
583
+ candidate_env(selector),
584
+ @driver_path.to_s,
585
+ 'benchmark-provider-merge2',
586
+ 'incoming',
587
+ 'current',
588
+ "#{item['id']}.#{item['dialect']}",
589
+ chdir: workspace
590
+ )
591
+ result = capture[:stdout].empty? ? {} : JSON.parse(capture[:stdout])
592
+ capture[:output] = capture[:status].zero? ? result.fetch('output') : ''
593
+ raw_result(item, 'ast-merge-provider.merge2', capture)
594
+ rescue JSON::ParserError, KeyError => e
595
+ capture ||= { stdout: '', stderr: '', status: 2, duration_ns: 0 }
596
+ capture[:stderr] = [capture[:stderr], e.message].reject(&:empty?).join("\n")
597
+ capture[:status] = 2
598
+ capture[:output] = ''
599
+ raw_result(item, 'ast-merge-provider.merge2', capture)
600
+ end
601
+
545
602
  def write_metamorphic_roles(item, workspace)
546
603
  %w[source transformed].each do |role|
547
604
  workspace.join(role).binwrite(item.dig('inputs', role, 'bytes'))
@@ -632,7 +689,12 @@ module Ast
632
689
  output = capture&.fetch(:output, '') || ''
633
690
  checks = equivalence_checks(item, output)
634
691
  markers = conflict_regions(output)
635
- classified = outcome || classify(item, capture.fetch(:status), checks)
692
+ classified = outcome || classify(
693
+ item,
694
+ capture.fetch(:status),
695
+ checks,
696
+ stderr: capture.fetch(:stderr, '')
697
+ )
636
698
  eligible = item.dig('oracle', 'score_eligible') && !%w[unsupported excluded_ambiguous].include?(classified)
637
699
  {
638
700
  'schema_version' => 'structuredmerge.benchmark.result/v1',
@@ -664,8 +726,15 @@ module Ast
664
726
  end
665
727
 
666
728
  def adapter_role(adapter)
667
- return 'baseline' if adapter.start_with?('git.')
668
- return 'candidate' if %w[ast-merge-git ast-merge-provider.diff2 structuredmerge.unsupported].include?(adapter)
729
+ return 'baseline' if adapter.start_with?('git.') || adapter == 'template.overwrite'
730
+
731
+ candidates = %w[
732
+ ast-merge-git
733
+ ast-merge-provider.diff2
734
+ ast-merge-provider.merge2
735
+ structuredmerge.unsupported
736
+ ]
737
+ return 'candidate' if candidates.include?(adapter)
669
738
 
670
739
  'competitor'
671
740
  end
@@ -792,18 +861,26 @@ module Ast
792
861
  'analyzed'
793
862
  end
794
863
 
795
- def classify(item, status, checks)
864
+ def classify(item, status, checks, stderr: '')
796
865
  expected = item.dig('expected', 'outcome')
797
866
  return 'excluded_ambiguous' if expected == 'excluded_ambiguous'
867
+ if expected == 'error'
868
+ return 'error' if status.nil?
869
+ return expected_error_diagnostic?(status, stderr) ? 'correct_clean' : 'error' if status >= 2
870
+ return status == 1 ? 'false_conflict' : 'false_auto_merge'
871
+ end
798
872
  return 'error' if status.nil? || status >= 2
799
- return status == 1 ? 'false_conflict' : 'false_auto_merge' if expected == 'error'
800
873
  return status == 1 ? 'true_conflict' : 'false_auto_merge' if expected == 'conflict'
801
874
  return 'false_conflict' if status == 1
802
- return 'correct_clean' if checks['acceptable']
875
+ return 'correct_clean' if checks['equivalence_acceptable']
803
876
 
804
877
  'false_auto_merge'
805
878
  end
806
879
 
880
+ def expected_error_diagnostic?(status, stderr)
881
+ status >= 2 && /\Aast-merge-git: parse_error:/i.match?(stderr.to_s)
882
+ end
883
+
807
884
  def equivalence_checks(item, output)
808
885
  expected = item.dig('expected', 'output', 'bytes')
809
886
  exact = !expected.nil? && output == expected
@@ -825,8 +902,19 @@ module Ast
825
902
  'accepted_equivalence' => selected&.fetch('class'),
826
903
  'parse_validity_only_accepted' => false
827
904
  }
828
- violations = preservation_violations(item, checks, output)
829
- checks.merge('preservation_violations' => violations, 'acceptable' => !selected.nil? && violations.empty?)
905
+ preservation = preservation_evaluations(item, checks, output)
906
+ violations = preservation.filter_map { |name, status| name if status == 'fail' }
907
+ unverified = preservation.filter_map { |name, status| name if status == 'unverified' }
908
+ equivalence_acceptable = !selected.nil?
909
+ preservation_acceptable = violations.empty? && unverified.empty?
910
+ checks.merge(
911
+ 'preservation_evaluations' => preservation,
912
+ 'preservation_violations' => violations,
913
+ 'preservation_unverified' => unverified,
914
+ 'equivalence_acceptable' => equivalence_acceptable,
915
+ 'preservation_acceptable' => preservation_acceptable,
916
+ 'acceptable' => equivalence_acceptable && preservation_acceptable
917
+ )
830
918
  end
831
919
 
832
920
  def structural_equivalence(item, output, expected)
@@ -872,35 +960,54 @@ module Ast
872
960
  'preservation' => {
873
961
  'eligible' => eligible,
874
962
  'requirements' => item['preservation_policy'],
875
- 'violations' => checks.fetch('preservation_violations')
963
+ 'violations' => checks.fetch('preservation_violations', []),
964
+ 'unverified' => checks.fetch('preservation_unverified', [])
876
965
  },
877
966
  'performance' => { 'quality_offset_allowed' => false }
878
967
  }
879
968
  end
880
969
 
881
- def preservation_violations(item, checks, output)
882
- return [] if checks['exact']
883
-
970
+ def preservation_evaluations(item, checks, output)
884
971
  expected = item.dig('expected', 'output', 'bytes')
885
- return [] unless expected
972
+ return {} unless expected
886
973
 
887
- item['preservation_policy'].filter_map do |name, requirement|
888
- next unless requirement == 'required'
889
- next unless preservation_violation?(name, output, expected, checks)
974
+ item['preservation_policy'].to_h do |name, requirement|
975
+ next [name, 'not_required'] unless requirement == 'required'
976
+ next [name, 'pass'] if checks['exact']
890
977
 
891
- name
978
+ [name, preservation_evaluation(name, output, expected, checks)]
892
979
  end
893
980
  end
894
981
 
895
- def preservation_violation?(name, output, expected, checks)
982
+ def preservation_evaluation(name, output, expected, checks)
896
983
  case name
897
- when 'encoding' then output.encoding != expected.encoding
898
- when 'line_endings' then line_ending_style(output) != line_ending_style(expected)
899
- when 'unknown_fields' then checks['structural'] != true
900
- else output != expected
984
+ when 'encoding'
985
+ encoding_signature(output) == encoding_signature(expected) ? 'pass' : 'fail'
986
+ when 'line_endings'
987
+ line_ending_style(output) == line_ending_style(expected) ? 'pass' : 'fail'
988
+ when 'unknown_fields'
989
+ checks['structural'] == true ? 'pass' : 'unverified'
990
+ when 'formatting', 'source_regions'
991
+ output == expected ? 'pass' : 'fail'
992
+ else
993
+ 'unverified'
901
994
  end
902
995
  end
903
996
 
997
+ def encoding_signature(content)
998
+ bytes = content.b
999
+ bom = if bytes.start_with?("\xEF\xBB\xBF".b)
1000
+ 'utf-8-bom'
1001
+ elsif bytes.start_with?("\xFF\xFE".b)
1002
+ 'utf-16le-bom'
1003
+ elsif bytes.start_with?("\xFE\xFF".b)
1004
+ 'utf-16be-bom'
1005
+ end
1006
+ return bom if bom
1007
+
1008
+ bytes.dup.force_encoding(Encoding::UTF_8).valid_encoding? ? 'utf-8' : 'binary'
1009
+ end
1010
+
904
1011
  def line_ending_style(content)
905
1012
  return 'crlf' if content.include?("\r\n")
906
1013
 
@@ -1066,6 +1173,11 @@ module Ast
1066
1173
  def build
1067
1174
  pairs = @results.group_by { |result| result['case_id'] }.values.map { |items| transition(items) }
1068
1175
  false_auto_merges = candidate_eligible.select { |item| item['outcome'] == 'false_auto_merge' }
1176
+ preservation_failures = candidate_eligible.select do |item|
1177
+ item.dig('dimensions', 'preservation', 'violations').any? ||
1178
+ item.dig('dimensions', 'preservation', 'unverified').any?
1179
+ end
1180
+ unexpected_errors = candidate_results.select { |item| item['outcome'] == 'error' }
1069
1181
  {
1070
1182
  'schema_version' => 'structuredmerge.benchmark.report/v1',
1071
1183
  'kind' => 'paired_aggregate_report',
@@ -1082,15 +1194,20 @@ module Ast
1082
1194
  },
1083
1195
  'effectiveness' => outcome_counts,
1084
1196
  'preservation' => {
1085
- 'eligible' => eligible.length,
1086
- 'violation_result_ids' => eligible.filter_map do |item|
1197
+ 'eligible' => candidate_eligible.length,
1198
+ 'violation_result_ids' => candidate_eligible.filter_map do |item|
1087
1199
  item['id'] if item.dig('dimensions', 'preservation', 'violations').any?
1088
- end
1200
+ end,
1201
+ 'unverified_result_ids' => candidate_eligible.filter_map do |item|
1202
+ item['id'] if item.dig('dimensions', 'preservation', 'unverified').any?
1203
+ end,
1204
+ 'gate' => preservation_failures.empty? ? 'pass' : 'fail'
1089
1205
  },
1090
1206
  'performance' => performance,
1091
- 'reliability' => { 'error_result_ids' => @results.filter_map do |item|
1092
- item['id'] if item['outcome'] == 'error'
1093
- end },
1207
+ 'reliability' => {
1208
+ 'error_result_ids' => unexpected_errors.map { |item| item['id'] },
1209
+ 'gate' => unexpected_errors.empty? ? 'pass' : 'fail'
1210
+ },
1094
1211
  'coverage' => coverage,
1095
1212
  'competitive' => competitive
1096
1213
  },
@@ -1099,7 +1216,7 @@ module Ast
1099
1216
  'newly_passing_case_ids' => pairs.filter_map { |pair| pair['case_id'] if pair['newly_passing'] },
1100
1217
  'newly_failing_case_ids' => pairs.filter_map { |pair| pair['case_id'] if pair['newly_failing'] },
1101
1218
  'changed_conflict_case_ids' => pairs.filter_map { |pair| pair['case_id'] if pair['changed_conflict'] },
1102
- 'hard_gate_failed' => false_auto_merges.any?,
1219
+ 'hard_gate_failed' => false_auto_merges.any? || preservation_failures.any? || unexpected_errors.any?,
1103
1220
  'scalar_score' => nil
1104
1221
  }
1105
1222
  end
@@ -1114,6 +1231,10 @@ module Ast
1114
1231
  eligible.select { |item| item['adapter_role'] == 'candidate' }
1115
1232
  end
1116
1233
 
1234
+ def candidate_results
1235
+ @results.select { |item| item['adapter_role'] == 'candidate' }
1236
+ end
1237
+
1117
1238
  def outcome_counts
1118
1239
  %w[correct_clean false_conflict true_conflict false_auto_merge error unsupported
1119
1240
  excluded_ambiguous].to_h do |outcome|
@@ -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.6'
9
+ VERSION = '7.1.7'
10
10
  end
11
11
  # Current gem version exposed at the traditional constant location.
12
12
  VERSION = Version::VERSION # Traditional Constant Location
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ast-merge-git
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.6
4
+ version: 7.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - '='
45
45
  - !ruby/object:Gem::Version
46
- version: 7.1.6
46
+ version: 7.1.7
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - '='
52
52
  - !ruby/object:Gem::Version
53
- version: 7.1.6
53
+ version: 7.1.7
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: version_gem
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -296,10 +296,10 @@ licenses:
296
296
  - PolyForm-Small-Business-1.0.0
297
297
  metadata:
298
298
  homepage_uri: https://structuredmerge.org
299
- source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.6
300
- changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.6/CHANGELOG.md
299
+ source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.7
300
+ changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.7/CHANGELOG.md
301
301
  bug_tracker_uri: https://github.com/structuredmerge/structuredmerge-ruby/issues
302
- documentation_uri: https://www.rubydoc.info/gems/ast-merge-git/7.1.6
302
+ documentation_uri: https://www.rubydoc.info/gems/ast-merge-git/7.1.7
303
303
  funding_uri: https://github.com/sponsors/pboling
304
304
  wiki_uri: https://github.com/structuredmerge/structuredmerge-ruby/wiki
305
305
  news_uri: https://www.railsbling.com/tags/ast-merge-git
metadata.gz.sig CHANGED
Binary file