##// END OF EJS Templates
rust-dirstate: create dirstate submodule...
Raphaël Gomès -
r42616:cc1cdce1 default draft
parent child Browse files
Show More
@@ -0,0 +1,28 b''
1 pub mod parsers;
2
3 #[derive(Debug, PartialEq, Copy, Clone)]
4 pub struct DirstateParents<'a> {
5 pub p1: &'a [u8],
6 pub p2: &'a [u8],
7 }
8
9 /// The C implementation uses all signed types. This will be an issue
10 /// either when 4GB+ source files are commonplace or in 2038, whichever
11 /// comes first.
12 #[derive(Debug, PartialEq)]
13 pub struct DirstateEntry {
14 pub state: i8,
15 pub mode: i32,
16 pub mtime: i32,
17 pub size: i32,
18 }
19
20 pub type DirstateVec = Vec<(Vec<u8>, DirstateEntry)>;
21
22 #[derive(Debug, PartialEq)]
23 pub struct CopyVecEntry<'a> {
24 pub path: &'a [u8],
25 pub copy_path: &'a [u8],
26 }
27
28 pub type CopyVec<'a> = Vec<CopyVecEntry<'a>>;
@@ -0,0 +1,388 b''
1 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
2 //
3 // This software may be used and distributed according to the terms of the
4 // GNU General Public License version 2 or any later version.
5
6 use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
7 use std::collections::HashMap;
8 use std::io::Cursor;
9 use {
10 CopyVec, CopyVecEntry, DirstateEntry, DirstatePackError, DirstateParents,
11 DirstateParseError, DirstateVec,
12 };
13
14 /// Parents are stored in the dirstate as byte hashes.
15 const PARENT_SIZE: usize = 20;
16 /// Dirstate entries have a static part of 8 + 32 + 32 + 32 + 32 bits.
17 const MIN_ENTRY_SIZE: usize = 17;
18
19 pub fn parse_dirstate(
20 contents: &[u8],
21 ) -> Result<(DirstateParents, DirstateVec, CopyVec), DirstateParseError> {
22 if contents.len() < PARENT_SIZE * 2 {
23 return Err(DirstateParseError::TooLittleData);
24 }
25
26 let mut dirstate_vec = vec![];
27 let mut copies = vec![];
28 let mut curr_pos = PARENT_SIZE * 2;
29 let parents = DirstateParents {
30 p1: &contents[..PARENT_SIZE],
31 p2: &contents[PARENT_SIZE..curr_pos],
32 };
33
34 while curr_pos < contents.len() {
35 if curr_pos + MIN_ENTRY_SIZE > contents.len() {
36 return Err(DirstateParseError::Overflow);
37 }
38 let entry_bytes = &contents[curr_pos..];
39
40 let mut cursor = Cursor::new(entry_bytes);
41 let state = cursor.read_i8()?;
42 let mode = cursor.read_i32::<BigEndian>()?;
43 let size = cursor.read_i32::<BigEndian>()?;
44 let mtime = cursor.read_i32::<BigEndian>()?;
45 let path_len = cursor.read_i32::<BigEndian>()? as usize;
46
47 if path_len > contents.len() - curr_pos {
48 return Err(DirstateParseError::Overflow);
49 }
50
51 // Slice instead of allocating a Vec needed for `read_exact`
52 let path = &entry_bytes[MIN_ENTRY_SIZE..MIN_ENTRY_SIZE + (path_len)];
53
54 let (path, copy) = match memchr::memchr(0, path) {
55 None => (path, None),
56 Some(i) => (&path[..i], Some(&path[(i + 1)..])),
57 };
58
59 if let Some(copy_path) = copy {
60 copies.push(CopyVecEntry { path, copy_path });
61 };
62 dirstate_vec.push((
63 path.to_owned(),
64 DirstateEntry {
65 state,
66 mode,
67 size,
68 mtime,
69 },
70 ));
71 curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len);
72 }
73
74 Ok((parents, dirstate_vec, copies))
75 }
76
77 pub fn pack_dirstate(
78 dirstate_vec: &DirstateVec,
79 copymap: &HashMap<Vec<u8>, Vec<u8>>,
80 parents: DirstateParents,
81 now: i32,
82 ) -> Result<(Vec<u8>, DirstateVec), DirstatePackError> {
83 if parents.p1.len() != PARENT_SIZE || parents.p2.len() != PARENT_SIZE {
84 return Err(DirstatePackError::CorruptedParent);
85 }
86
87 let expected_size: usize = dirstate_vec
88 .iter()
89 .map(|(ref filename, _)| {
90 let mut length = MIN_ENTRY_SIZE + filename.len();
91 if let Some(ref copy) = copymap.get(filename) {
92 length += copy.len() + 1;
93 }
94 length
95 })
96 .sum();
97 let expected_size = expected_size + PARENT_SIZE * 2;
98
99 let mut packed = Vec::with_capacity(expected_size);
100 let mut new_dirstate_vec = vec![];
101
102 packed.extend(parents.p1);
103 packed.extend(parents.p2);
104
105 for (ref filename, entry) in dirstate_vec {
106 let mut new_filename: Vec<u8> = filename.to_owned();
107 let mut new_mtime: i32 = entry.mtime;
108 if entry.state == 'n' as i8 && entry.mtime == now.into() {
109 // The file was last modified "simultaneously" with the current
110 // write to dirstate (i.e. within the same second for file-
111 // systems with a granularity of 1 sec). This commonly happens
112 // for at least a couple of files on 'update'.
113 // The user could change the file without changing its size
114 // within the same second. Invalidate the file's mtime in
115 // dirstate, forcing future 'status' calls to compare the
116 // contents of the file if the size is the same. This prevents
117 // mistakenly treating such files as clean.
118 new_mtime = -1;
119 new_dirstate_vec.push((
120 filename.to_owned(),
121 DirstateEntry {
122 mtime: new_mtime,
123 ..*entry
124 },
125 ));
126 }
127
128 if let Some(copy) = copymap.get(filename) {
129 new_filename.push('\0' as u8);
130 new_filename.extend(copy);
131 }
132
133 packed.write_i8(entry.state)?;
134 packed.write_i32::<BigEndian>(entry.mode)?;
135 packed.write_i32::<BigEndian>(entry.size)?;
136 packed.write_i32::<BigEndian>(new_mtime)?;
137 packed.write_i32::<BigEndian>(new_filename.len() as i32)?;
138 packed.extend(new_filename)
139 }
140
141 if packed.len() != expected_size {
142 return Err(DirstatePackError::BadSize(expected_size, packed.len()));
143 }
144
145 Ok((packed, new_dirstate_vec))
146 }
147
148 #[cfg(test)]
149 mod tests {
150 use super::*;
151
152 #[test]
153 fn test_pack_dirstate_empty() {
154 let dirstate_vec: DirstateVec = vec![];
155 let copymap = HashMap::new();
156 let parents = DirstateParents {
157 p1: b"12345678910111213141",
158 p2: b"00000000000000000000",
159 };
160 let now: i32 = 15000000;
161 let expected =
162 (b"1234567891011121314100000000000000000000".to_vec(), vec![]);
163
164 assert_eq!(
165 expected,
166 pack_dirstate(&dirstate_vec, &copymap, parents, now).unwrap()
167 );
168 }
169 #[test]
170 fn test_pack_dirstate_one_entry() {
171 let dirstate_vec: DirstateVec = vec![(
172 vec!['f' as u8, '1' as u8],
173 DirstateEntry {
174 state: 'n' as i8,
175 mode: 0o644,
176 size: 0,
177 mtime: 791231220,
178 },
179 )];
180 let copymap = HashMap::new();
181 let parents = DirstateParents {
182 p1: b"12345678910111213141",
183 p2: b"00000000000000000000",
184 };
185 let now: i32 = 15000000;
186 let expected = (
187 [
188 49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50,
189 49, 51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
190 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0,
191 0, 0, 0, 47, 41, 58, 244, 0, 0, 0, 2, 102, 49,
192 ]
193 .to_vec(),
194 vec![],
195 );
196
197 assert_eq!(
198 expected,
199 pack_dirstate(&dirstate_vec, &copymap, parents, now).unwrap()
200 );
201 }
202 #[test]
203 fn test_pack_dirstate_one_entry_with_copy() {
204 let dirstate_vec: DirstateVec = vec![(
205 b"f1".to_vec(),
206 DirstateEntry {
207 state: 'n' as i8,
208 mode: 0o644,
209 size: 0,
210 mtime: 791231220,
211 },
212 )];
213 let mut copymap = HashMap::new();
214 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
215 let parents = DirstateParents {
216 p1: b"12345678910111213141",
217 p2: b"00000000000000000000",
218 };
219 let now: i32 = 15000000;
220 let expected = (
221 [
222 49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50,
223 49, 51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
224 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0,
225 0, 0, 0, 47, 41, 58, 244, 0, 0, 0, 11, 102, 49, 0, 99, 111,
226 112, 121, 110, 97, 109, 101,
227 ]
228 .to_vec(),
229 vec![],
230 );
231
232 assert_eq!(
233 expected,
234 pack_dirstate(&dirstate_vec, &copymap, parents, now).unwrap()
235 );
236 }
237
238 #[test]
239 fn test_parse_pack_one_entry_with_copy() {
240 let dirstate_vec: DirstateVec = vec![(
241 b"f1".to_vec(),
242 DirstateEntry {
243 state: 'n' as i8,
244 mode: 0o644,
245 size: 0,
246 mtime: 791231220,
247 },
248 )];
249 let mut copymap = HashMap::new();
250 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
251 let parents = DirstateParents {
252 p1: b"12345678910111213141",
253 p2: b"00000000000000000000",
254 };
255 let now: i32 = 15000000;
256 let result =
257 pack_dirstate(&dirstate_vec, &copymap, parents, now).unwrap();
258
259 assert_eq!(
260 (
261 parents,
262 dirstate_vec,
263 copymap
264 .iter()
265 .map(|(k, v)| CopyVecEntry {
266 path: k.as_slice(),
267 copy_path: v.as_slice()
268 })
269 .collect()
270 ),
271 parse_dirstate(result.0.as_slice()).unwrap()
272 )
273 }
274
275 #[test]
276 fn test_parse_pack_multiple_entries_with_copy() {
277 let dirstate_vec: DirstateVec = vec![
278 (
279 b"f1".to_vec(),
280 DirstateEntry {
281 state: 'n' as i8,
282 mode: 0o644,
283 size: 0,
284 mtime: 791231220,
285 },
286 ),
287 (
288 b"f2".to_vec(),
289 DirstateEntry {
290 state: 'm' as i8,
291 mode: 0o777,
292 size: 1000,
293 mtime: 791231220,
294 },
295 ),
296 (
297 b"f3".to_vec(),
298 DirstateEntry {
299 state: 'r' as i8,
300 mode: 0o644,
301 size: 234553,
302 mtime: 791231220,
303 },
304 ),
305 (
306 b"f4\xF6".to_vec(),
307 DirstateEntry {
308 state: 'a' as i8,
309 mode: 0o644,
310 size: -1,
311 mtime: -1,
312 },
313 ),
314 ];
315 let mut copymap = HashMap::new();
316 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
317 copymap.insert(b"f4\xF6".to_vec(), b"copyname2".to_vec());
318 let parents = DirstateParents {
319 p1: b"12345678910111213141",
320 p2: b"00000000000000000000",
321 };
322 let now: i32 = 15000000;
323 let result =
324 pack_dirstate(&dirstate_vec, &copymap, parents, now).unwrap();
325
326 assert_eq!(
327 (parents, dirstate_vec, copymap),
328 parse_dirstate(result.0.as_slice())
329 .and_then(|(p, dvec, cvec)| Ok((
330 p,
331 dvec,
332 cvec.iter()
333 .map(|entry| (
334 entry.path.to_vec(),
335 entry.copy_path.to_vec()
336 ))
337 .collect()
338 )))
339 .unwrap()
340 )
341 }
342
343 #[test]
344 /// https://www.mercurial-scm.org/repo/hg/rev/af3f26b6bba4
345 fn test_parse_pack_one_entry_with_copy_and_time_conflict() {
346 let dirstate_vec: DirstateVec = vec![(
347 b"f1".to_vec(),
348 DirstateEntry {
349 state: 'n' as i8,
350 mode: 0o644,
351 size: 0,
352 mtime: 15000000,
353 },
354 )];
355 let mut copymap = HashMap::new();
356 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
357 let parents = DirstateParents {
358 p1: b"12345678910111213141",
359 p2: b"00000000000000000000",
360 };
361 let now: i32 = 15000000;
362 let result =
363 pack_dirstate(&dirstate_vec, &copymap, parents, now).unwrap();
364
365 assert_eq!(
366 (
367 parents,
368 vec![(
369 b"f1".to_vec(),
370 DirstateEntry {
371 state: 'n' as i8,
372 mode: 0o644,
373 size: 0,
374 mtime: -1
375 }
376 )],
377 copymap
378 .iter()
379 .map(|(k, v)| CopyVecEntry {
380 path: k.as_slice(),
381 copy_path: v.as_slice()
382 })
383 .collect()
384 ),
385 parse_dirstate(result.0.as_slice()).unwrap()
386 )
387 }
388 }
@@ -1,102 +1,102 b''
1 1 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
2 2 //
3 3 // This software may be used and distributed according to the terms of the
4 4 // GNU General Public License version 2 or any later version.
5 5 extern crate byteorder;
6 6 extern crate memchr;
7 7 #[macro_use]
8 8 extern crate lazy_static;
9 9 extern crate regex;
10 10
11 11 mod ancestors;
12 12 pub mod dagops;
13 13 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
14 14 mod dirstate;
15 15 pub mod discovery;
16 16 pub mod testing; // unconditionally built, for use from integration tests
17 17 pub use dirstate::{
18 pack_dirstate, parse_dirstate, CopyVec, CopyVecEntry, DirstateEntry,
19 DirstateParents, DirstateVec,
18 parsers::{pack_dirstate, parse_dirstate},
19 CopyVec, CopyVecEntry, DirstateEntry, DirstateParents, DirstateVec,
20 20 };
21 21 mod filepatterns;
22 22
23 23 pub use filepatterns::{
24 24 build_single_regex, read_pattern_file, PatternSyntax, PatternTuple,
25 25 };
26 26
27 27 /// Mercurial revision numbers
28 28 ///
29 29 /// As noted in revlog.c, revision numbers are actually encoded in
30 30 /// 4 bytes, and are liberally converted to ints, whence the i32
31 31 pub type Revision = i32;
32 32
33 33 /// Marker expressing the absence of a parent
34 34 ///
35 35 /// Independently of the actual representation, `NULL_REVISION` is guaranteed
36 36 /// to be smaller that all existing revisions.
37 37 pub const NULL_REVISION: Revision = -1;
38 38
39 39 /// Same as `mercurial.node.wdirrev`
40 40 ///
41 41 /// This is also equal to `i32::max_value()`, but it's better to spell
42 42 /// it out explicitely, same as in `mercurial.node`
43 43 pub const WORKING_DIRECTORY_REVISION: Revision = 0x7fffffff;
44 44
45 45 /// The simplest expression of what we need of Mercurial DAGs.
46 46 pub trait Graph {
47 47 /// Return the two parents of the given `Revision`.
48 48 ///
49 49 /// Each of the parents can be independently `NULL_REVISION`
50 50 fn parents(&self, Revision) -> Result<[Revision; 2], GraphError>;
51 51 }
52 52
53 53 pub type LineNumber = usize;
54 54
55 55 #[derive(Clone, Debug, PartialEq)]
56 56 pub enum GraphError {
57 57 ParentOutOfRange(Revision),
58 58 WorkingDirectoryUnsupported,
59 59 }
60 60
61 61 #[derive(Clone, Debug, PartialEq)]
62 62 pub enum DirstateParseError {
63 63 TooLittleData,
64 64 Overflow,
65 65 CorruptedEntry(String),
66 66 }
67 67
68 68 #[derive(Debug, PartialEq)]
69 69 pub enum DirstatePackError {
70 70 CorruptedEntry(String),
71 71 CorruptedParent,
72 72 BadSize(usize, usize),
73 73 }
74 74
75 75 impl From<std::io::Error> for DirstatePackError {
76 76 fn from(e: std::io::Error) -> Self {
77 77 DirstatePackError::CorruptedEntry(e.to_string())
78 78 }
79 79 }
80 80
81 81 impl From<std::io::Error> for DirstateParseError {
82 82 fn from(e: std::io::Error) -> Self {
83 83 DirstateParseError::CorruptedEntry(e.to_string())
84 84 }
85 85 }
86 86
87 87 #[derive(Debug)]
88 88 pub enum PatternError {
89 89 UnsupportedSyntax(String),
90 90 }
91 91
92 92 #[derive(Debug)]
93 93 pub enum PatternFileError {
94 94 IO(std::io::Error),
95 95 Pattern(PatternError, LineNumber),
96 96 }
97 97
98 98 impl From<std::io::Error> for PatternFileError {
99 99 fn from(e: std::io::Error) -> Self {
100 100 PatternFileError::IO(e)
101 101 }
102 102 }
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now