Skip to main content

cadvm_core/
status.rs

1//! Working-tree status: compare the working tree against a manifest.
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::path::PathBuf;
5
6use cadvm_store::ObjectId;
7
8use crate::error::Result;
9use crate::index::HashCache;
10use crate::model::Manifest;
11use crate::repo::Repository;
12use crate::worktree;
13
14/// The difference between the working tree and a reference manifest (HEAD).
15#[derive(Debug, Clone, Default, PartialEq, Eq)]
16pub struct WorkingTreeStatus {
17    /// Branch HEAD is on, if attached.
18    pub branch: Option<String>,
19    /// Files present in the working tree but not in the manifest.
20    pub new: Vec<PathBuf>,
21    /// Files present in both but with different content.
22    pub modified: Vec<PathBuf>,
23    /// Files in the manifest but missing from the working tree.
24    pub deleted: Vec<PathBuf>,
25}
26
27impl WorkingTreeStatus {
28    /// Whether the working tree matches the manifest exactly.
29    pub fn is_clean(&self) -> bool {
30        self.new.is_empty() && self.modified.is_empty() && self.deleted.is_empty()
31    }
32}
33
34/// Compute working-tree status against the HEAD manifest.
35pub fn working_tree_status(repo: &Repository) -> Result<WorkingTreeStatus> {
36    let manifest = repo.head_manifest()?;
37    status_against(repo, &manifest)
38}
39
40/// Compute working-tree status against an arbitrary manifest.
41pub fn status_against(repo: &Repository, manifest: &Manifest) -> Result<WorkingTreeStatus> {
42    let branch = repo.current_branch()?;
43
44    // Hash every tracked-format file currently on disk, reusing the size+mtime
45    // cache so unchanged (often large) files are not re-read.
46    let mut cache = HashCache::load(repo);
47    let mut working: BTreeMap<PathBuf, ObjectId> = BTreeMap::new();
48    let mut seen: BTreeSet<PathBuf> = BTreeSet::new();
49    for rel in worktree::scan_step_files(repo)? {
50        let hash = cache.hash(repo, &rel)?;
51        seen.insert(rel.clone());
52        working.insert(rel, hash);
53    }
54    cache.retain(&seen);
55    let _ = cache.save(repo); // best-effort: a cache write must never fail status
56
57    let mut status = WorkingTreeStatus {
58        branch,
59        ..Default::default()
60    };
61
62    // New / modified.
63    for (path, hash) in &working {
64        match manifest.files.get(path) {
65            None => status.new.push(path.clone()),
66            Some(entry) if &entry.raw_hash != hash => status.modified.push(path.clone()),
67            Some(_) => {}
68        }
69    }
70
71    // Deleted.
72    for path in manifest.files.keys() {
73        if !working.contains_key(path) {
74            status.deleted.push(path.clone());
75        }
76    }
77
78    status.new.sort();
79    status.modified.sort();
80    status.deleted.sort();
81    Ok(status)
82}