Skip to main content

clone_repository/
clone_repository.rs

1//! Run `cargo run --example clone_repository`; all repositories are disposable.
2use std::ops::ControlFlow;
3use std::sync::atomic::AtomicBool;
4use std::time::{Duration, Instant};
5
6use girt::clone::{BranchSelection, CloneRequest};
7use girt::fetch::{FetchLimits, FetchUpdateLimits};
8use girt::refs::{Expected, RefName, Reflog, Target};
9use girt::remote::Remote;
10use girt::transport::TransportControl;
11use girt::{Commit, CommitFields, EntryMode, InitKind, Repository, Signature, Tree, TreeEntry};
12
13fn main() -> Result<(), Box<dyn std::error::Error>> {
14    let root = tempfile::tempdir()?;
15    let source = Repository::init(
16        girt::ObjectFormat::Sha1,
17        root.path().join("source"),
18        InitKind::Bare,
19    )?;
20    let destination_path = root.path().join("destination");
21
22    let objects = source.loose_objects();
23    let blob = objects.write_blob(b"hello from the clone\n")?;
24    let tree = objects.write_tree(&Tree::new(
25        girt::ObjectFormat::Sha1,
26        vec![TreeEntry {
27            mode: EntryMode::Blob,
28            name: b"hello.txt".to_vec(),
29            id: blob,
30        }],
31    )?)?;
32
33    let identity = Signature {
34        name: b"Fetch Example".to_vec(),
35        email: b"fetch@example.com".to_vec(),
36        seconds: 1700000000,
37        offset_minutes: 0,
38    };
39    let commit = objects.write_commit(&Commit::new(CommitFields {
40        tree,
41        parents: Vec::new(),
42        author: identity.clone(),
43        committer: identity.clone(),
44        extra_headers: Vec::new(),
45        message: b"Original fetch example\n".to_vec(),
46    })?)?;
47
48    source.references()?.update_without_reflog(
49        &RefName::branch("main")?,
50        Target::Direct(commit),
51        Expected::Absent,
52    )?;
53
54    // The persisted URL is credential-free metadata. Transport selection stays explicit.
55    let source_path = std::fs::canonicalize(source.git_dir())?;
56    let url = source_path
57        .to_str()
58        .ok_or("example needs a UTF-8 temporary path")?;
59    let request = CloneRequest::prepare_tracking(
60        &destination_path,
61        InitKind::Worktree,
62        url.as_bytes(),
63        BranchSelection::Default,
64        Reflog::Preserve,
65    )?;
66
67    let cancel = AtomicBool::new(false);
68    let ready = request.receive_local(
69        &source_path,
70        FetchLimits::default(),
71        TransportControl::new(&cancel)
72            .with_deadline(Some(Instant::now() + Duration::from_secs(30))),
73        |_| ControlFlow::Continue(()),
74    )?;
75
76    assert!(!destination_path.exists());
77
78    // HTTP/SSH callers receive an owned download instead. Move it to a bounded worker, validate
79    // synchronously, then finish here. Join that worker even after requesting cancellation.
80    let report = match ready.finish(FetchUpdateLimits::default(), &cancel) {
81        Ok(report) => report,
82        Err(error) => {
83            // Preserve both reports: nested fetch/transaction failures retain partial effects.
84            // Production code should inspect the destination; never delete it automatically.
85            eprintln!(
86                "Clone effects: {:?}; failure: {}",
87                error.report, error.source
88            );
89            return Err(error.into());
90        }
91    };
92
93    let repository = report
94        .repository
95        .as_ref()
96        .ok_or("missing completed repository")?;
97    assert_eq!(
98        repository
99            .references()?
100            .read(&RefName::new("refs/heads/main")?)?,
101        Some(Target::Direct(commit))
102    );
103    assert!(!repository.git_dir().join("index").exists());
104    assert_eq!(std::fs::read_dir(&destination_path)?.count(), 1); // Only .git; no checkout.
105
106    let remote = Remote::find(repository.config(), b"origin")?.ok_or("missing origin")?;
107    assert_eq!(remote.fetch_url(), Some(url.as_bytes()));
108
109    // Pass remote.fetch_refspecs().clone() to FetchRequest::prepare for subsequent fetch.
110    println!(
111        "Created no-checkout repository at {} with {:?}",
112        destination_path.display(),
113        report.head
114    );
115    drop(report);
116
117    // The configured workflow composes the same core clone with checkout. Explicit empty inputs
118    // and environment isolate this disposable example from the caller's machine settings.
119    let checked_out = root.path().join("checked-out");
120    let outcome = girt::workflow::clone_with(
121        url,
122        &checked_out,
123        &girt::workflow::CloneOptions::default()
124            .with_checkout(cfg!(any(target_os = "linux", target_os = "macos"))),
125        &girt::config::ConfigInputs::default(),
126        &girt::workflow::Environment::default(),
127        &mut girt::workflow::NoCallbacks,
128    )?;
129
130    if cfg!(any(target_os = "linux", target_os = "macos")) {
131        assert!(
132            outcome
133                .checkout
134                .as_ref()
135                .is_some_and(|report| report.index_published)
136        );
137    }
138
139    if cfg!(any(target_os = "linux", target_os = "macos")) {
140        assert_eq!(
141            std::fs::read(checked_out.join("hello.txt"))?,
142            b"hello from the clone\n"
143        );
144    }
145
146    drop(outcome);
147    root.close()?; // Cleanup belongs to this disposable example, not the clone operation.
148    Ok(())
149}