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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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_with(None, Some(tree), Default::default(), &cancel)?;
/// let report = repo.checkout_tree_with(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_with(
&self,
baseline: Option<ObjectId>,
target: Option<ObjectId>,
limits: Limits,
cancel: &AtomicBool,
) -> Result<Report, Failure> {
self.checkout_tree_with_progress(baseline, target, limits, cancel, |_, _| {})
}
/// Materializes a tree with synchronous completed-operation counts `(done, total)`.
///
/// Reports zero after preparation, then each applied file or directory operation. The total
/// includes directories, not just files. The final notification follows index publication;
/// reaching the operation total alone does not establish publication. Observers must return
/// promptly and must not access the repository. A panic may leave the partial mutations
/// described by [`crate::checkout`]'s recovery contract.
///
/// # Errors
///
/// Returns [`Self::checkout_tree_with`]'s failures and partial mutation reports.
pub fn checkout_tree_with_progress(
&self,
baseline: Option<ObjectId>,
target: Option<ObjectId>,
limits: Limits,
cancel: &AtomicBool,
progress: impl FnMut(u64, u64),
) -> 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"))]
{
let mut progress = progress;
super::worktree::run_observed(
self,
baseline,
target,
limits,
cancel,
&mut |_, _| Ok(()),
&mut progress,
)
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
let _ = (baseline, target, limits, progress);
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![],
})
}
}
/// Materializes a tree with [`Limits::default`] and no cancellation.
///
/// Equivalent to `self.checkout_tree_with(baseline, target, Limits::default(), &never)` with a
/// flag that is never set; see [`Self::checkout_tree_with`] for the baseline contract, recovery
/// and platform limits. Use [`Self::checkout_tree_with_progress`] to observe progress or
/// cancel.
///
/// # Errors
///
/// Returns the failures of [`Self::checkout_tree_with`] except cancellation.
pub fn checkout_tree(
&self,
baseline: Option<ObjectId>,
target: Option<ObjectId>,
) -> Result<Report, Failure> {
self.checkout_tree_with(baseline, target, Limits::default(), &AtomicBool::new(false))
}
}