mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
Data loss, reproduced twice rather than inferred. A branch stores no data of its own, so its whole work lives in `file_branch_change`, and `update-branch-from-main` had a fast path that deleted every one of those rows whenever the update found nothing of main's to apply. The comment there assumed the branch was in sync with main, which holds only when the branch has no changes of its own, and `branch-diverged?` was already computed two lines above and consulted only inside `reposition-base!`. A branch with one fill edit of `#bb0000` and an untouched main, updated with no resolutions, returned `:updated` and came back `#000000` with an empty log. The user asked to catch up with a main that had not moved and lost their edit. Through the modal the same defect made the resolution that says "keep mine" the one that discards it. The fast path is now guarded by `(not branch-diverged?)`, so a diverged branch falls through to the squash that re-derives its net against the new base. The predicate is the right one: it compares the branch file's own revision counter against the revision the base was last positioned at, and a log row and a revision bump are one event, because `persist-branch-file!` is the log's only appender. The comment at the guard states the invariant, which is that the log may be emptied only when what it encodes already sits in the base. One correction to how the fast path was described while this was being chased: `(empty? changes)` does not mean the branch has nothing of its own. In the update direction the change set is MAIN's contribution, so a branch that is behind main in content already took the squash path and still does. The fast path is reached when main has nothing to contribute, which is what the third new test pins. The three sibling paths that also empty or replace a log were each judged and are sound, because each one writes the state elsewhere inside the same transaction before emptying. Three regression tests, one per reproduction plus the case the fast path exists for, asserting through the derived document rather than through log row counts, because a legitimate squash rewrites the log while preserving the state. Verified: the whole backend suite, 923 tests and 5,550 assertions with 0 failures. The two live demo branches were checked read-only and still hold their one op-log row each, so the hand-test fixture was never damaged.