##// END OF EJS Templates
rust-dirstate: rust implementation of dirstatemap...
Raphaël Gomès -
r42980:565c143f default draft
parent child Browse files
Show More
@@ -0,0 +1,433 b''
1 // dirstate_map.rs
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
7
8 use crate::{
9 dirstate::{parsers::PARENT_SIZE, EntryState},
10 pack_dirstate, parse_dirstate,
11 utils::copy_into_array,
12 CopyMap, DirsIterable, DirsMultiset, DirstateEntry, DirstateError,
13 DirstateMapError, DirstateParents, DirstateParseError, StateMap,
14 };
15 use core::borrow::Borrow;
16 use std::collections::{HashMap, HashSet};
17 use std::iter::FromIterator;
18 use std::ops::Deref;
19 use std::time::Duration;
20
21 pub type FileFoldMap = HashMap<Vec<u8>, Vec<u8>>;
22
23 const NULL_REVISION: [u8; 20] = [
24 b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0',
25 b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0',
26 ];
27 const MTIME_UNSET: i32 = -1;
28 const SIZE_DIRTY: i32 = -2;
29
30 #[derive(Default)]
31 pub struct DirstateMap {
32 state_map: StateMap,
33 pub copy_map: CopyMap,
34 file_fold_map: Option<FileFoldMap>,
35 pub dirs: Option<DirsMultiset>,
36 pub all_dirs: Option<DirsMultiset>,
37 non_normal_set: HashSet<Vec<u8>>,
38 other_parent_set: HashSet<Vec<u8>>,
39 parents: Option<DirstateParents>,
40 dirty_parents: bool,
41 }
42
43 /// Should only really be used in python interface code, for clarity
44 impl Deref for DirstateMap {
45 type Target = StateMap;
46
47 fn deref(&self) -> &Self::Target {
48 &self.state_map
49 }
50 }
51
52 impl FromIterator<(Vec<u8>, DirstateEntry)> for DirstateMap {
53 fn from_iter<I: IntoIterator<Item = (Vec<u8>, DirstateEntry)>>(
54 iter: I,
55 ) -> Self {
56 Self {
57 state_map: iter.into_iter().collect(),
58 ..Self::default()
59 }
60 }
61 }
62
63 impl DirstateMap {
64 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn clear(&mut self) {
69 self.state_map.clear();
70 self.copy_map.clear();
71 self.file_fold_map = None;
72 self.non_normal_set.clear();
73 self.other_parent_set.clear();
74 self.set_parents(DirstateParents {
75 p1: NULL_REVISION,
76 p2: NULL_REVISION,
77 })
78 }
79
80 /// Add a tracked file to the dirstate
81 pub fn add_file(
82 &mut self,
83 filename: &[u8],
84 old_state: EntryState,
85 entry: DirstateEntry,
86 ) {
87 if old_state == EntryState::Unknown || old_state == EntryState::Removed
88 {
89 if let Some(ref mut dirs) = self.dirs {
90 dirs.add_path(filename)
91 }
92 }
93 if old_state == EntryState::Unknown {
94 if let Some(ref mut all_dirs) = self.all_dirs {
95 all_dirs.add_path(filename)
96 }
97 }
98 self.state_map.insert(filename.to_owned(), entry.to_owned());
99
100 if entry.state != EntryState::Normal || entry.mtime == MTIME_UNSET {
101 self.non_normal_set.insert(filename.to_owned());
102 }
103
104 if entry.size == SIZE_DIRTY {
105 self.other_parent_set.insert(filename.to_owned());
106 }
107 }
108
109 /// Mark a file as removed in the dirstate.
110 ///
111 /// The `size` parameter is used to store sentinel values that indicate
112 /// the file's previous state. In the future, we should refactor this
113 /// to be more explicit about what that state is.
114 pub fn remove_file(
115 &mut self,
116 filename: &[u8],
117 old_state: EntryState,
118 size: i32,
119 ) -> Result<(), DirstateMapError> {
120 if old_state != EntryState::Unknown && old_state != EntryState::Removed
121 {
122 if let Some(ref mut dirs) = self.dirs {
123 dirs.delete_path(filename)?;
124 }
125 }
126 if old_state == EntryState::Unknown {
127 if let Some(ref mut all_dirs) = self.all_dirs {
128 all_dirs.add_path(filename);
129 }
130 }
131
132 if let Some(ref mut file_fold_map) = self.file_fold_map {
133 file_fold_map
134 .remove::<Vec<u8>>(filename.to_ascii_uppercase().as_ref());
135 }
136 self.state_map.insert(
137 filename.to_owned(),
138 DirstateEntry {
139 state: EntryState::Removed,
140 mode: 0,
141 size,
142 mtime: 0,
143 },
144 );
145 self.non_normal_set.insert(filename.to_owned());
146 Ok(())
147 }
148
149 /// Remove a file from the dirstate.
150 /// Returns `true` if the file was previously recorded.
151 pub fn drop_file(
152 &mut self,
153 filename: &[u8],
154 old_state: EntryState,
155 ) -> Result<bool, DirstateMapError> {
156 let exists = self
157 .state_map
158 .remove::<Vec<u8>>(filename.to_owned().as_ref())
159 .is_some();
160
161 if exists {
162 if old_state != EntryState::Removed {
163 if let Some(ref mut dirs) = self.dirs {
164 dirs.delete_path(filename)?;
165 }
166 }
167 if let Some(ref mut all_dirs) = self.all_dirs {
168 all_dirs.delete_path(filename)?;
169 }
170 }
171 if let Some(ref mut file_fold_map) = self.file_fold_map {
172 file_fold_map
173 .remove::<Vec<u8>>(filename.to_ascii_uppercase().as_ref());
174 }
175 self.non_normal_set
176 .remove::<Vec<u8>>(filename.to_owned().as_ref());
177
178 Ok(exists)
179 }
180
181 pub fn clear_ambiguous_times(
182 &mut self,
183 filenames: Vec<Vec<u8>>,
184 now: i32,
185 ) {
186 for filename in filenames {
187 let mut changed = false;
188 self.state_map
189 .entry(filename.to_owned())
190 .and_modify(|entry| {
191 if entry.state == EntryState::Normal && entry.mtime == now
192 {
193 changed = true;
194 *entry = DirstateEntry {
195 mtime: MTIME_UNSET,
196 ..*entry
197 };
198 }
199 });
200 if changed {
201 self.non_normal_set.insert(filename.to_owned());
202 }
203 }
204 }
205
206 pub fn non_normal_other_parent_entries(
207 &self,
208 ) -> (HashSet<Vec<u8>>, HashSet<Vec<u8>>) {
209 let mut non_normal = HashSet::new();
210 let mut other_parent = HashSet::new();
211
212 for (
213 filename,
214 DirstateEntry {
215 state, size, mtime, ..
216 },
217 ) in self.state_map.iter()
218 {
219 if *state != EntryState::Normal || *mtime == MTIME_UNSET {
220 non_normal.insert(filename.to_owned());
221 }
222 if *state == EntryState::Normal && *size == SIZE_DIRTY {
223 other_parent.insert(filename.to_owned());
224 }
225 }
226
227 (non_normal, other_parent)
228 }
229 /// Both of these setters and their uses appear to be the simplest way to
230 /// emulate a Python lazy property, but it is ugly and unidiomatic.
231 /// TODO One day, rewriting this struct using the typestate might be a
232 /// good idea.
233 pub fn set_all_dirs(&mut self) {
234 if self.all_dirs.is_none() {
235 self.all_dirs = Some(DirsMultiset::new(
236 DirsIterable::Dirstate(&self.state_map),
237 None,
238 ));
239 }
240 }
241
242 pub fn set_dirs(&mut self) {
243 if self.dirs.is_none() {
244 self.dirs = Some(DirsMultiset::new(
245 DirsIterable::Dirstate(&self.state_map),
246 Some(EntryState::Removed),
247 ));
248 }
249 }
250
251 pub fn has_tracked_dir(&mut self, directory: &[u8]) -> bool {
252 self.set_dirs();
253 self.dirs.as_ref().unwrap().contains(directory.as_ref())
254 }
255
256 pub fn has_dir(&mut self, directory: &[u8]) -> bool {
257 self.set_all_dirs();
258 self.all_dirs.as_ref().unwrap().contains(directory.as_ref())
259 }
260
261 pub fn parents(
262 &mut self,
263 file_contents: &[u8],
264 ) -> Result<DirstateParents, DirstateError> {
265 if let Some(ref parents) = self.parents {
266 return Ok(parents.clone());
267 }
268 let parents;
269 if file_contents.len() == 40 {
270 parents = DirstateParents {
271 p1: copy_into_array(&file_contents[..PARENT_SIZE]),
272 p2: copy_into_array(
273 &file_contents[PARENT_SIZE..PARENT_SIZE * 2],
274 ),
275 };
276 } else if file_contents.is_empty() {
277 parents = DirstateParents {
278 p1: NULL_REVISION,
279 p2: NULL_REVISION,
280 };
281 } else {
282 return Err(DirstateError::Parse(DirstateParseError::Damaged));
283 }
284
285 self.parents = Some(parents.to_owned());
286 Ok(parents.clone())
287 }
288
289 pub fn set_parents(&mut self, parents: DirstateParents) {
290 self.parents = Some(parents.clone());
291 self.dirty_parents = true;
292 }
293
294 pub fn read(
295 &mut self,
296 file_contents: &[u8],
297 ) -> Result<Option<DirstateParents>, DirstateError> {
298 if file_contents.is_empty() {
299 return Ok(None);
300 }
301
302 let parents = parse_dirstate(
303 &mut self.state_map,
304 &mut self.copy_map,
305 file_contents,
306 )?;
307
308 if !self.dirty_parents {
309 self.set_parents(parents.to_owned());
310 }
311
312 Ok(Some(parents))
313 }
314
315 pub fn pack(
316 &mut self,
317 parents: DirstateParents,
318 now: Duration,
319 ) -> Result<Vec<u8>, DirstateError> {
320 let packed =
321 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
322
323 self.dirty_parents = false;
324
325 let result = self.non_normal_other_parent_entries();
326 self.non_normal_set = result.0;
327 self.other_parent_set = result.1;
328 Ok(packed)
329 }
330 pub fn build_file_fold_map(&mut self) -> FileFoldMap {
331 if let Some(ref file_fold_map) = self.file_fold_map {
332 return file_fold_map.to_owned();
333 }
334 let mut new_file_fold_map = FileFoldMap::new();
335 for (filename, DirstateEntry { state, .. }) in self.state_map.borrow()
336 {
337 if *state == EntryState::Removed {
338 new_file_fold_map.insert(
339 filename.to_ascii_uppercase().to_owned(),
340 filename.to_owned(),
341 );
342 }
343 }
344 self.file_fold_map = Some(new_file_fold_map);
345 self.file_fold_map.to_owned().unwrap()
346 }
347 }
348
349 #[cfg(test)]
350 mod tests {
351 use super::*;
352
353 #[test]
354 fn test_dirs_multiset() {
355 let mut map = DirstateMap::new();
356 assert!(map.dirs.is_none());
357 assert!(map.all_dirs.is_none());
358
359 assert_eq!(false, map.has_dir(b"nope"));
360 assert!(map.all_dirs.is_some());
361 assert!(map.dirs.is_none());
362
363 assert_eq!(false, map.has_tracked_dir(b"nope"));
364 assert!(map.dirs.is_some());
365 }
366
367 #[test]
368 fn test_add_file() {
369 let mut map = DirstateMap::new();
370
371 assert_eq!(0, map.len());
372
373 map.add_file(
374 b"meh",
375 EntryState::Normal,
376 DirstateEntry {
377 state: EntryState::Normal,
378 mode: 1337,
379 mtime: 1337,
380 size: 1337,
381 },
382 );
383
384 assert_eq!(1, map.len());
385 assert_eq!(0, map.non_normal_set.len());
386 assert_eq!(0, map.other_parent_set.len());
387 }
388
389 #[test]
390 fn test_non_normal_other_parent_entries() {
391 let map: DirstateMap = [
392 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
393 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
394 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
395 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
396 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
397 (b"f6", (EntryState::Added, 1337, 1337, -1)),
398 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
399 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
400 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
401 (b"fa", (EntryState::Added, 1337, -2, 1337)),
402 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
403 ]
404 .iter()
405 .map(|(fname, (state, mode, size, mtime))| {
406 (
407 fname.to_vec(),
408 DirstateEntry {
409 state: *state,
410 mode: *mode,
411 size: *size,
412 mtime: *mtime,
413 },
414 )
415 })
416 .collect();
417
418 let non_normal = [
419 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
420 ]
421 .iter()
422 .map(|x| x.to_vec())
423 .collect();
424
425 let mut other_parent = HashSet::new();
426 other_parent.insert(b"f4".to_vec());
427
428 assert_eq!(
429 (non_normal, other_parent),
430 map.non_normal_other_parent_entries()
431 );
432 }
433 }
@@ -10,6 +10,7 b' use std::collections::HashMap;'
10 use std::convert::TryFrom;
10 use std::convert::TryFrom;
11
11
12 pub mod dirs_multiset;
12 pub mod dirs_multiset;
13 pub mod dirstate_map;
13 pub mod parsers;
14 pub mod parsers;
14
15
15 #[derive(Debug, PartialEq, Clone)]
16 #[derive(Debug, PartialEq, Clone)]
@@ -10,6 +10,7 b' pub mod discovery;'
10 pub mod testing; // unconditionally built, for use from integration tests
10 pub mod testing; // unconditionally built, for use from integration tests
11 pub use dirstate::{
11 pub use dirstate::{
12 dirs_multiset::DirsMultiset,
12 dirs_multiset::DirsMultiset,
13 dirstate_map::DirstateMap,
13 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
14 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
14 CopyMap, DirsIterable, DirstateEntry, DirstateParents, EntryState,
15 CopyMap, DirsIterable, DirstateEntry, DirstateParents, EntryState,
15 StateMap,
16 StateMap,
General Comments 0
You need to be logged in to leave comments. Login now