1use std::sync::{
2 atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
3 Arc,
4};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[repr(u8)]
8pub enum RepoTreeBuildPhase {
9 Starting = 0,
10 Objects = 1,
11 Refs = 2,
12 Info = 3,
13 Head = 4,
14 Index = 5,
15 GitDir = 6,
16 WorkingTree = 7,
17 Root = 8,
18 Done = 9,
19}
20
21impl RepoTreeBuildPhase {
22 fn from_u8(value: u8) -> Self {
23 match value {
24 1 => Self::Objects,
25 2 => Self::Refs,
26 3 => Self::Info,
27 4 => Self::Head,
28 5 => Self::Index,
29 6 => Self::GitDir,
30 7 => Self::WorkingTree,
31 8 => Self::Root,
32 9 => Self::Done,
33 _ => Self::Starting,
34 }
35 }
36
37 fn label(self) -> &'static str {
38 match self {
39 Self::Starting => "starting",
40 Self::Objects => "objects",
41 Self::Refs => "refs",
42 Self::Info => "info",
43 Self::Head => "HEAD",
44 Self::Index => "index",
45 Self::GitDir => ".git",
46 Self::WorkingTree => "working tree",
47 Self::Root => "root",
48 Self::Done => "done",
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct RepoTreeBuildProgressSnapshot {
55 pub phase: RepoTreeBuildPhase,
56 pub processed: usize,
57 pub total: Option<usize>,
58 pub object_blobs: usize,
59 pub object_prefixes: usize,
60 pub files: usize,
61 pub dirs: usize,
62 pub reused: usize,
63}
64
65impl RepoTreeBuildProgressSnapshot {
66 pub fn format_for_label(&self, label: &str) -> String {
67 let mut text = format!(" {}: {}", label, self.phase.label());
68
69 if self.phase != RepoTreeBuildPhase::Done {
70 match self.total {
71 Some(total) => {
72 text.push_str(&format!(
73 " {}/{}",
74 self.processed,
75 total.max(self.processed)
76 ));
77 }
78 None if self.processed > 0 => {
79 text.push_str(&format!(" {}", self.processed));
80 }
81 None => {}
82 }
83 }
84
85 match self.phase {
86 RepoTreeBuildPhase::Objects => {
87 text.push_str(&format!(
88 " ({} object blobs, {} prefixes)",
89 self.object_blobs, self.object_prefixes
90 ));
91 }
92 RepoTreeBuildPhase::Index => {
93 text.push_str(" entries");
94 }
95 RepoTreeBuildPhase::WorkingTree => {
96 text.push_str(&format!(
97 " ({} files, {} dirs, {} reused)",
98 self.files, self.dirs, self.reused
99 ));
100 }
101 RepoTreeBuildPhase::Done => {
102 text.push_str(&format!(
103 " ({} object blobs, {} files, {} dirs, {} reused)",
104 self.object_blobs, self.files, self.dirs, self.reused
105 ));
106 }
107 _ => {}
108 }
109
110 text
111 }
112}
113
114#[derive(Debug)]
115struct RepoTreeBuildProgressState {
116 phase: AtomicU8,
117 processed: AtomicUsize,
118 total: AtomicUsize,
119 total_known: AtomicBool,
120 object_blobs: AtomicUsize,
121 object_prefixes: AtomicUsize,
122 files: AtomicUsize,
123 dirs: AtomicUsize,
124 reused: AtomicUsize,
125}
126
127#[derive(Debug, Clone)]
128pub struct RepoTreeBuildProgress {
129 state: Arc<RepoTreeBuildProgressState>,
130}
131
132impl RepoTreeBuildProgress {
133 pub fn new() -> Self {
134 Self {
135 state: Arc::new(RepoTreeBuildProgressState {
136 phase: AtomicU8::new(RepoTreeBuildPhase::Starting as u8),
137 processed: AtomicUsize::new(0),
138 total: AtomicUsize::new(0),
139 total_known: AtomicBool::new(false),
140 object_blobs: AtomicUsize::new(0),
141 object_prefixes: AtomicUsize::new(0),
142 files: AtomicUsize::new(0),
143 dirs: AtomicUsize::new(0),
144 reused: AtomicUsize::new(0),
145 }),
146 }
147 }
148
149 pub(crate) fn start_phase(&self, phase: RepoTreeBuildPhase, total: Option<usize>) {
150 match phase {
151 RepoTreeBuildPhase::Objects => {
152 self.state.object_blobs.store(0, Ordering::Relaxed);
153 self.state.object_prefixes.store(0, Ordering::Relaxed);
154 }
155 RepoTreeBuildPhase::WorkingTree => {
156 self.state.files.store(0, Ordering::Relaxed);
157 self.state.dirs.store(0, Ordering::Relaxed);
158 self.state.reused.store(0, Ordering::Relaxed);
159 }
160 _ => {}
161 }
162
163 self.state.processed.store(0, Ordering::Relaxed);
164 match total {
165 Some(total) => {
166 self.state.total.store(total, Ordering::Relaxed);
167 self.state.total_known.store(true, Ordering::Relaxed);
168 }
169 None => {
170 self.state.total.store(0, Ordering::Relaxed);
171 self.state.total_known.store(false, Ordering::Relaxed);
172 }
173 }
174 self.state.phase.store(phase as u8, Ordering::Relaxed);
175 }
176
177 pub(crate) fn increment_processed(&self) {
178 self.state.processed.fetch_add(1, Ordering::Relaxed);
179 }
180
181 pub(crate) fn record_object_blob(&self) {
182 self.state.object_blobs.fetch_add(1, Ordering::Relaxed);
183 self.increment_processed();
184 }
185
186 pub(crate) fn record_object_prefix(&self) {
187 self.state.object_prefixes.fetch_add(1, Ordering::Relaxed);
188 }
189
190 pub(crate) fn record_index_entry(&self) {
191 self.increment_processed();
192 }
193
194 pub(crate) fn record_working_file(&self, reused: bool) {
195 self.state.files.fetch_add(1, Ordering::Relaxed);
196 if reused {
197 self.state.reused.fetch_add(1, Ordering::Relaxed);
198 }
199 self.increment_processed();
200 }
201
202 pub(crate) fn record_working_dir(&self, reused: bool) {
203 self.state.dirs.fetch_add(1, Ordering::Relaxed);
204 if reused {
205 self.state.reused.fetch_add(1, Ordering::Relaxed);
206 }
207 self.increment_processed();
208 }
209
210 pub(crate) fn mark_done(&self) {
211 self.state
212 .phase
213 .store(RepoTreeBuildPhase::Done as u8, Ordering::Relaxed);
214 }
215
216 pub fn snapshot(&self) -> RepoTreeBuildProgressSnapshot {
217 RepoTreeBuildProgressSnapshot {
218 phase: RepoTreeBuildPhase::from_u8(self.state.phase.load(Ordering::Relaxed)),
219 processed: self.state.processed.load(Ordering::Relaxed),
220 total: self
221 .state
222 .total_known
223 .load(Ordering::Relaxed)
224 .then(|| self.state.total.load(Ordering::Relaxed)),
225 object_blobs: self.state.object_blobs.load(Ordering::Relaxed),
226 object_prefixes: self.state.object_prefixes.load(Ordering::Relaxed),
227 files: self.state.files.load(Ordering::Relaxed),
228 dirs: self.state.dirs.load(Ordering::Relaxed),
229 reused: self.state.reused.load(Ordering::Relaxed),
230 }
231 }
232}
233
234impl Default for RepoTreeBuildProgress {
235 fn default() -> Self {
236 Self::new()
237 }
238}