markdown-merge 7.1.7 → 7.1.9
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/lib/markdown/merge/rspec/shared_examples/source_preserving_provider.rb +38 -4
- data/lib/markdown/merge/source_preserving_provider.rb +82 -12
- data/lib/markdown/merge/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +14 -14
- 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: 66a619cb22a744ed1c53eef7e27698358b66d760ea2f6383cbc28e22f42836a1
|
|
4
|
+
data.tar.gz: be63b11b684794e2632140a597a00f8445252f5bed8c8958c17c4b1dda013bb4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce50ef630a92e2e9235455c91cd3ae6f0af4e7bb3581a5f5278488350691ee7f415c703d9ad3c5073c490676d482ee387bc2f75a03f990f7df8c16af03b0a3a4
|
|
7
|
+
data.tar.gz: 1520e7547690a806d09a0bfb5f94acac5fd54704e4c2a93787c7be3d1d2e8abcc3e245bcff3af594341e4624fb660092d3863a6e7c305c7b910fb811a0edb102
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -143,6 +143,29 @@ RSpec.shared_examples 'Markdown::Merge::SourcePreservingProvider' do
|
|
|
143
143
|
)
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
+
it 'preserves current section customizations while inserting incoming-only sections in order' do
|
|
147
|
+
current = "# Title\n\ncurrent body\n\n# Last\n\ncurrent ending\n"
|
|
148
|
+
incoming = "# Title\n\ntemplate body\n\n# Added\n\nnew section\n\n# Last\n\ntemplate ending\n"
|
|
149
|
+
expected = "# Title\n\ncurrent body\n\n# Added\n\nnew section\n\n# Last\n\ncurrent ending\n"
|
|
150
|
+
|
|
151
|
+
result = provider.merge2(current_source: current, incoming_source: incoming)
|
|
152
|
+
|
|
153
|
+
expect(result).to include(ok: true, operation: :merge2, output: expected)
|
|
154
|
+
expect(result.fetch(:changes)).to contain_exactly(hash_including(change: :added))
|
|
155
|
+
expect(result.dig(:render_report, :provenance)).to contain_exactly(
|
|
156
|
+
hash_including(source_role: :current, copied_source: true),
|
|
157
|
+
hash_including(source_role: :incoming, copied_source: true),
|
|
158
|
+
hash_including(source_role: :current, copied_source: true)
|
|
159
|
+
)
|
|
160
|
+
expect(result.fetch(:verification)).to include(
|
|
161
|
+
output_reparsed: true,
|
|
162
|
+
semantic_match: true,
|
|
163
|
+
byte_provenance_verified: true,
|
|
164
|
+
delegated_backend_verified: true,
|
|
165
|
+
backend: provider_backend
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
146
169
|
it 'localizes a same-section conflict to the parser-proven owner range' do
|
|
147
170
|
result = provider.merge3(
|
|
148
171
|
base_source: base,
|
|
@@ -174,16 +197,27 @@ RSpec.shared_examples 'Markdown::Merge::SourcePreservingProvider' do
|
|
|
174
197
|
expect(result.dig(:render_report, :strategy)).to eq(:full_file_conflict)
|
|
175
198
|
end
|
|
176
199
|
|
|
177
|
-
expect(provider.analyze(source: "# Alpha\n\n## Nested\n\ntext\n")).to include(
|
|
178
|
-
ok: false,
|
|
179
|
-
diagnostics: include(hash_including(category: :unsafe_source_range))
|
|
180
|
-
)
|
|
200
|
+
expect(provider.analyze(source: "# Alpha\n\n## Nested\n\ntext\n")).to include(ok: true)
|
|
181
201
|
expect(provider.analyze(source: "headingless prose\n")).to include(
|
|
182
202
|
ok: false,
|
|
183
203
|
diagnostics: include(hash_including(category: :unsafe_source_range))
|
|
184
204
|
)
|
|
185
205
|
end
|
|
186
206
|
|
|
207
|
+
it 'merges independent nested leaf heading edits while preserving ancestor framing' do
|
|
208
|
+
result = provider.merge3(
|
|
209
|
+
base_source: "# Guide\n\n## Left\nOne.\n\n## Right\nOne.\n",
|
|
210
|
+
ours_source: "# Guide\n\n## Left\nTwo.\n\n## Right\nOne.\n",
|
|
211
|
+
theirs_source: "# Guide\n\n## Left\nOne.\n\n## Right\nTwo.\n"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
expect(result).to include(
|
|
215
|
+
ok: true,
|
|
216
|
+
output: "# Guide\n\n## Left\nTwo.\n\n## Right\nTwo.\n"
|
|
217
|
+
)
|
|
218
|
+
expect(result.dig(:verification, :semantic_match)).to be(true)
|
|
219
|
+
end
|
|
220
|
+
|
|
187
221
|
it 'fails closed for setext headings and non-whitespace prose outside sections' do
|
|
188
222
|
expect(provider.analyze(source: "Alpha\n=====\n\ntext\n")).to include(
|
|
189
223
|
ok: false,
|
|
@@ -198,14 +198,40 @@ module Markdown
|
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
def merge2(request)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
201
|
+
current = parse_document(:merge2, request, :current)
|
|
202
|
+
return current if provider_failure?(current)
|
|
203
|
+
|
|
204
|
+
incoming = parse_document(:merge2, request, :incoming)
|
|
205
|
+
return incoming if provider_failure?(incoming)
|
|
206
|
+
|
|
207
|
+
choices = current.owners.to_h { |owner| [owner.id, :current] }
|
|
208
|
+
incoming.owners.each { |owner| choices[owner.id] ||= :incoming }
|
|
209
|
+
decision = Decision.new(changes: [], conflicts: [], choices: choices.freeze)
|
|
210
|
+
documents = { current: current, incoming: incoming }.freeze
|
|
211
|
+
ordered = merged_order(documents, decision)
|
|
212
|
+
return unsafe_merge2_order_failure(request) unless ordered
|
|
213
|
+
|
|
214
|
+
fragments = ordered.map do |id|
|
|
215
|
+
role = decision.choices.fetch(id)
|
|
216
|
+
[documents.fetch(role).by_id.fetch(id), role]
|
|
217
|
+
end
|
|
218
|
+
output = composite_prefix(documents, :current) + fragments.map { |owner, _role| owner.source_text }.join
|
|
219
|
+
expected = fragments.map { |owner, _role| [owner.signature, owner.fingerprint] }
|
|
220
|
+
verification = verify_composite(output, expected)
|
|
221
|
+
unless verification[:semantic_match]
|
|
222
|
+
return render_failure(request, verification, :exact_markdown_composite, operation: :merge2)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
output_document = parse_source(output, :output)
|
|
226
|
+
changes = diff_documents(current, output_document)
|
|
227
|
+
result(
|
|
228
|
+
:merge2,
|
|
229
|
+
request,
|
|
230
|
+
output: output,
|
|
231
|
+
changes: changes,
|
|
232
|
+
render_report: composite_render_report(fragments, output),
|
|
233
|
+
verification: verification
|
|
207
234
|
)
|
|
208
|
-
merged.merge(operation: :merge2, verification: merged.fetch(:verification).except(:base_participated))
|
|
209
235
|
end
|
|
210
236
|
|
|
211
237
|
def merge3(request)
|
|
@@ -250,13 +276,17 @@ module Markdown
|
|
|
250
276
|
headings = parsed.fetch(:headings)
|
|
251
277
|
return { unsafe: :headingless_document, source_role: role } if headings.empty?
|
|
252
278
|
return { unsafe: :setext_heading, source_role: role } unless headings.all? { |heading| heading.style == :atx }
|
|
253
|
-
|
|
279
|
+
|
|
280
|
+
headings = independently_ownable_headings(headings)
|
|
281
|
+
return { unsafe: :nested_heading_hierarchy, source_role: role } if headings.empty?
|
|
254
282
|
|
|
255
283
|
starts = headings.map(&:start_line).map { |line| line_start_byte(source, line) }
|
|
256
284
|
if starts.any?(&:nil?) || starts != starts.sort.uniq
|
|
257
285
|
return { unsafe: :invalid_heading_start, source_role: role }
|
|
258
286
|
end
|
|
259
|
-
|
|
287
|
+
unless starts.first.zero? || heading_only_prefix?(source, parsed.fetch(:headings), starts.first)
|
|
288
|
+
return { unsafe: :source_before_first_section, source_role: role }
|
|
289
|
+
end
|
|
260
290
|
|
|
261
291
|
owners = headings.each_with_index.map do |heading, index|
|
|
262
292
|
signature = [heading.level, heading.text].freeze
|
|
@@ -288,6 +318,27 @@ module Markdown
|
|
|
288
318
|
{ parse_error: e.message, source_role: role }
|
|
289
319
|
end
|
|
290
320
|
|
|
321
|
+
# A nested heading is ownable when it has no descendant headings. Ancestor
|
|
322
|
+
# headings remain unmanaged framing, so edits to them still fail closed.
|
|
323
|
+
def independently_ownable_headings(headings)
|
|
324
|
+
headings.each_with_index.reject do |heading, index|
|
|
325
|
+
headings[(index + 1)..].any? do |candidate|
|
|
326
|
+
candidate.level > heading.level &&
|
|
327
|
+
headings[(index + 1)..].take_while { |item| item.level > heading.level }.include?(candidate)
|
|
328
|
+
end
|
|
329
|
+
end.map(&:first)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def heading_only_prefix?(source, headings, first_owner_start)
|
|
333
|
+
prefix = source.byteslice(0...first_owner_start).to_s
|
|
334
|
+
heading_lines = headings.select { |heading| line_start_byte(source, heading.start_line) < first_owner_start }
|
|
335
|
+
heading_lines.any? && prefix.lines.all? do |line|
|
|
336
|
+
line.strip.empty? || heading_lines.any? do |heading|
|
|
337
|
+
line.strip.match?(/\A\#{1,6}\s+#{Regexp.escape(heading.text.to_s)}\s*\z/)
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
291
342
|
def parse_exact_source(source, role)
|
|
292
343
|
parsed = parse_ast(source, role)
|
|
293
344
|
return parsed if provider_failure?(parsed)
|
|
@@ -517,7 +568,7 @@ module Markdown
|
|
|
517
568
|
owner = role && documents.fetch(role).by_id[id]
|
|
518
569
|
owner && [owner, role]
|
|
519
570
|
end
|
|
520
|
-
output = fragments.map { |owner, _role| owner.source_text }.join
|
|
571
|
+
output = composite_prefix(documents) + fragments.map { |owner, _role| owner.source_text }.join
|
|
521
572
|
expected = fragments.map { |owner, _role| [owner.signature, owner.fingerprint] }
|
|
522
573
|
verification = verify_composite(output, expected)
|
|
523
574
|
return render_failure(request, verification, :exact_markdown_composite, decision.changes) unless
|
|
@@ -615,6 +666,14 @@ module Markdown
|
|
|
615
666
|
}
|
|
616
667
|
end
|
|
617
668
|
|
|
669
|
+
def composite_prefix(documents, role = :ours)
|
|
670
|
+
document = documents.fetch(role)
|
|
671
|
+
first_owner = document.owners.first
|
|
672
|
+
return '' unless first_owner
|
|
673
|
+
|
|
674
|
+
document.source.byteslice(0...first_owner.start_byte).to_s
|
|
675
|
+
end
|
|
676
|
+
|
|
618
677
|
def render_conflicts(request, documents, decision)
|
|
619
678
|
localized = localized_conflict_output(request, documents, decision)
|
|
620
679
|
strategy = localized ? :section_localized_conflict : :full_file_conflict
|
|
@@ -714,9 +773,20 @@ module Markdown
|
|
|
714
773
|
)
|
|
715
774
|
end
|
|
716
775
|
|
|
717
|
-
def
|
|
776
|
+
def unsafe_merge2_order_failure(request)
|
|
718
777
|
failure(
|
|
719
|
-
:
|
|
778
|
+
:merge2,
|
|
779
|
+
request,
|
|
780
|
+
category: :unsafe_source_range,
|
|
781
|
+
message: 'Markdown section order cannot be proven across current and incoming sources.',
|
|
782
|
+
conflicts: [{ category: :incompatible_section_order, path: '<document>' }],
|
|
783
|
+
render_report: { strategy: :no_output, provenance: :exact_source_bytes }
|
|
784
|
+
)
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
def render_failure(request, verification, strategy, changes = [], operation: :merge3)
|
|
788
|
+
failure(
|
|
789
|
+
operation,
|
|
720
790
|
request,
|
|
721
791
|
category: :render_failure,
|
|
722
792
|
message: 'Markdown output failed backend reparse and ordered semantic verification.',
|
|
@@ -5,7 +5,7 @@ module Markdown
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = '7.1.
|
|
8
|
+
VERSION = '7.1.9'
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
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: markdown-merge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.1.
|
|
4
|
+
version: 7.1.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -43,28 +43,28 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - '='
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 7.1.
|
|
46
|
+
version: 7.1.9
|
|
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.
|
|
53
|
+
version: 7.1.9
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: tree_haver
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
57
57
|
requirements:
|
|
58
58
|
- - '='
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: 7.1.
|
|
60
|
+
version: 7.1.9
|
|
61
61
|
type: :runtime
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - '='
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: 7.1.
|
|
67
|
+
version: 7.1.9
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: version_gem
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -94,7 +94,7 @@ dependencies:
|
|
|
94
94
|
version: '3.0'
|
|
95
95
|
- - ">="
|
|
96
96
|
- !ruby/object:Gem::Version
|
|
97
|
-
version: 3.0.
|
|
97
|
+
version: 3.0.34
|
|
98
98
|
type: :development
|
|
99
99
|
prerelease: false
|
|
100
100
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -104,7 +104,7 @@ dependencies:
|
|
|
104
104
|
version: '3.0'
|
|
105
105
|
- - ">="
|
|
106
106
|
- !ruby/object:Gem::Version
|
|
107
|
-
version: 3.0.
|
|
107
|
+
version: 3.0.34
|
|
108
108
|
- !ruby/object:Gem::Dependency
|
|
109
109
|
name: bundler-audit
|
|
110
110
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -182,7 +182,7 @@ dependencies:
|
|
|
182
182
|
version: '3.2'
|
|
183
183
|
- - ">="
|
|
184
184
|
- !ruby/object:Gem::Version
|
|
185
|
-
version: 3.2.
|
|
185
|
+
version: 3.2.3
|
|
186
186
|
type: :development
|
|
187
187
|
prerelease: false
|
|
188
188
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -192,7 +192,7 @@ dependencies:
|
|
|
192
192
|
version: '3.2'
|
|
193
193
|
- - ">="
|
|
194
194
|
- !ruby/object:Gem::Version
|
|
195
|
-
version: 3.2.
|
|
195
|
+
version: 3.2.3
|
|
196
196
|
- !ruby/object:Gem::Dependency
|
|
197
197
|
name: kettle-test
|
|
198
198
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -222,7 +222,7 @@ dependencies:
|
|
|
222
222
|
version: '3.2'
|
|
223
223
|
- - ">="
|
|
224
224
|
- !ruby/object:Gem::Version
|
|
225
|
-
version: 3.2.
|
|
225
|
+
version: 3.2.7
|
|
226
226
|
type: :development
|
|
227
227
|
prerelease: false
|
|
228
228
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -232,7 +232,7 @@ dependencies:
|
|
|
232
232
|
version: '3.2'
|
|
233
233
|
- - ">="
|
|
234
234
|
- !ruby/object:Gem::Version
|
|
235
|
-
version: 3.2.
|
|
235
|
+
version: 3.2.7
|
|
236
236
|
- !ruby/object:Gem::Dependency
|
|
237
237
|
name: ruby-progressbar
|
|
238
238
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -345,10 +345,10 @@ licenses:
|
|
|
345
345
|
- PolyForm-Small-Business-1.0.0
|
|
346
346
|
metadata:
|
|
347
347
|
homepage_uri: https://structuredmerge.org
|
|
348
|
-
source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.
|
|
349
|
-
changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.
|
|
348
|
+
source_code_uri: https://github.com/structuredmerge/structuredmerge-ruby/tree/v7.1.9
|
|
349
|
+
changelog_uri: https://github.com/structuredmerge/structuredmerge-ruby/blob/v7.1.9/CHANGELOG.md
|
|
350
350
|
bug_tracker_uri: https://github.com/structuredmerge/structuredmerge-ruby/issues
|
|
351
|
-
documentation_uri: https://www.rubydoc.info/gems/markdown-merge/7.1.
|
|
351
|
+
documentation_uri: https://www.rubydoc.info/gems/markdown-merge/7.1.9
|
|
352
352
|
funding_uri: https://github.com/sponsors/pboling
|
|
353
353
|
wiki_uri: https://github.com/structuredmerge/structuredmerge-ruby/wiki
|
|
354
354
|
news_uri: https://www.railsbling.com/tags/markdown-merge
|
metadata.gz.sig
CHANGED
|
Binary file
|