1pub use crate::log_plan::yes_no;
9
10pub fn presence(value: bool) -> &'static str {
12 if value { "present" } else { "absent" }
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct MaintenanceInspectView {
18 pub commit_graph_present: bool,
19 pub commit_graph_nodes: usize,
20 pub commit_graph_bloom: usize,
21 pub worktree_index_present: bool,
22 pub worktree_index_files: usize,
23 pub worktree_index_directories: usize,
24 pub worktree_index_untracked_directories: usize,
25 pub change_monitor_backend: String,
26 pub change_monitor_status: String,
27 pub refs_threads: usize,
28 pub refs_markers: usize,
29 pub refs_remotes: usize,
30 pub refs_remote_threads: usize,
31 pub ref_summary_present: bool,
32 pub ref_summary_valid: bool,
33 pub ref_summary_threads: usize,
34 pub ref_summary_markers: usize,
35 pub ref_summary_remotes: usize,
36 pub ref_summary_remote_threads: usize,
37 pub pack_count: usize,
38 pub index_count: usize,
39 pub unpaired_packs: usize,
40 pub pending_install_intents: usize,
41 pub missing_blob_count: usize,
42 pub pull_planner_status: String,
43 pub pull_planner_manifests: usize,
44 pub pull_planner_entries: usize,
45}
46
47impl MaintenanceInspectView {
48 pub fn lines(&self) -> Vec<String> {
50 vec![
51 format!(
52 "Commit graph: {} (nodes: {}, bloom-covered: {})",
53 presence(self.commit_graph_present),
54 self.commit_graph_nodes,
55 self.commit_graph_bloom
56 ),
57 format!(
58 "Worktree index: {} (files: {}, directories: {}, untracked directories: {})",
59 presence(self.worktree_index_present),
60 self.worktree_index_files,
61 self.worktree_index_directories,
62 self.worktree_index_untracked_directories
63 ),
64 format!(
65 "Change monitor: {} / {}",
66 self.change_monitor_backend, self.change_monitor_status
67 ),
68 format!(
69 "Refs: {} threads, {} markers, {} remotes, {} remote threads",
70 self.refs_threads, self.refs_markers, self.refs_remotes, self.refs_remote_threads
71 ),
72 format!(
73 "Ref summary index: {} (valid: {}, threads: {}, markers: {}, remotes: {}, remote threads: {})",
74 presence(self.ref_summary_present),
75 yes_no(self.ref_summary_valid),
76 self.ref_summary_threads,
77 self.ref_summary_markers,
78 self.ref_summary_remotes,
79 self.ref_summary_remote_threads
80 ),
81 format!(
82 "Packs: {} pack files, {} indexes",
83 self.pack_count, self.index_count
84 ),
85 format!(
86 "Pack install: {} unpaired packs, {} pending install intents",
87 self.unpaired_packs, self.pending_install_intents
88 ),
89 format!("Partial fetch: {} missing blobs", self.missing_blob_count),
90 format!(
91 "Pull planner cache: {} (manifests: {}, planner entries: {})",
92 self.pull_planner_status, self.pull_planner_manifests, self.pull_planner_entries
93 ),
94 ]
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct MaintenanceRefreshView {
101 pub rebuilt_commit_graph: bool,
102 pub rebuilt_ref_summary_index: bool,
103 pub rebuilt_worktree_index: bool,
104 pub refreshed_change_monitor: bool,
105 pub rebuilt_pull_planner_cache: bool,
106 pub pruned_pull_planner_entries: usize,
107 pub pack_install_completed: u64,
108 pub pack_install_aborted: u64,
109 pub pack_install_skipped: u64,
110 pub pack_install_quarantined: u64,
111 pub unpaired_packs_pruned: u64,
112 pub unpaired_pack_bytes_freed: u64,
113 pub commit_graph_nodes_now: usize,
114 pub commit_graph_bloom_now: usize,
115 pub ref_summary_threads_now: usize,
116 pub ref_summary_markers_now: usize,
117 pub ref_summary_remotes_now: usize,
118 pub ref_summary_remote_threads_now: usize,
119 pub worktree_index_files_now: usize,
120 pub pull_planner_manifests_now: usize,
121 pub pull_planner_entries_now: usize,
122}
123
124impl MaintenanceRefreshView {
125 pub fn lines(&self) -> Vec<String> {
127 let mut lines = vec![
128 format!(
129 "Rebuilt commit graph: {}",
130 yes_no(self.rebuilt_commit_graph)
131 ),
132 format!(
133 "Rebuilt ref summary index: {}",
134 yes_no(self.rebuilt_ref_summary_index)
135 ),
136 format!(
137 "Rebuilt worktree index: {}",
138 yes_no(self.rebuilt_worktree_index)
139 ),
140 format!(
141 "Refreshed change monitor: {}",
142 yes_no(self.refreshed_change_monitor)
143 ),
144 format!(
145 "Rebuilt pull planner cache: {}",
146 yes_no(self.rebuilt_pull_planner_cache)
147 ),
148 format!(
149 "Pruned pull planner entries: {}",
150 self.pruned_pull_planner_entries
151 ),
152 ];
153 let mut recover = format!(
154 "Pack install intents recovered: {} completed, {} aborted",
155 self.pack_install_completed, self.pack_install_aborted
156 );
157 if self.pack_install_skipped > 0 || self.pack_install_quarantined > 0 {
158 recover.push_str(&format!(
159 " (skipped in-progress: {}, quarantined: {})",
160 self.pack_install_skipped, self.pack_install_quarantined
161 ));
162 }
163 lines.push(recover);
164 lines.push(if self.unpaired_packs_pruned > 0 {
165 format!(
166 "Pruned {} unpaired packs (freed {} bytes)",
167 self.unpaired_packs_pruned, self.unpaired_pack_bytes_freed
168 )
169 } else {
170 "No unpaired packs to prune".to_string()
171 });
172 lines.push(format!(
173 "Commit graph now has {} nodes and {} Bloom-covered nodes",
174 self.commit_graph_nodes_now, self.commit_graph_bloom_now
175 ));
176 lines.push(format!(
177 "Ref summary index now covers {} threads, {} markers, {} remotes, and {} remote threads",
178 self.ref_summary_threads_now,
179 self.ref_summary_markers_now,
180 self.ref_summary_remotes_now,
181 self.ref_summary_remote_threads_now
182 ));
183 lines.push(format!(
184 "Worktree index now has {} files cached",
185 self.worktree_index_files_now
186 ));
187 lines.push(format!(
188 "Pull planner cache now has {} manifests and {} planner entries",
189 self.pull_planner_manifests_now, self.pull_planner_entries_now
190 ));
191 lines
192 }
193}
194
195pub fn pack_install_recover_line(completed: u64, aborted: u64) -> String {
197 format!("Pack install intents recovered: {completed} completed, {aborted} aborted")
198}
199
200pub fn unpaired_packs_pruned_line(removed: u64, bytes_freed: u64) -> String {
202 if removed > 0 {
203 format!("Pruned {removed} unpaired packs (freed {bytes_freed} bytes)")
204 } else {
205 "No unpaired packs to prune".to_string()
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn inspect_view_lines_include_packs_and_install() {
215 let v = MaintenanceInspectView {
216 commit_graph_present: true,
217 commit_graph_nodes: 10,
218 commit_graph_bloom: 4,
219 worktree_index_present: false,
220 worktree_index_files: 1,
221 worktree_index_directories: 2,
222 worktree_index_untracked_directories: 3,
223 change_monitor_backend: "fs_events".into(),
224 change_monitor_status: "ready".into(),
225 refs_threads: 1,
226 refs_markers: 2,
227 refs_remotes: 3,
228 refs_remote_threads: 4,
229 ref_summary_present: true,
230 ref_summary_valid: false,
231 ref_summary_threads: 1,
232 ref_summary_markers: 2,
233 ref_summary_remotes: 3,
234 ref_summary_remote_threads: 4,
235 pack_count: 2,
236 index_count: 2,
237 unpaired_packs: 1,
238 pending_install_intents: 2,
239 missing_blob_count: 5,
240 pull_planner_status: "ready".into(),
241 pull_planner_manifests: 1,
242 pull_planner_entries: 2,
243 };
244 let lines = v.lines();
245 assert!(lines[0].contains("Commit graph: present"));
246 assert!(lines.iter().any(|l| l.contains("Pack install: 1 unpaired")));
247 assert!(lines.iter().any(|l| l.contains("Partial fetch: 5")));
248 }
249
250 #[test]
251 fn refresh_view_lines_include_recover_detail() {
252 let v = MaintenanceRefreshView {
253 rebuilt_commit_graph: true,
254 rebuilt_ref_summary_index: false,
255 rebuilt_worktree_index: true,
256 refreshed_change_monitor: false,
257 rebuilt_pull_planner_cache: true,
258 pruned_pull_planner_entries: 3,
259 pack_install_completed: 0,
260 pack_install_aborted: 1,
261 pack_install_skipped: 3,
262 pack_install_quarantined: 2,
263 unpaired_packs_pruned: 0,
264 unpaired_pack_bytes_freed: 0,
265 commit_graph_nodes_now: 9,
266 commit_graph_bloom_now: 2,
267 ref_summary_threads_now: 1,
268 ref_summary_markers_now: 2,
269 ref_summary_remotes_now: 3,
270 ref_summary_remote_threads_now: 4,
271 worktree_index_files_now: 42,
272 pull_planner_manifests_now: 1,
273 pull_planner_entries_now: 2,
274 };
275 let lines = v.lines();
276 assert!(lines.iter().any(|l| l.contains("skipped in-progress: 3")));
277 assert!(lines.iter().any(|l| l.contains("No unpaired packs")));
278 assert_eq!(
279 pack_install_recover_line(2, 1),
280 "Pack install intents recovered: 2 completed, 1 aborted"
281 );
282 assert_eq!(
283 unpaired_packs_pruned_line(3, 99),
284 "Pruned 3 unpaired packs (freed 99 bytes)"
285 );
286 }
287}