Skip to content

Commit 31c8dc3

Browse files
committed
feat: add IdStrategy::CommitOid for commit-OID-keyed entity refs
Allows Ledger::create to name refs after the OID of the commit it writes, providing a permanent, stable identity. Eliminates the need for callers to bypass create() and manually manage the tree/commit/ref dance. Closes: #6 Assisted-by: Claude Code (Claude Haiku 4.5)
1 parent 912b8ce commit 31c8dc3

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

crates/git-ledger/src/lib.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub enum IdStrategy<'a> {
2626
ContentAddressed(&'a [u8]),
2727
/// Use the caller's string directly.
2828
CallerProvided(&'a str),
29+
/// Name the record's ref after the OID of the commit that `create` writes.
30+
CommitOid,
2931
}
3032

3133
/// A mutation to apply to a record's fields.
@@ -215,6 +217,28 @@ impl Ledger for Repository {
215217
fields: &[(&str, &[u8])],
216218
message: &str,
217219
) -> Result<LedgerEntry, Error> {
220+
let tree_oid = build_fields_tree(self, fields)?;
221+
let tree = self.find_tree(tree_oid)?;
222+
let sig = self.signature()?;
223+
224+
if let IdStrategy::CommitOid = strategy {
225+
// Write the commit first (no ref), then name the ref after its OID.
226+
let commit_oid = self.commit(None, &sig, &sig, message, &tree, &[])?;
227+
let ref_name = if ref_prefix.ends_with('/') {
228+
format!("{}{}", ref_prefix, commit_oid)
229+
} else {
230+
format!("{}/{}", ref_prefix, commit_oid)
231+
};
232+
self.reference(&ref_name, commit_oid, false, message)?;
233+
let fields = read_fields(self, &tree, "")?;
234+
return Ok(LedgerEntry {
235+
id: commit_oid.to_string(),
236+
ref_: ref_name,
237+
commit: commit_oid,
238+
fields,
239+
});
240+
}
241+
218242
let id = match strategy {
219243
IdStrategy::Sequential => {
220244
let next = next_sequential_id(self, ref_prefix)?;
@@ -225,6 +249,7 @@ impl Ledger for Repository {
225249
oid.to_string()
226250
}
227251
IdStrategy::CallerProvided(s) => s.to_string(),
252+
IdStrategy::CommitOid => unreachable!(),
228253
};
229254

230255
let ref_name = if ref_prefix.ends_with('/') {
@@ -241,10 +266,6 @@ impl Ledger for Repository {
241266
)));
242267
}
243268

244-
let tree_oid = build_fields_tree(self, fields)?;
245-
let tree = self.find_tree(tree_oid)?;
246-
let sig = self.signature()?;
247-
248269
let commit_oid = self.commit(
249270
Some(&ref_name),
250271
&sig,

crates/git-ledger/src/tests.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,26 @@ fn delete_nested_field_removes_empty_parent() {
324324
// Both the nested field and its now-empty parent should be gone
325325
assert!(updated.fields.is_empty());
326326
}
327+
328+
#[test]
329+
fn create_commit_oid() {
330+
let (_dir, repo) = init_repo();
331+
332+
let entry = repo
333+
.create(
334+
PREFIX,
335+
&IdStrategy::CommitOid,
336+
&[("title", b"hello"), ("status", b"open")],
337+
"create record",
338+
)
339+
.unwrap();
340+
341+
// ID should be the commit OID (40 hex chars)
342+
assert_eq!(entry.id.len(), 40);
343+
assert_eq!(entry.id, entry.commit.to_string());
344+
// Ref should contain the commit OID
345+
assert_eq!(entry.ref_, format!("{}/{}", PREFIX, entry.id));
346+
// Should be readable back
347+
let read = repo.read(&entry.ref_).unwrap();
348+
assert_eq!(read.fields, entry.fields);
349+
}

0 commit comments

Comments
 (0)