mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
✨ Store branch state as an op log, not a file copy
Swap the storage and materialization layer of file branching to remove the per-branch data duplication, keeping the RPC contracts and the frontend unchanged. - file_branch_change (0154): one row per branch save, the blob-encoded change vector, ordered by revn; branch files store no data payload. - Read path: app.binfile.common/get-file* derives the branch file data by replaying the op log over the pinned merge-base snapshot, carrying the base's version and migration set so need-migration? stays quiet. - Save path: update-file routes branch files to persist-branch-file!, which appends the vector to the op log and updates the file row without any file_data write. - Creation: create-file-branch no longer duplicates main's data; it inserts an empty branch file row, grants the creator, and copies the library rels. - update-branch-from-main squashes the op log after integrating main's changes: the repositioned base already carries main's side, so the log is replaced with the net branch-only changes. - Kept (keep-branch) branches retain the base pin: their state is derived, so releasing the pin would leave them unreadable after GC. Tests: backend-tests.rpc-file-branch-oplog-test asserts the storage and derivation invariants; the existing branch, file, binfile, snapshot, thumbnail, and pull request suites pass unchanged against the swapped engine (84 tests, 1487 assertions, 0 failures).
This commit is contained in:
parent
bc22388776
commit
b043fcc76a
@ -332,11 +332,18 @@
|
||||
|
||||
;; same decode `fsnap/get-snapshot` applies: storage-backed
|
||||
;; snapshots carry the storage ref inside :metadata, so it must
|
||||
;; be decoded before `resolve-file-data` can fetch the object
|
||||
;; be decoded before `resolve-file-data` can fetch the object.
|
||||
;; NOTE the (cfg file) argument order of both fdata fns: the
|
||||
;; snapshot row is the FILE argument, never the cfg.
|
||||
base (-> base
|
||||
(d/update-when :metadata fdata/decode-metadata)
|
||||
(fdata/resolve-file-data cfg)
|
||||
(fdata/decode-file-data cfg))
|
||||
(d/update-when :metadata fdata/decode-metadata))
|
||||
|
||||
base (if (nil? (:migrations base))
|
||||
(dissoc base :migrations)
|
||||
(update base :migrations db/pgarray->set))
|
||||
|
||||
base (fdata/resolve-file-data cfg base)
|
||||
base (fdata/decode-file-data cfg base)
|
||||
|
||||
rows (db/exec! conn [sql:get-branch-changes id])
|
||||
|
||||
@ -346,9 +353,16 @@
|
||||
rows)
|
||||
|
||||
data (if decode? data (blob/encode data))]
|
||||
(assoc file :data data)))
|
||||
|
||||
|
||||
(-> file
|
||||
;; the derived data is the BASE's data plus the op log, so the
|
||||
;; file carries the base's version and migration set; this keeps
|
||||
;; `need-migration?` quiet while base and main share a version
|
||||
;; and migrates in memory (read-only) only when the base is
|
||||
;; actually stale
|
||||
(assoc :data data)
|
||||
(assoc :version (or (:version base) (:version file)))
|
||||
(cond-> (some? (:migrations base))
|
||||
(assoc :migrations (:migrations base))))))
|
||||
(defn- get-file*
|
||||
[{:keys [::db/conn] :as cfg} id
|
||||
{:keys [migrate?
|
||||
|
||||
@ -940,7 +940,7 @@
|
||||
;; into replayable ops; nothing has been
|
||||
;; persisted yet, so the transaction rollback
|
||||
;; undoes the snapshot and media copies above.
|
||||
(let [{:keys [squashed]
|
||||
(let [{net-changes :changes
|
||||
unsupported :unsupported}
|
||||
(bm/compute-changes main-data main-data (:data updated) {})]
|
||||
(when (seq unsupported)
|
||||
@ -948,7 +948,7 @@
|
||||
:code :unsupported-update-squash
|
||||
:hint "the update produces branch-only changes that cannot be replayed"
|
||||
:kinds (vec unsupported)))
|
||||
(persist-branch-update! cfg updated ts branch-id squashed))
|
||||
(persist-branch-update! cfg updated ts branch-id net-changes))
|
||||
(reposition-base! ts (:revn updated))
|
||||
|
||||
(mbus/pub! msgbus
|
||||
|
||||
@ -46,8 +46,11 @@
|
||||
(defn- oplog-rows [branch-file-id]
|
||||
(th/db-query :file-branch-change {:file-id branch-file-id}))
|
||||
|
||||
;; only "main" rows: the auto-file-snapshot machinery appends "snapshot"
|
||||
;; rows on every save, which are checkpoints, not a stored data payload
|
||||
(defn- stored-data-rows [file-id]
|
||||
(th/db-query :file-data {:file-id file-id}))
|
||||
(->> (th/db-query :file-data {:file-id file-id})
|
||||
(filterv #(= "main" (:type %)))))
|
||||
|
||||
(t/deftest branch-stores-no-data-and-derives-state
|
||||
(with-redefs [cf/flags (conj cf/flags :branching)]
|
||||
@ -144,7 +147,7 @@
|
||||
|
||||
(t/testing "the op log is replaced by the branch-only net"
|
||||
(let [rows (oplog-rows branch-file-id)
|
||||
ops (mapv (comp blob/decode :changes) rows)]
|
||||
ops (-> rows first :changes blob/decode)]
|
||||
(t/is (= 1 (count rows)))
|
||||
(t/is (some #(and (= :add-color (:type %))
|
||||
(= branch-color (get-in % [:color :id])))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user