Skip to main content

fetch_remote/
fetch_remote.rs

1//! Run `cargo run --example fetch_remote`; Git serves a disposable local repository.
2use std::collections::BTreeSet;
3use std::sync::atomic::AtomicBool;
4use std::time::{Duration, Instant};
5
6use girt::fetch::{FetchLimits, FetchRequest, FetchUpdateLimits, KnownHistory};
7use girt::refs::{Expected, RefName, Reflog, Target};
8use girt::remote::Remote;
9use girt::transport::TransportControl;
10use girt::{Commit, CommitFields, Config, InitKind, Repository, Signature, Tree};
11
12fn main() -> Result<(), Box<dyn std::error::Error>> {
13    let root = tempfile::tempdir()?;
14    let source = Repository::init(
15        girt::ObjectFormat::Sha1,
16        root.path().join("source"),
17        InitKind::Bare,
18    )?;
19
20    let destination_path = root.path().join("destination");
21    let destination =
22        Repository::init(girt::ObjectFormat::Sha1, &destination_path, InitKind::Bare)?;
23
24    let objects = source.loose_objects();
25    let tree = objects.write_tree(&Tree::new(girt::ObjectFormat::Sha1, Vec::new())?)?;
26
27    let identity = Signature {
28        name: b"Fetch Example".to_vec(),
29        email: b"fetch@example.com".to_vec(),
30        seconds: 1700000000,
31        offset_minutes: 0,
32    };
33    let commit = objects.write_commit(&Commit::new(CommitFields {
34        tree,
35        parents: Vec::new(),
36        author: identity.clone(),
37        committer: identity.clone(),
38        extra_headers: Vec::new(),
39        message: b"Original fetch example\n".to_vec(),
40    })?)?;
41
42    source.references()?.update_without_reflog(
43        &RefName::new("refs/heads/main")?,
44        Target::Direct(commit),
45        Expected::Absent,
46    )?;
47
48    // This explicit config snapshot could instead be destination.config(). The example uses a
49    // literal local endpoint token so config escaping is independent of temporary path spelling.
50    let config = Config::parse(
51        b"[remote \"origin\"]\nurl = local-example\nfetch = +refs/heads/*:refs/remotes/origin/*\n",
52    )?;
53
54    let remote = Remote::find(&config, b"origin")?.ok_or("missing remote")?;
55    assert_eq!(remote.fetch_url(), Some(b"local-example".as_slice()));
56
57    // Consumer endpoint policy resolves this token to the disposable source, with no credentials.
58    // An HTTP/SSH consumer constructs its explicit remote and chooses receive_http/receive_ssh.
59    let request = FetchRequest::prepare(
60        destination,
61        remote.fetch_refspecs().clone(),
62        BTreeSet::new(),
63        Reflog::Append {
64            committer: identity,
65            message: b"fetch origin".to_vec(),
66        },
67    )?;
68
69    let cancel = AtomicBool::new(false);
70    let control = TransportControl::new(&cancel)
71        .with_deadline(Some(Instant::now() + Duration::from_secs(30)));
72    let ready = request.receive_local(
73        source.git_dir(),
74        &KnownHistory::default(),
75        FetchLimits::default(),
76        control,
77        girt::fetch::LocalFetchObserver::default(),
78    )?;
79
80    let report = match ready.finish(FetchUpdateLimits::default(), &cancel) {
81        Ok(report) => report,
82        Err(error) => {
83            // Retain both parts: installed objects may remain, and a transaction error can contain
84            // partial reference/reflog effects. Retrying requires a fresh destination snapshot.
85            eprintln!(
86                "Fetch effects: {:?}; failure: {}",
87                error.report, error.source
88            );
89            return Err(error.into());
90        }
91    };
92
93    let destination = Repository::open(&destination_path)?;
94    assert_eq!(
95        destination
96            .references()?
97            .read(&RefName::new("refs/remotes/origin/main")?)?,
98        Some(Target::Direct(commit))
99    );
100    assert!(!destination.git_dir().join("FETCH_HEAD").exists());
101
102    println!(
103        "Validated {} objects, installed {:?}, published {} reference(s)",
104        report.objects,
105        report.installed,
106        report.references.len()
107    );
108
109    // TempDir removes both repositories after all synchronous work has completed.
110    Ok(())
111}