Skip to main content

cadvm_core/
worktree.rs

1//! Working-tree scanning helpers (find tracked-format files, hash them).
2
3use std::path::{Path, PathBuf};
4
5use cadvm_store::ObjectId;
6use walkdir::WalkDir;
7
8use crate::error::{CoreError, Result};
9use crate::format::CadFormat;
10use crate::ignore::IgnoreList;
11use crate::repo::{Repository, REPO_DIR};
12
13/// Scan the working tree for STEP/STP files, returning their repo-relative paths.
14///
15/// The `.cadvm` directory is always skipped. Other dot-directories are also
16/// skipped (they are not expected to hold CAD sources and scanning them is
17/// surprising), but dot-*files* are still considered if they carry a tracked
18/// extension. Paths matching `.cadvmignore` are excluded.
19pub fn scan_step_files(repo: &Repository) -> Result<Vec<PathBuf>> {
20    let root = repo.workdir();
21    let ignore = IgnoreList::load(repo)?;
22    let mut files = Vec::new();
23
24    let walker = WalkDir::new(root).into_iter().filter_entry(|entry| {
25        // Always descend into the root itself.
26        if entry.depth() == 0 {
27            return true;
28        }
29        let name = entry.file_name().to_string_lossy();
30        if entry.file_type().is_dir() {
31            // Skip the repo dir, any hidden directory, and ignored directories.
32            if name == REPO_DIR || name.starts_with('.') {
33                return false;
34            }
35            match entry.path().strip_prefix(root) {
36                Ok(rel) => !ignore.is_ignored(rel),
37                Err(_) => true,
38            }
39        } else {
40            true
41        }
42    });
43
44    for entry in walker {
45        let entry = entry.map_err(|e| {
46            CoreError::io(
47                e.path()
48                    .map(Path::to_path_buf)
49                    .unwrap_or_else(|| root.to_path_buf()),
50                e.into_io_error()
51                    .unwrap_or_else(|| std::io::Error::other("walk error")),
52            )
53        })?;
54        if !entry.file_type().is_file() {
55            continue;
56        }
57        let path = entry.path();
58        if CadFormat::from_path(path).is_none() {
59            continue;
60        }
61        let rel = path.strip_prefix(root).unwrap_or(path).to_path_buf();
62        if ignore.is_ignored(&rel) {
63            continue;
64        }
65        files.push(rel);
66    }
67
68    files.sort();
69    Ok(files)
70}
71
72/// Absolute path of a repo-relative working-tree path.
73pub fn abs_path(repo: &Repository, rel: &Path) -> PathBuf {
74    repo.workdir().join(rel)
75}
76
77/// Read the bytes of a working-tree file.
78pub fn read_working_file(repo: &Repository, rel: &Path) -> Result<Vec<u8>> {
79    let path = abs_path(repo, rel);
80    std::fs::read(&path).map_err(|e| CoreError::io(&path, e))
81}
82
83/// Hash a working-tree file by content (level-1 raw hash), streaming the file in
84/// fixed blocks so memory stays constant even for very large STEP files.
85pub fn hash_working_file(repo: &Repository, rel: &Path) -> Result<ObjectId> {
86    let path = abs_path(repo, rel);
87    let file = std::fs::File::open(&path).map_err(|e| CoreError::io(&path, e))?;
88    ObjectId::hash_reader(std::io::BufReader::new(file)).map_err(|e| CoreError::io(&path, e))
89}