git-ents.gitmain
⌘K
foforge
commit 1f213c2
feat: snapshot caches as parentless commits

A cache ref’s history has no audit value, so chaining each snapshot on the last pinned every blob sccache ever evicted, growing the object database without bound. Snapshots now fully replace the tip; replaced commits become unreachable and garbage-collectable.

feat: add store_tree_replace() to git-store Assisted-by: Claude:claude-haiku-4-5-20251001 Assisted-by: Claude:claude-fable-5

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/git-effect/src/cache.rs @@ -109,8 +109,10 @@ } /// Snapshot the sandbox's [`cache_dir`] for `name` back to [`cache_ref`], -/// replacing any prior snapshot as a new commit — the state [`restore`] will -/// pick up on this cache's next use. +/// replacing any prior snapshot with a parentless commit — cache history has +/// no audit value, so replaced snapshots become garbage-collectable instead +/// of pinned by a parent chain. The tip is the state [`restore`] will pick +/// up on this cache's next use. /// /// ## Requirements /// @@ -192,6 +194,6 @@ let store = git_store::Store::open(repo).map_err(|e| format!("could not open store: {e}"))?; store - .store_tree(&cache_ref(name), tree, "Update cache") + .store_tree_replace(&cache_ref(name), tree, "Update cache") .map_err(|e| format!("could not store cache {name}: {e}")) }
crates/git-store/src/lib.rs @@ -308,6 +308,24 @@ self.try_set_ref(refname, expected, commit) } + /// Write `tree` to `refname` as a parentless commit, fully replacing the + /// ref's prior tip. Like [`store_tree`], it uses a compare-and-swap shape + /// for consistency, but omits the parent chain. This is suited for refs + /// whose history has no audit value — e.g. cache snapshots — where old + /// commits become unreachable and garbage-collectable rather than pinned + /// by a parent chain. + pub fn store_tree_replace( + &self, + refname: &str, + tree: ObjectId, + message: &str, + ) -> Result<(), Error> { + let expected = self.ref_commit(refname)?; + let parents = Vec::new(); + let commit = self.write_commit(tree, parents, expected, message, None)?; + self.try_set_ref(refname, expected, commit) + } + /// The tree of `refname`'s tip commit. pub fn ref_tree(&self, refname: &str) -> Result<ObjectId, Error> { let commit = self @@ -922,6 +940,24 @@ let _ = store.ref_tree("refs/meta/raw").unwrap_err(); } + #[test] + fn store_tree_replace_writes_parentless_commits() { + let dir = repo(); + let store = Store::open(dir.path()).unwrap(); + let first = facet_git_tree::serialize_into(&"first".to_owned(), &store.odb).unwrap(); + let second = facet_git_tree::serialize_into(&"second".to_owned(), &store.odb).unwrap(); + store + .store_tree_replace("refs/meta/raw", first, "write") + .unwrap(); + store + .store_tree_replace("refs/meta/raw", second, "write") + .unwrap(); + assert_eq!(store.ref_tree("refs/meta/raw").unwrap(), second); + let tip = store.ref_commit("refs/meta/raw").unwrap().unwrap(); + let commit = store.read_commit(&tip).unwrap(); + assert!(commit.parents.is_empty()); + } + /// A minimal keyed item, used to exercise `load_item`/`store_item`/ /// `list_items`/`store_keyed`. #[derive(Facet, Clone, Debug, PartialEq)]