##// END OF EJS Templates
rust: simply use TryInto to convert slice to array...
Yuya Nishihara -
r43067:53995325 default
parent child Browse files
Show More
@@ -1,426 +1,426
1 1 // dirstate_map.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::{
9 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,
10 pack_dirstate, parse_dirstate, CopyMap, DirsIterable, DirsMultiset,
11 DirstateEntry, DirstateError, DirstateMapError, DirstateParents,
12 DirstateParseError, StateMap,
14 13 };
15 14 use core::borrow::Borrow;
16 15 use std::collections::{HashMap, HashSet};
16 use std::convert::TryInto;
17 17 use std::iter::FromIterator;
18 18 use std::ops::Deref;
19 19 use std::time::Duration;
20 20
21 21 pub type FileFoldMap = HashMap<Vec<u8>, Vec<u8>>;
22 22
23 23 const NULL_ID: [u8; 20] = [0; 20];
24 24 const MTIME_UNSET: i32 = -1;
25 25 const SIZE_DIRTY: i32 = -2;
26 26
27 27 #[derive(Default)]
28 28 pub struct DirstateMap {
29 29 state_map: StateMap,
30 30 pub copy_map: CopyMap,
31 31 file_fold_map: Option<FileFoldMap>,
32 32 pub dirs: Option<DirsMultiset>,
33 33 pub all_dirs: Option<DirsMultiset>,
34 34 non_normal_set: HashSet<Vec<u8>>,
35 35 other_parent_set: HashSet<Vec<u8>>,
36 36 parents: Option<DirstateParents>,
37 37 dirty_parents: bool,
38 38 }
39 39
40 40 /// Should only really be used in python interface code, for clarity
41 41 impl Deref for DirstateMap {
42 42 type Target = StateMap;
43 43
44 44 fn deref(&self) -> &Self::Target {
45 45 &self.state_map
46 46 }
47 47 }
48 48
49 49 impl FromIterator<(Vec<u8>, DirstateEntry)> for DirstateMap {
50 50 fn from_iter<I: IntoIterator<Item = (Vec<u8>, DirstateEntry)>>(
51 51 iter: I,
52 52 ) -> Self {
53 53 Self {
54 54 state_map: iter.into_iter().collect(),
55 55 ..Self::default()
56 56 }
57 57 }
58 58 }
59 59
60 60 impl DirstateMap {
61 61 pub fn new() -> Self {
62 62 Self::default()
63 63 }
64 64
65 65 pub fn clear(&mut self) {
66 66 self.state_map.clear();
67 67 self.copy_map.clear();
68 68 self.file_fold_map = None;
69 69 self.non_normal_set.clear();
70 70 self.other_parent_set.clear();
71 71 self.set_parents(DirstateParents {
72 72 p1: NULL_ID,
73 73 p2: NULL_ID,
74 74 })
75 75 }
76 76
77 77 /// Add a tracked file to the dirstate
78 78 pub fn add_file(
79 79 &mut self,
80 80 filename: &[u8],
81 81 old_state: EntryState,
82 82 entry: DirstateEntry,
83 83 ) {
84 84 if old_state == EntryState::Unknown || old_state == EntryState::Removed
85 85 {
86 86 if let Some(ref mut dirs) = self.dirs {
87 87 dirs.add_path(filename)
88 88 }
89 89 }
90 90 if old_state == EntryState::Unknown {
91 91 if let Some(ref mut all_dirs) = self.all_dirs {
92 92 all_dirs.add_path(filename)
93 93 }
94 94 }
95 95 self.state_map.insert(filename.to_owned(), entry.to_owned());
96 96
97 97 if entry.state != EntryState::Normal || entry.mtime == MTIME_UNSET {
98 98 self.non_normal_set.insert(filename.to_owned());
99 99 }
100 100
101 101 if entry.size == SIZE_DIRTY {
102 102 self.other_parent_set.insert(filename.to_owned());
103 103 }
104 104 }
105 105
106 106 /// Mark a file as removed in the dirstate.
107 107 ///
108 108 /// The `size` parameter is used to store sentinel values that indicate
109 109 /// the file's previous state. In the future, we should refactor this
110 110 /// to be more explicit about what that state is.
111 111 pub fn remove_file(
112 112 &mut self,
113 113 filename: &[u8],
114 114 old_state: EntryState,
115 115 size: i32,
116 116 ) -> Result<(), DirstateMapError> {
117 117 if old_state != EntryState::Unknown && old_state != EntryState::Removed
118 118 {
119 119 if let Some(ref mut dirs) = self.dirs {
120 120 dirs.delete_path(filename)?;
121 121 }
122 122 }
123 123 if old_state == EntryState::Unknown {
124 124 if let Some(ref mut all_dirs) = self.all_dirs {
125 125 all_dirs.add_path(filename);
126 126 }
127 127 }
128 128
129 129 if let Some(ref mut file_fold_map) = self.file_fold_map {
130 130 file_fold_map.remove(&filename.to_ascii_uppercase());
131 131 }
132 132 self.state_map.insert(
133 133 filename.to_owned(),
134 134 DirstateEntry {
135 135 state: EntryState::Removed,
136 136 mode: 0,
137 137 size,
138 138 mtime: 0,
139 139 },
140 140 );
141 141 self.non_normal_set.insert(filename.to_owned());
142 142 Ok(())
143 143 }
144 144
145 145 /// Remove a file from the dirstate.
146 146 /// Returns `true` if the file was previously recorded.
147 147 pub fn drop_file(
148 148 &mut self,
149 149 filename: &[u8],
150 150 old_state: EntryState,
151 151 ) -> Result<bool, DirstateMapError> {
152 152 let exists = self.state_map.remove(filename).is_some();
153 153
154 154 if exists {
155 155 if old_state != EntryState::Removed {
156 156 if let Some(ref mut dirs) = self.dirs {
157 157 dirs.delete_path(filename)?;
158 158 }
159 159 }
160 160 if let Some(ref mut all_dirs) = self.all_dirs {
161 161 all_dirs.delete_path(filename)?;
162 162 }
163 163 }
164 164 if let Some(ref mut file_fold_map) = self.file_fold_map {
165 165 file_fold_map.remove(&filename.to_ascii_uppercase());
166 166 }
167 167 self.non_normal_set.remove(filename);
168 168
169 169 Ok(exists)
170 170 }
171 171
172 172 pub fn clear_ambiguous_times(
173 173 &mut self,
174 174 filenames: Vec<Vec<u8>>,
175 175 now: i32,
176 176 ) {
177 177 for filename in filenames {
178 178 let mut changed = false;
179 179 self.state_map
180 180 .entry(filename.to_owned())
181 181 .and_modify(|entry| {
182 182 if entry.state == EntryState::Normal && entry.mtime == now
183 183 {
184 184 changed = true;
185 185 *entry = DirstateEntry {
186 186 mtime: MTIME_UNSET,
187 187 ..*entry
188 188 };
189 189 }
190 190 });
191 191 if changed {
192 192 self.non_normal_set.insert(filename.to_owned());
193 193 }
194 194 }
195 195 }
196 196
197 197 pub fn non_normal_other_parent_entries(
198 198 &self,
199 199 ) -> (HashSet<Vec<u8>>, HashSet<Vec<u8>>) {
200 200 let mut non_normal = HashSet::new();
201 201 let mut other_parent = HashSet::new();
202 202
203 203 for (
204 204 filename,
205 205 DirstateEntry {
206 206 state, size, mtime, ..
207 207 },
208 208 ) in self.state_map.iter()
209 209 {
210 210 if *state != EntryState::Normal || *mtime == MTIME_UNSET {
211 211 non_normal.insert(filename.to_owned());
212 212 }
213 213 if *state == EntryState::Normal && *size == SIZE_DIRTY {
214 214 other_parent.insert(filename.to_owned());
215 215 }
216 216 }
217 217
218 218 (non_normal, other_parent)
219 219 }
220 220
221 221 /// Both of these setters and their uses appear to be the simplest way to
222 222 /// emulate a Python lazy property, but it is ugly and unidiomatic.
223 223 /// TODO One day, rewriting this struct using the typestate might be a
224 224 /// good idea.
225 225 pub fn set_all_dirs(&mut self) {
226 226 if self.all_dirs.is_none() {
227 227 self.all_dirs = Some(DirsMultiset::new(
228 228 DirsIterable::Dirstate(&self.state_map),
229 229 None,
230 230 ));
231 231 }
232 232 }
233 233
234 234 pub fn set_dirs(&mut self) {
235 235 if self.dirs.is_none() {
236 236 self.dirs = Some(DirsMultiset::new(
237 237 DirsIterable::Dirstate(&self.state_map),
238 238 Some(EntryState::Removed),
239 239 ));
240 240 }
241 241 }
242 242
243 243 pub fn has_tracked_dir(&mut self, directory: &[u8]) -> bool {
244 244 self.set_dirs();
245 245 self.dirs.as_ref().unwrap().contains(directory)
246 246 }
247 247
248 248 pub fn has_dir(&mut self, directory: &[u8]) -> bool {
249 249 self.set_all_dirs();
250 250 self.all_dirs.as_ref().unwrap().contains(directory)
251 251 }
252 252
253 253 pub fn parents(
254 254 &mut self,
255 255 file_contents: &[u8],
256 256 ) -> Result<DirstateParents, DirstateError> {
257 257 if let Some(ref parents) = self.parents {
258 258 return Ok(parents.clone());
259 259 }
260 260 let parents;
261 261 if file_contents.len() == PARENT_SIZE * 2 {
262 262 parents = DirstateParents {
263 p1: copy_into_array(&file_contents[..PARENT_SIZE]),
264 p2: copy_into_array(
265 &file_contents[PARENT_SIZE..PARENT_SIZE * 2],
266 ),
263 p1: file_contents[..PARENT_SIZE].try_into().unwrap(),
264 p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2]
265 .try_into()
266 .unwrap(),
267 267 };
268 268 } else if file_contents.is_empty() {
269 269 parents = DirstateParents {
270 270 p1: NULL_ID,
271 271 p2: NULL_ID,
272 272 };
273 273 } else {
274 274 return Err(DirstateError::Parse(DirstateParseError::Damaged));
275 275 }
276 276
277 277 self.parents = Some(parents.to_owned());
278 278 Ok(parents.clone())
279 279 }
280 280
281 281 pub fn set_parents(&mut self, parents: DirstateParents) {
282 282 self.parents = Some(parents.clone());
283 283 self.dirty_parents = true;
284 284 }
285 285
286 286 pub fn read(
287 287 &mut self,
288 288 file_contents: &[u8],
289 289 ) -> Result<Option<DirstateParents>, DirstateError> {
290 290 if file_contents.is_empty() {
291 291 return Ok(None);
292 292 }
293 293
294 294 let parents = parse_dirstate(
295 295 &mut self.state_map,
296 296 &mut self.copy_map,
297 297 file_contents,
298 298 )?;
299 299
300 300 if !self.dirty_parents {
301 301 self.set_parents(parents.to_owned());
302 302 }
303 303
304 304 Ok(Some(parents))
305 305 }
306 306
307 307 pub fn pack(
308 308 &mut self,
309 309 parents: DirstateParents,
310 310 now: Duration,
311 311 ) -> Result<Vec<u8>, DirstateError> {
312 312 let packed =
313 313 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
314 314
315 315 self.dirty_parents = false;
316 316
317 317 let result = self.non_normal_other_parent_entries();
318 318 self.non_normal_set = result.0;
319 319 self.other_parent_set = result.1;
320 320 Ok(packed)
321 321 }
322 322
323 323 pub fn build_file_fold_map(&mut self) -> FileFoldMap {
324 324 if let Some(ref file_fold_map) = self.file_fold_map {
325 325 return file_fold_map.to_owned();
326 326 }
327 327 let mut new_file_fold_map = FileFoldMap::new();
328 328 for (filename, DirstateEntry { state, .. }) in self.state_map.borrow()
329 329 {
330 330 if *state == EntryState::Removed {
331 331 new_file_fold_map.insert(
332 332 filename.to_ascii_uppercase().to_owned(),
333 333 filename.to_owned(),
334 334 );
335 335 }
336 336 }
337 337 self.file_fold_map = Some(new_file_fold_map);
338 338 self.file_fold_map.to_owned().unwrap()
339 339 }
340 340 }
341 341
342 342 #[cfg(test)]
343 343 mod tests {
344 344 use super::*;
345 345
346 346 #[test]
347 347 fn test_dirs_multiset() {
348 348 let mut map = DirstateMap::new();
349 349 assert!(map.dirs.is_none());
350 350 assert!(map.all_dirs.is_none());
351 351
352 352 assert_eq!(false, map.has_dir(b"nope"));
353 353 assert!(map.all_dirs.is_some());
354 354 assert!(map.dirs.is_none());
355 355
356 356 assert_eq!(false, map.has_tracked_dir(b"nope"));
357 357 assert!(map.dirs.is_some());
358 358 }
359 359
360 360 #[test]
361 361 fn test_add_file() {
362 362 let mut map = DirstateMap::new();
363 363
364 364 assert_eq!(0, map.len());
365 365
366 366 map.add_file(
367 367 b"meh",
368 368 EntryState::Normal,
369 369 DirstateEntry {
370 370 state: EntryState::Normal,
371 371 mode: 1337,
372 372 mtime: 1337,
373 373 size: 1337,
374 374 },
375 375 );
376 376
377 377 assert_eq!(1, map.len());
378 378 assert_eq!(0, map.non_normal_set.len());
379 379 assert_eq!(0, map.other_parent_set.len());
380 380 }
381 381
382 382 #[test]
383 383 fn test_non_normal_other_parent_entries() {
384 384 let map: DirstateMap = [
385 385 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
386 386 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
387 387 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
388 388 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
389 389 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
390 390 (b"f6", (EntryState::Added, 1337, 1337, -1)),
391 391 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
392 392 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
393 393 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
394 394 (b"fa", (EntryState::Added, 1337, -2, 1337)),
395 395 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
396 396 ]
397 397 .iter()
398 398 .map(|(fname, (state, mode, size, mtime))| {
399 399 (
400 400 fname.to_vec(),
401 401 DirstateEntry {
402 402 state: *state,
403 403 mode: *mode,
404 404 size: *size,
405 405 mtime: *mtime,
406 406 },
407 407 )
408 408 })
409 409 .collect();
410 410
411 411 let non_normal = [
412 412 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
413 413 ]
414 414 .iter()
415 415 .map(|x| x.to_vec())
416 416 .collect();
417 417
418 418 let mut other_parent = HashSet::new();
419 419 other_parent.insert(b"f4".to_vec());
420 420
421 421 assert_eq!(
422 422 (non_normal, other_parent),
423 423 map.non_normal_other_parent_entries()
424 424 );
425 425 }
426 426 }
@@ -1,415 +1,414
1 1 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
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
6 6 use crate::{
7 7 dirstate::{CopyMap, EntryState, StateMap},
8 utils::copy_into_array,
9 8 DirstateEntry, DirstatePackError, DirstateParents, DirstateParseError,
10 9 };
11 10 use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
12 11 use std::convert::{TryFrom, TryInto};
13 12 use std::io::Cursor;
14 13 use std::time::Duration;
15 14
16 15 /// Parents are stored in the dirstate as byte hashes.
17 16 pub const PARENT_SIZE: usize = 20;
18 17 /// Dirstate entries have a static part of 8 + 32 + 32 + 32 + 32 bits.
19 18 const MIN_ENTRY_SIZE: usize = 17;
20 19
21 20 // TODO parse/pack: is mutate-on-loop better for performance?
22 21
23 22 pub fn parse_dirstate(
24 23 state_map: &mut StateMap,
25 24 copy_map: &mut CopyMap,
26 25 contents: &[u8],
27 26 ) -> Result<DirstateParents, DirstateParseError> {
28 27 if contents.len() < PARENT_SIZE * 2 {
29 28 return Err(DirstateParseError::TooLittleData);
30 29 }
31 30
32 31 let mut curr_pos = PARENT_SIZE * 2;
33 32 let parents = DirstateParents {
34 p1: copy_into_array(&contents[..PARENT_SIZE]),
35 p2: copy_into_array(&contents[PARENT_SIZE..curr_pos]),
33 p1: contents[..PARENT_SIZE].try_into().unwrap(),
34 p2: contents[PARENT_SIZE..curr_pos].try_into().unwrap(),
36 35 };
37 36
38 37 while curr_pos < contents.len() {
39 38 if curr_pos + MIN_ENTRY_SIZE > contents.len() {
40 39 return Err(DirstateParseError::Overflow);
41 40 }
42 41 let entry_bytes = &contents[curr_pos..];
43 42
44 43 let mut cursor = Cursor::new(entry_bytes);
45 44 let state = EntryState::try_from(cursor.read_u8()?)?;
46 45 let mode = cursor.read_i32::<BigEndian>()?;
47 46 let size = cursor.read_i32::<BigEndian>()?;
48 47 let mtime = cursor.read_i32::<BigEndian>()?;
49 48 let path_len = cursor.read_i32::<BigEndian>()? as usize;
50 49
51 50 if path_len > contents.len() - curr_pos {
52 51 return Err(DirstateParseError::Overflow);
53 52 }
54 53
55 54 // Slice instead of allocating a Vec needed for `read_exact`
56 55 let path = &entry_bytes[MIN_ENTRY_SIZE..MIN_ENTRY_SIZE + (path_len)];
57 56
58 57 let (path, copy) = match memchr::memchr(0, path) {
59 58 None => (path, None),
60 59 Some(i) => (&path[..i], Some(&path[(i + 1)..])),
61 60 };
62 61
63 62 if let Some(copy_path) = copy {
64 63 copy_map.insert(path.to_owned(), copy_path.to_owned());
65 64 };
66 65 state_map.insert(
67 66 path.to_owned(),
68 67 DirstateEntry {
69 68 state,
70 69 mode,
71 70 size,
72 71 mtime,
73 72 },
74 73 );
75 74 curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len);
76 75 }
77 76
78 77 Ok(parents)
79 78 }
80 79
81 80 /// `now` is the duration in seconds since the Unix epoch
82 81 pub fn pack_dirstate(
83 82 state_map: &mut StateMap,
84 83 copy_map: &CopyMap,
85 84 parents: DirstateParents,
86 85 now: Duration,
87 86 ) -> Result<Vec<u8>, DirstatePackError> {
88 87 // TODO move away from i32 before 2038.
89 88 let now: i32 = now.as_secs().try_into().expect("time overflow");
90 89
91 90 let expected_size: usize = state_map
92 91 .iter()
93 92 .map(|(filename, _)| {
94 93 let mut length = MIN_ENTRY_SIZE + filename.len();
95 94 if let Some(copy) = copy_map.get(filename) {
96 95 length += copy.len() + 1;
97 96 }
98 97 length
99 98 })
100 99 .sum();
101 100 let expected_size = expected_size + PARENT_SIZE * 2;
102 101
103 102 let mut packed = Vec::with_capacity(expected_size);
104 103 let mut new_state_map = vec![];
105 104
106 105 packed.extend(&parents.p1);
107 106 packed.extend(&parents.p2);
108 107
109 108 for (filename, entry) in state_map.iter() {
110 109 let mut new_filename: Vec<u8> = filename.to_owned();
111 110 let mut new_mtime: i32 = entry.mtime;
112 111 if entry.state == EntryState::Normal && entry.mtime == now {
113 112 // The file was last modified "simultaneously" with the current
114 113 // write to dirstate (i.e. within the same second for file-
115 114 // systems with a granularity of 1 sec). This commonly happens
116 115 // for at least a couple of files on 'update'.
117 116 // The user could change the file without changing its size
118 117 // within the same second. Invalidate the file's mtime in
119 118 // dirstate, forcing future 'status' calls to compare the
120 119 // contents of the file if the size is the same. This prevents
121 120 // mistakenly treating such files as clean.
122 121 new_mtime = -1;
123 122 new_state_map.push((
124 123 filename.to_owned(),
125 124 DirstateEntry {
126 125 mtime: new_mtime,
127 126 ..*entry
128 127 },
129 128 ));
130 129 }
131 130
132 131 if let Some(copy) = copy_map.get(filename) {
133 132 new_filename.push('\0' as u8);
134 133 new_filename.extend(copy);
135 134 }
136 135
137 136 packed.write_u8(entry.state.into())?;
138 137 packed.write_i32::<BigEndian>(entry.mode)?;
139 138 packed.write_i32::<BigEndian>(entry.size)?;
140 139 packed.write_i32::<BigEndian>(new_mtime)?;
141 140 packed.write_i32::<BigEndian>(new_filename.len() as i32)?;
142 141 packed.extend(new_filename)
143 142 }
144 143
145 144 if packed.len() != expected_size {
146 145 return Err(DirstatePackError::BadSize(expected_size, packed.len()));
147 146 }
148 147
149 148 state_map.extend(new_state_map);
150 149
151 150 Ok(packed)
152 151 }
153 152
154 153 #[cfg(test)]
155 154 mod tests {
156 155 use super::*;
157 156 use std::collections::HashMap;
158 157
159 158 #[test]
160 159 fn test_pack_dirstate_empty() {
161 160 let mut state_map: StateMap = HashMap::new();
162 161 let copymap = HashMap::new();
163 162 let parents = DirstateParents {
164 163 p1: *b"12345678910111213141",
165 164 p2: *b"00000000000000000000",
166 165 };
167 166 let now = Duration::new(15000000, 0);
168 167 let expected = b"1234567891011121314100000000000000000000".to_vec();
169 168
170 169 assert_eq!(
171 170 expected,
172 171 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
173 172 );
174 173
175 174 assert!(state_map.is_empty())
176 175 }
177 176 #[test]
178 177 fn test_pack_dirstate_one_entry() {
179 178 let expected_state_map: StateMap = [(
180 179 b"f1".to_vec(),
181 180 DirstateEntry {
182 181 state: EntryState::Normal,
183 182 mode: 0o644,
184 183 size: 0,
185 184 mtime: 791231220,
186 185 },
187 186 )]
188 187 .iter()
189 188 .cloned()
190 189 .collect();
191 190 let mut state_map = expected_state_map.clone();
192 191
193 192 let copymap = HashMap::new();
194 193 let parents = DirstateParents {
195 194 p1: *b"12345678910111213141",
196 195 p2: *b"00000000000000000000",
197 196 };
198 197 let now = Duration::new(15000000, 0);
199 198 let expected = [
200 199 49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49,
201 200 51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
202 201 48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0, 0, 0, 0, 47,
203 202 41, 58, 244, 0, 0, 0, 2, 102, 49,
204 203 ]
205 204 .to_vec();
206 205
207 206 assert_eq!(
208 207 expected,
209 208 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
210 209 );
211 210
212 211 assert_eq!(expected_state_map, state_map);
213 212 }
214 213 #[test]
215 214 fn test_pack_dirstate_one_entry_with_copy() {
216 215 let expected_state_map: StateMap = [(
217 216 b"f1".to_vec(),
218 217 DirstateEntry {
219 218 state: EntryState::Normal,
220 219 mode: 0o644,
221 220 size: 0,
222 221 mtime: 791231220,
223 222 },
224 223 )]
225 224 .iter()
226 225 .cloned()
227 226 .collect();
228 227 let mut state_map = expected_state_map.clone();
229 228 let mut copymap = HashMap::new();
230 229 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
231 230 let parents = DirstateParents {
232 231 p1: *b"12345678910111213141",
233 232 p2: *b"00000000000000000000",
234 233 };
235 234 let now = Duration::new(15000000, 0);
236 235 let expected = [
237 236 49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49,
238 237 51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
239 238 48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0, 0, 0, 0, 47,
240 239 41, 58, 244, 0, 0, 0, 11, 102, 49, 0, 99, 111, 112, 121, 110, 97,
241 240 109, 101,
242 241 ]
243 242 .to_vec();
244 243
245 244 assert_eq!(
246 245 expected,
247 246 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
248 247 );
249 248 assert_eq!(expected_state_map, state_map);
250 249 }
251 250
252 251 #[test]
253 252 fn test_parse_pack_one_entry_with_copy() {
254 253 let mut state_map: StateMap = [(
255 254 b"f1".to_vec(),
256 255 DirstateEntry {
257 256 state: EntryState::Normal,
258 257 mode: 0o644,
259 258 size: 0,
260 259 mtime: 791231220,
261 260 },
262 261 )]
263 262 .iter()
264 263 .cloned()
265 264 .collect();
266 265 let mut copymap = HashMap::new();
267 266 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
268 267 let parents = DirstateParents {
269 268 p1: *b"12345678910111213141",
270 269 p2: *b"00000000000000000000",
271 270 };
272 271 let now = Duration::new(15000000, 0);
273 272 let result =
274 273 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
275 274 .unwrap();
276 275
277 276 let mut new_state_map: StateMap = HashMap::new();
278 277 let mut new_copy_map: CopyMap = HashMap::new();
279 278 let new_parents = parse_dirstate(
280 279 &mut new_state_map,
281 280 &mut new_copy_map,
282 281 result.as_slice(),
283 282 )
284 283 .unwrap();
285 284 assert_eq!(
286 285 (parents, state_map, copymap),
287 286 (new_parents, new_state_map, new_copy_map)
288 287 )
289 288 }
290 289
291 290 #[test]
292 291 fn test_parse_pack_multiple_entries_with_copy() {
293 292 let mut state_map: StateMap = [
294 293 (
295 294 b"f1".to_vec(),
296 295 DirstateEntry {
297 296 state: EntryState::Normal,
298 297 mode: 0o644,
299 298 size: 0,
300 299 mtime: 791231220,
301 300 },
302 301 ),
303 302 (
304 303 b"f2".to_vec(),
305 304 DirstateEntry {
306 305 state: EntryState::Merged,
307 306 mode: 0o777,
308 307 size: 1000,
309 308 mtime: 791231220,
310 309 },
311 310 ),
312 311 (
313 312 b"f3".to_vec(),
314 313 DirstateEntry {
315 314 state: EntryState::Removed,
316 315 mode: 0o644,
317 316 size: 234553,
318 317 mtime: 791231220,
319 318 },
320 319 ),
321 320 (
322 321 b"f4\xF6".to_vec(),
323 322 DirstateEntry {
324 323 state: EntryState::Added,
325 324 mode: 0o644,
326 325 size: -1,
327 326 mtime: -1,
328 327 },
329 328 ),
330 329 ]
331 330 .iter()
332 331 .cloned()
333 332 .collect();
334 333 let mut copymap = HashMap::new();
335 334 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
336 335 copymap.insert(b"f4\xF6".to_vec(), b"copyname2".to_vec());
337 336 let parents = DirstateParents {
338 337 p1: *b"12345678910111213141",
339 338 p2: *b"00000000000000000000",
340 339 };
341 340 let now = Duration::new(15000000, 0);
342 341 let result =
343 342 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
344 343 .unwrap();
345 344
346 345 let mut new_state_map: StateMap = HashMap::new();
347 346 let mut new_copy_map: CopyMap = HashMap::new();
348 347 let new_parents = parse_dirstate(
349 348 &mut new_state_map,
350 349 &mut new_copy_map,
351 350 result.as_slice(),
352 351 )
353 352 .unwrap();
354 353 assert_eq!(
355 354 (parents, state_map, copymap),
356 355 (new_parents, new_state_map, new_copy_map)
357 356 )
358 357 }
359 358
360 359 #[test]
361 360 /// https://www.mercurial-scm.org/repo/hg/rev/af3f26b6bba4
362 361 fn test_parse_pack_one_entry_with_copy_and_time_conflict() {
363 362 let mut state_map: StateMap = [(
364 363 b"f1".to_vec(),
365 364 DirstateEntry {
366 365 state: EntryState::Normal,
367 366 mode: 0o644,
368 367 size: 0,
369 368 mtime: 15000000,
370 369 },
371 370 )]
372 371 .iter()
373 372 .cloned()
374 373 .collect();
375 374 let mut copymap = HashMap::new();
376 375 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
377 376 let parents = DirstateParents {
378 377 p1: *b"12345678910111213141",
379 378 p2: *b"00000000000000000000",
380 379 };
381 380 let now = Duration::new(15000000, 0);
382 381 let result =
383 382 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
384 383 .unwrap();
385 384
386 385 let mut new_state_map: StateMap = HashMap::new();
387 386 let mut new_copy_map: CopyMap = HashMap::new();
388 387 let new_parents = parse_dirstate(
389 388 &mut new_state_map,
390 389 &mut new_copy_map,
391 390 result.as_slice(),
392 391 )
393 392 .unwrap();
394 393
395 394 assert_eq!(
396 395 (
397 396 parents,
398 397 [(
399 398 b"f1".to_vec(),
400 399 DirstateEntry {
401 400 state: EntryState::Normal,
402 401 mode: 0o644,
403 402 size: 0,
404 403 mtime: -1
405 404 }
406 405 )]
407 406 .iter()
408 407 .cloned()
409 408 .collect::<StateMap>(),
410 409 copymap,
411 410 ),
412 411 (new_parents, new_state_map, new_copy_map)
413 412 )
414 413 }
415 414 }
@@ -1,101 +1,84
1 1 // utils module
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Contains useful functions, traits, structs, etc. for use in core.
9 9
10 10 pub mod files;
11 11
12 use std::convert::AsMut;
13
14 /// Takes a slice and copies it into an array.
15 ///
16 /// # Panics
17 ///
18 /// Will panic if the slice and target array don't have the same length.
19 pub fn copy_into_array<A, T>(slice: &[T]) -> A
20 where
21 A: Sized + Default + AsMut<[T]>,
22 T: Copy,
23 {
24 let mut a = Default::default();
25 <A as AsMut<[T]>>::as_mut(&mut a).copy_from_slice(slice);
26 a
27 }
28
29 12 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
30 13 ///
31 14 /// # Examples
32 15 ///
33 16 /// ```
34 17 /// use crate::hg::utils::replace_slice;
35 18 /// let mut line = b"I hate writing tests!".to_vec();
36 19 /// replace_slice(&mut line, b"hate", b"love");
37 20 /// assert_eq!(
38 21 /// line,
39 22 /// b"I love writing tests!".to_vec()
40 23 ///);
41 24 ///
42 25 /// ```
43 26 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
44 27 where
45 28 T: Clone + PartialEq,
46 29 {
47 30 if buf.len() < from.len() || from.len() != to.len() {
48 31 return;
49 32 }
50 33 for i in 0..=buf.len() - from.len() {
51 34 if buf[i..].starts_with(from) {
52 35 buf[i..(i + from.len())].clone_from_slice(to);
53 36 }
54 37 }
55 38 }
56 39
57 40 pub trait SliceExt {
58 41 fn trim_end(&self) -> &Self;
59 42 fn trim_start(&self) -> &Self;
60 43 fn trim(&self) -> &Self;
61 44 }
62 45
63 46 fn is_not_whitespace(c: &u8) -> bool {
64 47 !(*c as char).is_whitespace()
65 48 }
66 49
67 50 impl SliceExt for [u8] {
68 51 fn trim_end(&self) -> &[u8] {
69 52 if let Some(last) = self.iter().rposition(is_not_whitespace) {
70 53 &self[..last + 1]
71 54 } else {
72 55 &[]
73 56 }
74 57 }
75 58 fn trim_start(&self) -> &[u8] {
76 59 if let Some(first) = self.iter().position(is_not_whitespace) {
77 60 &self[first..]
78 61 } else {
79 62 &[]
80 63 }
81 64 }
82 65
83 66 /// ```
84 67 /// use hg::utils::SliceExt;
85 68 /// assert_eq!(
86 69 /// b" to trim ".trim(),
87 70 /// b"to trim"
88 71 /// );
89 72 /// assert_eq!(
90 73 /// b"to trim ".trim(),
91 74 /// b"to trim"
92 75 /// );
93 76 /// assert_eq!(
94 77 /// b" to trim".trim(),
95 78 /// b"to trim"
96 79 /// );
97 80 /// ```
98 81 fn trim(&self) -> &[u8] {
99 82 self.trim_start().trim_end()
100 83 }
101 84 }
@@ -1,508 +1,510
1 1 // dirstate_map.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 9 //! `hg-core` package.
10 10
11 11 use std::cell::RefCell;
12 12 use std::convert::TryInto;
13 13 use std::time::Duration;
14 14
15 15 use cpython::{
16 16 exc, ObjectProtocol, PyBool, PyBytes, PyClone, PyDict, PyErr, PyObject,
17 17 PyResult, PyTuple, Python, PythonObject, ToPyObject,
18 18 };
19 19 use libc::c_char;
20 20
21 21 use crate::{
22 22 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
23 23 dirstate::{decapsule_make_dirstate_tuple, dirs_multiset::Dirs},
24 24 ref_sharing::PySharedState,
25 25 };
26 26 use hg::{
27 utils::copy_into_array, DirsIterable, DirsMultiset, DirstateEntry,
27 DirsIterable, DirsMultiset, DirstateEntry,
28 28 DirstateMap as RustDirstateMap, DirstateParents, DirstateParseError,
29 29 EntryState,
30 30 };
31 31
32 32 // TODO
33 33 // This object needs to share references to multiple members of its Rust
34 34 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
35 35 // Right now `CopyMap` is done, but it needs to have an explicit reference
36 36 // to `RustDirstateMap` which itself needs to have an encapsulation for
37 37 // every method in `CopyMap` (copymapcopy, etc.).
38 38 // This is ugly and hard to maintain.
39 39 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
40 40 // `py_class!` is already implemented and does not mention
41 41 // `RustDirstateMap`, rightfully so.
42 42 // All attributes also have to have a separate refcount data attribute for
43 43 // leaks, with all methods that go along for reference sharing.
44 44 py_class!(pub class DirstateMap |py| {
45 45 data inner: RefCell<RustDirstateMap>;
46 46 data py_shared_state: PySharedState;
47 47
48 48 def __new__(_cls, _root: PyObject) -> PyResult<Self> {
49 49 let inner = RustDirstateMap::default();
50 50 Self::create_instance(
51 51 py,
52 52 RefCell::new(inner),
53 53 PySharedState::default()
54 54 )
55 55 }
56 56
57 57 def clear(&self) -> PyResult<PyObject> {
58 58 self.borrow_mut(py)?.clear();
59 59 Ok(py.None())
60 60 }
61 61
62 62 def get(
63 63 &self,
64 64 key: PyObject,
65 65 default: Option<PyObject> = None
66 66 ) -> PyResult<Option<PyObject>> {
67 67 let key = key.extract::<PyBytes>(py)?;
68 68 match self.inner(py).borrow().get(key.data(py)) {
69 69 Some(entry) => {
70 70 // Explicitly go through u8 first, then cast to
71 71 // platform-specific `c_char`.
72 72 let state: u8 = entry.state.into();
73 73 Ok(Some(decapsule_make_dirstate_tuple(py)?(
74 74 state as c_char,
75 75 entry.mode,
76 76 entry.size,
77 77 entry.mtime,
78 78 )))
79 79 },
80 80 None => Ok(default)
81 81 }
82 82 }
83 83
84 84 def addfile(
85 85 &self,
86 86 f: PyObject,
87 87 oldstate: PyObject,
88 88 state: PyObject,
89 89 mode: PyObject,
90 90 size: PyObject,
91 91 mtime: PyObject
92 92 ) -> PyResult<PyObject> {
93 93 self.borrow_mut(py)?.add_file(
94 94 f.extract::<PyBytes>(py)?.data(py),
95 95 oldstate.extract::<PyBytes>(py)?.data(py)[0]
96 96 .try_into()
97 97 .map_err(|e: DirstateParseError| {
98 98 PyErr::new::<exc::ValueError, _>(py, e.to_string())
99 99 })?,
100 100 DirstateEntry {
101 101 state: state.extract::<PyBytes>(py)?.data(py)[0]
102 102 .try_into()
103 103 .map_err(|e: DirstateParseError| {
104 104 PyErr::new::<exc::ValueError, _>(py, e.to_string())
105 105 })?,
106 106 mode: mode.extract(py)?,
107 107 size: size.extract(py)?,
108 108 mtime: mtime.extract(py)?,
109 109 },
110 110 );
111 111 Ok(py.None())
112 112 }
113 113
114 114 def removefile(
115 115 &self,
116 116 f: PyObject,
117 117 oldstate: PyObject,
118 118 size: PyObject
119 119 ) -> PyResult<PyObject> {
120 120 self.borrow_mut(py)?
121 121 .remove_file(
122 122 f.extract::<PyBytes>(py)?.data(py),
123 123 oldstate.extract::<PyBytes>(py)?.data(py)[0]
124 124 .try_into()
125 125 .map_err(|e: DirstateParseError| {
126 126 PyErr::new::<exc::ValueError, _>(py, e.to_string())
127 127 })?,
128 128 size.extract(py)?,
129 129 )
130 130 .or_else(|_| {
131 131 Err(PyErr::new::<exc::OSError, _>(
132 132 py,
133 133 "Dirstate error".to_string(),
134 134 ))
135 135 })?;
136 136 Ok(py.None())
137 137 }
138 138
139 139 def dropfile(
140 140 &self,
141 141 f: PyObject,
142 142 oldstate: PyObject
143 143 ) -> PyResult<PyBool> {
144 144 self.borrow_mut(py)?
145 145 .drop_file(
146 146 f.extract::<PyBytes>(py)?.data(py),
147 147 oldstate.extract::<PyBytes>(py)?.data(py)[0]
148 148 .try_into()
149 149 .map_err(|e: DirstateParseError| {
150 150 PyErr::new::<exc::ValueError, _>(py, e.to_string())
151 151 })?,
152 152 )
153 153 .and_then(|b| Ok(b.to_py_object(py)))
154 154 .or_else(|_| {
155 155 Err(PyErr::new::<exc::OSError, _>(
156 156 py,
157 157 "Dirstate error".to_string(),
158 158 ))
159 159 })
160 160 }
161 161
162 162 def clearambiguoustimes(
163 163 &self,
164 164 files: PyObject,
165 165 now: PyObject
166 166 ) -> PyResult<PyObject> {
167 167 let files: PyResult<Vec<Vec<u8>>> = files
168 168 .iter(py)?
169 169 .map(|filename| {
170 170 Ok(filename?.extract::<PyBytes>(py)?.data(py).to_owned())
171 171 })
172 172 .collect();
173 173 self.inner(py)
174 174 .borrow_mut()
175 175 .clear_ambiguous_times(files?, now.extract(py)?);
176 176 Ok(py.None())
177 177 }
178 178
179 179 // TODO share the reference
180 180 def nonnormalentries(&self) -> PyResult<PyObject> {
181 181 let (non_normal, other_parent) =
182 182 self.inner(py).borrow().non_normal_other_parent_entries();
183 183
184 184 let locals = PyDict::new(py);
185 185 locals.set_item(
186 186 py,
187 187 "non_normal",
188 188 non_normal
189 189 .iter()
190 190 .map(|v| PyBytes::new(py, &v))
191 191 .collect::<Vec<PyBytes>>()
192 192 .to_py_object(py),
193 193 )?;
194 194 locals.set_item(
195 195 py,
196 196 "other_parent",
197 197 other_parent
198 198 .iter()
199 199 .map(|v| PyBytes::new(py, &v))
200 200 .collect::<Vec<PyBytes>>()
201 201 .to_py_object(py),
202 202 )?;
203 203
204 204 py.eval("set(non_normal), set(other_parent)", None, Some(&locals))
205 205 }
206 206
207 207 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
208 208 let d = d.extract::<PyBytes>(py)?;
209 209 Ok(self
210 210 .inner(py)
211 211 .borrow_mut()
212 212 .has_tracked_dir(d.data(py))
213 213 .to_py_object(py))
214 214 }
215 215
216 216 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
217 217 let d = d.extract::<PyBytes>(py)?;
218 218 Ok(self
219 219 .inner(py)
220 220 .borrow_mut()
221 221 .has_dir(d.data(py))
222 222 .to_py_object(py))
223 223 }
224 224
225 225 def parents(&self, st: PyObject) -> PyResult<PyTuple> {
226 226 self.inner(py)
227 227 .borrow_mut()
228 228 .parents(st.extract::<PyBytes>(py)?.data(py))
229 229 .and_then(|d| {
230 230 Ok((PyBytes::new(py, &d.p1), PyBytes::new(py, &d.p2))
231 231 .to_py_object(py))
232 232 })
233 233 .or_else(|_| {
234 234 Err(PyErr::new::<exc::OSError, _>(
235 235 py,
236 236 "Dirstate error".to_string(),
237 237 ))
238 238 })
239 239 }
240 240
241 241 def setparents(&self, p1: PyObject, p2: PyObject) -> PyResult<PyObject> {
242 let p1 = copy_into_array(p1.extract::<PyBytes>(py)?.data(py));
243 let p2 = copy_into_array(p2.extract::<PyBytes>(py)?.data(py));
242 // TODO: don't panic; raise Python exception instead.
243 let p1 = p1.extract::<PyBytes>(py)?.data(py).try_into().unwrap();
244 let p2 = p2.extract::<PyBytes>(py)?.data(py).try_into().unwrap();
244 245
245 246 self.inner(py)
246 247 .borrow_mut()
247 248 .set_parents(DirstateParents { p1, p2 });
248 249 Ok(py.None())
249 250 }
250 251
251 252 def read(&self, st: PyObject) -> PyResult<Option<PyObject>> {
252 253 match self
253 254 .inner(py)
254 255 .borrow_mut()
255 256 .read(st.extract::<PyBytes>(py)?.data(py))
256 257 {
257 258 Ok(Some(parents)) => Ok(Some(
258 259 (PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2))
259 260 .to_py_object(py)
260 261 .into_object(),
261 262 )),
262 263 Ok(None) => Ok(Some(py.None())),
263 264 Err(_) => Err(PyErr::new::<exc::OSError, _>(
264 265 py,
265 266 "Dirstate error".to_string(),
266 267 )),
267 268 }
268 269 }
269 270 def write(
270 271 &self,
271 272 p1: PyObject,
272 273 p2: PyObject,
273 274 now: PyObject
274 275 ) -> PyResult<PyBytes> {
275 276 let now = Duration::new(now.extract(py)?, 0);
276 277 let parents = DirstateParents {
277 p1: copy_into_array(p1.extract::<PyBytes>(py)?.data(py)),
278 p2: copy_into_array(p2.extract::<PyBytes>(py)?.data(py)),
278 // TODO: don't panic; raise Python exception instead.
279 p1: p1.extract::<PyBytes>(py)?.data(py).try_into().unwrap(),
280 p2: p2.extract::<PyBytes>(py)?.data(py).try_into().unwrap(),
279 281 };
280 282
281 283 match self.borrow_mut(py)?.pack(parents, now) {
282 284 Ok(packed) => Ok(PyBytes::new(py, &packed)),
283 285 Err(_) => Err(PyErr::new::<exc::OSError, _>(
284 286 py,
285 287 "Dirstate error".to_string(),
286 288 )),
287 289 }
288 290 }
289 291
290 292 def filefoldmapasdict(&self) -> PyResult<PyDict> {
291 293 let dict = PyDict::new(py);
292 294 for (key, value) in
293 295 self.borrow_mut(py)?.build_file_fold_map().iter()
294 296 {
295 297 dict.set_item(py, key, value)?;
296 298 }
297 299 Ok(dict)
298 300 }
299 301
300 302 def __len__(&self) -> PyResult<usize> {
301 303 Ok(self.inner(py).borrow().len())
302 304 }
303 305
304 306 def __contains__(&self, key: PyObject) -> PyResult<bool> {
305 307 let key = key.extract::<PyBytes>(py)?;
306 308 Ok(self.inner(py).borrow().contains_key(key.data(py)))
307 309 }
308 310
309 311 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
310 312 let key = key.extract::<PyBytes>(py)?;
311 313 let key = key.data(py);
312 314 match self.inner(py).borrow().get(key) {
313 315 Some(entry) => {
314 316 // Explicitly go through u8 first, then cast to
315 317 // platform-specific `c_char`.
316 318 let state: u8 = entry.state.into();
317 319 Ok(decapsule_make_dirstate_tuple(py)?(
318 320 state as c_char,
319 321 entry.mode,
320 322 entry.size,
321 323 entry.mtime,
322 324 ))
323 325 },
324 326 None => Err(PyErr::new::<exc::KeyError, _>(
325 327 py,
326 328 String::from_utf8_lossy(key),
327 329 )),
328 330 }
329 331 }
330 332
331 333 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
332 334 DirstateMapKeysIterator::from_inner(
333 335 py,
334 336 Some(DirstateMapLeakedRef::new(py, &self)),
335 337 Box::new(self.leak_immutable(py)?.iter()),
336 338 )
337 339 }
338 340
339 341 def items(&self) -> PyResult<DirstateMapItemsIterator> {
340 342 DirstateMapItemsIterator::from_inner(
341 343 py,
342 344 Some(DirstateMapLeakedRef::new(py, &self)),
343 345 Box::new(self.leak_immutable(py)?.iter()),
344 346 )
345 347 }
346 348
347 349 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
348 350 DirstateMapKeysIterator::from_inner(
349 351 py,
350 352 Some(DirstateMapLeakedRef::new(py, &self)),
351 353 Box::new(self.leak_immutable(py)?.iter()),
352 354 )
353 355 }
354 356
355 357 def getdirs(&self) -> PyResult<Dirs> {
356 358 // TODO don't copy, share the reference
357 359 self.inner(py).borrow_mut().set_dirs();
358 360 Dirs::from_inner(
359 361 py,
360 362 DirsMultiset::new(
361 363 DirsIterable::Dirstate(&self.inner(py).borrow()),
362 364 Some(EntryState::Removed),
363 365 ),
364 366 )
365 367 }
366 368 def getalldirs(&self) -> PyResult<Dirs> {
367 369 // TODO don't copy, share the reference
368 370 self.inner(py).borrow_mut().set_all_dirs();
369 371 Dirs::from_inner(
370 372 py,
371 373 DirsMultiset::new(
372 374 DirsIterable::Dirstate(&self.inner(py).borrow()),
373 375 None,
374 376 ),
375 377 )
376 378 }
377 379
378 380 // TODO all copymap* methods, see docstring above
379 381 def copymapcopy(&self) -> PyResult<PyDict> {
380 382 let dict = PyDict::new(py);
381 383 for (key, value) in self.inner(py).borrow().copy_map.iter() {
382 384 dict.set_item(py, PyBytes::new(py, key), PyBytes::new(py, value))?;
383 385 }
384 386 Ok(dict)
385 387 }
386 388
387 389 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
388 390 let key = key.extract::<PyBytes>(py)?;
389 391 match self.inner(py).borrow().copy_map.get(key.data(py)) {
390 392 Some(copy) => Ok(PyBytes::new(py, copy)),
391 393 None => Err(PyErr::new::<exc::KeyError, _>(
392 394 py,
393 395 String::from_utf8_lossy(key.data(py)),
394 396 )),
395 397 }
396 398 }
397 399 def copymap(&self) -> PyResult<CopyMap> {
398 400 CopyMap::from_inner(py, self.clone_ref(py))
399 401 }
400 402
401 403 def copymaplen(&self) -> PyResult<usize> {
402 404 Ok(self.inner(py).borrow().copy_map.len())
403 405 }
404 406 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
405 407 let key = key.extract::<PyBytes>(py)?;
406 408 Ok(self.inner(py).borrow().copy_map.contains_key(key.data(py)))
407 409 }
408 410 def copymapget(
409 411 &self,
410 412 key: PyObject,
411 413 default: Option<PyObject>
412 414 ) -> PyResult<Option<PyObject>> {
413 415 let key = key.extract::<PyBytes>(py)?;
414 416 match self.inner(py).borrow().copy_map.get(key.data(py)) {
415 417 Some(copy) => Ok(Some(PyBytes::new(py, copy).into_object())),
416 418 None => Ok(default),
417 419 }
418 420 }
419 421 def copymapsetitem(
420 422 &self,
421 423 key: PyObject,
422 424 value: PyObject
423 425 ) -> PyResult<PyObject> {
424 426 let key = key.extract::<PyBytes>(py)?;
425 427 let value = value.extract::<PyBytes>(py)?;
426 428 self.inner(py)
427 429 .borrow_mut()
428 430 .copy_map
429 431 .insert(key.data(py).to_vec(), value.data(py).to_vec());
430 432 Ok(py.None())
431 433 }
432 434 def copymappop(
433 435 &self,
434 436 key: PyObject,
435 437 default: Option<PyObject>
436 438 ) -> PyResult<Option<PyObject>> {
437 439 let key = key.extract::<PyBytes>(py)?;
438 440 match self.inner(py).borrow_mut().copy_map.remove(key.data(py)) {
439 441 Some(_) => Ok(None),
440 442 None => Ok(default),
441 443 }
442 444 }
443 445
444 446 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
445 447 CopyMapKeysIterator::from_inner(
446 448 py,
447 449 Some(DirstateMapLeakedRef::new(py, &self)),
448 450 Box::new(self.leak_immutable(py)?.copy_map.iter()),
449 451 )
450 452 }
451 453
452 454 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
453 455 CopyMapItemsIterator::from_inner(
454 456 py,
455 457 Some(DirstateMapLeakedRef::new(py, &self)),
456 458 Box::new(self.leak_immutable(py)?.copy_map.iter()),
457 459 )
458 460 }
459 461
460 462 });
461 463
462 464 impl DirstateMap {
463 465 fn translate_key(
464 466 py: Python,
465 467 res: (&Vec<u8>, &DirstateEntry),
466 468 ) -> PyResult<Option<PyBytes>> {
467 469 Ok(Some(PyBytes::new(py, res.0)))
468 470 }
469 471 fn translate_key_value(
470 472 py: Python,
471 473 res: (&Vec<u8>, &DirstateEntry),
472 474 ) -> PyResult<Option<(PyBytes, PyObject)>> {
473 475 let (f, entry) = res;
474 476
475 477 // Explicitly go through u8 first, then cast to
476 478 // platform-specific `c_char`.
477 479 let state: u8 = entry.state.into();
478 480 Ok(Some((
479 481 PyBytes::new(py, f),
480 482 decapsule_make_dirstate_tuple(py)?(
481 483 state as c_char,
482 484 entry.mode,
483 485 entry.size,
484 486 entry.mtime,
485 487 ),
486 488 )))
487 489 }
488 490 }
489 491
490 492 py_shared_ref!(DirstateMap, RustDirstateMap, inner, DirstateMapLeakedRef,);
491 493
492 494 py_shared_mapping_iterator!(
493 495 DirstateMapKeysIterator,
494 496 DirstateMapLeakedRef,
495 497 Vec<u8>,
496 498 DirstateEntry,
497 499 DirstateMap::translate_key,
498 500 Option<PyBytes>
499 501 );
500 502
501 503 py_shared_mapping_iterator!(
502 504 DirstateMapItemsIterator,
503 505 DirstateMapLeakedRef,
504 506 Vec<u8>,
505 507 DirstateEntry,
506 508 DirstateMap::translate_key_value,
507 509 Option<(PyBytes, PyObject)>
508 510 );
@@ -1,207 +1,208
1 1 // parsers.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Bindings for the `hg::dirstate::parsers` module provided by the
9 9 //! `hg-core` package.
10 10 //!
11 11 //! From Python, this will be seen as `mercurial.rustext.parsers`
12 12 //!
13 13 use cpython::{
14 14 exc, PyBytes, PyDict, PyErr, PyInt, PyModule, PyResult, PyTuple, Python,
15 15 PythonObject, ToPyObject,
16 16 };
17 17 use hg::{
18 pack_dirstate, parse_dirstate, utils::copy_into_array, DirstateEntry,
18 pack_dirstate, parse_dirstate, DirstateEntry,
19 19 DirstatePackError, DirstateParents, DirstateParseError, PARENT_SIZE,
20 20 };
21 21 use std::collections::HashMap;
22 use std::convert::TryInto;
22 23
23 24 use libc::c_char;
24 25
25 26 use crate::dirstate::{decapsule_make_dirstate_tuple, extract_dirstate};
26 27 use std::time::Duration;
27 28
28 29 fn parse_dirstate_wrapper(
29 30 py: Python,
30 31 dmap: PyDict,
31 32 copymap: PyDict,
32 33 st: PyBytes,
33 34 ) -> PyResult<PyTuple> {
34 35 let mut dirstate_map = HashMap::new();
35 36 let mut copies = HashMap::new();
36 37
37 38 match parse_dirstate(&mut dirstate_map, &mut copies, st.data(py)) {
38 39 Ok(parents) => {
39 40 for (filename, entry) in dirstate_map {
40 41 // Explicitly go through u8 first, then cast to
41 42 // platform-specific `c_char` because Into<u8> has a specific
42 43 // implementation while `as c_char` would just do a naive enum
43 44 // cast.
44 45 let state: u8 = entry.state.into();
45 46
46 47 dmap.set_item(
47 48 py,
48 49 PyBytes::new(py, &filename),
49 50 decapsule_make_dirstate_tuple(py)?(
50 51 state as c_char,
51 52 entry.mode,
52 53 entry.size,
53 54 entry.mtime,
54 55 ),
55 56 )?;
56 57 }
57 58 for (path, copy_path) in copies {
58 59 copymap.set_item(
59 60 py,
60 61 PyBytes::new(py, &path),
61 62 PyBytes::new(py, &copy_path),
62 63 )?;
63 64 }
64 65 Ok(
65 66 (PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2))
66 67 .to_py_object(py),
67 68 )
68 69 }
69 70 Err(e) => Err(PyErr::new::<exc::ValueError, _>(
70 71 py,
71 72 match e {
72 73 DirstateParseError::TooLittleData => {
73 74 "too little data for parents".to_string()
74 75 }
75 76 DirstateParseError::Overflow => {
76 77 "overflow in dirstate".to_string()
77 78 }
78 79 DirstateParseError::CorruptedEntry(e) => e,
79 80 DirstateParseError::Damaged => {
80 81 "dirstate appears to be damaged".to_string()
81 82 }
82 83 },
83 84 )),
84 85 }
85 86 }
86 87
87 88 fn pack_dirstate_wrapper(
88 89 py: Python,
89 90 dmap: PyDict,
90 91 copymap: PyDict,
91 92 pl: PyTuple,
92 93 now: PyInt,
93 94 ) -> PyResult<PyBytes> {
94 95 let p1 = pl.get_item(py, 0).extract::<PyBytes>(py)?;
95 96 let p1: &[u8] = p1.data(py);
96 97 let p2 = pl.get_item(py, 1).extract::<PyBytes>(py)?;
97 98 let p2: &[u8] = p2.data(py);
98 99
99 100 let mut dirstate_map = extract_dirstate(py, &dmap)?;
100 101
101 102 let copies: Result<HashMap<Vec<u8>, Vec<u8>>, PyErr> = copymap
102 103 .items(py)
103 104 .iter()
104 105 .map(|(key, value)| {
105 106 Ok((
106 107 key.extract::<PyBytes>(py)?.data(py).to_owned(),
107 108 value.extract::<PyBytes>(py)?.data(py).to_owned(),
108 109 ))
109 110 })
110 111 .collect();
111 112
112 113 if p1.len() != PARENT_SIZE || p2.len() != PARENT_SIZE {
113 114 return Err(PyErr::new::<exc::ValueError, _>(
114 115 py,
115 116 "expected a 20-byte hash".to_string(),
116 117 ));
117 118 }
118 119
119 120 match pack_dirstate(
120 121 &mut dirstate_map,
121 122 &copies?,
122 123 DirstateParents {
123 p1: copy_into_array(&p1),
124 p2: copy_into_array(&p2),
124 p1: p1.try_into().unwrap(),
125 p2: p2.try_into().unwrap(),
125 126 },
126 127 Duration::from_secs(now.as_object().extract::<u64>(py)?),
127 128 ) {
128 129 Ok(packed) => {
129 130 for (
130 131 filename,
131 132 DirstateEntry {
132 133 state,
133 134 mode,
134 135 size,
135 136 mtime,
136 137 },
137 138 ) in dirstate_map
138 139 {
139 140 // Explicitly go through u8 first, then cast to
140 141 // platform-specific `c_char` because Into<u8> has a specific
141 142 // implementation while `as c_char` would just do a naive enum
142 143 // cast.
143 144 let state: u8 = state.into();
144 145 dmap.set_item(
145 146 py,
146 147 PyBytes::new(py, &filename[..]),
147 148 decapsule_make_dirstate_tuple(py)?(
148 149 state as c_char,
149 150 mode,
150 151 size,
151 152 mtime,
152 153 ),
153 154 )?;
154 155 }
155 156 Ok(PyBytes::new(py, &packed))
156 157 }
157 158 Err(error) => Err(PyErr::new::<exc::ValueError, _>(
158 159 py,
159 160 match error {
160 161 DirstatePackError::CorruptedParent => {
161 162 "expected a 20-byte hash".to_string()
162 163 }
163 164 DirstatePackError::CorruptedEntry(e) => e,
164 165 DirstatePackError::BadSize(expected, actual) => {
165 166 format!("bad dirstate size: {} != {}", actual, expected)
166 167 }
167 168 },
168 169 )),
169 170 }
170 171 }
171 172
172 173 /// Create the module, with `__package__` given from parent
173 174 pub fn init_parsers_module(py: Python, package: &str) -> PyResult<PyModule> {
174 175 let dotted_name = &format!("{}.parsers", package);
175 176 let m = PyModule::new(py, dotted_name)?;
176 177
177 178 m.add(py, "__package__", package)?;
178 179 m.add(py, "__doc__", "Parsers - Rust implementation")?;
179 180
180 181 m.add(
181 182 py,
182 183 "parse_dirstate",
183 184 py_fn!(
184 185 py,
185 186 parse_dirstate_wrapper(dmap: PyDict, copymap: PyDict, st: PyBytes)
186 187 ),
187 188 )?;
188 189 m.add(
189 190 py,
190 191 "pack_dirstate",
191 192 py_fn!(
192 193 py,
193 194 pack_dirstate_wrapper(
194 195 dmap: PyDict,
195 196 copymap: PyDict,
196 197 pl: PyTuple,
197 198 now: PyInt
198 199 )
199 200 ),
200 201 )?;
201 202
202 203 let sys = PyModule::import(py, "sys")?;
203 204 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
204 205 sys_modules.set_item(py, dotted_name, &m)?;
205 206
206 207 Ok(m)
207 208 }
General Comments 0
You need to be logged in to leave comments. Login now