1use std::sync::atomic::AtomicBool;
3use std::time::{Duration, Instant};
4
5use girt::push::{ForcePolicy, PreparedPush, PushCommand, PushLimits, send_local};
6use girt::refs::RefName;
7use girt::transport::TransportControl;
8use girt::{
9 Commit, CommitFields, EntryMode, ObjectKind, Repository, Signature, Tag, TagFields, Tree,
10 TreeEntry,
11};
12
13fn repository() -> Result<(tempfile::TempDir, Repository), Box<dyn std::error::Error>> {
14 let root = tempfile::tempdir()?;
15 std::fs::create_dir(root.path().join("objects"))?;
16 std::fs::create_dir(root.path().join("refs"))?;
17 std::fs::write(root.path().join("HEAD"), b"ref: refs/heads/main\n")?;
18 std::fs::write(root.path().join("config"), b"[core]\nbare=true\n")?;
19 let repo = Repository::open(root.path())?;
20 Ok((root, repo))
21}
22
23fn main() -> Result<(), Box<dyn std::error::Error>> {
24 let (_source_root, source) = repository()?;
25 let (_destination_root, destination) = repository()?;
26
27 let loose = source.loose_objects();
28 let blob = loose.write_blob(b"Published by girt through native local storage\n")?;
29 let tree = loose.write_tree(&Tree::new(
30 girt::ObjectFormat::Sha1,
31 vec![TreeEntry {
32 mode: EntryMode::Blob,
33 name: b"hello.txt".to_vec(),
34 id: blob,
35 }],
36 )?)?;
37
38 let author = Signature::now(b"Push Example", b"push@example.com")?;
39 let commit = loose.write_commit(&Commit::new(CommitFields {
40 tree,
41 parents: vec![],
42 author: author.clone(),
43 committer: author.clone(),
44 extra_headers: vec![],
45 message: b"Local push fixture\n".to_vec(),
46 })?)?;
47
48 let tag = loose.write_tag(&Tag::new(TagFields {
49 target: commit,
50 target_kind: ObjectKind::Commit,
51 name: b"v1".to_vec(),
52 tagger: Some(author),
53 extra_headers: vec![],
54 message: b"First publication\n".to_vec(),
55 })?)?;
56
57 let cancel = AtomicBool::new(false);
58 let objects = source.objects()?;
59 let commands = vec![
60 PushCommand::new(RefName::new("refs/heads/main")?, None, commit)
61 .with_force(ForcePolicy::FastForwardOnly),
62 PushCommand::new(RefName::new("refs/tags/v1")?, None, tag)
63 .with_force(ForcePolicy::FastForwardOnly),
64 ];
65 let prepared = PreparedPush::new(&objects, commands, &[], PushLimits::default(), &cancel)?;
66
67 let report = send_local(
68 destination.git_dir(),
69 girt::push::LocalPushRequest::new(&prepared),
70 TransportControl::new(&cancel)
71 .with_deadline(Some(Instant::now() + Duration::from_secs(30))),
72 |_| {},
73 )?;
74
75 if !report.all_succeeded() {
77 return Err(format!("native local push rejected updates: {report:?}").into());
78 }
79
80 let restored = destination
81 .objects()?
82 .read(blob)?
83 .ok_or("missing pushed blob")?;
84 assert_eq!(
85 restored.data(),
86 b"Published by girt through native local storage\n"
87 );
88
89 println!(
90 "Published branch and annotated tag: {} objects, {} pack bytes",
91 prepared.object_count(),
92 prepared.pack_bytes()
93 );
94
95 let repeated = PreparedPush::new(
96 &objects,
97 vec![
98 PushCommand::new(RefName::new("refs/heads/main")?, Some(commit), commit)
99 .with_force(ForcePolicy::FastForwardOnly),
100 ],
101 &[commit],
102 PushLimits::default(),
103 &cancel,
104 )?;
105
106 let report = send_local(
107 destination.git_dir(),
108 girt::push::LocalPushRequest::new(&repeated),
109 TransportControl::new(&cancel)
110 .with_deadline(Some(Instant::now() + Duration::from_secs(30))),
111 |_| {},
112 )?;
113
114 if !report.all_succeeded() {
115 return Err(format!("repeat rejected: {report:?}").into());
116 }
117 assert_eq!(repeated.object_count(), 0);
118 println!(
119 "Repeated push: {} objects, {} pack bytes",
120 repeated.object_count(),
121 repeated.pack_bytes()
122 );
123
124 Ok(())
125}