##// END OF EJS Templates
rust-dirstate: use EntryState enum instead of literals...
Raphaël Gomès -
r42976:7102ef7d default draft
parent child Browse files
Show More
@@ -1,38 +1,79 b''
1 // dirstate module
1 // dirstate module
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
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.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::DirstateParseError;
8 use std::collections::HashMap;
9 use std::collections::HashMap;
10 use std::convert::TryFrom;
9
11
10 pub mod dirs_multiset;
12 pub mod dirs_multiset;
11 pub mod parsers;
13 pub mod parsers;
12
14
13 #[derive(Debug, PartialEq, Clone)]
15 #[derive(Debug, PartialEq, Clone)]
14 pub struct DirstateParents {
16 pub struct DirstateParents {
15 pub p1: [u8; 20],
17 pub p1: [u8; 20],
16 pub p2: [u8; 20],
18 pub p2: [u8; 20],
17 }
19 }
18
20
19 /// The C implementation uses all signed types. This will be an issue
21 /// The C implementation uses all signed types. This will be an issue
20 /// either when 4GB+ source files are commonplace or in 2038, whichever
22 /// either when 4GB+ source files are commonplace or in 2038, whichever
21 /// comes first.
23 /// comes first.
22 #[derive(Debug, PartialEq, Copy, Clone)]
24 #[derive(Debug, PartialEq, Copy, Clone)]
23 pub struct DirstateEntry {
25 pub struct DirstateEntry {
24 pub state: i8,
26 pub state: EntryState,
25 pub mode: i32,
27 pub mode: i32,
26 pub mtime: i32,
28 pub mtime: i32,
27 pub size: i32,
29 pub size: i32,
28 }
30 }
29
31
30 pub type StateMap = HashMap<Vec<u8>, DirstateEntry>;
32 pub type StateMap = HashMap<Vec<u8>, DirstateEntry>;
31 pub type CopyMap = HashMap<Vec<u8>, Vec<u8>>;
33 pub type CopyMap = HashMap<Vec<u8>, Vec<u8>>;
32
34
33 /// The Python implementation passes either a mapping (dirstate) or a flat
35 /// The Python implementation passes either a mapping (dirstate) or a flat
34 /// iterable (manifest)
36 /// iterable (manifest)
35 pub enum DirsIterable<'a> {
37 pub enum DirsIterable<'a> {
36 Dirstate(&'a HashMap<Vec<u8>, DirstateEntry>),
38 Dirstate(&'a HashMap<Vec<u8>, DirstateEntry>),
37 Manifest(&'a Vec<Vec<u8>>),
39 Manifest(&'a Vec<Vec<u8>>),
38 }
40 }
41
42 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
43 pub enum EntryState {
44 Normal,
45 Added,
46 Removed,
47 Merged,
48 Unknown,
49 }
50
51 impl TryFrom<u8> for EntryState {
52 type Error = DirstateParseError;
53
54 fn try_from(value: u8) -> Result<Self, Self::Error> {
55 match value {
56 b'n' => Ok(EntryState::Normal),
57 b'a' => Ok(EntryState::Added),
58 b'r' => Ok(EntryState::Removed),
59 b'm' => Ok(EntryState::Merged),
60 b'?' => Ok(EntryState::Unknown),
61 _ => Err(DirstateParseError::CorruptedEntry(format!(
62 "Incorrect entry state {}",
63 value
64 ))),
65 }
66 }
67 }
68
69 impl Into<u8> for EntryState {
70 fn into(self) -> u8 {
71 match self {
72 EntryState::Normal => b'n',
73 EntryState::Added => b'a',
74 EntryState::Removed => b'r',
75 EntryState::Merged => b'm',
76 EntryState::Unknown => b'?',
77 }
78 }
79 }
@@ -1,328 +1,341 b''
1 // dirs_multiset.rs
1 // dirs_multiset.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
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.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! A multiset of directory names.
8 //! A multiset of directory names.
9 //!
9 //!
10 //! Used to counts the references to directories in a manifest or dirstate.
10 //! Used to counts the references to directories in a manifest or dirstate.
11 use crate::{utils::files, DirsIterable, DirstateEntry, DirstateMapError};
11 use crate::{
12 dirstate::EntryState, utils::files, DirsIterable, DirstateEntry,
13 DirstateMapError,
14 };
12 use std::collections::hash_map::{Entry, Iter};
15 use std::collections::hash_map::{Entry, Iter};
13 use std::collections::HashMap;
16 use std::collections::HashMap;
14
17
15 #[derive(PartialEq, Debug)]
18 #[derive(PartialEq, Debug)]
16 pub struct DirsMultiset {
19 pub struct DirsMultiset {
17 inner: HashMap<Vec<u8>, u32>,
20 inner: HashMap<Vec<u8>, u32>,
18 }
21 }
19
22
20 impl DirsMultiset {
23 impl DirsMultiset {
21 /// Initializes the multiset from a dirstate or a manifest.
24 /// Initializes the multiset from a dirstate or a manifest.
22 ///
25 ///
23 /// If `skip_state` is provided, skips dirstate entries with equal state.
26 /// If `skip_state` is provided, skips dirstate entries with equal state.
24 pub fn new(iterable: DirsIterable, skip_state: Option<i8>) -> Self {
27 pub fn new(
28 iterable: DirsIterable,
29 skip_state: Option<EntryState>,
30 ) -> Self {
25 let mut multiset = DirsMultiset {
31 let mut multiset = DirsMultiset {
26 inner: HashMap::new(),
32 inner: HashMap::new(),
27 };
33 };
28
34
29 match iterable {
35 match iterable {
30 DirsIterable::Dirstate(vec) => {
36 DirsIterable::Dirstate(vec) => {
31 for (filename, DirstateEntry { state, .. }) in vec {
37 for (filename, DirstateEntry { state, .. }) in vec {
32 // This `if` is optimized out of the loop
38 // This `if` is optimized out of the loop
33 if let Some(skip) = skip_state {
39 if let Some(skip) = skip_state {
34 if skip != *state {
40 if skip != *state {
35 multiset.add_path(filename);
41 multiset.add_path(filename);
36 }
42 }
37 } else {
43 } else {
38 multiset.add_path(filename);
44 multiset.add_path(filename);
39 }
45 }
40 }
46 }
41 }
47 }
42 DirsIterable::Manifest(vec) => {
48 DirsIterable::Manifest(vec) => {
43 for filename in vec {
49 for filename in vec {
44 multiset.add_path(filename);
50 multiset.add_path(filename);
45 }
51 }
46 }
52 }
47 }
53 }
48
54
49 multiset
55 multiset
50 }
56 }
51
57
52 /// Increases the count of deepest directory contained in the path.
58 /// Increases the count of deepest directory contained in the path.
53 ///
59 ///
54 /// If the directory is not yet in the map, adds its parents.
60 /// If the directory is not yet in the map, adds its parents.
55 pub fn add_path(&mut self, path: &[u8]) {
61 pub fn add_path(&mut self, path: &[u8]) {
56 for subpath in files::find_dirs(path) {
62 for subpath in files::find_dirs(path) {
57 if let Some(val) = self.inner.get_mut(subpath) {
63 if let Some(val) = self.inner.get_mut(subpath) {
58 *val += 1;
64 *val += 1;
59 break;
65 break;
60 }
66 }
61 self.inner.insert(subpath.to_owned(), 1);
67 self.inner.insert(subpath.to_owned(), 1);
62 }
68 }
63 }
69 }
64
70
65 /// Decreases the count of deepest directory contained in the path.
71 /// Decreases the count of deepest directory contained in the path.
66 ///
72 ///
67 /// If it is the only reference, decreases all parents until one is
73 /// If it is the only reference, decreases all parents until one is
68 /// removed.
74 /// removed.
69 /// If the directory is not in the map, something horrible has happened.
75 /// If the directory is not in the map, something horrible has happened.
70 pub fn delete_path(
76 pub fn delete_path(
71 &mut self,
77 &mut self,
72 path: &[u8],
78 path: &[u8],
73 ) -> Result<(), DirstateMapError> {
79 ) -> Result<(), DirstateMapError> {
74 for subpath in files::find_dirs(path) {
80 for subpath in files::find_dirs(path) {
75 match self.inner.entry(subpath.to_owned()) {
81 match self.inner.entry(subpath.to_owned()) {
76 Entry::Occupied(mut entry) => {
82 Entry::Occupied(mut entry) => {
77 let val = entry.get().clone();
83 let val = entry.get().clone();
78 if val > 1 {
84 if val > 1 {
79 entry.insert(val - 1);
85 entry.insert(val - 1);
80 break;
86 break;
81 }
87 }
82 entry.remove();
88 entry.remove();
83 }
89 }
84 Entry::Vacant(_) => {
90 Entry::Vacant(_) => {
85 return Err(DirstateMapError::PathNotFound(
91 return Err(DirstateMapError::PathNotFound(
86 path.to_owned(),
92 path.to_owned(),
87 ))
93 ))
88 }
94 }
89 };
95 };
90 }
96 }
91
97
92 Ok(())
98 Ok(())
93 }
99 }
94
100
95 pub fn contains_key(&self, key: &[u8]) -> bool {
101 pub fn contains_key(&self, key: &[u8]) -> bool {
96 self.inner.contains_key(key)
102 self.inner.contains_key(key)
97 }
103 }
98
104
99 pub fn iter(&self) -> Iter<Vec<u8>, u32> {
105 pub fn iter(&self) -> Iter<Vec<u8>, u32> {
100 self.inner.iter()
106 self.inner.iter()
101 }
107 }
102
108
103 pub fn len(&self) -> usize {
109 pub fn len(&self) -> usize {
104 self.inner.len()
110 self.inner.len()
105 }
111 }
106 }
112 }
107
113
108 #[cfg(test)]
114 #[cfg(test)]
109 mod tests {
115 mod tests {
110 use super::*;
116 use super::*;
111 use std::collections::HashMap;
117 use std::collections::HashMap;
112
118
113 #[test]
119 #[test]
114 fn test_delete_path_path_not_found() {
120 fn test_delete_path_path_not_found() {
115 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
121 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
116 let path = b"doesnotexist/";
122 let path = b"doesnotexist/";
117 assert_eq!(
123 assert_eq!(
118 Err(DirstateMapError::PathNotFound(path.to_vec())),
124 Err(DirstateMapError::PathNotFound(path.to_vec())),
119 map.delete_path(path)
125 map.delete_path(path)
120 );
126 );
121 }
127 }
122
128
123 #[test]
129 #[test]
124 fn test_delete_path_empty_path() {
130 fn test_delete_path_empty_path() {
125 let mut map =
131 let mut map =
126 DirsMultiset::new(DirsIterable::Manifest(&vec![vec![]]), None);
132 DirsMultiset::new(DirsIterable::Manifest(&vec![vec![]]), None);
127 let path = b"";
133 let path = b"";
128 assert_eq!(Ok(()), map.delete_path(path));
134 assert_eq!(Ok(()), map.delete_path(path));
129 assert_eq!(
135 assert_eq!(
130 Err(DirstateMapError::PathNotFound(path.to_vec())),
136 Err(DirstateMapError::PathNotFound(path.to_vec())),
131 map.delete_path(path)
137 map.delete_path(path)
132 );
138 );
133 }
139 }
134
140
135 #[test]
141 #[test]
136 fn test_delete_path_successful() {
142 fn test_delete_path_successful() {
137 let mut map = DirsMultiset {
143 let mut map = DirsMultiset {
138 inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)]
144 inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)]
139 .iter()
145 .iter()
140 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
146 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
141 .collect(),
147 .collect(),
142 };
148 };
143
149
144 assert_eq!(Ok(()), map.delete_path(b"a/b/"));
150 assert_eq!(Ok(()), map.delete_path(b"a/b/"));
145 assert_eq!(Ok(()), map.delete_path(b"a/b/"));
151 assert_eq!(Ok(()), map.delete_path(b"a/b/"));
146 assert_eq!(
152 assert_eq!(
147 Err(DirstateMapError::PathNotFound(b"a/b/".to_vec())),
153 Err(DirstateMapError::PathNotFound(b"a/b/".to_vec())),
148 map.delete_path(b"a/b/")
154 map.delete_path(b"a/b/")
149 );
155 );
150
156
151 assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
157 assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
152 assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
158 assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
153 eprintln!("{:?}", map);
159 eprintln!("{:?}", map);
154 assert_eq!(Ok(()), map.delete_path(b"a/"));
160 assert_eq!(Ok(()), map.delete_path(b"a/"));
155 eprintln!("{:?}", map);
161 eprintln!("{:?}", map);
156
162
157 assert_eq!(Ok(()), map.delete_path(b"a/c/"));
163 assert_eq!(Ok(()), map.delete_path(b"a/c/"));
158 assert_eq!(
164 assert_eq!(
159 Err(DirstateMapError::PathNotFound(b"a/c/".to_vec())),
165 Err(DirstateMapError::PathNotFound(b"a/c/".to_vec())),
160 map.delete_path(b"a/c/")
166 map.delete_path(b"a/c/")
161 );
167 );
162 }
168 }
163
169
164 #[test]
170 #[test]
165 fn test_add_path_empty_path() {
171 fn test_add_path_empty_path() {
166 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
172 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
167 let path = b"";
173 let path = b"";
168 map.add_path(path);
174 map.add_path(path);
169
175
170 assert_eq!(1, map.len());
176 assert_eq!(1, map.len());
171 }
177 }
172
178
173 #[test]
179 #[test]
174 fn test_add_path_successful() {
180 fn test_add_path_successful() {
175 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
181 let mut map = DirsMultiset::new(DirsIterable::Manifest(&vec![]), None);
176
182
177 map.add_path(b"a/");
183 map.add_path(b"a/");
178 assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
184 assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
179 assert_eq!(1, *map.inner.get(&Vec::new()).unwrap());
185 assert_eq!(1, *map.inner.get(&Vec::new()).unwrap());
180 assert_eq!(2, map.len());
186 assert_eq!(2, map.len());
181
187
182 // Non directory should be ignored
188 // Non directory should be ignored
183 map.add_path(b"a");
189 map.add_path(b"a");
184 assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
190 assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
185 assert_eq!(2, map.len());
191 assert_eq!(2, map.len());
186
192
187 // Non directory will still add its base
193 // Non directory will still add its base
188 map.add_path(b"a/b");
194 map.add_path(b"a/b");
189 assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
195 assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
190 assert_eq!(2, map.len());
196 assert_eq!(2, map.len());
191
197
192 // Duplicate path works
198 // Duplicate path works
193 map.add_path(b"a/");
199 map.add_path(b"a/");
194 assert_eq!(3, *map.inner.get(&b"a".to_vec()).unwrap());
200 assert_eq!(3, *map.inner.get(&b"a".to_vec()).unwrap());
195
201
196 // Nested dir adds to its base
202 // Nested dir adds to its base
197 map.add_path(b"a/b/");
203 map.add_path(b"a/b/");
198 assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap());
204 assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap());
199 assert_eq!(1, *map.inner.get(&b"a/b".to_vec()).unwrap());
205 assert_eq!(1, *map.inner.get(&b"a/b".to_vec()).unwrap());
200
206
201 // but not its base's base, because it already existed
207 // but not its base's base, because it already existed
202 map.add_path(b"a/b/c/");
208 map.add_path(b"a/b/c/");
203 assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap());
209 assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap());
204 assert_eq!(2, *map.inner.get(&b"a/b".to_vec()).unwrap());
210 assert_eq!(2, *map.inner.get(&b"a/b".to_vec()).unwrap());
205
211
206 map.add_path(b"a/c/");
212 map.add_path(b"a/c/");
207 assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
213 assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
208
214
209 let expected = DirsMultiset {
215 let expected = DirsMultiset {
210 inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)]
216 inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)]
211 .iter()
217 .iter()
212 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
218 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
213 .collect(),
219 .collect(),
214 };
220 };
215 assert_eq!(map, expected);
221 assert_eq!(map, expected);
216 }
222 }
217
223
218 #[test]
224 #[test]
219 fn test_dirsmultiset_new_empty() {
225 fn test_dirsmultiset_new_empty() {
220 use DirsIterable::{Dirstate, Manifest};
226 use DirsIterable::{Dirstate, Manifest};
221
227
222 let new = DirsMultiset::new(Manifest(&vec![]), None);
228 let new = DirsMultiset::new(Manifest(&vec![]), None);
223 let expected = DirsMultiset {
229 let expected = DirsMultiset {
224 inner: HashMap::new(),
230 inner: HashMap::new(),
225 };
231 };
226 assert_eq!(expected, new);
232 assert_eq!(expected, new);
227
233
228 let new = DirsMultiset::new(Dirstate(&HashMap::new()), None);
234 let new = DirsMultiset::new(Dirstate(&HashMap::new()), None);
229 let expected = DirsMultiset {
235 let expected = DirsMultiset {
230 inner: HashMap::new(),
236 inner: HashMap::new(),
231 };
237 };
232 assert_eq!(expected, new);
238 assert_eq!(expected, new);
233 }
239 }
234
240
235 #[test]
241 #[test]
236 fn test_dirsmultiset_new_no_skip() {
242 fn test_dirsmultiset_new_no_skip() {
237 use DirsIterable::{Dirstate, Manifest};
243 use DirsIterable::{Dirstate, Manifest};
238
244
239 let input_vec = ["a/", "b/", "a/c", "a/d/"]
245 let input_vec = ["a/", "b/", "a/c", "a/d/"]
240 .iter()
246 .iter()
241 .map(|e| e.as_bytes().to_vec())
247 .map(|e| e.as_bytes().to_vec())
242 .collect();
248 .collect();
243 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
249 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
244 .iter()
250 .iter()
245 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
251 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
246 .collect();
252 .collect();
247
253
248 let new = DirsMultiset::new(Manifest(&input_vec), None);
254 let new = DirsMultiset::new(Manifest(&input_vec), None);
249 let expected = DirsMultiset {
255 let expected = DirsMultiset {
250 inner: expected_inner,
256 inner: expected_inner,
251 };
257 };
252 assert_eq!(expected, new);
258 assert_eq!(expected, new);
253
259
254 let input_map = ["a/", "b/", "a/c", "a/d/"]
260 let input_map = ["a/", "b/", "a/c", "a/d/"]
255 .iter()
261 .iter()
256 .map(|f| {
262 .map(|f| {
257 (
263 (
258 f.as_bytes().to_vec(),
264 f.as_bytes().to_vec(),
259 DirstateEntry {
265 DirstateEntry {
260 state: 0,
266 state: EntryState::Normal,
261 mode: 0,
267 mode: 0,
262 mtime: 0,
268 mtime: 0,
263 size: 0,
269 size: 0,
264 },
270 },
265 )
271 )
266 })
272 })
267 .collect();
273 .collect();
268 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
274 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
269 .iter()
275 .iter()
270 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
276 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
271 .collect();
277 .collect();
272
278
273 let new = DirsMultiset::new(Dirstate(&input_map), None);
279 let new = DirsMultiset::new(Dirstate(&input_map), None);
274 let expected = DirsMultiset {
280 let expected = DirsMultiset {
275 inner: expected_inner,
281 inner: expected_inner,
276 };
282 };
277 assert_eq!(expected, new);
283 assert_eq!(expected, new);
278 }
284 }
279
285
280 #[test]
286 #[test]
281 fn test_dirsmultiset_new_skip() {
287 fn test_dirsmultiset_new_skip() {
282 use DirsIterable::{Dirstate, Manifest};
288 use DirsIterable::{Dirstate, Manifest};
283
289
284 let input_vec = ["a/", "b/", "a/c", "a/d/"]
290 let input_vec = ["a/", "b/", "a/c", "a/d/"]
285 .iter()
291 .iter()
286 .map(|e| e.as_bytes().to_vec())
292 .map(|e| e.as_bytes().to_vec())
287 .collect();
293 .collect();
288 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
294 let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
289 .iter()
295 .iter()
290 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
296 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
291 .collect();
297 .collect();
292
298
293 let new = DirsMultiset::new(Manifest(&input_vec), Some('n' as i8));
299 let new =
300 DirsMultiset::new(Manifest(&input_vec), Some(EntryState::Normal));
294 let expected = DirsMultiset {
301 let expected = DirsMultiset {
295 inner: expected_inner,
302 inner: expected_inner,
296 };
303 };
297 // Skip does not affect a manifest
304 // Skip does not affect a manifest
298 assert_eq!(expected, new);
305 assert_eq!(expected, new);
299
306
300 let input_map =
307 let input_map = [
301 [("a/", 'n'), ("a/b/", 'n'), ("a/c", 'r'), ("a/d/", 'm')]
308 ("a/", EntryState::Normal),
302 .iter()
309 ("a/b/", EntryState::Normal),
303 .map(|(f, state)| {
310 ("a/c", EntryState::Removed),
304 (
311 ("a/d/", EntryState::Merged),
305 f.as_bytes().to_vec(),
312 ]
306 DirstateEntry {
313 .iter()
307 state: *state as i8,
314 .map(|(f, state)| {
308 mode: 0,
315 (
309 mtime: 0,
316 f.as_bytes().to_vec(),
310 size: 0,
317 DirstateEntry {
311 },
318 state: *state,
312 )
319 mode: 0,
313 })
320 mtime: 0,
314 .collect();
321 size: 0,
322 },
323 )
324 })
325 .collect();
315
326
316 // "a" incremented with "a/c" and "a/d/"
327 // "a" incremented with "a/c" and "a/d/"
317 let expected_inner = [("", 1), ("a", 2), ("a/d", 1)]
328 let expected_inner = [("", 1), ("a", 2), ("a/d", 1)]
318 .iter()
329 .iter()
319 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
330 .map(|(k, v)| (k.as_bytes().to_vec(), *v))
320 .collect();
331 .collect();
321
332
322 let new = DirsMultiset::new(Dirstate(&input_map), Some('n' as i8));
333 let new =
334 DirsMultiset::new(Dirstate(&input_map), Some(EntryState::Normal));
323 let expected = DirsMultiset {
335 let expected = DirsMultiset {
324 inner: expected_inner,
336 inner: expected_inner,
325 };
337 };
326 assert_eq!(expected, new);
338 assert_eq!(expected, new);
327 }
339 }
340
328 }
341 }
@@ -1,414 +1,415 b''
1 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
1 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
2 //
2 //
3 // This software may be used and distributed according to the terms of the
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.
4 // GNU General Public License version 2 or any later version.
5
5
6 use crate::{
6 use crate::{
7 dirstate::{CopyMap, StateMap},
7 dirstate::{CopyMap, EntryState, StateMap},
8 utils::copy_into_array,
8 utils::copy_into_array,
9 DirstateEntry, DirstatePackError, DirstateParents, DirstateParseError,
9 DirstateEntry, DirstatePackError, DirstateParents, DirstateParseError,
10 };
10 };
11 use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
11 use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
12 use std::convert::TryInto;
12 use std::convert::{TryFrom, TryInto};
13 use std::io::Cursor;
13 use std::io::Cursor;
14 use std::time::Duration;
14 use std::time::Duration;
15
15
16 /// Parents are stored in the dirstate as byte hashes.
16 /// Parents are stored in the dirstate as byte hashes.
17 pub const PARENT_SIZE: usize = 20;
17 pub const PARENT_SIZE: usize = 20;
18 /// Dirstate entries have a static part of 8 + 32 + 32 + 32 + 32 bits.
18 /// Dirstate entries have a static part of 8 + 32 + 32 + 32 + 32 bits.
19 const MIN_ENTRY_SIZE: usize = 17;
19 const MIN_ENTRY_SIZE: usize = 17;
20
20
21 // TODO parse/pack: is mutate-on-loop better for performance?
21 // TODO parse/pack: is mutate-on-loop better for performance?
22
22
23 pub fn parse_dirstate(
23 pub fn parse_dirstate(
24 state_map: &mut StateMap,
24 state_map: &mut StateMap,
25 copy_map: &mut CopyMap,
25 copy_map: &mut CopyMap,
26 contents: &[u8],
26 contents: &[u8],
27 ) -> Result<DirstateParents, DirstateParseError> {
27 ) -> Result<DirstateParents, DirstateParseError> {
28 if contents.len() < PARENT_SIZE * 2 {
28 if contents.len() < PARENT_SIZE * 2 {
29 return Err(DirstateParseError::TooLittleData);
29 return Err(DirstateParseError::TooLittleData);
30 }
30 }
31
31
32 let mut curr_pos = PARENT_SIZE * 2;
32 let mut curr_pos = PARENT_SIZE * 2;
33 let parents = DirstateParents {
33 let parents = DirstateParents {
34 p1: copy_into_array(&contents[..PARENT_SIZE]),
34 p1: copy_into_array(&contents[..PARENT_SIZE]),
35 p2: copy_into_array(&contents[PARENT_SIZE..curr_pos]),
35 p2: copy_into_array(&contents[PARENT_SIZE..curr_pos]),
36 };
36 };
37
37
38 while curr_pos < contents.len() {
38 while curr_pos < contents.len() {
39 if curr_pos + MIN_ENTRY_SIZE > contents.len() {
39 if curr_pos + MIN_ENTRY_SIZE > contents.len() {
40 return Err(DirstateParseError::Overflow);
40 return Err(DirstateParseError::Overflow);
41 }
41 }
42 let entry_bytes = &contents[curr_pos..];
42 let entry_bytes = &contents[curr_pos..];
43
43
44 let mut cursor = Cursor::new(entry_bytes);
44 let mut cursor = Cursor::new(entry_bytes);
45 let state = cursor.read_i8()?;
45 let state = EntryState::try_from(cursor.read_u8()?)?;
46 let mode = cursor.read_i32::<BigEndian>()?;
46 let mode = cursor.read_i32::<BigEndian>()?;
47 let size = cursor.read_i32::<BigEndian>()?;
47 let size = cursor.read_i32::<BigEndian>()?;
48 let mtime = cursor.read_i32::<BigEndian>()?;
48 let mtime = cursor.read_i32::<BigEndian>()?;
49 let path_len = cursor.read_i32::<BigEndian>()? as usize;
49 let path_len = cursor.read_i32::<BigEndian>()? as usize;
50
50
51 if path_len > contents.len() - curr_pos {
51 if path_len > contents.len() - curr_pos {
52 return Err(DirstateParseError::Overflow);
52 return Err(DirstateParseError::Overflow);
53 }
53 }
54
54
55 // Slice instead of allocating a Vec needed for `read_exact`
55 // Slice instead of allocating a Vec needed for `read_exact`
56 let path = &entry_bytes[MIN_ENTRY_SIZE..MIN_ENTRY_SIZE + (path_len)];
56 let path = &entry_bytes[MIN_ENTRY_SIZE..MIN_ENTRY_SIZE + (path_len)];
57
57
58 let (path, copy) = match memchr::memchr(0, path) {
58 let (path, copy) = match memchr::memchr(0, path) {
59 None => (path, None),
59 None => (path, None),
60 Some(i) => (&path[..i], Some(&path[(i + 1)..])),
60 Some(i) => (&path[..i], Some(&path[(i + 1)..])),
61 };
61 };
62
62
63 if let Some(copy_path) = copy {
63 if let Some(copy_path) = copy {
64 copy_map.insert(path.to_owned(), copy_path.to_owned());
64 copy_map.insert(path.to_owned(), copy_path.to_owned());
65 };
65 };
66 state_map.insert(
66 state_map.insert(
67 path.to_owned(),
67 path.to_owned(),
68 DirstateEntry {
68 DirstateEntry {
69 state,
69 state,
70 mode,
70 mode,
71 size,
71 size,
72 mtime,
72 mtime,
73 },
73 },
74 );
74 );
75 curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len);
75 curr_pos = curr_pos + MIN_ENTRY_SIZE + (path_len);
76 }
76 }
77
77
78 Ok(parents)
78 Ok(parents)
79 }
79 }
80
80
81 /// `now` is the duration in seconds since the Unix epoch
81 /// `now` is the duration in seconds since the Unix epoch
82 pub fn pack_dirstate(
82 pub fn pack_dirstate(
83 state_map: &mut StateMap,
83 state_map: &mut StateMap,
84 copy_map: &CopyMap,
84 copy_map: &CopyMap,
85 parents: DirstateParents,
85 parents: DirstateParents,
86 now: Duration,
86 now: Duration,
87 ) -> Result<Vec<u8>, DirstatePackError> {
87 ) -> Result<Vec<u8>, DirstatePackError> {
88 // TODO move away from i32 before 2038.
88 // TODO move away from i32 before 2038.
89 let now: i32 = now.as_secs().try_into().expect("time overflow");
89 let now: i32 = now.as_secs().try_into().expect("time overflow");
90
90
91 let expected_size: usize = state_map
91 let expected_size: usize = state_map
92 .iter()
92 .iter()
93 .map(|(filename, _)| {
93 .map(|(filename, _)| {
94 let mut length = MIN_ENTRY_SIZE + filename.len();
94 let mut length = MIN_ENTRY_SIZE + filename.len();
95 if let Some(ref copy) = copy_map.get(filename) {
95 if let Some(ref copy) = copy_map.get(filename) {
96 length += copy.len() + 1;
96 length += copy.len() + 1;
97 }
97 }
98 length
98 length
99 })
99 })
100 .sum();
100 .sum();
101 let expected_size = expected_size + PARENT_SIZE * 2;
101 let expected_size = expected_size + PARENT_SIZE * 2;
102
102
103 let mut packed = Vec::with_capacity(expected_size);
103 let mut packed = Vec::with_capacity(expected_size);
104 let mut new_state_map = vec![];
104 let mut new_state_map = vec![];
105
105
106 packed.extend(&parents.p1);
106 packed.extend(&parents.p1);
107 packed.extend(&parents.p2);
107 packed.extend(&parents.p2);
108
108
109 for (ref filename, entry) in state_map.iter() {
109 for (ref filename, entry) in state_map.iter() {
110 let mut new_filename: Vec<u8> = filename.to_vec();
110 let mut new_filename: Vec<u8> = filename.to_vec();
111 let mut new_mtime: i32 = entry.mtime;
111 let mut new_mtime: i32 = entry.mtime;
112 if entry.state == 'n' as i8 && entry.mtime == now.into() {
112 if entry.state == EntryState::Normal && entry.mtime == now {
113 // The file was last modified "simultaneously" with the current
113 // The file was last modified "simultaneously" with the current
114 // write to dirstate (i.e. within the same second for file-
114 // write to dirstate (i.e. within the same second for file-
115 // systems with a granularity of 1 sec). This commonly happens
115 // systems with a granularity of 1 sec). This commonly happens
116 // for at least a couple of files on 'update'.
116 // for at least a couple of files on 'update'.
117 // The user could change the file without changing its size
117 // The user could change the file without changing its size
118 // within the same second. Invalidate the file's mtime in
118 // within the same second. Invalidate the file's mtime in
119 // dirstate, forcing future 'status' calls to compare the
119 // dirstate, forcing future 'status' calls to compare the
120 // contents of the file if the size is the same. This prevents
120 // contents of the file if the size is the same. This prevents
121 // mistakenly treating such files as clean.
121 // mistakenly treating such files as clean.
122 new_mtime = -1;
122 new_mtime = -1;
123 new_state_map.push((
123 new_state_map.push((
124 filename.to_owned().to_vec(),
124 filename.to_owned().to_vec(),
125 DirstateEntry {
125 DirstateEntry {
126 mtime: new_mtime,
126 mtime: new_mtime,
127 ..*entry
127 ..*entry
128 },
128 },
129 ));
129 ));
130 }
130 }
131
131
132 if let Some(copy) = copy_map.get(*filename) {
132 if let Some(copy) = copy_map.get(*filename) {
133 new_filename.push('\0' as u8);
133 new_filename.push('\0' as u8);
134 new_filename.extend(copy);
134 new_filename.extend(copy);
135 }
135 }
136
136
137 packed.write_i8(entry.state)?;
137 packed.write_u8(entry.state.into())?;
138 packed.write_i32::<BigEndian>(entry.mode)?;
138 packed.write_i32::<BigEndian>(entry.mode)?;
139 packed.write_i32::<BigEndian>(entry.size)?;
139 packed.write_i32::<BigEndian>(entry.size)?;
140 packed.write_i32::<BigEndian>(new_mtime)?;
140 packed.write_i32::<BigEndian>(new_mtime)?;
141 packed.write_i32::<BigEndian>(new_filename.len() as i32)?;
141 packed.write_i32::<BigEndian>(new_filename.len() as i32)?;
142 packed.extend(new_filename)
142 packed.extend(new_filename)
143 }
143 }
144
144
145 if packed.len() != expected_size {
145 if packed.len() != expected_size {
146 return Err(DirstatePackError::BadSize(expected_size, packed.len()));
146 return Err(DirstatePackError::BadSize(expected_size, packed.len()));
147 }
147 }
148
148
149 state_map.extend(new_state_map);
149 state_map.extend(new_state_map);
150
150
151 Ok(packed)
151 Ok(packed)
152 }
152 }
153
153 #[cfg(test)]
154 #[cfg(test)]
154 mod tests {
155 mod tests {
155 use super::*;
156 use super::*;
156 use std::collections::HashMap;
157 use std::collections::HashMap;
157
158
158 #[test]
159 #[test]
159 fn test_pack_dirstate_empty() {
160 fn test_pack_dirstate_empty() {
160 let mut state_map: StateMap = HashMap::new();
161 let mut state_map: StateMap = HashMap::new();
161 let copymap = HashMap::new();
162 let copymap = HashMap::new();
162 let parents = DirstateParents {
163 let parents = DirstateParents {
163 p1: *b"12345678910111213141",
164 p1: *b"12345678910111213141",
164 p2: *b"00000000000000000000",
165 p2: *b"00000000000000000000",
165 };
166 };
166 let now = Duration::new(15000000, 0);
167 let now = Duration::new(15000000, 0);
167 let expected = b"1234567891011121314100000000000000000000".to_vec();
168 let expected = b"1234567891011121314100000000000000000000".to_vec();
168
169
169 assert_eq!(
170 assert_eq!(
170 expected,
171 expected,
171 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
172 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
172 );
173 );
173
174
174 assert!(state_map.is_empty())
175 assert!(state_map.is_empty())
175 }
176 }
176 #[test]
177 #[test]
177 fn test_pack_dirstate_one_entry() {
178 fn test_pack_dirstate_one_entry() {
178 let expected_state_map: StateMap = [(
179 let expected_state_map: StateMap = [(
179 b"f1".to_vec(),
180 b"f1".to_vec(),
180 DirstateEntry {
181 DirstateEntry {
181 state: 'n' as i8,
182 state: EntryState::Normal,
182 mode: 0o644,
183 mode: 0o644,
183 size: 0,
184 size: 0,
184 mtime: 791231220,
185 mtime: 791231220,
185 },
186 },
186 )]
187 )]
187 .iter()
188 .iter()
188 .cloned()
189 .cloned()
189 .collect();
190 .collect();
190 let mut state_map = expected_state_map.clone();
191 let mut state_map = expected_state_map.clone();
191
192
192 let copymap = HashMap::new();
193 let copymap = HashMap::new();
193 let parents = DirstateParents {
194 let parents = DirstateParents {
194 p1: *b"12345678910111213141",
195 p1: *b"12345678910111213141",
195 p2: *b"00000000000000000000",
196 p2: *b"00000000000000000000",
196 };
197 };
197 let now = Duration::new(15000000, 0);
198 let now = Duration::new(15000000, 0);
198 let expected = [
199 let expected = [
199 49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49,
200 49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49,
200 51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
201 51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
201 48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0, 0, 0, 0, 47,
202 48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0, 0, 0, 0, 47,
202 41, 58, 244, 0, 0, 0, 2, 102, 49,
203 41, 58, 244, 0, 0, 0, 2, 102, 49,
203 ]
204 ]
204 .to_vec();
205 .to_vec();
205
206
206 assert_eq!(
207 assert_eq!(
207 expected,
208 expected,
208 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
209 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
209 );
210 );
210
211
211 assert_eq!(expected_state_map, state_map);
212 assert_eq!(expected_state_map, state_map);
212 }
213 }
213 #[test]
214 #[test]
214 fn test_pack_dirstate_one_entry_with_copy() {
215 fn test_pack_dirstate_one_entry_with_copy() {
215 let expected_state_map: StateMap = [(
216 let expected_state_map: StateMap = [(
216 b"f1".to_vec(),
217 b"f1".to_vec(),
217 DirstateEntry {
218 DirstateEntry {
218 state: 'n' as i8,
219 state: EntryState::Normal,
219 mode: 0o644,
220 mode: 0o644,
220 size: 0,
221 size: 0,
221 mtime: 791231220,
222 mtime: 791231220,
222 },
223 },
223 )]
224 )]
224 .iter()
225 .iter()
225 .cloned()
226 .cloned()
226 .collect();
227 .collect();
227 let mut state_map = expected_state_map.clone();
228 let mut state_map = expected_state_map.clone();
228 let mut copymap = HashMap::new();
229 let mut copymap = HashMap::new();
229 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
230 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
230 let parents = DirstateParents {
231 let parents = DirstateParents {
231 p1: *b"12345678910111213141",
232 p1: *b"12345678910111213141",
232 p2: *b"00000000000000000000",
233 p2: *b"00000000000000000000",
233 };
234 };
234 let now = Duration::new(15000000, 0);
235 let now = Duration::new(15000000, 0);
235 let expected = [
236 let expected = [
236 49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49,
237 49, 50, 51, 52, 53, 54, 55, 56, 57, 49, 48, 49, 49, 49, 50, 49,
237 51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
238 51, 49, 52, 49, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
238 48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0, 0, 0, 0, 47,
239 48, 48, 48, 48, 48, 48, 48, 48, 110, 0, 0, 1, 164, 0, 0, 0, 0, 47,
239 41, 58, 244, 0, 0, 0, 11, 102, 49, 0, 99, 111, 112, 121, 110, 97,
240 41, 58, 244, 0, 0, 0, 11, 102, 49, 0, 99, 111, 112, 121, 110, 97,
240 109, 101,
241 109, 101,
241 ]
242 ]
242 .to_vec();
243 .to_vec();
243
244
244 assert_eq!(
245 assert_eq!(
245 expected,
246 expected,
246 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
247 pack_dirstate(&mut state_map, &copymap, parents, now).unwrap()
247 );
248 );
248 assert_eq!(expected_state_map, state_map);
249 assert_eq!(expected_state_map, state_map);
249 }
250 }
250
251
251 #[test]
252 #[test]
252 fn test_parse_pack_one_entry_with_copy() {
253 fn test_parse_pack_one_entry_with_copy() {
253 let mut state_map: StateMap = [(
254 let mut state_map: StateMap = [(
254 b"f1".to_vec(),
255 b"f1".to_vec(),
255 DirstateEntry {
256 DirstateEntry {
256 state: 'n' as i8,
257 state: EntryState::Normal,
257 mode: 0o644,
258 mode: 0o644,
258 size: 0,
259 size: 0,
259 mtime: 791231220,
260 mtime: 791231220,
260 },
261 },
261 )]
262 )]
262 .iter()
263 .iter()
263 .cloned()
264 .cloned()
264 .collect();
265 .collect();
265 let mut copymap = HashMap::new();
266 let mut copymap = HashMap::new();
266 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
267 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
267 let parents = DirstateParents {
268 let parents = DirstateParents {
268 p1: *b"12345678910111213141",
269 p1: *b"12345678910111213141",
269 p2: *b"00000000000000000000",
270 p2: *b"00000000000000000000",
270 };
271 };
271 let now = Duration::new(15000000, 0);
272 let now = Duration::new(15000000, 0);
272 let result =
273 let result =
273 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
274 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
274 .unwrap();
275 .unwrap();
275
276
276 let mut new_state_map: StateMap = HashMap::new();
277 let mut new_state_map: StateMap = HashMap::new();
277 let mut new_copy_map: CopyMap = HashMap::new();
278 let mut new_copy_map: CopyMap = HashMap::new();
278 let new_parents = parse_dirstate(
279 let new_parents = parse_dirstate(
279 &mut new_state_map,
280 &mut new_state_map,
280 &mut new_copy_map,
281 &mut new_copy_map,
281 result.as_slice(),
282 result.as_slice(),
282 )
283 )
283 .unwrap();
284 .unwrap();
284 assert_eq!(
285 assert_eq!(
285 (parents, state_map, copymap),
286 (parents, state_map, copymap),
286 (new_parents, new_state_map, new_copy_map)
287 (new_parents, new_state_map, new_copy_map)
287 )
288 )
288 }
289 }
289
290
290 #[test]
291 #[test]
291 fn test_parse_pack_multiple_entries_with_copy() {
292 fn test_parse_pack_multiple_entries_with_copy() {
292 let mut state_map: StateMap = [
293 let mut state_map: StateMap = [
293 (
294 (
294 b"f1".to_vec(),
295 b"f1".to_vec(),
295 DirstateEntry {
296 DirstateEntry {
296 state: 'n' as i8,
297 state: EntryState::Normal,
297 mode: 0o644,
298 mode: 0o644,
298 size: 0,
299 size: 0,
299 mtime: 791231220,
300 mtime: 791231220,
300 },
301 },
301 ),
302 ),
302 (
303 (
303 b"f2".to_vec(),
304 b"f2".to_vec(),
304 DirstateEntry {
305 DirstateEntry {
305 state: 'm' as i8,
306 state: EntryState::Merged,
306 mode: 0o777,
307 mode: 0o777,
307 size: 1000,
308 size: 1000,
308 mtime: 791231220,
309 mtime: 791231220,
309 },
310 },
310 ),
311 ),
311 (
312 (
312 b"f3".to_vec(),
313 b"f3".to_vec(),
313 DirstateEntry {
314 DirstateEntry {
314 state: 'r' as i8,
315 state: EntryState::Removed,
315 mode: 0o644,
316 mode: 0o644,
316 size: 234553,
317 size: 234553,
317 mtime: 791231220,
318 mtime: 791231220,
318 },
319 },
319 ),
320 ),
320 (
321 (
321 b"f4\xF6".to_vec(),
322 b"f4\xF6".to_vec(),
322 DirstateEntry {
323 DirstateEntry {
323 state: 'a' as i8,
324 state: EntryState::Added,
324 mode: 0o644,
325 mode: 0o644,
325 size: -1,
326 size: -1,
326 mtime: -1,
327 mtime: -1,
327 },
328 },
328 ),
329 ),
329 ]
330 ]
330 .iter()
331 .iter()
331 .cloned()
332 .cloned()
332 .collect();
333 .collect();
333 let mut copymap = HashMap::new();
334 let mut copymap = HashMap::new();
334 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
335 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
335 copymap.insert(b"f4\xF6".to_vec(), b"copyname2".to_vec());
336 copymap.insert(b"f4\xF6".to_vec(), b"copyname2".to_vec());
336 let parents = DirstateParents {
337 let parents = DirstateParents {
337 p1: *b"12345678910111213141",
338 p1: *b"12345678910111213141",
338 p2: *b"00000000000000000000",
339 p2: *b"00000000000000000000",
339 };
340 };
340 let now = Duration::new(15000000, 0);
341 let now = Duration::new(15000000, 0);
341 let result =
342 let result =
342 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
343 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
343 .unwrap();
344 .unwrap();
344
345
345 let mut new_state_map: StateMap = HashMap::new();
346 let mut new_state_map: StateMap = HashMap::new();
346 let mut new_copy_map: CopyMap = HashMap::new();
347 let mut new_copy_map: CopyMap = HashMap::new();
347 let new_parents = parse_dirstate(
348 let new_parents = parse_dirstate(
348 &mut new_state_map,
349 &mut new_state_map,
349 &mut new_copy_map,
350 &mut new_copy_map,
350 result.as_slice(),
351 result.as_slice(),
351 )
352 )
352 .unwrap();
353 .unwrap();
353 assert_eq!(
354 assert_eq!(
354 (parents, state_map, copymap),
355 (parents, state_map, copymap),
355 (new_parents, new_state_map, new_copy_map)
356 (new_parents, new_state_map, new_copy_map)
356 )
357 )
357 }
358 }
358
359
359 #[test]
360 #[test]
360 /// https://www.mercurial-scm.org/repo/hg/rev/af3f26b6bba4
361 /// https://www.mercurial-scm.org/repo/hg/rev/af3f26b6bba4
361 fn test_parse_pack_one_entry_with_copy_and_time_conflict() {
362 fn test_parse_pack_one_entry_with_copy_and_time_conflict() {
362 let mut state_map: StateMap = [(
363 let mut state_map: StateMap = [(
363 b"f1".to_vec(),
364 b"f1".to_vec(),
364 DirstateEntry {
365 DirstateEntry {
365 state: 'n' as i8,
366 state: EntryState::Normal,
366 mode: 0o644,
367 mode: 0o644,
367 size: 0,
368 size: 0,
368 mtime: 15000000,
369 mtime: 15000000,
369 },
370 },
370 )]
371 )]
371 .iter()
372 .iter()
372 .cloned()
373 .cloned()
373 .collect();
374 .collect();
374 let mut copymap = HashMap::new();
375 let mut copymap = HashMap::new();
375 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
376 copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
376 let parents = DirstateParents {
377 let parents = DirstateParents {
377 p1: *b"12345678910111213141",
378 p1: *b"12345678910111213141",
378 p2: *b"00000000000000000000",
379 p2: *b"00000000000000000000",
379 };
380 };
380 let now = Duration::new(15000000, 0);
381 let now = Duration::new(15000000, 0);
381 let result =
382 let result =
382 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
383 pack_dirstate(&mut state_map, &copymap, parents.clone(), now)
383 .unwrap();
384 .unwrap();
384
385
385 let mut new_state_map: StateMap = HashMap::new();
386 let mut new_state_map: StateMap = HashMap::new();
386 let mut new_copy_map: CopyMap = HashMap::new();
387 let mut new_copy_map: CopyMap = HashMap::new();
387 let new_parents = parse_dirstate(
388 let new_parents = parse_dirstate(
388 &mut new_state_map,
389 &mut new_state_map,
389 &mut new_copy_map,
390 &mut new_copy_map,
390 result.as_slice(),
391 result.as_slice(),
391 )
392 )
392 .unwrap();
393 .unwrap();
393
394
394 assert_eq!(
395 assert_eq!(
395 (
396 (
396 parents,
397 parents,
397 [(
398 [(
398 b"f1".to_vec(),
399 b"f1".to_vec(),
399 DirstateEntry {
400 DirstateEntry {
400 state: 'n' as i8,
401 state: EntryState::Normal,
401 mode: 0o644,
402 mode: 0o644,
402 size: 0,
403 size: 0,
403 mtime: -1
404 mtime: -1
404 }
405 }
405 )]
406 )]
406 .iter()
407 .iter()
407 .cloned()
408 .cloned()
408 .collect::<StateMap>(),
409 .collect::<StateMap>(),
409 copymap,
410 copymap,
410 ),
411 ),
411 (new_parents, new_state_map, new_copy_map)
412 (new_parents, new_state_map, new_copy_map)
412 )
413 )
413 }
414 }
414 }
415 }
@@ -1,105 +1,148 b''
1 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
1 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
2 //
2 //
3 // This software may be used and distributed according to the terms of the
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.
4 // GNU General Public License version 2 or any later version.
5 mod ancestors;
5 mod ancestors;
6 pub mod dagops;
6 pub mod dagops;
7 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
7 pub use ancestors::{AncestorsIterator, LazyAncestors, MissingAncestors};
8 mod dirstate;
8 mod dirstate;
9 pub mod discovery;
9 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 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
13 parsers::{pack_dirstate, parse_dirstate, PARENT_SIZE},
14 CopyMap, DirsIterable, DirstateEntry, DirstateParents, StateMap,
14 CopyMap, DirsIterable, DirstateEntry, DirstateParents, EntryState,
15 StateMap,
15 };
16 };
16 mod filepatterns;
17 mod filepatterns;
17 pub mod utils;
18 pub mod utils;
18
19
19 pub use filepatterns::{
20 pub use filepatterns::{
20 build_single_regex, read_pattern_file, PatternSyntax, PatternTuple,
21 build_single_regex, read_pattern_file, PatternSyntax, PatternTuple,
21 };
22 };
22
23
23 /// Mercurial revision numbers
24 /// Mercurial revision numbers
24 ///
25 ///
25 /// As noted in revlog.c, revision numbers are actually encoded in
26 /// As noted in revlog.c, revision numbers are actually encoded in
26 /// 4 bytes, and are liberally converted to ints, whence the i32
27 /// 4 bytes, and are liberally converted to ints, whence the i32
27 pub type Revision = i32;
28 pub type Revision = i32;
28
29
29 /// Marker expressing the absence of a parent
30 /// Marker expressing the absence of a parent
30 ///
31 ///
31 /// Independently of the actual representation, `NULL_REVISION` is guaranteed
32 /// Independently of the actual representation, `NULL_REVISION` is guaranteed
32 /// to be smaller that all existing revisions.
33 /// to be smaller that all existing revisions.
33 pub const NULL_REVISION: Revision = -1;
34 pub const NULL_REVISION: Revision = -1;
34
35
35 /// Same as `mercurial.node.wdirrev`
36 /// Same as `mercurial.node.wdirrev`
36 ///
37 ///
37 /// This is also equal to `i32::max_value()`, but it's better to spell
38 /// This is also equal to `i32::max_value()`, but it's better to spell
38 /// it out explicitely, same as in `mercurial.node`
39 /// it out explicitely, same as in `mercurial.node`
39 pub const WORKING_DIRECTORY_REVISION: Revision = 0x7fffffff;
40 pub const WORKING_DIRECTORY_REVISION: Revision = 0x7fffffff;
40
41
41 /// The simplest expression of what we need of Mercurial DAGs.
42 /// The simplest expression of what we need of Mercurial DAGs.
42 pub trait Graph {
43 pub trait Graph {
43 /// Return the two parents of the given `Revision`.
44 /// Return the two parents of the given `Revision`.
44 ///
45 ///
45 /// Each of the parents can be independently `NULL_REVISION`
46 /// Each of the parents can be independently `NULL_REVISION`
46 fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError>;
47 fn parents(&self, rev: Revision) -> Result<[Revision; 2], GraphError>;
47 }
48 }
48
49
49 pub type LineNumber = usize;
50 pub type LineNumber = usize;
50
51
51 #[derive(Clone, Debug, PartialEq)]
52 #[derive(Clone, Debug, PartialEq)]
52 pub enum GraphError {
53 pub enum GraphError {
53 ParentOutOfRange(Revision),
54 ParentOutOfRange(Revision),
54 WorkingDirectoryUnsupported,
55 WorkingDirectoryUnsupported,
55 }
56 }
56
57
57 #[derive(Clone, Debug, PartialEq)]
58 #[derive(Clone, Debug, PartialEq)]
58 pub enum DirstateParseError {
59 pub enum DirstateParseError {
59 TooLittleData,
60 TooLittleData,
60 Overflow,
61 Overflow,
61 CorruptedEntry(String),
62 CorruptedEntry(String),
62 Damaged,
63 Damaged,
63 }
64 }
64
65
66 impl From<std::io::Error> for DirstateParseError {
67 fn from(e: std::io::Error) -> Self {
68 DirstateParseError::CorruptedEntry(e.to_string())
69 }
70 }
71
72 impl ToString for DirstateParseError {
73 fn to_string(&self) -> String {
74 use crate::DirstateParseError::*;
75 match self {
76 TooLittleData => "Too little data for dirstate.".to_string(),
77 Overflow => "Overflow in dirstate.".to_string(),
78 CorruptedEntry(e) => format!("Corrupted entry: {:?}.", e),
79 Damaged => "Dirstate appears to be damaged.".to_string(),
80 }
81 }
82 }
83
65 #[derive(Debug, PartialEq)]
84 #[derive(Debug, PartialEq)]
66 pub enum DirstatePackError {
85 pub enum DirstatePackError {
67 CorruptedEntry(String),
86 CorruptedEntry(String),
68 CorruptedParent,
87 CorruptedParent,
69 BadSize(usize, usize),
88 BadSize(usize, usize),
70 }
89 }
71
90
91 impl From<std::io::Error> for DirstatePackError {
92 fn from(e: std::io::Error) -> Self {
93 DirstatePackError::CorruptedEntry(e.to_string())
94 }
95 }
72 #[derive(Debug, PartialEq)]
96 #[derive(Debug, PartialEq)]
73 pub enum DirstateMapError {
97 pub enum DirstateMapError {
74 PathNotFound(Vec<u8>),
98 PathNotFound(Vec<u8>),
75 EmptyPath,
99 EmptyPath,
76 }
100 }
77
101
78 impl From<std::io::Error> for DirstatePackError {
102 pub enum DirstateError {
79 fn from(e: std::io::Error) -> Self {
103 Parse(DirstateParseError),
80 DirstatePackError::CorruptedEntry(e.to_string())
104 Pack(DirstatePackError),
105 Map(DirstateMapError),
106 IO(std::io::Error),
107 }
108
109 impl From<DirstateParseError> for DirstateError {
110 fn from(e: DirstateParseError) -> Self {
111 DirstateError::Parse(e)
81 }
112 }
82 }
113 }
83
114
84 impl From<std::io::Error> for DirstateParseError {
115 impl From<DirstatePackError> for DirstateError {
85 fn from(e: std::io::Error) -> Self {
116 fn from(e: DirstatePackError) -> Self {
86 DirstateParseError::CorruptedEntry(e.to_string())
117 DirstateError::Pack(e)
87 }
118 }
88 }
119 }
89
120
90 #[derive(Debug)]
121 #[derive(Debug)]
91 pub enum PatternError {
122 pub enum PatternError {
92 UnsupportedSyntax(String),
123 UnsupportedSyntax(String),
93 }
124 }
94
125
95 #[derive(Debug)]
126 #[derive(Debug)]
96 pub enum PatternFileError {
127 pub enum PatternFileError {
97 IO(std::io::Error),
128 IO(std::io::Error),
98 Pattern(PatternError, LineNumber),
129 Pattern(PatternError, LineNumber),
99 }
130 }
100
131
101 impl From<std::io::Error> for PatternFileError {
132 impl From<std::io::Error> for PatternFileError {
102 fn from(e: std::io::Error) -> Self {
133 fn from(e: std::io::Error) -> Self {
103 PatternFileError::IO(e)
134 PatternFileError::IO(e)
104 }
135 }
105 }
136 }
137
138 impl From<DirstateMapError> for DirstateError {
139 fn from(e: DirstateMapError) -> Self {
140 DirstateError::Map(e)
141 }
142 }
143
144 impl From<std::io::Error> for DirstateError {
145 fn from(e: std::io::Error) -> Self {
146 DirstateError::IO(e)
147 }
148 }
@@ -1,97 +1,103 b''
1 // dirstate.rs
1 // dirstate.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
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.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::dirstate` module provided by the
8 //! Bindings for the `hg::dirstate` module provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10 //!
10 //!
11 //! From Python, this will be seen as `mercurial.rustext.dirstate`
11 //! From Python, this will be seen as `mercurial.rustext.dirstate`
12 mod dirs_multiset;
12 mod dirs_multiset;
13 use crate::dirstate::dirs_multiset::Dirs;
13 use crate::dirstate::dirs_multiset::Dirs;
14 use cpython::{
14 use cpython::{
15 PyBytes, PyDict, PyErr, PyModule, PyObject, PyResult, PySequence, Python,
15 exc, PyBytes, PyDict, PyErr, PyModule, PyObject, PyResult, PySequence,
16 Python,
16 };
17 };
17 use hg::{DirstateEntry, StateMap};
18 use hg::{DirstateEntry, DirstateParseError, EntryState, StateMap};
18 use libc::{c_char, c_int};
19 use libc::{c_char, c_int};
19 #[cfg(feature = "python27")]
20 #[cfg(feature = "python27")]
20 use python27_sys::PyCapsule_Import;
21 use python27_sys::PyCapsule_Import;
21 #[cfg(feature = "python3")]
22 #[cfg(feature = "python3")]
22 use python3_sys::PyCapsule_Import;
23 use python3_sys::PyCapsule_Import;
24 use std::convert::TryFrom;
23 use std::ffi::CStr;
25 use std::ffi::CStr;
24 use std::mem::transmute;
26 use std::mem::transmute;
25
27
26 /// C code uses a custom `dirstate_tuple` type, checks in multiple instances
28 /// C code uses a custom `dirstate_tuple` type, checks in multiple instances
27 /// for this type, and raises a Python `Exception` if the check does not pass.
29 /// for this type, and raises a Python `Exception` if the check does not pass.
28 /// Because this type differs only in name from the regular Python tuple, it
30 /// Because this type differs only in name from the regular Python tuple, it
29 /// would be a good idea in the near future to remove it entirely to allow
31 /// would be a good idea in the near future to remove it entirely to allow
30 /// for a pure Python tuple of the same effective structure to be used,
32 /// for a pure Python tuple of the same effective structure to be used,
31 /// rendering this type and the capsule below useless.
33 /// rendering this type and the capsule below useless.
32 type MakeDirstateTupleFn = extern "C" fn(
34 type MakeDirstateTupleFn = extern "C" fn(
33 state: c_char,
35 state: c_char,
34 mode: c_int,
36 mode: c_int,
35 size: c_int,
37 size: c_int,
36 mtime: c_int,
38 mtime: c_int,
37 ) -> PyObject;
39 ) -> PyObject;
38
40
39 /// This is largely a copy/paste from cindex.rs, pending the merge of a
41 /// This is largely a copy/paste from cindex.rs, pending the merge of a
40 /// `py_capsule_fn!` macro in the rust-cpython project:
42 /// `py_capsule_fn!` macro in the rust-cpython project:
41 /// https://github.com/dgrunwald/rust-cpython/pull/169
43 /// https://github.com/dgrunwald/rust-cpython/pull/169
42 pub fn decapsule_make_dirstate_tuple(
44 pub fn decapsule_make_dirstate_tuple(
43 py: Python,
45 py: Python,
44 ) -> PyResult<MakeDirstateTupleFn> {
46 ) -> PyResult<MakeDirstateTupleFn> {
45 unsafe {
47 unsafe {
46 let caps_name = CStr::from_bytes_with_nul_unchecked(
48 let caps_name = CStr::from_bytes_with_nul_unchecked(
47 b"mercurial.cext.parsers.make_dirstate_tuple_CAPI\0",
49 b"mercurial.cext.parsers.make_dirstate_tuple_CAPI\0",
48 );
50 );
49 let from_caps = PyCapsule_Import(caps_name.as_ptr(), 0);
51 let from_caps = PyCapsule_Import(caps_name.as_ptr(), 0);
50 if from_caps.is_null() {
52 if from_caps.is_null() {
51 return Err(PyErr::fetch(py));
53 return Err(PyErr::fetch(py));
52 }
54 }
53 Ok(transmute(from_caps))
55 Ok(transmute(from_caps))
54 }
56 }
55 }
57 }
56
58
57 pub fn extract_dirstate(py: Python, dmap: &PyDict) -> Result<StateMap, PyErr> {
59 pub fn extract_dirstate(py: Python, dmap: &PyDict) -> Result<StateMap, PyErr> {
58 dmap.items(py)
60 dmap.items(py)
59 .iter()
61 .iter()
60 .map(|(filename, stats)| {
62 .map(|(filename, stats)| {
61 let stats = stats.extract::<PySequence>(py)?;
63 let stats = stats.extract::<PySequence>(py)?;
62 let state = stats.get_item(py, 0)?.extract::<PyBytes>(py)?;
64 let state = stats.get_item(py, 0)?.extract::<PyBytes>(py)?;
63 let state = state.data(py)[0] as i8;
65 let state = EntryState::try_from(state.data(py)[0]).map_err(
66 |e: DirstateParseError| {
67 PyErr::new::<exc::ValueError, _>(py, e.to_string())
68 },
69 )?;
64 let mode = stats.get_item(py, 1)?.extract(py)?;
70 let mode = stats.get_item(py, 1)?.extract(py)?;
65 let size = stats.get_item(py, 2)?.extract(py)?;
71 let size = stats.get_item(py, 2)?.extract(py)?;
66 let mtime = stats.get_item(py, 3)?.extract(py)?;
72 let mtime = stats.get_item(py, 3)?.extract(py)?;
67 let filename = filename.extract::<PyBytes>(py)?;
73 let filename = filename.extract::<PyBytes>(py)?;
68 let filename = filename.data(py);
74 let filename = filename.data(py);
69 Ok((
75 Ok((
70 filename.to_owned(),
76 filename.to_owned(),
71 DirstateEntry {
77 DirstateEntry {
72 state,
78 state,
73 mode,
79 mode,
74 size,
80 size,
75 mtime,
81 mtime,
76 },
82 },
77 ))
83 ))
78 })
84 })
79 .collect()
85 .collect()
80 }
86 }
81
87
82 /// Create the module, with `__package__` given from parent
88 /// Create the module, with `__package__` given from parent
83 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
89 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
84 let dotted_name = &format!("{}.dirstate", package);
90 let dotted_name = &format!("{}.dirstate", package);
85 let m = PyModule::new(py, dotted_name)?;
91 let m = PyModule::new(py, dotted_name)?;
86
92
87 m.add(py, "__package__", package)?;
93 m.add(py, "__package__", package)?;
88 m.add(py, "__doc__", "Dirstate - Rust implementation")?;
94 m.add(py, "__doc__", "Dirstate - Rust implementation")?;
89
95
90 m.add_class::<Dirs>(py)?;
96 m.add_class::<Dirs>(py)?;
91
97
92 let sys = PyModule::import(py, "sys")?;
98 let sys = PyModule::import(py, "sys")?;
93 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
99 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
94 sys_modules.set_item(py, dotted_name, &m)?;
100 sys_modules.set_item(py, dotted_name, &m)?;
95
101
96 Ok(m)
102 Ok(m)
97 }
103 }
@@ -1,108 +1,118 b''
1 // dirs_multiset.rs
1 // dirs_multiset.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
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.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::dirstate::dirs_multiset` file provided by the
8 //! Bindings for the `hg::dirstate::dirs_multiset` file provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::RefCell;
11 use std::cell::RefCell;
12
12
13 use cpython::{
13 use cpython::{
14 exc, ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult,
14 exc, ObjectProtocol, PyBytes, PyDict, PyErr, PyObject, PyResult,
15 ToPyObject,
15 ToPyObject,
16 };
16 };
17
17
18 use crate::dirstate::extract_dirstate;
18 use crate::dirstate::extract_dirstate;
19 use hg::{DirsIterable, DirsMultiset, DirstateMapError};
19 use hg::{
20 DirsIterable, DirsMultiset, DirstateMapError, DirstateParseError,
21 EntryState,
22 };
23 use std::convert::TryInto;
20
24
21 py_class!(pub class Dirs |py| {
25 py_class!(pub class Dirs |py| {
22 data dirs_map: RefCell<DirsMultiset>;
26 data dirs_map: RefCell<DirsMultiset>;
23
27
24 // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
28 // `map` is either a `dict` or a flat iterator (usually a `set`, sometimes
25 // a `list`)
29 // a `list`)
26 def __new__(
30 def __new__(
27 _cls,
31 _cls,
28 map: PyObject,
32 map: PyObject,
29 skip: Option<PyObject> = None
33 skip: Option<PyObject> = None
30 ) -> PyResult<Self> {
34 ) -> PyResult<Self> {
31 let mut skip_state: Option<i8> = None;
35 let mut skip_state: Option<EntryState> = None;
32 if let Some(skip) = skip {
36 if let Some(skip) = skip {
33 skip_state = Some(skip.extract::<PyBytes>(py)?.data(py)[0] as i8);
37 skip_state = Some(
38 skip.extract::<PyBytes>(py)?.data(py)[0]
39 .try_into()
40 .map_err(|e: DirstateParseError| {
41 PyErr::new::<exc::ValueError, _>(py, e.to_string())
42 })?,
43 );
34 }
44 }
35 let inner = if let Ok(map) = map.cast_as::<PyDict>(py) {
45 let inner = if let Ok(map) = map.cast_as::<PyDict>(py) {
36 let dirstate = extract_dirstate(py, &map)?;
46 let dirstate = extract_dirstate(py, &map)?;
37 DirsMultiset::new(
47 DirsMultiset::new(
38 DirsIterable::Dirstate(&dirstate),
48 DirsIterable::Dirstate(&dirstate),
39 skip_state,
49 skip_state,
40 )
50 )
41 } else {
51 } else {
42 let map: Result<Vec<Vec<u8>>, PyErr> = map
52 let map: Result<Vec<Vec<u8>>, PyErr> = map
43 .iter(py)?
53 .iter(py)?
44 .map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned()))
54 .map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned()))
45 .collect();
55 .collect();
46 DirsMultiset::new(
56 DirsMultiset::new(
47 DirsIterable::Manifest(&map?),
57 DirsIterable::Manifest(&map?),
48 skip_state,
58 skip_state,
49 )
59 )
50 };
60 };
51
61
52 Self::create_instance(py, RefCell::new(inner))
62 Self::create_instance(py, RefCell::new(inner))
53 }
63 }
54
64
55 def addpath(&self, path: PyObject) -> PyResult<PyObject> {
65 def addpath(&self, path: PyObject) -> PyResult<PyObject> {
56 self.dirs_map(py).borrow_mut().add_path(
66 self.dirs_map(py).borrow_mut().add_path(
57 path.extract::<PyBytes>(py)?.data(py),
67 path.extract::<PyBytes>(py)?.data(py),
58 );
68 );
59 Ok(py.None())
69 Ok(py.None())
60 }
70 }
61
71
62 def delpath(&self, path: PyObject) -> PyResult<PyObject> {
72 def delpath(&self, path: PyObject) -> PyResult<PyObject> {
63 self.dirs_map(py).borrow_mut().delete_path(
73 self.dirs_map(py).borrow_mut().delete_path(
64 path.extract::<PyBytes>(py)?.data(py),
74 path.extract::<PyBytes>(py)?.data(py),
65 )
75 )
66 .and(Ok(py.None()))
76 .and(Ok(py.None()))
67 .or_else(|e| {
77 .or_else(|e| {
68 match e {
78 match e {
69 DirstateMapError::PathNotFound(_p) => {
79 DirstateMapError::PathNotFound(_p) => {
70 Err(PyErr::new::<exc::ValueError, _>(
80 Err(PyErr::new::<exc::ValueError, _>(
71 py,
81 py,
72 "expected a value, found none".to_string(),
82 "expected a value, found none".to_string(),
73 ))
83 ))
74 }
84 }
75 DirstateMapError::EmptyPath => {
85 DirstateMapError::EmptyPath => {
76 Ok(py.None())
86 Ok(py.None())
77 }
87 }
78 }
88 }
79 })
89 })
80 }
90 }
81
91
82 // This is really inefficient on top of being ugly, but it's an easy way
92 // This is really inefficient on top of being ugly, but it's an easy way
83 // of having it work to continue working on the rest of the module
93 // of having it work to continue working on the rest of the module
84 // hopefully bypassing Python entirely pretty soon.
94 // hopefully bypassing Python entirely pretty soon.
85 def __iter__(&self) -> PyResult<PyObject> {
95 def __iter__(&self) -> PyResult<PyObject> {
86 let dict = PyDict::new(py);
96 let dict = PyDict::new(py);
87
97
88 for (key, value) in self.dirs_map(py).borrow().iter() {
98 for (key, value) in self.dirs_map(py).borrow().iter() {
89 dict.set_item(
99 dict.set_item(
90 py,
100 py,
91 PyBytes::new(py, &key[..]),
101 PyBytes::new(py, &key[..]),
92 value.to_py_object(py),
102 value.to_py_object(py),
93 )?;
103 )?;
94 }
104 }
95
105
96 let locals = PyDict::new(py);
106 let locals = PyDict::new(py);
97 locals.set_item(py, "obj", dict)?;
107 locals.set_item(py, "obj", dict)?;
98
108
99 py.eval("iter(obj)", None, Some(&locals))
109 py.eval("iter(obj)", None, Some(&locals))
100 }
110 }
101
111
102 def __contains__(&self, item: PyObject) -> PyResult<bool> {
112 def __contains__(&self, item: PyObject) -> PyResult<bool> {
103 Ok(self
113 Ok(self
104 .dirs_map(py)
114 .dirs_map(py)
105 .borrow()
115 .borrow()
106 .contains_key(item.extract::<PyBytes>(py)?.data(py).as_ref()))
116 .contains_key(item.extract::<PyBytes>(py)?.data(py).as_ref()))
107 }
117 }
108 });
118 });
@@ -1,196 +1,207 b''
1 // parsers.rs
1 // parsers.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
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.
6 // GNU General Public License version 2 or any later version.
7
7
8 //! Bindings for the `hg::dirstate::parsers` module provided by the
8 //! Bindings for the `hg::dirstate::parsers` module provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10 //!
10 //!
11 //! From Python, this will be seen as `mercurial.rustext.parsers`
11 //! From Python, this will be seen as `mercurial.rustext.parsers`
12 //!
12 //!
13 use cpython::{
13 use cpython::{
14 exc, PyBytes, PyDict, PyErr, PyInt, PyModule, PyResult, PyTuple, Python,
14 exc, PyBytes, PyDict, PyErr, PyInt, PyModule, PyResult, PyTuple, Python,
15 ToPyObject,
15 ToPyObject,
16 };
16 };
17 use hg::{
17 use hg::{
18 pack_dirstate, parse_dirstate, utils::copy_into_array, DirstateEntry,
18 pack_dirstate, parse_dirstate, utils::copy_into_array, DirstateEntry,
19 DirstatePackError, DirstateParents, DirstateParseError, PARENT_SIZE,
19 DirstatePackError, DirstateParents, DirstateParseError, PARENT_SIZE,
20 };
20 };
21 use std::collections::HashMap;
21 use std::collections::HashMap;
22
22
23 use libc::c_char;
23 use libc::c_char;
24
24
25 use crate::dirstate::{decapsule_make_dirstate_tuple, extract_dirstate};
25 use crate::dirstate::{decapsule_make_dirstate_tuple, extract_dirstate};
26 use std::time::Duration;
26 use std::time::Duration;
27
27
28 fn parse_dirstate_wrapper(
28 fn parse_dirstate_wrapper(
29 py: Python,
29 py: Python,
30 dmap: PyDict,
30 dmap: PyDict,
31 copymap: PyDict,
31 copymap: PyDict,
32 st: PyBytes,
32 st: PyBytes,
33 ) -> PyResult<PyTuple> {
33 ) -> PyResult<PyTuple> {
34 let mut dirstate_map = HashMap::new();
34 let mut dirstate_map = HashMap::new();
35 let mut copies = HashMap::new();
35 let mut copies = HashMap::new();
36
36
37 match parse_dirstate(&mut dirstate_map, &mut copies, st.data(py)) {
37 match parse_dirstate(&mut dirstate_map, &mut copies, st.data(py)) {
38 Ok(parents) => {
38 Ok(parents) => {
39 for (filename, entry) in dirstate_map {
39 for (filename, entry) in dirstate_map {
40 // Explicitly go through u8 first, then cast to
41 // platform-specific `c_char` because Into<u8> has a specific
42 // implementation while `as c_char` would just do a naive enum
43 // cast.
44 let state: u8 = entry.state.into();
45
40 dmap.set_item(
46 dmap.set_item(
41 py,
47 py,
42 PyBytes::new(py, &filename),
48 PyBytes::new(py, &filename),
43 decapsule_make_dirstate_tuple(py)?(
49 decapsule_make_dirstate_tuple(py)?(
44 entry.state as c_char,
50 state as c_char,
45 entry.mode,
51 entry.mode,
46 entry.size,
52 entry.size,
47 entry.mtime,
53 entry.mtime,
48 ),
54 ),
49 )?;
55 )?;
50 }
56 }
51 for (path, copy_path) in copies {
57 for (path, copy_path) in copies {
52 copymap.set_item(
58 copymap.set_item(
53 py,
59 py,
54 PyBytes::new(py, &path),
60 PyBytes::new(py, &path),
55 PyBytes::new(py, &copy_path),
61 PyBytes::new(py, &copy_path),
56 )?;
62 )?;
57 }
63 }
58 Ok(
64 Ok(
59 (PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2))
65 (PyBytes::new(py, &parents.p1), PyBytes::new(py, &parents.p2))
60 .to_py_object(py),
66 .to_py_object(py),
61 )
67 )
62 }
68 }
63 Err(e) => Err(PyErr::new::<exc::ValueError, _>(
69 Err(e) => Err(PyErr::new::<exc::ValueError, _>(
64 py,
70 py,
65 match e {
71 match e {
66 DirstateParseError::TooLittleData => {
72 DirstateParseError::TooLittleData => {
67 "too little data for parents".to_string()
73 "too little data for parents".to_string()
68 }
74 }
69 DirstateParseError::Overflow => {
75 DirstateParseError::Overflow => {
70 "overflow in dirstate".to_string()
76 "overflow in dirstate".to_string()
71 }
77 }
72 DirstateParseError::CorruptedEntry(e) => e,
78 DirstateParseError::CorruptedEntry(e) => e,
73 DirstateParseError::Damaged => {
79 DirstateParseError::Damaged => {
74 "dirstate appears to be damaged".to_string()
80 "dirstate appears to be damaged".to_string()
75 }
81 }
76 },
82 },
77 )),
83 )),
78 }
84 }
79 }
85 }
80
86
81 fn pack_dirstate_wrapper(
87 fn pack_dirstate_wrapper(
82 py: Python,
88 py: Python,
83 dmap: PyDict,
89 dmap: PyDict,
84 copymap: PyDict,
90 copymap: PyDict,
85 pl: PyTuple,
91 pl: PyTuple,
86 now: PyInt,
92 now: PyInt,
87 ) -> PyResult<PyBytes> {
93 ) -> PyResult<PyBytes> {
88 let p1 = pl.get_item(py, 0).extract::<PyBytes>(py)?;
94 let p1 = pl.get_item(py, 0).extract::<PyBytes>(py)?;
89 let p1: &[u8] = p1.data(py);
95 let p1: &[u8] = p1.data(py);
90 let p2 = pl.get_item(py, 1).extract::<PyBytes>(py)?;
96 let p2 = pl.get_item(py, 1).extract::<PyBytes>(py)?;
91 let p2: &[u8] = p2.data(py);
97 let p2: &[u8] = p2.data(py);
92
98
93 let mut dirstate_map = extract_dirstate(py, &dmap)?;
99 let mut dirstate_map = extract_dirstate(py, &dmap)?;
94
100
95 let copies: Result<HashMap<Vec<u8>, Vec<u8>>, PyErr> = copymap
101 let copies: Result<HashMap<Vec<u8>, Vec<u8>>, PyErr> = copymap
96 .items(py)
102 .items(py)
97 .iter()
103 .iter()
98 .map(|(key, value)| {
104 .map(|(key, value)| {
99 Ok((
105 Ok((
100 key.extract::<PyBytes>(py)?.data(py).to_owned(),
106 key.extract::<PyBytes>(py)?.data(py).to_owned(),
101 value.extract::<PyBytes>(py)?.data(py).to_owned(),
107 value.extract::<PyBytes>(py)?.data(py).to_owned(),
102 ))
108 ))
103 })
109 })
104 .collect();
110 .collect();
105
111
106 if p1.len() != PARENT_SIZE || p2.len() != PARENT_SIZE {
112 if p1.len() != PARENT_SIZE || p2.len() != PARENT_SIZE {
107 return Err(PyErr::new::<exc::ValueError, _>(
113 return Err(PyErr::new::<exc::ValueError, _>(
108 py,
114 py,
109 "expected a 20-byte hash".to_string(),
115 "expected a 20-byte hash".to_string(),
110 ));
116 ));
111 }
117 }
112
118
113 match pack_dirstate(
119 match pack_dirstate(
114 &mut dirstate_map,
120 &mut dirstate_map,
115 &copies?,
121 &copies?,
116 DirstateParents {
122 DirstateParents {
117 p1: copy_into_array(&p1),
123 p1: copy_into_array(&p1),
118 p2: copy_into_array(&p2),
124 p2: copy_into_array(&p2),
119 },
125 },
120 Duration::from_secs(now.value(py) as u64),
126 Duration::from_secs(now.value(py) as u64),
121 ) {
127 ) {
122 Ok(packed) => {
128 Ok(packed) => {
123 for (
129 for (
124 filename,
130 filename,
125 DirstateEntry {
131 DirstateEntry {
126 state,
132 state,
127 mode,
133 mode,
128 size,
134 size,
129 mtime,
135 mtime,
130 },
136 },
131 ) in dirstate_map
137 ) in dirstate_map
132 {
138 {
139 // Explicitly go through u8 first, then cast to
140 // platform-specific `c_char` because Into<u8> has a specific
141 // implementation while `as c_char` would just do a naive enum
142 // cast.
143 let state: u8 = state.into();
133 dmap.set_item(
144 dmap.set_item(
134 py,
145 py,
135 PyBytes::new(py, &filename[..]),
146 PyBytes::new(py, &filename[..]),
136 decapsule_make_dirstate_tuple(py)?(
147 decapsule_make_dirstate_tuple(py)?(
137 state as c_char,
148 state as c_char,
138 mode,
149 mode,
139 size,
150 size,
140 mtime,
151 mtime,
141 ),
152 ),
142 )?;
153 )?;
143 }
154 }
144 Ok(PyBytes::new(py, &packed))
155 Ok(PyBytes::new(py, &packed))
145 }
156 }
146 Err(error) => Err(PyErr::new::<exc::ValueError, _>(
157 Err(error) => Err(PyErr::new::<exc::ValueError, _>(
147 py,
158 py,
148 match error {
159 match error {
149 DirstatePackError::CorruptedParent => {
160 DirstatePackError::CorruptedParent => {
150 "expected a 20-byte hash".to_string()
161 "expected a 20-byte hash".to_string()
151 }
162 }
152 DirstatePackError::CorruptedEntry(e) => e,
163 DirstatePackError::CorruptedEntry(e) => e,
153 DirstatePackError::BadSize(expected, actual) => {
164 DirstatePackError::BadSize(expected, actual) => {
154 format!("bad dirstate size: {} != {}", actual, expected)
165 format!("bad dirstate size: {} != {}", actual, expected)
155 }
166 }
156 },
167 },
157 )),
168 )),
158 }
169 }
159 }
170 }
160
171
161 /// Create the module, with `__package__` given from parent
172 /// Create the module, with `__package__` given from parent
162 pub fn init_parsers_module(py: Python, package: &str) -> PyResult<PyModule> {
173 pub fn init_parsers_module(py: Python, package: &str) -> PyResult<PyModule> {
163 let dotted_name = &format!("{}.parsers", package);
174 let dotted_name = &format!("{}.parsers", package);
164 let m = PyModule::new(py, dotted_name)?;
175 let m = PyModule::new(py, dotted_name)?;
165
176
166 m.add(py, "__package__", package)?;
177 m.add(py, "__package__", package)?;
167 m.add(py, "__doc__", "Parsers - Rust implementation")?;
178 m.add(py, "__doc__", "Parsers - Rust implementation")?;
168
179
169 m.add(
180 m.add(
170 py,
181 py,
171 "parse_dirstate",
182 "parse_dirstate",
172 py_fn!(
183 py_fn!(
173 py,
184 py,
174 parse_dirstate_wrapper(dmap: PyDict, copymap: PyDict, st: PyBytes)
185 parse_dirstate_wrapper(dmap: PyDict, copymap: PyDict, st: PyBytes)
175 ),
186 ),
176 )?;
187 )?;
177 m.add(
188 m.add(
178 py,
189 py,
179 "pack_dirstate",
190 "pack_dirstate",
180 py_fn!(
191 py_fn!(
181 py,
192 py,
182 pack_dirstate_wrapper(
193 pack_dirstate_wrapper(
183 dmap: PyDict,
194 dmap: PyDict,
184 copymap: PyDict,
195 copymap: PyDict,
185 pl: PyTuple,
196 pl: PyTuple,
186 now: PyInt
197 now: PyInt
187 )
198 )
188 ),
199 ),
189 )?;
200 )?;
190
201
191 let sys = PyModule::import(py, "sys")?;
202 let sys = PyModule::import(py, "sys")?;
192 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
203 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
193 sys_modules.set_item(py, dotted_name, &m)?;
204 sys_modules.set_item(py, dotted_name, &m)?;
194
205
195 Ok(m)
206 Ok(m)
196 }
207 }
General Comments 0
You need to be logged in to leave comments. Login now