Artifact 49ac641a9bcf8c194da89d6d7c21eb84165682494eeceae15a5d20ad487858c4:
- File
internal/merge/marker.go
— part of check-in
[dcec15d91f]
at
2026-10-19 01:50:20
on branch feat/submanifest-reader
— fix(merge): write fossil's merge3 output byte for byte
Port fossil 2.28's merge3.c and the diff it runs on (break_into_lines, break_into_tokens, longestCommonSequence, diff_step, diff_optimize, the UTF-8/UTF-16 BOM handling) so Merge and Update align, merge and mark conflicts exactly as fossil does: line-numbered markers, the token-level suggested resolution, CRLF markers, and binary input left unmerged. HasConflictMarkers follows contains_merge_marker in skipping CR runs.
A fossil-binary comparison test checks 1237 generated and hand-written cases against "fossil 3-way-merge" and UTF-16 cases against "fossil merge"; fossil commit refuses a file holding our markers.
Tracked in #447 (user: [email protected], size: 2097) [annotate] [blame] [check-ins using] [more...]
package merge import "bytes" // Conflict markers, byte for byte the lines fossil's merge writes (merge3.c), // so a conflict go-libfossil writes is one fossil recognizes, and the other // way round. In the merged output the begin, ancestor and merged-in markers // carry a " (line N)" suffix, and each marker ends its line. const ( markerBegin = "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<" markerAncestor = "||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||" markerMergedIn = "======= MERGED IN content follows ===============================" markerEnd = ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" // markerSuggested introduces the token-level resolution written between // the local copy and the ancestor. markerSuggested = "####### SUGGESTED CONFLICT RESOLUTION follows ###################" ) // markerLen is the length every marker shares; detection compares this many // bytes at the start of each line, as fossil does. const markerLen = len(markerBegin) // Fossil's detector assumes every marker has the same length. var _ = [1]struct{}{}[len(markerAncestor)-markerLen] var _ = [1]struct{}{}[len(markerMergedIn)-markerLen] var _ = [1]struct{}{}[len(markerEnd)-markerLen] var _ = [1]struct{}{}[len(markerSuggested)-markerLen] // HasConflictMarkers reports whether data contains a line that starts with // one of the opening or middle conflict markers. This is fossil's // contains_merge_marker: a file is in conflict exactly when it holds one, so // a conflict is resolved by editing the markers out, with nothing to record. // As in fossil, a line starts after any run of '\n' and '\r' bytes. func HasConflictMarkers(data []byte) bool { markers := [...]string{markerBegin, markerSuggested, markerAncestor, markerMergedIn} n := len(data) - markerLen + 1 for i := 0; i < n; { for _, m := range markers { if bytes.HasPrefix(data[i:], []byte(m)) { return true } } for i < n && data[i] != '\n' { i++ } for i < n && (data[i] == '\n' || data[i] == '\r') { i++ } } return false }