bash-merge 2.0.6 → 7.1.1
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/LICENSE.md +13 -0
- data/README.md +100 -639
- data/lib/bash/merge/comment_tracker.rb +36 -100
- data/lib/bash/merge/debug_logger.rb +10 -10
- data/lib/bash/merge/emitter.rb +45 -33
- data/lib/bash/merge/file_analysis.rb +133 -33
- data/lib/bash/merge/freeze_node.rb +9 -9
- data/lib/bash/merge/merge_result.rb +11 -5
- data/lib/bash/merge/node_wrapper.rb +62 -36
- data/lib/bash/merge/smart_merger.rb +641 -32
- data/lib/bash/merge/version.rb +5 -4
- data/lib/bash/merge.rb +116 -25
- data/lib/bash-merge.rb +7 -1
- data/sig/bash/merge.rbs +3 -255
- data.tar.gz.sig +0 -0
- metadata +83 -88
- metadata.gz.sig +0 -0
- data/CHANGELOG.md +0 -211
- data/CITATION.cff +0 -20
- data/CODE_OF_CONDUCT.md +0 -134
- data/CONTRIBUTING.md +0 -227
- data/FUNDING.md +0 -74
- data/LICENSE.txt +0 -21
- data/REEK +0 -0
- data/RUBOCOP.md +0 -71
- data/SECURITY.md +0 -21
- data/lib/bash/merge/conflict_resolver.rb +0 -199
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Bash
|
|
4
|
-
module Merge
|
|
5
|
-
# Resolves conflicts between template and destination Bash content
|
|
6
|
-
# using structural signatures and configurable preferences.
|
|
7
|
-
#
|
|
8
|
-
# @example Basic usage
|
|
9
|
-
# resolver = ConflictResolver.new(template_analysis, dest_analysis)
|
|
10
|
-
# resolver.resolve(result)
|
|
11
|
-
class ConflictResolver < ::Ast::Merge::ConflictResolverBase
|
|
12
|
-
# Creates a new ConflictResolver
|
|
13
|
-
#
|
|
14
|
-
# @param template_analysis [FileAnalysis] Analyzed template file
|
|
15
|
-
# @param dest_analysis [FileAnalysis] Analyzed destination file
|
|
16
|
-
# @param preference [Symbol, Hash] Which version to prefer when
|
|
17
|
-
# nodes have matching signatures:
|
|
18
|
-
# - :destination (default) - Keep destination version (customizations)
|
|
19
|
-
# - :template - Use template version (updates)
|
|
20
|
-
# @param add_template_only_nodes [Boolean] Whether to add nodes only in template
|
|
21
|
-
# @param match_refiner [#call, nil] Optional match refiner for fuzzy matching
|
|
22
|
-
# @param options [Hash] Additional options for forward compatibility
|
|
23
|
-
# @param node_typing [Hash{Symbol,String => #call}, nil] Node typing configuration
|
|
24
|
-
# for per-node-type preferences
|
|
25
|
-
def initialize(template_analysis, dest_analysis, preference: :destination, add_template_only_nodes: false, match_refiner: nil, node_typing: nil, **options)
|
|
26
|
-
super(
|
|
27
|
-
strategy: :batch,
|
|
28
|
-
preference: preference,
|
|
29
|
-
template_analysis: template_analysis,
|
|
30
|
-
dest_analysis: dest_analysis,
|
|
31
|
-
add_template_only_nodes: add_template_only_nodes,
|
|
32
|
-
match_refiner: match_refiner,
|
|
33
|
-
**options
|
|
34
|
-
)
|
|
35
|
-
@node_typing = node_typing
|
|
36
|
-
@emitter = Emitter.new
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Resolve conflicts and populate the result
|
|
40
|
-
#
|
|
41
|
-
# @param result [MergeResult] Result object to populate
|
|
42
|
-
def resolve(result)
|
|
43
|
-
DebugLogger.time("ConflictResolver#resolve") do
|
|
44
|
-
template_nodes = @template_analysis.nodes
|
|
45
|
-
dest_nodes = @dest_analysis.nodes
|
|
46
|
-
|
|
47
|
-
# Clear emitter for fresh merge
|
|
48
|
-
@emitter.clear
|
|
49
|
-
|
|
50
|
-
# Build signature maps
|
|
51
|
-
template_by_sig = build_signature_map(template_nodes, @template_analysis)
|
|
52
|
-
dest_by_sig = build_signature_map(dest_nodes, @dest_analysis)
|
|
53
|
-
|
|
54
|
-
# Track which nodes have been processed
|
|
55
|
-
processed_template_sigs = ::Set.new
|
|
56
|
-
processed_dest_sigs = ::Set.new
|
|
57
|
-
|
|
58
|
-
# Process nodes via emitter
|
|
59
|
-
merge_nodes_to_emitter(
|
|
60
|
-
template_nodes,
|
|
61
|
-
dest_nodes,
|
|
62
|
-
template_by_sig,
|
|
63
|
-
dest_by_sig,
|
|
64
|
-
processed_template_sigs,
|
|
65
|
-
processed_dest_sigs,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
# Transfer emitter output to result
|
|
69
|
-
emitted_content = @emitter.to_s
|
|
70
|
-
unless emitted_content.empty?
|
|
71
|
-
emitted_content.lines.each do |line|
|
|
72
|
-
result.add_line(line.chomp, decision: MergeResult::DECISION_MERGED, source: :merged)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
DebugLogger.debug("Conflict resolution complete", {
|
|
77
|
-
template_nodes: template_nodes.size,
|
|
78
|
-
dest_nodes: dest_nodes.size,
|
|
79
|
-
result_lines: result.line_count,
|
|
80
|
-
})
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
private
|
|
85
|
-
|
|
86
|
-
def merge_nodes_to_emitter(template_nodes, dest_nodes, template_by_sig, dest_by_sig, processed_template_sigs, processed_dest_sigs)
|
|
87
|
-
# First pass: Process destination nodes and find matches
|
|
88
|
-
dest_nodes.each do |dest_node|
|
|
89
|
-
dest_sig = @dest_analysis.generate_signature(dest_node)
|
|
90
|
-
|
|
91
|
-
# Freeze blocks from destination are always preserved
|
|
92
|
-
if freeze_node?(dest_node)
|
|
93
|
-
emit_freeze_block(dest_node)
|
|
94
|
-
processed_dest_sigs << dest_sig if dest_sig
|
|
95
|
-
next
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
if dest_sig && template_by_sig[dest_sig]
|
|
99
|
-
# Found matching node in template
|
|
100
|
-
template_info = template_by_sig[dest_sig].first
|
|
101
|
-
template_node = template_info[:node]
|
|
102
|
-
|
|
103
|
-
# Decide which to emit based on preference
|
|
104
|
-
emit_preferred_node(template_node, dest_node)
|
|
105
|
-
|
|
106
|
-
processed_dest_sigs << dest_sig
|
|
107
|
-
processed_template_sigs << dest_sig
|
|
108
|
-
else
|
|
109
|
-
# Destination-only node - always keep
|
|
110
|
-
emit_node(dest_node, @dest_analysis)
|
|
111
|
-
processed_dest_sigs << dest_sig if dest_sig
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Second pass: Add template-only nodes if configured
|
|
116
|
-
return unless @add_template_only_nodes
|
|
117
|
-
|
|
118
|
-
template_nodes.each do |template_node|
|
|
119
|
-
template_sig = @template_analysis.generate_signature(template_node)
|
|
120
|
-
|
|
121
|
-
# Skip if already processed (matched with dest)
|
|
122
|
-
next if template_sig && processed_template_sigs.include?(template_sig)
|
|
123
|
-
|
|
124
|
-
# Skip freeze blocks from template
|
|
125
|
-
next if freeze_node?(template_node)
|
|
126
|
-
|
|
127
|
-
# Add template-only node
|
|
128
|
-
emit_node(template_node, @template_analysis)
|
|
129
|
-
processed_template_sigs << template_sig if template_sig
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def emit_preferred_node(template_node, dest_node)
|
|
134
|
-
if preference_for_pair(template_node, dest_node) == :destination
|
|
135
|
-
emit_node(dest_node, @dest_analysis)
|
|
136
|
-
else
|
|
137
|
-
emit_node(template_node, @template_analysis)
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def preference_for_pair(template_node, dest_node)
|
|
142
|
-
return @preference unless @preference.is_a?(Hash)
|
|
143
|
-
|
|
144
|
-
typed_template = apply_node_typing(template_node)
|
|
145
|
-
typed_dest = apply_node_typing(dest_node)
|
|
146
|
-
|
|
147
|
-
if Ast::Merge::NodeTyping.typed_node?(typed_template)
|
|
148
|
-
merge_type = Ast::Merge::NodeTyping.merge_type_for(typed_template)
|
|
149
|
-
return @preference.fetch(merge_type) { default_preference } if merge_type
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
if Ast::Merge::NodeTyping.typed_node?(typed_dest)
|
|
153
|
-
merge_type = Ast::Merge::NodeTyping.merge_type_for(typed_dest)
|
|
154
|
-
return @preference.fetch(merge_type) { default_preference } if merge_type
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
default_preference
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def apply_node_typing(node)
|
|
161
|
-
return node unless @node_typing
|
|
162
|
-
return node unless node
|
|
163
|
-
|
|
164
|
-
Ast::Merge::NodeTyping.process(node, @node_typing)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Emit a single node to the emitter
|
|
168
|
-
# @param node [NodeWrapper] Node to emit
|
|
169
|
-
# @param analysis [FileAnalysis] Analysis for accessing source
|
|
170
|
-
def emit_node(node, analysis)
|
|
171
|
-
return if freeze_node?(node)
|
|
172
|
-
|
|
173
|
-
# Emit leading comments
|
|
174
|
-
if node.start_line
|
|
175
|
-
leading = analysis.comment_tracker.leading_comments_before(node.start_line)
|
|
176
|
-
leading.each do |comment|
|
|
177
|
-
@emitter.emit_tracked_comment(comment)
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Emit the node content
|
|
182
|
-
if node.start_line && node.end_line
|
|
183
|
-
lines = []
|
|
184
|
-
(node.start_line..node.end_line).each do |line_num|
|
|
185
|
-
line = analysis.line_at(line_num)
|
|
186
|
-
lines << line if line
|
|
187
|
-
end
|
|
188
|
-
@emitter.emit_raw_lines(lines)
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
# Emit a freeze block
|
|
193
|
-
# @param freeze_node [FreezeNode] Freeze block to emit
|
|
194
|
-
def emit_freeze_block(freeze_node)
|
|
195
|
-
@emitter.emit_raw_lines(freeze_node.lines)
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|