Skip to main content

reference_transaction/
reference_transaction.rs

1//! Publish an unborn branch and a tag, then delete the tag while retaining its reflog.
2use girt::refs::{Expected, RefEdit, RefName, Reflog, TransactionError};
3use girt::{Commit, CommitFields, Repository, Signature};
4
5fn main() -> Result<(), Box<dyn std::error::Error>> {
6    let directory = tempfile::tempdir()?;
7    let repo = Repository::init(
8        girt::ObjectFormat::Sha256,
9        directory.path().join("repository"),
10        girt::InitKind::Bare,
11    )?;
12
13    let objects = repo.loose_objects();
14    let tree = objects.write_tree(&repo.new_tree(Vec::new())?)?;
15
16    let committer = Signature {
17        name: b"Example Writer".to_vec(),
18        email: b"writer@example.com".to_vec(),
19        seconds: 1700000000,
20        offset_minutes: 0,
21    };
22
23    let commit = Commit::new(CommitFields {
24        tree,
25        parents: Vec::new(),
26        author: committer.clone(),
27        committer: committer.clone(),
28        extra_headers: Vec::new(),
29        message: b"Initial commit\n".to_vec(),
30    })?;
31    let id = objects.write_commit(&commit)?;
32
33    let head = RefName::HEAD;
34    let tag = RefName::tag("initial")?;
35    let edits = [
36        {
37            let input = RefEdit::new(
38                head.clone(),
39                Some(girt::refs::Target::Direct(id)),
40                Expected::Absent,
41                Reflog::Append {
42                    committer: committer.clone(),
43                    message: b"initial publication".to_vec(),
44                },
45            );
46            input.with_dereference(girt::refs::Dereference::Follow)
47        },
48        RefEdit::create(
49            tag.clone(),
50            id,
51            Reflog::Append {
52                committer: committer.clone(),
53                message: b"mark initial".to_vec(),
54            },
55        ),
56    ];
57
58    let refs = repo.references()?;
59    match refs.transaction(&edits) {
60        Ok(outcomes) => println!("Published: {outcomes:?}"),
61        Err(TransactionError::Prepare { operation, source }) => {
62            eprintln!("No refs or logs changed; operation {operation:?}: {source}");
63            return Err(source.into());
64        }
65        Err(error @ TransactionError::Publish { .. }) => {
66            // Inspect per-ref and per-log outcomes before deciding whether/how to retry.
67            eprintln!("Partial publication: {error:?}");
68            return Err(error.into());
69        }
70        Err(error) => return Err(error.into()),
71    }
72
73    refs.transaction(&[RefEdit::delete(
74        tag.clone(),
75        id,
76        Reflog::Append {
77            committer,
78            message: b"remove temporary tag".to_vec(),
79        },
80    )])?;
81
82    assert_eq!(refs.reflog(&head)?.unwrap().len(), 1);
83    assert_eq!(refs.reflog(&tag)?.unwrap().len(), 2);
84    assert_eq!(refs.read(&tag)?, None);
85
86    // A retention ref may already exist at this value, but must never overwrite another value.
87    let keep = RefName::new("refs/jj/keep-example")?;
88    refs.transaction(&[RefEdit::new(
89        keep.clone(),
90        Some(girt::refs::Target::Direct(id)),
91        Expected::AbsentOr(girt::refs::Target::Direct(id)),
92        Reflog::Preserve,
93    )])?;
94    refs.transaction(&[RefEdit::new(keep, None, Expected::Exists, Reflog::Delete)])?;
95    Ok(())
96}