mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
Two defects with one cause, which is that a comparison and a write want different identifier frames and the code used one frame for both. Bringing main into a branch compared in the branch's frame. It remapped main and the base into that frame, which puts the branch's inherited content where it cannot agree with the base: everything the branch inherited still names main, so every reference the remap rewrote read as a difference. On a demo branch that was 364 of 368 reported conflicts, and none of them was an edit anybody made. The comparison now runs in main's frame, exactly as the compare view and the listing summary already do, and the changes it produces are translated into the branch's frame before they are applied and logged. That translation is the new `branch_merge.cljc::remap-changes`, which is a separate function rather than a call to `remap-refs` for a reason its docstring gives: `relink-refs` finds a reference where the attribute and the value travel together, and a change vector carries the identifier alone. The write map is renamed to `apply-map`, so no later reader can reach for it as the comparison map. The compare also decoded its merge base twice. The derive resolves and decodes the pinned snapshot in order to fold the op log over it, and the caller then decoded the same bytes again for the comparison's third version, which is about 406 ms on the design-system file and is paid by every uncached branch summary in a listing as well. The derive now offers the pre-fold value it already holds, behind an explicit opt-in, so an ordinary read gains neither a key nor a cost, and a branch read that forgets the flag raises rather than comparing against nothing. The invariant a comment now states is that the comparison's base is the pre-fold value, which is sound because the fold is pure: `process-changes` threads a value, every method builds persistently, and `PointerMap`'s `assoc` and `without` return new objects, so the pre-fold base shares structure with the folded result and costs no extra memory for the pages the log did not touch. The new test asserts both halves, because either alone passes a wrong implementation. A branch carrying only inherited content, updated from a main that changed elsewhere, reports no conflicts and leaves an empty op log. Main and the branch repainting the same shape still reports exactly one conflict, naming that shape. Verified: the whole backend suite, 919 tests and 5,525 assertions with 0 failures.