void_core/index/
rebuild.rs1use std::collections::HashMap;
4use std::path::Path;
5
6use camino::Utf8PathBuf;
7use ignore::WalkBuilder;
8use rayon::prelude::*;
9
10use crate::crypto::KeyVault;
11use crate::metadata::manifest_tree::TreeManifest;
12use crate::refs;
13
14use crate::store::{FsStore, ObjectStoreExt};
15use void_crypto::EncryptedCommit;
16use crate::support::configure_walker;
17use crate::{Result, VoidError};
18
19use super::entry::index_entry_from_file;
20use super::io::write_index;
21use super::types::IndexEntry;
22
23#[derive(Debug, Clone)]
25pub struct RebuildResult {
26 pub entries_rebuilt: usize,
28 pub from_head: usize,
30 pub from_working_tree: usize,
32}
33
34
35
36pub fn rebuild_index(void_dir: &Path, workspace: &Path, vault: &KeyVault) -> Result<RebuildResult> {
48 let void_dir_utf8 = Utf8PathBuf::try_from(void_dir.to_path_buf())
49 .map_err(|e| VoidError::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e)))?;
50 let workspace_utf8 = Utf8PathBuf::try_from(workspace.to_path_buf())
51 .map_err(|e| VoidError::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e)))?;
52
53 let head_commit_cid = refs::resolve_head(&void_dir_utf8)?;
55 let mut head_entries: HashMap<String, IndexEntry> = HashMap::new();
56 let mut commit_cid_typed: Option<void_crypto::CommitCid> = None;
57
58 if let Some(ref cid_typed) = head_commit_cid {
59 commit_cid_typed = Some(cid_typed.clone());
60
61 let objects_dir = Utf8PathBuf::try_from(void_dir.join("objects"))
62 .map_err(|e| VoidError::Io(std::io::Error::new(std::io::ErrorKind::InvalidData, e)))?;
63 let store = FsStore::new(objects_dir)?;
64
65 let commit_cid = crate::cid::from_bytes(cid_typed.as_bytes())?;
66 let commit_encrypted: EncryptedCommit = store.get_blob(&commit_cid)?;
67 let (commit_bytes, reader) = crate::crypto::CommitReader::open_with_vault(vault, &commit_encrypted)?;
68 let commit = commit_bytes.parse()?;
69
70 let manifest = TreeManifest::from_commit(&store, &commit, &reader)?
71 .ok_or_else(|| VoidError::IntegrityError {
72 expected: "manifest_cid present on commit".into(),
73 actual: "None".into(),
74 })?;
75
76 for me in manifest.iter() {
77 let me = me?;
78 head_entries.insert(
79 me.path.clone(),
80 IndexEntry {
81 path: me.path.clone(),
82 content_hash: me.content_hash,
83 mtime_secs: 0,
84 mtime_nanos: 0,
85 size: me.length,
86 materialized: true,
87 },
88 );
89 }
90 }
91
92 let void_dir_name = void_dir
94 .file_name()
95 .and_then(|s| s.to_str())
96 .unwrap_or(".void")
97 .to_string();
98
99 let mut builder = WalkBuilder::new(workspace);
100 let walker = configure_walker(&mut builder)
101 .filter_entry({
102 let void_dir_name = void_dir_name.clone();
103 move |entry| {
104 let name = entry.file_name().to_string_lossy();
105 name != void_dir_name
106 && name != ".git"
107 && name != "node_modules"
108 && name != ".DS_Store"
109 }
110 })
111 .build();
112
113 let file_paths: Vec<String> = walker
114 .flatten()
115 .filter(|entry| entry.file_type().map(|t| t.is_file()).unwrap_or(false))
116 .filter_map(|entry| {
117 let path = entry.path().to_path_buf();
118 let rel = path.strip_prefix(workspace).ok()?;
119 Some(rel.to_string_lossy().replace('\\', "/"))
120 })
121 .collect();
122
123 let results: Vec<Option<(IndexEntry, bool)>> = file_paths
125 .par_iter()
126 .map(|rel_path| {
127 let current_entry = match index_entry_from_file(&workspace_utf8, rel_path) {
128 Ok(entry) => entry,
129 Err(_) => return Ok(None),
130 };
131
132 if let Some(head_entry) = head_entries.get(rel_path) {
133 if current_entry.content_hash == head_entry.content_hash {
134 Ok(Some((IndexEntry {
136 path: rel_path.clone(),
137 content_hash: head_entry.content_hash,
138 mtime_secs: current_entry.mtime_secs,
139 mtime_nanos: current_entry.mtime_nanos,
140 size: current_entry.size,
141 materialized: true,
142 }, true)))
143 } else {
144 Ok(Some((current_entry, false)))
145 }
146 } else {
147 Ok(Some((current_entry, false)))
148 }
149 })
150 .collect::<Result<Vec<_>>>()?;
151
152 let mut entries = Vec::new();
153 let mut from_head = 0usize;
154 let mut from_working_tree = 0usize;
155
156 for result in results {
157 let Some((entry, is_from_head)) = result else { continue };
158 if is_from_head {
159 from_head += 1;
160 } else {
161 from_working_tree += 1;
162 }
163 entries.push(entry);
164 }
165
166 let entries_rebuilt = entries.len();
168 write_index(void_dir, vault.index_key()?, commit_cid_typed, entries)?;
169
170 Ok(RebuildResult {
171 entries_rebuilt,
172 from_head,
173 from_working_tree,
174 })
175}