##// END OF EJS Templates
rust: introduce SIZE_FROM_OTHER_PARENT constant...
Raphaël Gomès -
r44003:8210c3f4 default
parent child Browse files
Show More
@@ -1,77 +1,82 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::{utils::hg_path::HgPathBuf, DirstateParseError};
8 use crate::{utils::hg_path::HgPathBuf, DirstateParseError};
9 use std::collections::hash_map;
9 use std::collections::hash_map;
10 use std::collections::HashMap;
10 use std::collections::HashMap;
11 use std::convert::TryFrom;
11 use std::convert::TryFrom;
12
12
13 pub mod dirs_multiset;
13 pub mod dirs_multiset;
14 pub mod dirstate_map;
14 pub mod dirstate_map;
15 pub mod parsers;
15 pub mod parsers;
16 pub mod status;
16 pub mod status;
17
17
18 #[derive(Debug, PartialEq, Clone)]
18 #[derive(Debug, PartialEq, Clone)]
19 pub struct DirstateParents {
19 pub struct DirstateParents {
20 pub p1: [u8; 20],
20 pub p1: [u8; 20],
21 pub p2: [u8; 20],
21 pub p2: [u8; 20],
22 }
22 }
23
23
24 /// The C implementation uses all signed types. This will be an issue
24 /// The C implementation uses all signed types. This will be an issue
25 /// either when 4GB+ source files are commonplace or in 2038, whichever
25 /// either when 4GB+ source files are commonplace or in 2038, whichever
26 /// comes first.
26 /// comes first.
27 #[derive(Debug, PartialEq, Copy, Clone)]
27 #[derive(Debug, PartialEq, Copy, Clone)]
28 pub struct DirstateEntry {
28 pub struct DirstateEntry {
29 pub state: EntryState,
29 pub state: EntryState,
30 pub mode: i32,
30 pub mode: i32,
31 pub mtime: i32,
31 pub mtime: i32,
32 pub size: i32,
32 pub size: i32,
33 }
33 }
34
34
35 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
36 /// other parent. This allows revert to pick the right status back during a
37 /// merge.
38 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
39
35 pub type StateMap = HashMap<HgPathBuf, DirstateEntry>;
40 pub type StateMap = HashMap<HgPathBuf, DirstateEntry>;
36 pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>;
41 pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>;
37 pub type CopyMap = HashMap<HgPathBuf, HgPathBuf>;
42 pub type CopyMap = HashMap<HgPathBuf, HgPathBuf>;
38 pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>;
43 pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>;
39
44
40 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
45 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
41 pub enum EntryState {
46 pub enum EntryState {
42 Normal,
47 Normal,
43 Added,
48 Added,
44 Removed,
49 Removed,
45 Merged,
50 Merged,
46 Unknown,
51 Unknown,
47 }
52 }
48
53
49 impl TryFrom<u8> for EntryState {
54 impl TryFrom<u8> for EntryState {
50 type Error = DirstateParseError;
55 type Error = DirstateParseError;
51
56
52 fn try_from(value: u8) -> Result<Self, Self::Error> {
57 fn try_from(value: u8) -> Result<Self, Self::Error> {
53 match value {
58 match value {
54 b'n' => Ok(EntryState::Normal),
59 b'n' => Ok(EntryState::Normal),
55 b'a' => Ok(EntryState::Added),
60 b'a' => Ok(EntryState::Added),
56 b'r' => Ok(EntryState::Removed),
61 b'r' => Ok(EntryState::Removed),
57 b'm' => Ok(EntryState::Merged),
62 b'm' => Ok(EntryState::Merged),
58 b'?' => Ok(EntryState::Unknown),
63 b'?' => Ok(EntryState::Unknown),
59 _ => Err(DirstateParseError::CorruptedEntry(format!(
64 _ => Err(DirstateParseError::CorruptedEntry(format!(
60 "Incorrect entry state {}",
65 "Incorrect entry state {}",
61 value
66 value
62 ))),
67 ))),
63 }
68 }
64 }
69 }
65 }
70 }
66
71
67 impl Into<u8> for EntryState {
72 impl Into<u8> for EntryState {
68 fn into(self) -> u8 {
73 fn into(self) -> u8 {
69 match self {
74 match self {
70 EntryState::Normal => b'n',
75 EntryState::Normal => b'n',
71 EntryState::Added => b'a',
76 EntryState::Added => b'a',
72 EntryState::Removed => b'r',
77 EntryState::Removed => b'r',
73 EntryState::Merged => b'm',
78 EntryState::Merged => b'm',
74 EntryState::Unknown => b'?',
79 EntryState::Unknown => b'?',
75 }
80 }
76 }
81 }
77 }
82 }
@@ -1,424 +1,426 b''
1 // dirstate_map.rs
1 // dirstate_map.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 use crate::utils::hg_path::{HgPath, HgPathBuf};
9 use crate::{
8 use crate::{
10 dirstate::{parsers::PARENT_SIZE, EntryState},
9 dirstate::{parsers::PARENT_SIZE, EntryState, SIZE_FROM_OTHER_PARENT},
11 pack_dirstate, parse_dirstate,
10 pack_dirstate, parse_dirstate,
12 utils::files::normalize_case,
11 utils::{
12 files::normalize_case,
13 hg_path::{HgPath, HgPathBuf},
14 },
13 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError,
15 CopyMap, DirsMultiset, DirstateEntry, DirstateError, DirstateMapError,
14 DirstateParents, DirstateParseError, StateMap,
16 DirstateParents, DirstateParseError, StateMap,
15 };
17 };
16 use core::borrow::Borrow;
18 use core::borrow::Borrow;
17 use std::collections::{HashMap, HashSet};
19 use std::collections::{HashMap, HashSet};
18 use std::convert::TryInto;
20 use std::convert::TryInto;
19 use std::iter::FromIterator;
21 use std::iter::FromIterator;
20 use std::ops::Deref;
22 use std::ops::Deref;
21 use std::time::Duration;
23 use std::time::Duration;
22
24
23 pub type FileFoldMap = HashMap<HgPathBuf, HgPathBuf>;
25 pub type FileFoldMap = HashMap<HgPathBuf, HgPathBuf>;
24
26
25 const NULL_ID: [u8; 20] = [0; 20];
27 const NULL_ID: [u8; 20] = [0; 20];
26 const MTIME_UNSET: i32 = -1;
28 const MTIME_UNSET: i32 = -1;
27 const SIZE_DIRTY: i32 = -2;
28
29
29 #[derive(Default)]
30 #[derive(Default)]
30 pub struct DirstateMap {
31 pub struct DirstateMap {
31 state_map: StateMap,
32 state_map: StateMap,
32 pub copy_map: CopyMap,
33 pub copy_map: CopyMap,
33 file_fold_map: Option<FileFoldMap>,
34 file_fold_map: Option<FileFoldMap>,
34 pub dirs: Option<DirsMultiset>,
35 pub dirs: Option<DirsMultiset>,
35 pub all_dirs: Option<DirsMultiset>,
36 pub all_dirs: Option<DirsMultiset>,
36 non_normal_set: HashSet<HgPathBuf>,
37 non_normal_set: HashSet<HgPathBuf>,
37 other_parent_set: HashSet<HgPathBuf>,
38 other_parent_set: HashSet<HgPathBuf>,
38 parents: Option<DirstateParents>,
39 parents: Option<DirstateParents>,
39 dirty_parents: bool,
40 dirty_parents: bool,
40 }
41 }
41
42
42 /// Should only really be used in python interface code, for clarity
43 /// Should only really be used in python interface code, for clarity
43 impl Deref for DirstateMap {
44 impl Deref for DirstateMap {
44 type Target = StateMap;
45 type Target = StateMap;
45
46
46 fn deref(&self) -> &Self::Target {
47 fn deref(&self) -> &Self::Target {
47 &self.state_map
48 &self.state_map
48 }
49 }
49 }
50 }
50
51
51 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
52 impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
52 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
53 fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
53 iter: I,
54 iter: I,
54 ) -> Self {
55 ) -> Self {
55 Self {
56 Self {
56 state_map: iter.into_iter().collect(),
57 state_map: iter.into_iter().collect(),
57 ..Self::default()
58 ..Self::default()
58 }
59 }
59 }
60 }
60 }
61 }
61
62
62 impl DirstateMap {
63 impl DirstateMap {
63 pub fn new() -> Self {
64 pub fn new() -> Self {
64 Self::default()
65 Self::default()
65 }
66 }
66
67
67 pub fn clear(&mut self) {
68 pub fn clear(&mut self) {
68 self.state_map.clear();
69 self.state_map.clear();
69 self.copy_map.clear();
70 self.copy_map.clear();
70 self.file_fold_map = None;
71 self.file_fold_map = None;
71 self.non_normal_set.clear();
72 self.non_normal_set.clear();
72 self.other_parent_set.clear();
73 self.other_parent_set.clear();
73 self.set_parents(&DirstateParents {
74 self.set_parents(&DirstateParents {
74 p1: NULL_ID,
75 p1: NULL_ID,
75 p2: NULL_ID,
76 p2: NULL_ID,
76 })
77 })
77 }
78 }
78
79
79 /// Add a tracked file to the dirstate
80 /// Add a tracked file to the dirstate
80 pub fn add_file(
81 pub fn add_file(
81 &mut self,
82 &mut self,
82 filename: &HgPath,
83 filename: &HgPath,
83 old_state: EntryState,
84 old_state: EntryState,
84 entry: DirstateEntry,
85 entry: DirstateEntry,
85 ) {
86 ) {
86 if old_state == EntryState::Unknown || old_state == EntryState::Removed
87 if old_state == EntryState::Unknown || old_state == EntryState::Removed
87 {
88 {
88 if let Some(ref mut dirs) = self.dirs {
89 if let Some(ref mut dirs) = self.dirs {
89 dirs.add_path(filename)
90 dirs.add_path(filename)
90 }
91 }
91 }
92 }
92 if old_state == EntryState::Unknown {
93 if old_state == EntryState::Unknown {
93 if let Some(ref mut all_dirs) = self.all_dirs {
94 if let Some(ref mut all_dirs) = self.all_dirs {
94 all_dirs.add_path(filename)
95 all_dirs.add_path(filename)
95 }
96 }
96 }
97 }
97 self.state_map.insert(filename.to_owned(), entry.to_owned());
98 self.state_map.insert(filename.to_owned(), entry.to_owned());
98
99
99 if entry.state != EntryState::Normal || entry.mtime == MTIME_UNSET {
100 if entry.state != EntryState::Normal || entry.mtime == MTIME_UNSET {
100 self.non_normal_set.insert(filename.to_owned());
101 self.non_normal_set.insert(filename.to_owned());
101 }
102 }
102
103
103 if entry.size == SIZE_DIRTY {
104 if entry.size == SIZE_FROM_OTHER_PARENT {
104 self.other_parent_set.insert(filename.to_owned());
105 self.other_parent_set.insert(filename.to_owned());
105 }
106 }
106 }
107 }
107
108
108 /// Mark a file as removed in the dirstate.
109 /// Mark a file as removed in the dirstate.
109 ///
110 ///
110 /// The `size` parameter is used to store sentinel values that indicate
111 /// The `size` parameter is used to store sentinel values that indicate
111 /// the file's previous state. In the future, we should refactor this
112 /// the file's previous state. In the future, we should refactor this
112 /// to be more explicit about what that state is.
113 /// to be more explicit about what that state is.
113 pub fn remove_file(
114 pub fn remove_file(
114 &mut self,
115 &mut self,
115 filename: &HgPath,
116 filename: &HgPath,
116 old_state: EntryState,
117 old_state: EntryState,
117 size: i32,
118 size: i32,
118 ) -> Result<(), DirstateMapError> {
119 ) -> Result<(), DirstateMapError> {
119 if old_state != EntryState::Unknown && old_state != EntryState::Removed
120 if old_state != EntryState::Unknown && old_state != EntryState::Removed
120 {
121 {
121 if let Some(ref mut dirs) = self.dirs {
122 if let Some(ref mut dirs) = self.dirs {
122 dirs.delete_path(filename)?;
123 dirs.delete_path(filename)?;
123 }
124 }
124 }
125 }
125 if old_state == EntryState::Unknown {
126 if old_state == EntryState::Unknown {
126 if let Some(ref mut all_dirs) = self.all_dirs {
127 if let Some(ref mut all_dirs) = self.all_dirs {
127 all_dirs.add_path(filename);
128 all_dirs.add_path(filename);
128 }
129 }
129 }
130 }
130
131
131 if let Some(ref mut file_fold_map) = self.file_fold_map {
132 if let Some(ref mut file_fold_map) = self.file_fold_map {
132 file_fold_map.remove(&normalize_case(filename));
133 file_fold_map.remove(&normalize_case(filename));
133 }
134 }
134 self.state_map.insert(
135 self.state_map.insert(
135 filename.to_owned(),
136 filename.to_owned(),
136 DirstateEntry {
137 DirstateEntry {
137 state: EntryState::Removed,
138 state: EntryState::Removed,
138 mode: 0,
139 mode: 0,
139 size,
140 size,
140 mtime: 0,
141 mtime: 0,
141 },
142 },
142 );
143 );
143 self.non_normal_set.insert(filename.to_owned());
144 self.non_normal_set.insert(filename.to_owned());
144 Ok(())
145 Ok(())
145 }
146 }
146
147
147 /// Remove a file from the dirstate.
148 /// Remove a file from the dirstate.
148 /// Returns `true` if the file was previously recorded.
149 /// Returns `true` if the file was previously recorded.
149 pub fn drop_file(
150 pub fn drop_file(
150 &mut self,
151 &mut self,
151 filename: &HgPath,
152 filename: &HgPath,
152 old_state: EntryState,
153 old_state: EntryState,
153 ) -> Result<bool, DirstateMapError> {
154 ) -> Result<bool, DirstateMapError> {
154 let exists = self.state_map.remove(filename).is_some();
155 let exists = self.state_map.remove(filename).is_some();
155
156
156 if exists {
157 if exists {
157 if old_state != EntryState::Removed {
158 if old_state != EntryState::Removed {
158 if let Some(ref mut dirs) = self.dirs {
159 if let Some(ref mut dirs) = self.dirs {
159 dirs.delete_path(filename)?;
160 dirs.delete_path(filename)?;
160 }
161 }
161 }
162 }
162 if let Some(ref mut all_dirs) = self.all_dirs {
163 if let Some(ref mut all_dirs) = self.all_dirs {
163 all_dirs.delete_path(filename)?;
164 all_dirs.delete_path(filename)?;
164 }
165 }
165 }
166 }
166 if let Some(ref mut file_fold_map) = self.file_fold_map {
167 if let Some(ref mut file_fold_map) = self.file_fold_map {
167 file_fold_map.remove(&normalize_case(filename));
168 file_fold_map.remove(&normalize_case(filename));
168 }
169 }
169 self.non_normal_set.remove(filename);
170 self.non_normal_set.remove(filename);
170
171
171 Ok(exists)
172 Ok(exists)
172 }
173 }
173
174
174 pub fn clear_ambiguous_times(
175 pub fn clear_ambiguous_times(
175 &mut self,
176 &mut self,
176 filenames: Vec<HgPathBuf>,
177 filenames: Vec<HgPathBuf>,
177 now: i32,
178 now: i32,
178 ) {
179 ) {
179 for filename in filenames {
180 for filename in filenames {
180 let mut changed = false;
181 let mut changed = false;
181 self.state_map
182 self.state_map
182 .entry(filename.to_owned())
183 .entry(filename.to_owned())
183 .and_modify(|entry| {
184 .and_modify(|entry| {
184 if entry.state == EntryState::Normal && entry.mtime == now
185 if entry.state == EntryState::Normal && entry.mtime == now
185 {
186 {
186 changed = true;
187 changed = true;
187 *entry = DirstateEntry {
188 *entry = DirstateEntry {
188 mtime: MTIME_UNSET,
189 mtime: MTIME_UNSET,
189 ..*entry
190 ..*entry
190 };
191 };
191 }
192 }
192 });
193 });
193 if changed {
194 if changed {
194 self.non_normal_set.insert(filename.to_owned());
195 self.non_normal_set.insert(filename.to_owned());
195 }
196 }
196 }
197 }
197 }
198 }
198
199
199 pub fn non_normal_other_parent_entries(
200 pub fn non_normal_other_parent_entries(
200 &self,
201 &self,
201 ) -> (HashSet<HgPathBuf>, HashSet<HgPathBuf>) {
202 ) -> (HashSet<HgPathBuf>, HashSet<HgPathBuf>) {
202 let mut non_normal = HashSet::new();
203 let mut non_normal = HashSet::new();
203 let mut other_parent = HashSet::new();
204 let mut other_parent = HashSet::new();
204
205
205 for (
206 for (
206 filename,
207 filename,
207 DirstateEntry {
208 DirstateEntry {
208 state, size, mtime, ..
209 state, size, mtime, ..
209 },
210 },
210 ) in self.state_map.iter()
211 ) in self.state_map.iter()
211 {
212 {
212 if *state != EntryState::Normal || *mtime == MTIME_UNSET {
213 if *state != EntryState::Normal || *mtime == MTIME_UNSET {
213 non_normal.insert(filename.to_owned());
214 non_normal.insert(filename.to_owned());
214 }
215 }
215 if *state == EntryState::Normal && *size == SIZE_DIRTY {
216 if *state == EntryState::Normal && *size == SIZE_FROM_OTHER_PARENT
217 {
216 other_parent.insert(filename.to_owned());
218 other_parent.insert(filename.to_owned());
217 }
219 }
218 }
220 }
219
221
220 (non_normal, other_parent)
222 (non_normal, other_parent)
221 }
223 }
222
224
223 /// Both of these setters and their uses appear to be the simplest way to
225 /// Both of these setters and their uses appear to be the simplest way to
224 /// emulate a Python lazy property, but it is ugly and unidiomatic.
226 /// emulate a Python lazy property, but it is ugly and unidiomatic.
225 /// TODO One day, rewriting this struct using the typestate might be a
227 /// TODO One day, rewriting this struct using the typestate might be a
226 /// good idea.
228 /// good idea.
227 pub fn set_all_dirs(&mut self) {
229 pub fn set_all_dirs(&mut self) {
228 if self.all_dirs.is_none() {
230 if self.all_dirs.is_none() {
229 self.all_dirs =
231 self.all_dirs =
230 Some(DirsMultiset::from_dirstate(&self.state_map, None));
232 Some(DirsMultiset::from_dirstate(&self.state_map, None));
231 }
233 }
232 }
234 }
233
235
234 pub fn set_dirs(&mut self) {
236 pub fn set_dirs(&mut self) {
235 if self.dirs.is_none() {
237 if self.dirs.is_none() {
236 self.dirs = Some(DirsMultiset::from_dirstate(
238 self.dirs = Some(DirsMultiset::from_dirstate(
237 &self.state_map,
239 &self.state_map,
238 Some(EntryState::Removed),
240 Some(EntryState::Removed),
239 ));
241 ));
240 }
242 }
241 }
243 }
242
244
243 pub fn has_tracked_dir(&mut self, directory: &HgPath) -> bool {
245 pub fn has_tracked_dir(&mut self, directory: &HgPath) -> bool {
244 self.set_dirs();
246 self.set_dirs();
245 self.dirs.as_ref().unwrap().contains(directory)
247 self.dirs.as_ref().unwrap().contains(directory)
246 }
248 }
247
249
248 pub fn has_dir(&mut self, directory: &HgPath) -> bool {
250 pub fn has_dir(&mut self, directory: &HgPath) -> bool {
249 self.set_all_dirs();
251 self.set_all_dirs();
250 self.all_dirs.as_ref().unwrap().contains(directory)
252 self.all_dirs.as_ref().unwrap().contains(directory)
251 }
253 }
252
254
253 pub fn parents(
255 pub fn parents(
254 &mut self,
256 &mut self,
255 file_contents: &[u8],
257 file_contents: &[u8],
256 ) -> Result<&DirstateParents, DirstateError> {
258 ) -> Result<&DirstateParents, DirstateError> {
257 if let Some(ref parents) = self.parents {
259 if let Some(ref parents) = self.parents {
258 return Ok(parents);
260 return Ok(parents);
259 }
261 }
260 let parents;
262 let parents;
261 if file_contents.len() == PARENT_SIZE * 2 {
263 if file_contents.len() == PARENT_SIZE * 2 {
262 parents = DirstateParents {
264 parents = DirstateParents {
263 p1: file_contents[..PARENT_SIZE].try_into().unwrap(),
265 p1: file_contents[..PARENT_SIZE].try_into().unwrap(),
264 p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2]
266 p2: file_contents[PARENT_SIZE..PARENT_SIZE * 2]
265 .try_into()
267 .try_into()
266 .unwrap(),
268 .unwrap(),
267 };
269 };
268 } else if file_contents.is_empty() {
270 } else if file_contents.is_empty() {
269 parents = DirstateParents {
271 parents = DirstateParents {
270 p1: NULL_ID,
272 p1: NULL_ID,
271 p2: NULL_ID,
273 p2: NULL_ID,
272 };
274 };
273 } else {
275 } else {
274 return Err(DirstateError::Parse(DirstateParseError::Damaged));
276 return Err(DirstateError::Parse(DirstateParseError::Damaged));
275 }
277 }
276
278
277 self.parents = Some(parents);
279 self.parents = Some(parents);
278 Ok(self.parents.as_ref().unwrap())
280 Ok(self.parents.as_ref().unwrap())
279 }
281 }
280
282
281 pub fn set_parents(&mut self, parents: &DirstateParents) {
283 pub fn set_parents(&mut self, parents: &DirstateParents) {
282 self.parents = Some(parents.clone());
284 self.parents = Some(parents.clone());
283 self.dirty_parents = true;
285 self.dirty_parents = true;
284 }
286 }
285
287
286 pub fn read(
288 pub fn read(
287 &mut self,
289 &mut self,
288 file_contents: &[u8],
290 file_contents: &[u8],
289 ) -> Result<Option<DirstateParents>, DirstateError> {
291 ) -> Result<Option<DirstateParents>, DirstateError> {
290 if file_contents.is_empty() {
292 if file_contents.is_empty() {
291 return Ok(None);
293 return Ok(None);
292 }
294 }
293
295
294 let parents = parse_dirstate(
296 let parents = parse_dirstate(
295 &mut self.state_map,
297 &mut self.state_map,
296 &mut self.copy_map,
298 &mut self.copy_map,
297 file_contents,
299 file_contents,
298 )?;
300 )?;
299
301
300 if !self.dirty_parents {
302 if !self.dirty_parents {
301 self.set_parents(&parents);
303 self.set_parents(&parents);
302 }
304 }
303
305
304 Ok(Some(parents))
306 Ok(Some(parents))
305 }
307 }
306
308
307 pub fn pack(
309 pub fn pack(
308 &mut self,
310 &mut self,
309 parents: DirstateParents,
311 parents: DirstateParents,
310 now: Duration,
312 now: Duration,
311 ) -> Result<Vec<u8>, DirstateError> {
313 ) -> Result<Vec<u8>, DirstateError> {
312 let packed =
314 let packed =
313 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
315 pack_dirstate(&mut self.state_map, &self.copy_map, parents, now)?;
314
316
315 self.dirty_parents = false;
317 self.dirty_parents = false;
316
318
317 let result = self.non_normal_other_parent_entries();
319 let result = self.non_normal_other_parent_entries();
318 self.non_normal_set = result.0;
320 self.non_normal_set = result.0;
319 self.other_parent_set = result.1;
321 self.other_parent_set = result.1;
320 Ok(packed)
322 Ok(packed)
321 }
323 }
322
324
323 pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
325 pub fn build_file_fold_map(&mut self) -> &FileFoldMap {
324 if let Some(ref file_fold_map) = self.file_fold_map {
326 if let Some(ref file_fold_map) = self.file_fold_map {
325 return file_fold_map;
327 return file_fold_map;
326 }
328 }
327 let mut new_file_fold_map = FileFoldMap::new();
329 let mut new_file_fold_map = FileFoldMap::new();
328 for (filename, DirstateEntry { state, .. }) in self.state_map.borrow()
330 for (filename, DirstateEntry { state, .. }) in self.state_map.borrow()
329 {
331 {
330 if *state == EntryState::Removed {
332 if *state == EntryState::Removed {
331 new_file_fold_map
333 new_file_fold_map
332 .insert(normalize_case(filename), filename.to_owned());
334 .insert(normalize_case(filename), filename.to_owned());
333 }
335 }
334 }
336 }
335 self.file_fold_map = Some(new_file_fold_map);
337 self.file_fold_map = Some(new_file_fold_map);
336 self.file_fold_map.as_ref().unwrap()
338 self.file_fold_map.as_ref().unwrap()
337 }
339 }
338 }
340 }
339
341
340 #[cfg(test)]
342 #[cfg(test)]
341 mod tests {
343 mod tests {
342 use super::*;
344 use super::*;
343
345
344 #[test]
346 #[test]
345 fn test_dirs_multiset() {
347 fn test_dirs_multiset() {
346 let mut map = DirstateMap::new();
348 let mut map = DirstateMap::new();
347 assert!(map.dirs.is_none());
349 assert!(map.dirs.is_none());
348 assert!(map.all_dirs.is_none());
350 assert!(map.all_dirs.is_none());
349
351
350 assert_eq!(false, map.has_dir(HgPath::new(b"nope")));
352 assert_eq!(false, map.has_dir(HgPath::new(b"nope")));
351 assert!(map.all_dirs.is_some());
353 assert!(map.all_dirs.is_some());
352 assert!(map.dirs.is_none());
354 assert!(map.dirs.is_none());
353
355
354 assert_eq!(false, map.has_tracked_dir(HgPath::new(b"nope")));
356 assert_eq!(false, map.has_tracked_dir(HgPath::new(b"nope")));
355 assert!(map.dirs.is_some());
357 assert!(map.dirs.is_some());
356 }
358 }
357
359
358 #[test]
360 #[test]
359 fn test_add_file() {
361 fn test_add_file() {
360 let mut map = DirstateMap::new();
362 let mut map = DirstateMap::new();
361
363
362 assert_eq!(0, map.len());
364 assert_eq!(0, map.len());
363
365
364 map.add_file(
366 map.add_file(
365 HgPath::new(b"meh"),
367 HgPath::new(b"meh"),
366 EntryState::Normal,
368 EntryState::Normal,
367 DirstateEntry {
369 DirstateEntry {
368 state: EntryState::Normal,
370 state: EntryState::Normal,
369 mode: 1337,
371 mode: 1337,
370 mtime: 1337,
372 mtime: 1337,
371 size: 1337,
373 size: 1337,
372 },
374 },
373 );
375 );
374
376
375 assert_eq!(1, map.len());
377 assert_eq!(1, map.len());
376 assert_eq!(0, map.non_normal_set.len());
378 assert_eq!(0, map.non_normal_set.len());
377 assert_eq!(0, map.other_parent_set.len());
379 assert_eq!(0, map.other_parent_set.len());
378 }
380 }
379
381
380 #[test]
382 #[test]
381 fn test_non_normal_other_parent_entries() {
383 fn test_non_normal_other_parent_entries() {
382 let map: DirstateMap = [
384 let map: DirstateMap = [
383 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
385 (b"f1", (EntryState::Removed, 1337, 1337, 1337)),
384 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
386 (b"f2", (EntryState::Normal, 1337, 1337, -1)),
385 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
387 (b"f3", (EntryState::Normal, 1337, 1337, 1337)),
386 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
388 (b"f4", (EntryState::Normal, 1337, -2, 1337)),
387 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
389 (b"f5", (EntryState::Added, 1337, 1337, 1337)),
388 (b"f6", (EntryState::Added, 1337, 1337, -1)),
390 (b"f6", (EntryState::Added, 1337, 1337, -1)),
389 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
391 (b"f7", (EntryState::Merged, 1337, 1337, -1)),
390 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
392 (b"f8", (EntryState::Merged, 1337, 1337, 1337)),
391 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
393 (b"f9", (EntryState::Merged, 1337, -2, 1337)),
392 (b"fa", (EntryState::Added, 1337, -2, 1337)),
394 (b"fa", (EntryState::Added, 1337, -2, 1337)),
393 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
395 (b"fb", (EntryState::Removed, 1337, -2, 1337)),
394 ]
396 ]
395 .iter()
397 .iter()
396 .map(|(fname, (state, mode, size, mtime))| {
398 .map(|(fname, (state, mode, size, mtime))| {
397 (
399 (
398 HgPathBuf::from_bytes(fname.as_ref()),
400 HgPathBuf::from_bytes(fname.as_ref()),
399 DirstateEntry {
401 DirstateEntry {
400 state: *state,
402 state: *state,
401 mode: *mode,
403 mode: *mode,
402 size: *size,
404 size: *size,
403 mtime: *mtime,
405 mtime: *mtime,
404 },
406 },
405 )
407 )
406 })
408 })
407 .collect();
409 .collect();
408
410
409 let non_normal = [
411 let non_normal = [
410 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
412 b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
411 ]
413 ]
412 .iter()
414 .iter()
413 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
415 .map(|x| HgPathBuf::from_bytes(x.as_ref()))
414 .collect();
416 .collect();
415
417
416 let mut other_parent = HashSet::new();
418 let mut other_parent = HashSet::new();
417 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
419 other_parent.insert(HgPathBuf::from_bytes(b"f4"));
418
420
419 assert_eq!(
421 assert_eq!(
420 (non_normal, other_parent),
422 (non_normal, other_parent),
421 map.non_normal_other_parent_entries()
423 map.non_normal_other_parent_entries()
422 );
424 );
423 }
425 }
424 }
426 }
@@ -1,223 +1,228 b''
1 // status.rs
1 // status.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 //! Rust implementation of dirstate.status (dirstate.py).
8 //! Rust implementation of dirstate.status (dirstate.py).
9 //! It is currently missing a lot of functionality compared to the Python one
9 //! It is currently missing a lot of functionality compared to the Python one
10 //! and will only be triggered in narrow cases.
10 //! and will only be triggered in narrow cases.
11
11
12 use crate::utils::files::HgMetadata;
12 use crate::{
13 use crate::utils::hg_path::{hg_path_to_path_buf, HgPath};
13 dirstate::SIZE_FROM_OTHER_PARENT,
14 use crate::{CopyMap, DirstateEntry, DirstateMap, EntryState};
14 utils::{
15 files::HgMetadata,
16 hg_path::{hg_path_to_path_buf, HgPath},
17 },
18 CopyMap, DirstateEntry, DirstateMap, EntryState,
19 };
15 use rayon::prelude::*;
20 use rayon::prelude::*;
16 use std::path::Path;
21 use std::path::Path;
17
22
18 /// Marker enum used to dispatch new status entries into the right collections.
23 /// Marker enum used to dispatch new status entries into the right collections.
19 /// Is similar to `crate::EntryState`, but represents the transient state of
24 /// Is similar to `crate::EntryState`, but represents the transient state of
20 /// entries during the lifetime of a command.
25 /// entries during the lifetime of a command.
21 enum Dispatch {
26 enum Dispatch {
22 Unsure,
27 Unsure,
23 Modified,
28 Modified,
24 Added,
29 Added,
25 Removed,
30 Removed,
26 Deleted,
31 Deleted,
27 Clean,
32 Clean,
28 Unknown,
33 Unknown,
29 }
34 }
30
35
31 /// Dates and times that are outside the 31-bit signed range are compared
36 /// Dates and times that are outside the 31-bit signed range are compared
32 /// modulo 2^31. This should prevent hg from behaving badly with very large
37 /// modulo 2^31. This should prevent hg from behaving badly with very large
33 /// files or corrupt dates while still having a high probability of detecting
38 /// files or corrupt dates while still having a high probability of detecting
34 /// changes. (issue2608)
39 /// changes. (issue2608)
35 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
40 /// TODO I haven't found a way of having `b` be `Into<i32>`, since `From<u64>`
36 /// is not defined for `i32`, and there is no `As` trait. This forces the
41 /// is not defined for `i32`, and there is no `As` trait. This forces the
37 /// caller to cast `b` as `i32`.
42 /// caller to cast `b` as `i32`.
38 fn mod_compare(a: i32, b: i32) -> bool {
43 fn mod_compare(a: i32, b: i32) -> bool {
39 a & i32::max_value() != b & i32::max_value()
44 a & i32::max_value() != b & i32::max_value()
40 }
45 }
41
46
42 /// The file corresponding to the dirstate entry was found on the filesystem.
47 /// The file corresponding to the dirstate entry was found on the filesystem.
43 fn dispatch_found(
48 fn dispatch_found(
44 filename: impl AsRef<HgPath>,
49 filename: impl AsRef<HgPath>,
45 entry: DirstateEntry,
50 entry: DirstateEntry,
46 metadata: HgMetadata,
51 metadata: HgMetadata,
47 copy_map: &CopyMap,
52 copy_map: &CopyMap,
48 check_exec: bool,
53 check_exec: bool,
49 list_clean: bool,
54 list_clean: bool,
50 last_normal_time: i64,
55 last_normal_time: i64,
51 ) -> Dispatch {
56 ) -> Dispatch {
52 let DirstateEntry {
57 let DirstateEntry {
53 state,
58 state,
54 mode,
59 mode,
55 mtime,
60 mtime,
56 size,
61 size,
57 } = entry;
62 } = entry;
58
63
59 let HgMetadata {
64 let HgMetadata {
60 st_mode,
65 st_mode,
61 st_size,
66 st_size,
62 st_mtime,
67 st_mtime,
63 ..
68 ..
64 } = metadata;
69 } = metadata;
65
70
66 match state {
71 match state {
67 EntryState::Normal => {
72 EntryState::Normal => {
68 let size_changed = mod_compare(size, st_size as i32);
73 let size_changed = mod_compare(size, st_size as i32);
69 let mode_changed =
74 let mode_changed =
70 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
75 (mode ^ st_mode as i32) & 0o100 != 0o000 && check_exec;
71 let metadata_changed = size >= 0 && (size_changed || mode_changed);
76 let metadata_changed = size >= 0 && (size_changed || mode_changed);
72 let other_parent = size == -2;
77 let other_parent = size == SIZE_FROM_OTHER_PARENT;
73 if metadata_changed
78 if metadata_changed
74 || other_parent
79 || other_parent
75 || copy_map.contains_key(filename.as_ref())
80 || copy_map.contains_key(filename.as_ref())
76 {
81 {
77 Dispatch::Modified
82 Dispatch::Modified
78 } else if mod_compare(mtime, st_mtime as i32) {
83 } else if mod_compare(mtime, st_mtime as i32) {
79 Dispatch::Unsure
84 Dispatch::Unsure
80 } else if st_mtime == last_normal_time {
85 } else if st_mtime == last_normal_time {
81 // the file may have just been marked as normal and
86 // the file may have just been marked as normal and
82 // it may have changed in the same second without
87 // it may have changed in the same second without
83 // changing its size. This can happen if we quickly
88 // changing its size. This can happen if we quickly
84 // do multiple commits. Force lookup, so we don't
89 // do multiple commits. Force lookup, so we don't
85 // miss such a racy file change.
90 // miss such a racy file change.
86 Dispatch::Unsure
91 Dispatch::Unsure
87 } else if list_clean {
92 } else if list_clean {
88 Dispatch::Clean
93 Dispatch::Clean
89 } else {
94 } else {
90 Dispatch::Unknown
95 Dispatch::Unknown
91 }
96 }
92 }
97 }
93 EntryState::Merged => Dispatch::Modified,
98 EntryState::Merged => Dispatch::Modified,
94 EntryState::Added => Dispatch::Added,
99 EntryState::Added => Dispatch::Added,
95 EntryState::Removed => Dispatch::Removed,
100 EntryState::Removed => Dispatch::Removed,
96 EntryState::Unknown => Dispatch::Unknown,
101 EntryState::Unknown => Dispatch::Unknown,
97 }
102 }
98 }
103 }
99
104
100 /// The file corresponding to this Dirstate entry is missing.
105 /// The file corresponding to this Dirstate entry is missing.
101 fn dispatch_missing(state: EntryState) -> Dispatch {
106 fn dispatch_missing(state: EntryState) -> Dispatch {
102 match state {
107 match state {
103 // File was removed from the filesystem during commands
108 // File was removed from the filesystem during commands
104 EntryState::Normal | EntryState::Merged | EntryState::Added => {
109 EntryState::Normal | EntryState::Merged | EntryState::Added => {
105 Dispatch::Deleted
110 Dispatch::Deleted
106 }
111 }
107 // File was removed, everything is normal
112 // File was removed, everything is normal
108 EntryState::Removed => Dispatch::Removed,
113 EntryState::Removed => Dispatch::Removed,
109 // File is unknown to Mercurial, everything is normal
114 // File is unknown to Mercurial, everything is normal
110 EntryState::Unknown => Dispatch::Unknown,
115 EntryState::Unknown => Dispatch::Unknown,
111 }
116 }
112 }
117 }
113
118
114 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
119 /// Stat all entries in the `DirstateMap` and mark them for dispatch into
115 /// the relevant collections.
120 /// the relevant collections.
116 fn stat_dmap_entries(
121 fn stat_dmap_entries(
117 dmap: &DirstateMap,
122 dmap: &DirstateMap,
118 root_dir: impl AsRef<Path> + Sync + Send,
123 root_dir: impl AsRef<Path> + Sync + Send,
119 check_exec: bool,
124 check_exec: bool,
120 list_clean: bool,
125 list_clean: bool,
121 last_normal_time: i64,
126 last_normal_time: i64,
122 ) -> impl ParallelIterator<Item = std::io::Result<(&HgPath, Dispatch)>> {
127 ) -> impl ParallelIterator<Item = std::io::Result<(&HgPath, Dispatch)>> {
123 dmap.par_iter().map(move |(filename, entry)| {
128 dmap.par_iter().map(move |(filename, entry)| {
124 let filename: &HgPath = filename;
129 let filename: &HgPath = filename;
125 let filename_as_path = hg_path_to_path_buf(filename)?;
130 let filename_as_path = hg_path_to_path_buf(filename)?;
126 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
131 let meta = root_dir.as_ref().join(filename_as_path).symlink_metadata();
127
132
128 match meta {
133 match meta {
129 Ok(ref m)
134 Ok(ref m)
130 if !(m.file_type().is_file()
135 if !(m.file_type().is_file()
131 || m.file_type().is_symlink()) =>
136 || m.file_type().is_symlink()) =>
132 {
137 {
133 Ok((filename, dispatch_missing(entry.state)))
138 Ok((filename, dispatch_missing(entry.state)))
134 }
139 }
135 Ok(m) => Ok((
140 Ok(m) => Ok((
136 filename,
141 filename,
137 dispatch_found(
142 dispatch_found(
138 filename,
143 filename,
139 *entry,
144 *entry,
140 HgMetadata::from_metadata(m),
145 HgMetadata::from_metadata(m),
141 &dmap.copy_map,
146 &dmap.copy_map,
142 check_exec,
147 check_exec,
143 list_clean,
148 list_clean,
144 last_normal_time,
149 last_normal_time,
145 ),
150 ),
146 )),
151 )),
147 Err(ref e)
152 Err(ref e)
148 if e.kind() == std::io::ErrorKind::NotFound
153 if e.kind() == std::io::ErrorKind::NotFound
149 || e.raw_os_error() == Some(20) =>
154 || e.raw_os_error() == Some(20) =>
150 {
155 {
151 // Rust does not yet have an `ErrorKind` for
156 // Rust does not yet have an `ErrorKind` for
152 // `NotADirectory` (errno 20)
157 // `NotADirectory` (errno 20)
153 // It happens if the dirstate contains `foo/bar` and
158 // It happens if the dirstate contains `foo/bar` and
154 // foo is not a directory
159 // foo is not a directory
155 Ok((filename, dispatch_missing(entry.state)))
160 Ok((filename, dispatch_missing(entry.state)))
156 }
161 }
157 Err(e) => Err(e),
162 Err(e) => Err(e),
158 }
163 }
159 })
164 })
160 }
165 }
161
166
162 pub struct StatusResult<'a> {
167 pub struct StatusResult<'a> {
163 pub modified: Vec<&'a HgPath>,
168 pub modified: Vec<&'a HgPath>,
164 pub added: Vec<&'a HgPath>,
169 pub added: Vec<&'a HgPath>,
165 pub removed: Vec<&'a HgPath>,
170 pub removed: Vec<&'a HgPath>,
166 pub deleted: Vec<&'a HgPath>,
171 pub deleted: Vec<&'a HgPath>,
167 pub clean: Vec<&'a HgPath>,
172 pub clean: Vec<&'a HgPath>,
168 // TODO ignored
173 // TODO ignored
169 // TODO unknown
174 // TODO unknown
170 }
175 }
171
176
172 fn build_response(
177 fn build_response(
173 results: Vec<(&HgPath, Dispatch)>,
178 results: Vec<(&HgPath, Dispatch)>,
174 ) -> (Vec<&HgPath>, StatusResult) {
179 ) -> (Vec<&HgPath>, StatusResult) {
175 let mut lookup = vec![];
180 let mut lookup = vec![];
176 let mut modified = vec![];
181 let mut modified = vec![];
177 let mut added = vec![];
182 let mut added = vec![];
178 let mut removed = vec![];
183 let mut removed = vec![];
179 let mut deleted = vec![];
184 let mut deleted = vec![];
180 let mut clean = vec![];
185 let mut clean = vec![];
181
186
182 for (filename, dispatch) in results.into_iter() {
187 for (filename, dispatch) in results.into_iter() {
183 match dispatch {
188 match dispatch {
184 Dispatch::Unknown => {}
189 Dispatch::Unknown => {}
185 Dispatch::Unsure => lookup.push(filename),
190 Dispatch::Unsure => lookup.push(filename),
186 Dispatch::Modified => modified.push(filename),
191 Dispatch::Modified => modified.push(filename),
187 Dispatch::Added => added.push(filename),
192 Dispatch::Added => added.push(filename),
188 Dispatch::Removed => removed.push(filename),
193 Dispatch::Removed => removed.push(filename),
189 Dispatch::Deleted => deleted.push(filename),
194 Dispatch::Deleted => deleted.push(filename),
190 Dispatch::Clean => clean.push(filename),
195 Dispatch::Clean => clean.push(filename),
191 }
196 }
192 }
197 }
193
198
194 (
199 (
195 lookup,
200 lookup,
196 StatusResult {
201 StatusResult {
197 modified,
202 modified,
198 added,
203 added,
199 removed,
204 removed,
200 deleted,
205 deleted,
201 clean,
206 clean,
202 },
207 },
203 )
208 )
204 }
209 }
205
210
206 pub fn status(
211 pub fn status(
207 dmap: &DirstateMap,
212 dmap: &DirstateMap,
208 root_dir: impl AsRef<Path> + Sync + Send + Copy,
213 root_dir: impl AsRef<Path> + Sync + Send + Copy,
209 list_clean: bool,
214 list_clean: bool,
210 last_normal_time: i64,
215 last_normal_time: i64,
211 check_exec: bool,
216 check_exec: bool,
212 ) -> std::io::Result<(Vec<&HgPath>, StatusResult)> {
217 ) -> std::io::Result<(Vec<&HgPath>, StatusResult)> {
213 let results: std::io::Result<_> = stat_dmap_entries(
218 let results: std::io::Result<_> = stat_dmap_entries(
214 &dmap,
219 &dmap,
215 root_dir,
220 root_dir,
216 check_exec,
221 check_exec,
217 list_clean,
222 list_clean,
218 last_normal_time,
223 last_normal_time,
219 )
224 )
220 .collect();
225 .collect();
221
226
222 Ok(build_response(results?))
227 Ok(build_response(results?))
223 }
228 }
General Comments 0
You need to be logged in to leave comments. Login now