1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use std::sync::atomic::AtomicBool;
use super::{Failure, Limits, Report};
use crate::{ObjectId, Repository};
impl Repository {
/// Materializes raw bytes and POSIX modes, then publishes the index from a verified clean
/// baseline.
///
/// `None` means an empty tree for either input. For a no-checkout clone, use an empty baseline
/// and the desired commit's tree as target. For updates, supply the tree represented by the
/// current index. HEAD and references remain unchanged, so Git may report staged changes
/// relative to HEAD after success. See [`crate::checkout`] for raw semantics, caller exclusion,
/// platform limits and failure recovery. No previous status report authorizes this operation.
///
/// # Errors
///
/// Returns [`Failure`] on dirty/staged/conflicted state, obstruction, unsupported input,
/// cancellation, exceeded limits or I/O. Its report records prior mutations and publication;
/// cleanup failures are separate. Preparation errors leave worktree content unchanged.
///
/// # Replace a clean checkout, then remove its tracked content
///
/// This example owns the disposable repository exclusively. The second call supplies the tree
/// represented by the index as its baseline; supplying `None` again would lose that contract.
///
/// ```
/// use std::sync::atomic::AtomicBool;
///
/// use girt::{InitKind, ObjectFormat, Repository};
/// # if cfg!(not(any(target_os = "linux", target_os = "macos"))) { return Ok(()); }
/// let directory = tempfile::tempdir()?;
/// let repo = Repository::init(
/// ObjectFormat::Sha1,
/// directory.path().join("r"),
/// InitKind::Worktree,
/// )?;
///
/// use girt::{EntryMode, Tree, TreeEntry};
/// let loose = repo.loose_objects();
/// let blob = loose.write_blob(b"hello\n")?;
/// let tree = loose.write_tree(&Tree::new(
/// ObjectFormat::Sha1,
/// vec![TreeEntry {
/// name: b"hello.txt".to_vec(),
/// mode: EntryMode::Blob,
/// id: blob,
/// }],
/// )?)?;
/// let cancel = AtomicBool::new(false);
/// repo.checkout_tree(None, Some(tree), Default::default(), &cancel)?;
/// let report = repo.checkout_tree(Some(tree), None, Default::default(), &cancel)?;
/// assert!(report.index_published);
/// assert!(!repo.worktree().unwrap().join("hello.txt").exists());
/// assert!(loose.read_blob(blob, 1024).is_ok()); // Removing a file does not delete its object.
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn checkout_tree(
&self,
baseline: Option<ObjectId>,
target: Option<ObjectId>,
limits: Limits,
cancel: &AtomicBool,
) -> Result<Report, Failure> {
let _lock_operation = crate::locks::operation();
for id in baseline.into_iter().chain(target) {
id.require_format(self.object_format())
.map_err(|error| Failure {
cause: Box::new(error.into()),
report: Report::default(),
cleanup: vec![],
})?;
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
return super::worktree::run(self, baseline, target, limits, cancel, &mut |_, _| Ok(()));
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
let _ = (baseline, target, limits);
let cause = if cancel.load(std::sync::atomic::Ordering::Relaxed) {
super::Error::Cancelled
} else {
super::types::refused(b"", "checkout requires Linux/macOS")
};
Err(Failure {
cause: Box::new(cause),
report: Report::default(),
cleanup: vec![],
})
}
}
}