Skip to content
MediumEditing SystemsPython 3

Patch Conflict Detector

Apply optimistic multi-file patches atomically while rejecting stale or overlapping edits.

40m3 sample tests9 hidden tests

Implement apply_patches(files, patches), an atomic patch applier with .

Requirements

  • files maps file path to text.
  • Each patch is (path, start, end, expected_old_text, replacement).
  • Validate all patches before mutating anything.
  • Raise ValueError for invalid ranges (start < 0, end < start, or end > len(text)), missing files, overlapping patches in the same file, and expected-text mismatches.
  • Adjacent ranges that only touch at an endpoint are allowed. Multiple empty inserts at the same offset are allowed and preserve input order. Order by (start, end) stably within each file; an insert at a replacement's start appears before the replacement, and an insert at its end appears after.
  • An empty insert strictly inside a nonempty patch range is an overlap conflict. An insertion must have expected_old_text="".
  • Apply patches from the end of each file so offsets stay stable.
  • Return a new dictionary and leave input untouched.

Example

The first patch succeeds without changing files. The stale patch then fails against the same input, which remains untouched.

python
1files = {"app.py": "print('old')\n"} 2patches = [("app.py", 6, 11, "'old'", "'new'")] 3assert apply_patches(files, patches)["app.py"] == "print('new')\n" 4assert files["app.py"] == "print('old')\n" 5 6try: 7 apply_patches(files, [("app.py", 6, 11, "'stale'", "'new'")]) 8except ValueError: 9 pass 10else: 11 raise AssertionError("expected stale patch conflict") 12 13assert files["app.py"] == "print('old')\n"

Constraints

  • No diff parser is required.
  • Offsets address each file's original text, not a result from an earlier patch. Use Python code-point indexes or Java UTF-16 code-unit indexes for the selected language.

What the expected text proves

Expected text checks a particular slice of the input snapshot. If "hello!" replaces an earlier "hello", a patch expecting "hello" at [0, 5) still matches: the changed suffix is outside its checked range. If you need to reject any change to the file, require an expected whole-file hash or version as a follow-up.

All-or-nothing here means returning a fully built new map or raising without changing the caller's map. There are no filesystem writes or concurrent-writer guarantees. A later invalid file must prevent the whole call from returning a partially patched result.

For files={"a": "abc"}, patches [("a", 1, 2, "b", "R"), ("a", 1, 1, "", "L"), ("a", 2, 2, "", "E")] produce {"a": "aLREc"}. Both expected slices are checked against the original abc; right-to-left application avoids shifting the remaining original offsets. Sorting by start alone is insufficient because an input replacement can precede an insert at that same start.

Editor