jujutsu_lib/
index_store.rs1use std::collections::{HashMap, HashSet};
16use std::fs::File;
17use std::io;
18use std::io::{Read, Write};
19use std::path::PathBuf;
20use std::sync::Arc;
21
22use itertools::Itertools;
23use tempfile::NamedTempFile;
24
25use crate::backend::CommitId;
26use crate::commit::Commit;
27use crate::dag_walk;
28use crate::file_util::persist_content_addressed_temp_file;
29use crate::index::{Index, IndexLoadError, MutableIndex, ReadonlyIndex};
30use crate::op_store::OperationId;
31use crate::operation::Operation;
32use crate::store::Store;
33
34pub struct IndexStore {
35 dir: PathBuf,
36}
37
38impl IndexStore {
39 pub fn init(dir: PathBuf) -> Self {
40 std::fs::create_dir(dir.join("operations")).unwrap();
41 IndexStore { dir }
42 }
43
44 pub fn reinit(&self) {
45 std::fs::remove_dir_all(self.dir.join("operations")).unwrap();
46 IndexStore::init(self.dir.clone());
47 }
48
49 pub fn load(dir: PathBuf) -> IndexStore {
50 IndexStore { dir }
51 }
52
53 pub fn get_index_at_op(&self, op: &Operation, store: &Arc<Store>) -> Arc<ReadonlyIndex> {
54 let op_id_hex = op.id().hex();
55 let op_id_file = self.dir.join("operations").join(op_id_hex);
56 if op_id_file.exists() {
57 match self.load_index_at_operation(
58 store.commit_id_length(),
59 store.change_id_length(),
60 op.id(),
61 ) {
62 Err(IndexLoadError::IndexCorrupt(_)) => {
63 println!("The index was corrupt (maybe the format has changed). Reindexing...");
67 std::fs::remove_dir_all(self.dir.join("operations")).unwrap();
68 std::fs::create_dir(self.dir.join("operations")).unwrap();
69 self.index_at_operation(store, op).unwrap()
70 }
71 result => result.unwrap(),
72 }
73 } else {
74 self.index_at_operation(store, op).unwrap()
75 }
76 }
77
78 pub fn write_index(&self, index: MutableIndex) -> io::Result<Arc<ReadonlyIndex>> {
79 index.save_in(self.dir.clone())
80 }
81
82 fn load_index_at_operation(
83 &self,
84 commit_id_length: usize,
85 change_id_length: usize,
86 op_id: &OperationId,
87 ) -> Result<Arc<ReadonlyIndex>, IndexLoadError> {
88 let op_id_file = self.dir.join("operations").join(op_id.hex());
89 let mut buf = vec![];
90 File::open(op_id_file)
91 .unwrap()
92 .read_to_end(&mut buf)
93 .unwrap();
94 let index_file_id_hex = String::from_utf8(buf).unwrap();
95 let index_file_path = self.dir.join(&index_file_id_hex);
96 let mut index_file = File::open(index_file_path).unwrap();
97 ReadonlyIndex::load_from(
98 &mut index_file,
99 self.dir.clone(),
100 index_file_id_hex,
101 commit_id_length,
102 change_id_length,
103 )
104 }
105
106 fn index_at_operation(
107 &self,
108 store: &Arc<Store>,
109 operation: &Operation,
110 ) -> io::Result<Arc<ReadonlyIndex>> {
111 let view = operation.view();
112 let operations_dir = self.dir.join("operations");
113 let commit_id_length = store.commit_id_length();
114 let change_id_length = store.change_id_length();
115 let mut new_heads = view.heads().clone();
116 let mut parent_op_id: Option<OperationId> = None;
117 for op in dag_walk::bfs(
118 vec![operation.clone()],
119 Box::new(|op: &Operation| op.id().clone()),
120 Box::new(|op: &Operation| op.parents()),
121 ) {
122 if operations_dir.join(op.id().hex()).is_file() {
123 if parent_op_id.is_none() {
124 parent_op_id = Some(op.id().clone())
125 }
126 } else {
127 for head in op.view().heads() {
128 new_heads.insert(head.clone());
129 }
130 }
131 }
132 let mut data;
133 let maybe_parent_file;
134 match parent_op_id {
135 None => {
136 maybe_parent_file = None;
137 data = MutableIndex::full(commit_id_length, change_id_length);
138 }
139 Some(parent_op_id) => {
140 let parent_file = self
141 .load_index_at_operation(commit_id_length, change_id_length, &parent_op_id)
142 .unwrap();
143 maybe_parent_file = Some(parent_file.clone());
144 data = MutableIndex::incremental(parent_file)
145 }
146 }
147
148 let mut heads = new_heads.into_iter().collect_vec();
149 heads.sort();
150 let commits = topo_order_earlier_first(store, heads, maybe_parent_file);
151
152 for commit in &commits {
153 data.add_commit(commit);
154 }
155
156 let index_file = data.save_in(self.dir.clone())?;
157
158 self.associate_file_with_operation(&index_file, operation.id())?;
159
160 Ok(index_file)
161 }
162
163 pub fn associate_file_with_operation(
165 &self,
166 index: &ReadonlyIndex,
167 op_id: &OperationId,
168 ) -> io::Result<()> {
169 let mut temp_file = NamedTempFile::new_in(&self.dir)?;
170 let file = temp_file.as_file_mut();
171 file.write_all(index.name().as_bytes())?;
172 persist_content_addressed_temp_file(
173 temp_file,
174 self.dir.join("operations").join(op_id.hex()),
175 )?;
176 Ok(())
177 }
178}
179
180fn topo_order_earlier_first(
183 store: &Arc<Store>,
184 heads: Vec<CommitId>,
185 parent_file: Option<Arc<ReadonlyIndex>>,
186) -> Vec<Commit> {
187 let mut work = vec![];
190 for head in &heads {
191 work.push(store.get_commit(head).unwrap());
192 }
193 let mut commits = vec![];
194 let mut visited = HashSet::new();
195 let mut in_parent_file = HashSet::new();
196 let parent_file_source = parent_file.as_ref().map(|file| file.as_ref());
197 while let Some(commit) = work.pop() {
198 if parent_file_source.map_or(false, |index| index.has_id(commit.id())) {
199 in_parent_file.insert(commit.id().clone());
200 continue;
201 } else if !visited.insert(commit.id().clone()) {
202 continue;
203 }
204
205 work.extend(commit.parents());
206 work.extend(commit.predecessors());
207 commits.push(commit);
208 }
209 drop(visited);
210
211 let mut waiting = HashMap::new();
219
220 let mut result = vec![];
221 let mut visited = in_parent_file;
222 while let Some(commit) = commits.pop() {
223 let mut waiting_for_earlier_commit = false;
224 for earlier in commit
225 .parent_ids()
226 .iter()
227 .chain(commit.predecessor_ids().iter())
228 {
229 if !visited.contains(earlier) {
230 waiting
231 .entry(earlier.clone())
232 .or_insert_with(Vec::new)
233 .push(commit.clone());
234 waiting_for_earlier_commit = true;
235 break;
236 }
237 }
238 if !waiting_for_earlier_commit {
239 visited.insert(commit.id().clone());
240 if let Some(dependents) = waiting.remove(commit.id()) {
241 commits.extend(dependents);
242 }
243 result.push(commit);
244 }
245 }
246 assert!(waiting.is_empty());
247 result
248}