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: |
|
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: |
|
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 |
|
|
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_ |
|
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 == |
|
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_ |
|
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, ©map, parents, now).unwrap() |
|
172 | pack_dirstate(&mut state_map, ©map, 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: |
|
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, ©map, parents, now).unwrap() |
|
209 | pack_dirstate(&mut state_map, ©map, 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: |
|
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, ©map, parents, now).unwrap() |
|
247 | pack_dirstate(&mut state_map, ©map, 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: |
|
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, ©map, parents.clone(), now) |
|
274 | pack_dirstate(&mut state_map, ©map, 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: |
|
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: |
|
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: |
|
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: |
|
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, ©map, parents.clone(), now) |
|
343 | pack_dirstate(&mut state_map, ©map, 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: |
|
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, ©map, parents.clone(), now) |
|
383 | pack_dirstate(&mut state_map, ©map, 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: |
|
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, State |
|
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< |
|
115 | impl From<DirstatePackError> for DirstateError { | |
85 |
fn from(e: |
|
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, |
|
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] |
|
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< |
|
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 |
|
|
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, ©_path), |
|
61 | PyBytes::new(py, ©_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