FA-75641 / Text diff and three-way merge / Open access
Line mapping through hunks: the line after a changed range is reported as removed · case 01
A comment on the first unchanged line after a hunk is dropped.
ROOT CAUSE
The in-range test includes the exclusive end of the old range.
VERIFIED REPAIR
An old line is inside the hunk when start <= line < start + length.
Unsuccessful approach: Excluding the last line of the range maps a deleted line as if it survived.
Case contract
Hunks [old_start, old_len, new_start, new_len] use unified conventions (start is the line before when the length is 0) and are sorted. An old line maps to None if it lies in a hunk's old range; otherwise it moves by the sum of new_len - old_len over hunks entirely before it. A zero-length hunk after line s affects lines from s+1 on. Return the mapped line numbers.
Why this case matters
Review tools re-anchor comments on changed files by mapping old line numbers through the diff.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(hunks, lines):
res = []
for ln in lines:
shift = 0
out = ln
for os_, ol, ns, nl in hunks:
s = os_ if ol > 0 else os_ + 1
if ln < s:
break
if ln <= s + ol:
out = None
break
shift += nl - ol
res.append(None if out is None else ln + shift)
return res
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = {
1: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 1]], [5, 6, 7]], [5, 7, 8]), ('pure deletion', [[[3, 1, 2, 0]], [2, 3, 3, 4, 10]], [2, None, None, 3, 9]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 1]], [1, 1])],
2: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 2]], [5, 6, 7]], [5, 8, 9]), ('pure deletion', [[[3, 2, 2, 0]], [2, 3, 4, 5, 10]], [2, None, None, 3, 8]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 2]], [1, 2])],
3: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 3]], [5, 6, 7]], [5, 9, 10]), ('pure deletion', [[[3, 3, 2, 0]], [2, 3, 5, 6, 10]], [2, None, None, 3, 7]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 3]], [1, 3])],
4: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 4]], [5, 6, 7]], [5, 10, 11]), ('pure deletion', [[[3, 4, 2, 0]], [2, 3, 6, 7, 10]], [2, None, None, 3, 6]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 4]], [1, 4])],
5: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 5]], [5, 6, 7]], [5, 11, 12]), ('pure deletion', [[[3, 5, 2, 0]], [2, 3, 7, 8, 10]], [2, None, None, 3, 5]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 5]], [1, 5])],
}[N]
for label, args, expected in cases:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| lines before and after a replacement | [4, None, None, None, 9] | [4, None, None, 8, 9] | Failed |
| pure insertion after line five | [5, None, 8] | [5, 7, 8] | Failed |
| pure deletion | [2, None, None, None, 9] | [2, None, None, 3, 9] | Failed |
| insertion at the top of the file | [None, 4] | [3, 4] | Failed |
| several hunks accumulate | [1, None, None, 10, None, None, 19, None] | [1, None, 4, 10, None, 12, 19, 24] | Failed |
| line just after a hunk | [None, None, 13] | [None, 12, 13] | Failed |
| last line of a deletion | [None, None] | [None, 4] | Failed |
| no hunks | [1, 1] | [1, 1] | Passed |
SHA-256 / 721a30f51284aeccced13abfd0456e74320849bf716bab9e2b9a6a8b41d1c52c
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(hunks, lines):
res = []
for ln in lines:
shift = 0
out = ln
for os_, ol, ns, nl in hunks:
s = os_ if ol > 0 else os_ + 1
if ln < s:
break
if ln < s + ol - 1:
out = None
break
shift += nl - ol
res.append(None if out is None else ln + shift)
return res
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = {
1: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 1]], [5, 6, 7]], [5, 7, 8]), ('pure deletion', [[[3, 1, 2, 0]], [2, 3, 3, 4, 10]], [2, None, None, 3, 9]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 1]], [1, 1])],
2: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 2]], [5, 6, 7]], [5, 8, 9]), ('pure deletion', [[[3, 2, 2, 0]], [2, 3, 4, 5, 10]], [2, None, None, 3, 8]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 2]], [1, 2])],
3: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 3]], [5, 6, 7]], [5, 9, 10]), ('pure deletion', [[[3, 3, 2, 0]], [2, 3, 5, 6, 10]], [2, None, None, 3, 7]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 3]], [1, 3])],
4: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 4]], [5, 6, 7]], [5, 10, 11]), ('pure deletion', [[[3, 4, 2, 0]], [2, 3, 6, 7, 10]], [2, None, None, 3, 6]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 4]], [1, 4])],
5: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 5]], [5, 6, 7]], [5, 11, 12]), ('pure deletion', [[[3, 5, 2, 0]], [2, 3, 7, 8, 10]], [2, None, None, 3, 5]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 5]], [1, 5])],
}[N]
for label, args, expected in cases:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| lines before and after a replacement | [4, None, 7, 8, 9] | [4, None, None, 8, 9] | Failed |
| pure insertion after line five | [5, 7, 8] | [5, 7, 8] | Passed |
| pure deletion | [2, 2, 2, 3, 9] | [2, None, None, 3, 9] | Failed |
| insertion at the top of the file | [3, 4] | [3, 4] | Passed |
| several hunks accumulate | [1, 3, 4, 10, None, 12, 19, 24] | [1, None, 4, 10, None, 12, 19, 24] | Failed |
| line just after a hunk | [11, 12, 13] | [None, 12, 13] | Failed |
| last line of a deletion | [3, 4] | [None, 4] | Failed |
| no hunks | [1, 1] | [1, 1] | Passed |
SHA-256 / 6a16f3b55e8a77392f78b8499b8435d1567a73dadaed2d0c5f0d7f5be50b7c17
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(hunks, lines):
res = []
for ln in lines:
shift = 0
out = ln
for os_, ol, ns, nl in hunks:
s = os_ if ol > 0 else os_ + 1
if ln < s:
break
if ln < s + ol:
out = None
break
shift += nl - ol
res.append(None if out is None else ln + shift)
return res
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = {
1: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 1]], [5, 6, 7]], [5, 7, 8]), ('pure deletion', [[[3, 1, 2, 0]], [2, 3, 3, 4, 10]], [2, None, None, 3, 9]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 1]], [1, 1])],
2: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 2]], [5, 6, 7]], [5, 8, 9]), ('pure deletion', [[[3, 2, 2, 0]], [2, 3, 4, 5, 10]], [2, None, None, 3, 8]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 2]], [1, 2])],
3: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 3]], [5, 6, 7]], [5, 9, 10]), ('pure deletion', [[[3, 3, 2, 0]], [2, 3, 5, 6, 10]], [2, None, None, 3, 7]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 3]], [1, 3])],
4: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 4]], [5, 6, 7]], [5, 10, 11]), ('pure deletion', [[[3, 4, 2, 0]], [2, 3, 6, 7, 10]], [2, None, None, 3, 6]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 4]], [1, 4])],
5: [('lines before and after a replacement', [[[5, 2, 5, 3]], [4, 5, 6, 7, 8]], [4, None, None, 8, 9]), ('pure insertion after line five', [[[5, 0, 6, 5]], [5, 6, 7]], [5, 11, 12]), ('pure deletion', [[[3, 5, 2, 0]], [2, 3, 7, 8, 10]], [2, None, None, 3, 5]), ('insertion at the top of the file', [[[0, 0, 1, 2]], [1, 2]], [3, 4]), ('several hunks accumulate', [[[2, 1, 2, 2], [10, 3, 11, 1], [20, 0, 19, 4]], [1, 2, 3, 9, 10, 13, 20, 21]], [1, None, 4, 10, None, 12, 19, 24]), ('line just after a hunk', [[[7, 2, 7, 5]], [8, 9, 10]], [None, 12, 13]), ('last line of a deletion', [[[4, 3, 3, 0]], [6, 7]], [None, 4]), ('no hunks', [[], [1, 5]], [1, 5])],
}[N]
for label, args, expected in cases:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| lines before and after a replacement | [4, None, None, 8, 9] | [4, None, None, 8, 9] | Passed |
| pure insertion after line five | [5, 7, 8] | [5, 7, 8] | Passed |
| pure deletion | [2, None, None, 3, 9] | [2, None, None, 3, 9] | Passed |
| insertion at the top of the file | [3, 4] | [3, 4] | Passed |
| several hunks accumulate | [1, None, 4, 10, None, 12, 19, 24] | [1, None, 4, 10, None, 12, 19, 24] | Passed |
| line just after a hunk | [None, 12, 13] | [None, 12, 13] | Passed |
| last line of a deletion | [None, 4] | [None, 4] | Passed |
| no hunks | [1, 1] | [1, 1] | Passed |
SHA-256 / aab39124f240e0d5b7f21f67c12b75c82c38c65ca3b9144132e6026cc1d781f7
Verification & scope
A deterministic, bounded teaching model of one diff, patch or merge rule with stipulated conventions; it is not a production diff or version-control implementation and makes no claim of conformance to any specific tool. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:49:08.856868+00:00.
Case digest / 720fc15d82ab898a3fa190e72ba5d0b3fef96c37e50bfc461038244117a4652d