Verify working-copy consistency against the graph. Read-only. See the module docs for the two checks performed. Returns a [`VerifyReport`]; `report.is_healthy()` is `true` when no problems were found.
(&self)
| 103 | /// [`VerifyReport`]; `report.is_healthy()` is `true` when no problems were |
| 104 | /// found. |
| 105 | pub fn verify_working_copy(&self) -> Result<VerifyReport, RepositoryError> { |
| 106 | let mut report = VerifyReport::default(); |
| 107 | |
| 108 | // One status pass classifies every path (Modified/Added/Deleted/ |
| 109 | // Conflicted/Untracked). Clean files are not emitted, so we treat |
| 110 | // "absent from this map" as clean. |
| 111 | let status = self.status(StatusOptions::default())?; |
| 112 | let mut status_by_path: HashMap<String, FileStatus> = HashMap::new(); |
| 113 | for e in status.entries() { |
| 114 | status_by_path.insert(e.path().to_string_lossy().to_string(), e.status()); |
| 115 | } |
| 116 | |
| 117 | let conflicted: std::collections::HashSet<String> = |
| 118 | self.list_conflicts()?.into_iter().map(|(p, _)| p).collect(); |
| 119 | report.conflicted_files = conflicted.len(); |
| 120 | |
| 121 | // Files visible (tracked + recorded) on the current view. |
| 122 | let visible = self.visible_file_paths(&self.current_view)?; |
| 123 | |
| 124 | for path in &visible { |
| 125 | let st = status_by_path.get(path).copied(); |
| 126 | |
| 127 | // ── Check 1: materialization drift on clean files ────────────── |
| 128 | match st { |
| 129 | // Uncommitted edits are expected divergence — skip, but count. |
| 130 | Some(FileStatus::Modified) |
| 131 | | Some(FileStatus::Added) |
| 132 | | Some(FileStatus::Deleted) => { |
| 133 | report.uncommitted_skipped += 1; |
| 134 | } |
| 135 | // Conflicted files legitimately differ (markers on disk). |
| 136 | Some(FileStatus::Conflicted) => {} |
| 137 | // Clean (absent from status) or other benign states: the disk |
| 138 | // must match the graph exactly. |
| 139 | _ => { |
| 140 | let abs = self.root.join(path); |
| 141 | let disk = std::fs::read(&abs).ok(); |
| 142 | let graph = self.get_file_content_on_view(path, &self.current_view)?; |
| 143 | if let (Some(disk), Some(graph)) = (disk.as_ref(), graph.as_ref()) { |
| 144 | report.clean_files_checked += 1; |
| 145 | if disk != graph { |
| 146 | report.problems.push(VerifyProblem::MaterializationDrift { |
| 147 | path: path.clone(), |
| 148 | disk_len: disk.len(), |
| 149 | graph_len: graph.len(), |
| 150 | }); |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | // ── Check 2: conflict honesty (the three signals must agree) ─── |
| 157 | let abs = self.root.join(path); |
| 158 | let markers = std::fs::read(&abs) |
| 159 | .map(|b| has_markers(&b)) |
| 160 | .unwrap_or(false); |
| 161 | let status_conflicted = st == Some(FileStatus::Conflicted); |
| 162 | let listed = conflicted.contains(path); |