Skip to main content

git_remote_htree/git/
progress.rs

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}