git-ents.gitmain
⌘K
foforge
commit a984797
feat: reintroduce extra-parents support to git-store

A document commit can once again carry commits outside its own ref’s history as extra parents, so a cross-document provenance edge (e.g. a reply’s genesis commit pointing at the comment it replies to can be cut into the DAG without disturbing first-parent ancestry.

feat: add store_with_parents/store_authored_with_parents/store_item_authored_with_parents refactor: expose Store::ref_commit to resolve another ref’s tip commit Assisted-by: Claude:claude-sonnet-5 EOF )

Joseph D. Carpinelli · 1 month ago

Reviews

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

Start a review

verdict

crates/git-store/src/lib.rs @@ -178,7 +178,25 @@ value: &T, message: &str, ) -> Result<(), Error> { - self.store_impl(refname, value, message, None) + self.store_impl(refname, value, message, None, &[]) + } + + /// Like [`store`](Self::store), but also parenting the written commit on + /// each of `extra_parents`, beyond the ref's own prior tip. This records a + /// cross-document provenance edge — e.g. a reply comment's genesis commit + /// carries the replied-to comment's tip as a second parent, a + /// happens-after proof of exactly what was replied to — while + /// first-parent ancestry stays the document's own history, so + /// [`history`](Self::history) and [`provenance`](Self::provenance) are + /// unaffected. + pub fn store_with_parents<T: for<'a> Facet<'a>>( + &self, + refname: &str, + value: &T, + message: &str, + extra_parents: &[ObjectId], + ) -> Result<(), Error> { + self.store_impl(refname, value, message, None, extra_parents) } /// Like [`store`](Self::store), but attributing authorship to `author` @@ -192,7 +210,21 @@ message: &str, author: (&str, &str), ) -> Result<(), Error> { - self.store_impl(refname, value, message, Some(author)) + self.store_impl(refname, value, message, Some(author), &[]) + } + + /// Like [`store_authored`](Self::store_authored), but also parenting the + /// written commit on each of `extra_parents`, per + /// [`store_with_parents`](Self::store_with_parents). + pub fn store_authored_with_parents<T: for<'a> Facet<'a>>( + &self, + refname: &str, + value: &T, + message: &str, + author: (&str, &str), + extra_parents: &[ObjectId], + ) -> Result<(), Error> { + self.store_impl(refname, value, message, Some(author), extra_parents) } /// ## Requirements @@ -204,11 +236,15 @@ value: &T, message: &str, author: Option<(&str, &str)>, + extra_parents: &[ObjectId], ) -> Result<(), Error> { let mut expected = self.ref_commit(refname)?; let mut tree = facet_git_tree::serialize_into(value, &self.odb)?; for _ in 0..=MAX_MERGE_RETRIES { - let parents = expected.into_iter().collect(); + let parents = expected + .into_iter() + .chain(extra_parents.iter().copied()) + .collect(); let commit = self.write_commit(tree, parents, message, author)?; match self.try_set_ref(refname, expected, commit) { Ok(()) => return Ok(()), @@ -351,6 +387,27 @@ self.store_authored(&item_ref(prefix, id)?, value, message, author) } + /// Like [`store_item_authored`](Self::store_item_authored), but also + /// parenting the written commit on each of `extra_parents`, per + /// [`store_with_parents`](Self::store_with_parents). + pub fn store_item_authored_with_parents<T: for<'a> Facet<'a>>( + &self, + prefix: &str, + id: &str, + value: &T, + message: &str, + author: (&str, &str), + extra_parents: &[ObjectId], + ) -> Result<(), Error> { + self.store_authored_with_parents( + &item_ref(prefix, id)?, + value, + message, + author, + extra_parents, + ) + } + /// Like [`store_item`](Self::store_item), but for a [`HasId`] value that /// carries its own collection key, so the caller does not pass it twice. pub fn store_keyed<T: for<'a> Facet<'a> + HasId>( @@ -492,8 +549,11 @@ Ok(refs.into_iter().map(|(_seconds, name)| name).collect()) } - /// Resolve `refname` to the object id of its commit, or `None` when absent. - fn ref_commit(&self, refname: &str) -> Result<Option<ObjectId>, Error> { + /// Resolve `refname` to the object id of its tip commit, or `None` when + /// absent — the primitive a caller reaches for when it needs the commit + /// itself (e.g. to parent another commit on it), not just the document + /// [`load`](Self::load) reads out of it. + pub fn ref_commit(&self, refname: &str) -> Result<Option<ObjectId>, Error> { match self .repo .try_find_reference(refname)