##// END OF EJS Templates
dirstate-tree: Add #[timed] attribute to `status` and `DirstateMap::read`...
Simon Sapin -
r47888:c92e6376 default
parent child Browse files
Show More
@@ -1,650 +1,652 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use std::path::PathBuf;
3 use std::path::PathBuf;
3 use std::{collections::BTreeMap, convert::TryInto};
4 use std::{collections::BTreeMap, convert::TryInto};
4
5
5 use super::path_with_basename::WithBasename;
6 use super::path_with_basename::WithBasename;
6 use crate::dirstate::parsers::clear_ambiguous_mtime;
7 use crate::dirstate::parsers::clear_ambiguous_mtime;
7 use crate::dirstate::parsers::pack_entry;
8 use crate::dirstate::parsers::pack_entry;
8 use crate::dirstate::parsers::packed_entry_size;
9 use crate::dirstate::parsers::packed_entry_size;
9 use crate::dirstate::parsers::parse_dirstate_entries;
10 use crate::dirstate::parsers::parse_dirstate_entries;
10 use crate::dirstate::parsers::parse_dirstate_parents;
11 use crate::dirstate::parsers::parse_dirstate_parents;
11 use crate::dirstate::parsers::Timestamp;
12 use crate::dirstate::parsers::Timestamp;
12 use crate::matchers::Matcher;
13 use crate::matchers::Matcher;
13 use crate::revlog::node::NULL_NODE;
14 use crate::revlog::node::NULL_NODE;
14 use crate::utils::hg_path::{HgPath, HgPathBuf};
15 use crate::utils::hg_path::{HgPath, HgPathBuf};
15 use crate::CopyMapIter;
16 use crate::CopyMapIter;
16 use crate::DirstateEntry;
17 use crate::DirstateEntry;
17 use crate::DirstateError;
18 use crate::DirstateError;
18 use crate::DirstateMapError;
19 use crate::DirstateMapError;
19 use crate::DirstateParents;
20 use crate::DirstateParents;
20 use crate::DirstateStatus;
21 use crate::DirstateStatus;
21 use crate::EntryState;
22 use crate::EntryState;
22 use crate::PatternFileWarning;
23 use crate::PatternFileWarning;
23 use crate::StateMapIter;
24 use crate::StateMapIter;
24 use crate::StatusError;
25 use crate::StatusError;
25 use crate::StatusOptions;
26 use crate::StatusOptions;
26
27
27 pub struct DirstateMap {
28 pub struct DirstateMap {
28 parents: Option<DirstateParents>,
29 parents: Option<DirstateParents>,
29 dirty_parents: bool,
30 dirty_parents: bool,
30 pub(super) root: ChildNodes,
31 pub(super) root: ChildNodes,
31
32
32 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
33 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
33 nodes_with_entry_count: usize,
34 nodes_with_entry_count: usize,
34
35
35 /// Number of nodes anywhere in the tree that have
36 /// Number of nodes anywhere in the tree that have
36 /// `.copy_source.is_some()`.
37 /// `.copy_source.is_some()`.
37 nodes_with_copy_source_count: usize,
38 nodes_with_copy_source_count: usize,
38 }
39 }
39
40
40 /// Using a plain `HgPathBuf` of the full path from the repository root as a
41 /// Using a plain `HgPathBuf` of the full path from the repository root as a
41 /// map key would also work: all paths in a given map have the same parent
42 /// map key would also work: all paths in a given map have the same parent
42 /// path, so comparing full paths gives the same result as comparing base
43 /// path, so comparing full paths gives the same result as comparing base
43 /// names. However `BTreeMap` would waste time always re-comparing the same
44 /// names. However `BTreeMap` would waste time always re-comparing the same
44 /// string prefix.
45 /// string prefix.
45 pub(super) type ChildNodes = BTreeMap<WithBasename<HgPathBuf>, Node>;
46 pub(super) type ChildNodes = BTreeMap<WithBasename<HgPathBuf>, Node>;
46
47
47 /// Represents a file or a directory
48 /// Represents a file or a directory
48 #[derive(Default)]
49 #[derive(Default)]
49 pub(super) struct Node {
50 pub(super) struct Node {
50 /// `None` for directories
51 /// `None` for directories
51 pub(super) entry: Option<DirstateEntry>,
52 pub(super) entry: Option<DirstateEntry>,
52
53
53 pub(super) copy_source: Option<HgPathBuf>,
54 pub(super) copy_source: Option<HgPathBuf>,
54
55
55 pub(super) children: ChildNodes,
56 pub(super) children: ChildNodes,
56
57
57 /// How many (non-inclusive) descendants of this node are tracked files
58 /// How many (non-inclusive) descendants of this node are tracked files
58 tracked_descendants_count: usize,
59 tracked_descendants_count: usize,
59 }
60 }
60
61
61 impl Node {
62 impl Node {
62 /// Whether this node has a `DirstateEntry` with `.state.is_tracked()`
63 /// Whether this node has a `DirstateEntry` with `.state.is_tracked()`
63 fn is_tracked_file(&self) -> bool {
64 fn is_tracked_file(&self) -> bool {
64 if let Some(entry) = &self.entry {
65 if let Some(entry) = &self.entry {
65 entry.state.is_tracked()
66 entry.state.is_tracked()
66 } else {
67 } else {
67 false
68 false
68 }
69 }
69 }
70 }
70
71
71 pub(super) fn state(&self) -> Option<EntryState> {
72 pub(super) fn state(&self) -> Option<EntryState> {
72 self.entry.as_ref().map(|entry| entry.state)
73 self.entry.as_ref().map(|entry| entry.state)
73 }
74 }
74 }
75 }
75
76
76 /// `(full_path, entry, copy_source)`
77 /// `(full_path, entry, copy_source)`
77 type NodeDataMut<'a> = (
78 type NodeDataMut<'a> = (
78 &'a WithBasename<HgPathBuf>,
79 &'a WithBasename<HgPathBuf>,
79 &'a mut Option<DirstateEntry>,
80 &'a mut Option<DirstateEntry>,
80 &'a mut Option<HgPathBuf>,
81 &'a mut Option<HgPathBuf>,
81 );
82 );
82
83
83 impl DirstateMap {
84 impl DirstateMap {
84 pub fn new() -> Self {
85 pub fn new() -> Self {
85 Self {
86 Self {
86 parents: None,
87 parents: None,
87 dirty_parents: false,
88 dirty_parents: false,
88 root: ChildNodes::new(),
89 root: ChildNodes::new(),
89 nodes_with_entry_count: 0,
90 nodes_with_entry_count: 0,
90 nodes_with_copy_source_count: 0,
91 nodes_with_copy_source_count: 0,
91 }
92 }
92 }
93 }
93
94
94 fn get_node(&self, path: &HgPath) -> Option<&Node> {
95 fn get_node(&self, path: &HgPath) -> Option<&Node> {
95 let mut children = &self.root;
96 let mut children = &self.root;
96 let mut components = path.components();
97 let mut components = path.components();
97 let mut component =
98 let mut component =
98 components.next().expect("expected at least one components");
99 components.next().expect("expected at least one components");
99 loop {
100 loop {
100 let child = children.get(component)?;
101 let child = children.get(component)?;
101 if let Some(next_component) = components.next() {
102 if let Some(next_component) = components.next() {
102 component = next_component;
103 component = next_component;
103 children = &child.children;
104 children = &child.children;
104 } else {
105 } else {
105 return Some(child);
106 return Some(child);
106 }
107 }
107 }
108 }
108 }
109 }
109
110
110 /// Returns a mutable reference to the node at `path` if it exists
111 /// Returns a mutable reference to the node at `path` if it exists
111 ///
112 ///
112 /// This takes `root` instead of `&mut self` so that callers can mutate
113 /// This takes `root` instead of `&mut self` so that callers can mutate
113 /// other fields while the returned borrow is still valid
114 /// other fields while the returned borrow is still valid
114 fn get_node_mut<'tree>(
115 fn get_node_mut<'tree>(
115 root: &'tree mut ChildNodes,
116 root: &'tree mut ChildNodes,
116 path: &HgPath,
117 path: &HgPath,
117 ) -> Option<&'tree mut Node> {
118 ) -> Option<&'tree mut Node> {
118 Self::each_and_get(root, path, |_| {})
119 Self::each_and_get(root, path, |_| {})
119 }
120 }
120
121
121 /// Call `each` for each ancestor node of the one at `path` (not including
122 /// Call `each` for each ancestor node of the one at `path` (not including
122 /// that node itself), starting from nearest the root.
123 /// that node itself), starting from nearest the root.
123 ///
124 ///
124 /// Panics (possibly after some calls to `each`) if there is no node at
125 /// Panics (possibly after some calls to `each`) if there is no node at
125 /// `path`.
126 /// `path`.
126 fn for_each_ancestor_node<'tree>(
127 fn for_each_ancestor_node<'tree>(
127 &mut self,
128 &mut self,
128 path: &HgPath,
129 path: &HgPath,
129 each: impl FnMut(&mut Node),
130 each: impl FnMut(&mut Node),
130 ) {
131 ) {
131 let parent = path.parent();
132 let parent = path.parent();
132 if !parent.is_empty() {
133 if !parent.is_empty() {
133 Self::each_and_get(&mut self.root, parent, each)
134 Self::each_and_get(&mut self.root, parent, each)
134 .expect("missing dirstate node");
135 .expect("missing dirstate node");
135 }
136 }
136 }
137 }
137
138
138 /// Common implementation detail of `get_node_mut` and
139 /// Common implementation detail of `get_node_mut` and
139 /// `for_each_ancestor_node`
140 /// `for_each_ancestor_node`
140 fn each_and_get<'tree>(
141 fn each_and_get<'tree>(
141 root: &'tree mut ChildNodes,
142 root: &'tree mut ChildNodes,
142 path: &HgPath,
143 path: &HgPath,
143 mut each: impl FnMut(&mut Node),
144 mut each: impl FnMut(&mut Node),
144 ) -> Option<&'tree mut Node> {
145 ) -> Option<&'tree mut Node> {
145 let mut children = root;
146 let mut children = root;
146 let mut components = path.components();
147 let mut components = path.components();
147 let mut component =
148 let mut component =
148 components.next().expect("expected at least one components");
149 components.next().expect("expected at least one components");
149 loop {
150 loop {
150 let child = children.get_mut(component)?;
151 let child = children.get_mut(component)?;
151 each(child);
152 each(child);
152 if let Some(next_component) = components.next() {
153 if let Some(next_component) = components.next() {
153 component = next_component;
154 component = next_component;
154 children = &mut child.children;
155 children = &mut child.children;
155 } else {
156 } else {
156 return Some(child);
157 return Some(child);
157 }
158 }
158 }
159 }
159 }
160 }
160
161
161 fn get_or_insert_node<'tree>(
162 fn get_or_insert_node<'tree>(
162 root: &'tree mut ChildNodes,
163 root: &'tree mut ChildNodes,
163 path: &HgPath,
164 path: &HgPath,
164 ) -> &'tree mut Node {
165 ) -> &'tree mut Node {
165 let mut child_nodes = root;
166 let mut child_nodes = root;
166 let mut inclusive_ancestor_paths =
167 let mut inclusive_ancestor_paths =
167 WithBasename::inclusive_ancestors_of(path);
168 WithBasename::inclusive_ancestors_of(path);
168 let mut ancestor_path = inclusive_ancestor_paths
169 let mut ancestor_path = inclusive_ancestor_paths
169 .next()
170 .next()
170 .expect("expected at least one inclusive ancestor");
171 .expect("expected at least one inclusive ancestor");
171 loop {
172 loop {
172 // TODO: can we avoid allocating an owned key in cases where the
173 // TODO: can we avoid allocating an owned key in cases where the
173 // map already contains that key, without introducing double
174 // map already contains that key, without introducing double
174 // lookup?
175 // lookup?
175 let child_node =
176 let child_node =
176 child_nodes.entry(ancestor_path.to_owned()).or_default();
177 child_nodes.entry(ancestor_path.to_owned()).or_default();
177 if let Some(next) = inclusive_ancestor_paths.next() {
178 if let Some(next) = inclusive_ancestor_paths.next() {
178 ancestor_path = next;
179 ancestor_path = next;
179 child_nodes = &mut child_node.children;
180 child_nodes = &mut child_node.children;
180 } else {
181 } else {
181 return child_node;
182 return child_node;
182 }
183 }
183 }
184 }
184 }
185 }
185
186
186 /// The meaning of `new_copy_source` is:
187 /// The meaning of `new_copy_source` is:
187 ///
188 ///
188 /// * `Some(Some(x))`: set `Node::copy_source` to `Some(x)`
189 /// * `Some(Some(x))`: set `Node::copy_source` to `Some(x)`
189 /// * `Some(None)`: set `Node::copy_source` to `None`
190 /// * `Some(None)`: set `Node::copy_source` to `None`
190 /// * `None`: leave `Node::copy_source` unchanged
191 /// * `None`: leave `Node::copy_source` unchanged
191 fn add_file_node(
192 fn add_file_node(
192 &mut self,
193 &mut self,
193 path: &HgPath,
194 path: &HgPath,
194 new_entry: DirstateEntry,
195 new_entry: DirstateEntry,
195 new_copy_source: Option<Option<HgPathBuf>>,
196 new_copy_source: Option<Option<HgPathBuf>>,
196 ) {
197 ) {
197 let node = Self::get_or_insert_node(&mut self.root, path);
198 let node = Self::get_or_insert_node(&mut self.root, path);
198 if node.entry.is_none() {
199 if node.entry.is_none() {
199 self.nodes_with_entry_count += 1
200 self.nodes_with_entry_count += 1
200 }
201 }
201 if let Some(source) = &new_copy_source {
202 if let Some(source) = &new_copy_source {
202 if node.copy_source.is_none() && source.is_some() {
203 if node.copy_source.is_none() && source.is_some() {
203 self.nodes_with_copy_source_count += 1
204 self.nodes_with_copy_source_count += 1
204 }
205 }
205 if node.copy_source.is_some() && source.is_none() {
206 if node.copy_source.is_some() && source.is_none() {
206 self.nodes_with_copy_source_count -= 1
207 self.nodes_with_copy_source_count -= 1
207 }
208 }
208 }
209 }
209 let tracked_count_increment =
210 let tracked_count_increment =
210 match (node.is_tracked_file(), new_entry.state.is_tracked()) {
211 match (node.is_tracked_file(), new_entry.state.is_tracked()) {
211 (false, true) => 1,
212 (false, true) => 1,
212 (true, false) => -1,
213 (true, false) => -1,
213 _ => 0,
214 _ => 0,
214 };
215 };
215
216
216 node.entry = Some(new_entry);
217 node.entry = Some(new_entry);
217 if let Some(source) = new_copy_source {
218 if let Some(source) = new_copy_source {
218 node.copy_source = source
219 node.copy_source = source
219 }
220 }
220 // Borrow of `self.root` through `node` ends here
221 // Borrow of `self.root` through `node` ends here
221
222
222 match tracked_count_increment {
223 match tracked_count_increment {
223 1 => self.for_each_ancestor_node(path, |node| {
224 1 => self.for_each_ancestor_node(path, |node| {
224 node.tracked_descendants_count += 1
225 node.tracked_descendants_count += 1
225 }),
226 }),
226 // We can’t use `+= -1` because the counter is unsigned
227 // We can’t use `+= -1` because the counter is unsigned
227 -1 => self.for_each_ancestor_node(path, |node| {
228 -1 => self.for_each_ancestor_node(path, |node| {
228 node.tracked_descendants_count -= 1
229 node.tracked_descendants_count -= 1
229 }),
230 }),
230 _ => {}
231 _ => {}
231 }
232 }
232 }
233 }
233
234
234 fn iter_nodes<'a>(
235 fn iter_nodes<'a>(
235 &'a self,
236 &'a self,
236 ) -> impl Iterator<Item = (&'a WithBasename<HgPathBuf>, &'a Node)> + 'a
237 ) -> impl Iterator<Item = (&'a WithBasename<HgPathBuf>, &'a Node)> + 'a
237 {
238 {
238 // Depth first tree traversal.
239 // Depth first tree traversal.
239 //
240 //
240 // If we could afford internal iteration and recursion,
241 // If we could afford internal iteration and recursion,
241 // this would look like:
242 // this would look like:
242 //
243 //
243 // ```
244 // ```
244 // fn traverse_children(
245 // fn traverse_children(
245 // children: &ChildNodes,
246 // children: &ChildNodes,
246 // each: &mut impl FnMut(&Node),
247 // each: &mut impl FnMut(&Node),
247 // ) {
248 // ) {
248 // for child in children.values() {
249 // for child in children.values() {
249 // traverse_children(&child.children, each);
250 // traverse_children(&child.children, each);
250 // each(child);
251 // each(child);
251 // }
252 // }
252 // }
253 // }
253 // ```
254 // ```
254 //
255 //
255 // However we want an external iterator and therefore can’t use the
256 // However we want an external iterator and therefore can’t use the
256 // call stack. Use an explicit stack instead:
257 // call stack. Use an explicit stack instead:
257 let mut stack = Vec::new();
258 let mut stack = Vec::new();
258 let mut iter = self.root.iter();
259 let mut iter = self.root.iter();
259 std::iter::from_fn(move || {
260 std::iter::from_fn(move || {
260 while let Some((key, child_node)) = iter.next() {
261 while let Some((key, child_node)) = iter.next() {
261 // Pseudo-recursion
262 // Pseudo-recursion
262 let new_iter = child_node.children.iter();
263 let new_iter = child_node.children.iter();
263 let old_iter = std::mem::replace(&mut iter, new_iter);
264 let old_iter = std::mem::replace(&mut iter, new_iter);
264 stack.push((key, child_node, old_iter));
265 stack.push((key, child_node, old_iter));
265 }
266 }
266 // Found the end of a `children.iter()` iterator.
267 // Found the end of a `children.iter()` iterator.
267 if let Some((key, child_node, next_iter)) = stack.pop() {
268 if let Some((key, child_node, next_iter)) = stack.pop() {
268 // "Return" from pseudo-recursion by restoring state from the
269 // "Return" from pseudo-recursion by restoring state from the
269 // explicit stack
270 // explicit stack
270 iter = next_iter;
271 iter = next_iter;
271
272
272 Some((key, child_node))
273 Some((key, child_node))
273 } else {
274 } else {
274 // Reached the bottom of the stack, we’re done
275 // Reached the bottom of the stack, we’re done
275 None
276 None
276 }
277 }
277 })
278 })
278 }
279 }
279
280
280 /// Mutable iterator for the `(entry, copy source)` of each node.
281 /// Mutable iterator for the `(entry, copy source)` of each node.
281 ///
282 ///
282 /// It would not be safe to yield mutable references to nodes themeselves
283 /// It would not be safe to yield mutable references to nodes themeselves
283 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
284 /// with `-> impl Iterator<Item = &mut Node>` since child nodes are
284 /// reachable from their ancestor nodes, potentially creating multiple
285 /// reachable from their ancestor nodes, potentially creating multiple
285 /// `&mut` references to a given node.
286 /// `&mut` references to a given node.
286 fn iter_node_data_mut<'a>(
287 fn iter_node_data_mut<'a>(
287 &'a mut self,
288 &'a mut self,
288 ) -> impl Iterator<Item = NodeDataMut<'a>> + 'a {
289 ) -> impl Iterator<Item = NodeDataMut<'a>> + 'a {
289 // Explict stack for pseudo-recursion, see `iter_nodes` above.
290 // Explict stack for pseudo-recursion, see `iter_nodes` above.
290 let mut stack = Vec::new();
291 let mut stack = Vec::new();
291 let mut iter = self.root.iter_mut();
292 let mut iter = self.root.iter_mut();
292 std::iter::from_fn(move || {
293 std::iter::from_fn(move || {
293 while let Some((key, child_node)) = iter.next() {
294 while let Some((key, child_node)) = iter.next() {
294 // Pseudo-recursion
295 // Pseudo-recursion
295 let data =
296 let data =
296 (key, &mut child_node.entry, &mut child_node.copy_source);
297 (key, &mut child_node.entry, &mut child_node.copy_source);
297 let new_iter = child_node.children.iter_mut();
298 let new_iter = child_node.children.iter_mut();
298 let old_iter = std::mem::replace(&mut iter, new_iter);
299 let old_iter = std::mem::replace(&mut iter, new_iter);
299 stack.push((data, old_iter));
300 stack.push((data, old_iter));
300 }
301 }
301 // Found the end of a `children.values_mut()` iterator.
302 // Found the end of a `children.values_mut()` iterator.
302 if let Some((data, next_iter)) = stack.pop() {
303 if let Some((data, next_iter)) = stack.pop() {
303 // "Return" from pseudo-recursion by restoring state from the
304 // "Return" from pseudo-recursion by restoring state from the
304 // explicit stack
305 // explicit stack
305 iter = next_iter;
306 iter = next_iter;
306
307
307 Some(data)
308 Some(data)
308 } else {
309 } else {
309 // Reached the bottom of the stack, we’re done
310 // Reached the bottom of the stack, we’re done
310 None
311 None
311 }
312 }
312 })
313 })
313 }
314 }
314 }
315 }
315
316
316 impl super::dispatch::DirstateMapMethods for DirstateMap {
317 impl super::dispatch::DirstateMapMethods for DirstateMap {
317 fn clear(&mut self) {
318 fn clear(&mut self) {
318 self.set_parents(&DirstateParents {
319 self.set_parents(&DirstateParents {
319 p1: NULL_NODE,
320 p1: NULL_NODE,
320 p2: NULL_NODE,
321 p2: NULL_NODE,
321 });
322 });
322 self.root.clear();
323 self.root.clear();
323 self.nodes_with_entry_count = 0;
324 self.nodes_with_entry_count = 0;
324 self.nodes_with_copy_source_count = 0;
325 self.nodes_with_copy_source_count = 0;
325 }
326 }
326
327
327 fn add_file(
328 fn add_file(
328 &mut self,
329 &mut self,
329 filename: &HgPath,
330 filename: &HgPath,
330 _old_state: EntryState,
331 _old_state: EntryState,
331 entry: DirstateEntry,
332 entry: DirstateEntry,
332 ) -> Result<(), DirstateMapError> {
333 ) -> Result<(), DirstateMapError> {
333 self.add_file_node(filename, entry, None);
334 self.add_file_node(filename, entry, None);
334 Ok(())
335 Ok(())
335 }
336 }
336
337
337 fn remove_file(
338 fn remove_file(
338 &mut self,
339 &mut self,
339 filename: &HgPath,
340 filename: &HgPath,
340 _old_state: EntryState,
341 _old_state: EntryState,
341 size: i32,
342 size: i32,
342 ) -> Result<(), DirstateMapError> {
343 ) -> Result<(), DirstateMapError> {
343 let entry = DirstateEntry {
344 let entry = DirstateEntry {
344 state: EntryState::Removed,
345 state: EntryState::Removed,
345 mode: 0,
346 mode: 0,
346 size,
347 size,
347 mtime: 0,
348 mtime: 0,
348 };
349 };
349 self.add_file_node(filename, entry, None);
350 self.add_file_node(filename, entry, None);
350 Ok(())
351 Ok(())
351 }
352 }
352
353
353 fn drop_file(
354 fn drop_file(
354 &mut self,
355 &mut self,
355 filename: &HgPath,
356 filename: &HgPath,
356 _old_state: EntryState,
357 _old_state: EntryState,
357 ) -> Result<bool, DirstateMapError> {
358 ) -> Result<bool, DirstateMapError> {
358 if let Some(node) = Self::get_node_mut(&mut self.root, filename) {
359 if let Some(node) = Self::get_node_mut(&mut self.root, filename) {
359 let was_tracked = node.is_tracked_file();
360 let was_tracked = node.is_tracked_file();
360 let had_entry = node.entry.is_some();
361 let had_entry = node.entry.is_some();
361 let had_copy_source = node.copy_source.is_some();
362 let had_copy_source = node.copy_source.is_some();
362
363
363 // TODO: this leaves in the tree a "non-file" node. Should we
364 // TODO: this leaves in the tree a "non-file" node. Should we
364 // remove the node instead, together with ancestor nodes for
365 // remove the node instead, together with ancestor nodes for
365 // directories that become empty?
366 // directories that become empty?
366 node.entry = None;
367 node.entry = None;
367 node.copy_source = None;
368 node.copy_source = None;
368
369
369 if had_entry {
370 if had_entry {
370 self.nodes_with_entry_count -= 1
371 self.nodes_with_entry_count -= 1
371 }
372 }
372 if had_copy_source {
373 if had_copy_source {
373 self.nodes_with_copy_source_count -= 1
374 self.nodes_with_copy_source_count -= 1
374 }
375 }
375 if was_tracked {
376 if was_tracked {
376 self.for_each_ancestor_node(filename, |node| {
377 self.for_each_ancestor_node(filename, |node| {
377 node.tracked_descendants_count -= 1
378 node.tracked_descendants_count -= 1
378 })
379 })
379 }
380 }
380 Ok(had_entry)
381 Ok(had_entry)
381 } else {
382 } else {
382 Ok(false)
383 Ok(false)
383 }
384 }
384 }
385 }
385
386
386 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
387 fn clear_ambiguous_times(&mut self, filenames: Vec<HgPathBuf>, now: i32) {
387 for filename in filenames {
388 for filename in filenames {
388 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
389 if let Some(node) = Self::get_node_mut(&mut self.root, &filename) {
389 if let Some(entry) = node.entry.as_mut() {
390 if let Some(entry) = node.entry.as_mut() {
390 clear_ambiguous_mtime(entry, now);
391 clear_ambiguous_mtime(entry, now);
391 }
392 }
392 }
393 }
393 }
394 }
394 }
395 }
395
396
396 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
397 fn non_normal_entries_contains(&mut self, key: &HgPath) -> bool {
397 self.get_node(key)
398 self.get_node(key)
398 .and_then(|node| node.entry.as_ref())
399 .and_then(|node| node.entry.as_ref())
399 .map_or(false, DirstateEntry::is_non_normal)
400 .map_or(false, DirstateEntry::is_non_normal)
400 }
401 }
401
402
402 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
403 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
403 // Do nothing, this `DirstateMap` does not have a separate "non normal
404 // Do nothing, this `DirstateMap` does not have a separate "non normal
404 // entries" set that need to be kept up to date
405 // entries" set that need to be kept up to date
405 }
406 }
406
407
407 fn non_normal_or_other_parent_paths(
408 fn non_normal_or_other_parent_paths(
408 &mut self,
409 &mut self,
409 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_> {
410 ) -> Box<dyn Iterator<Item = &HgPathBuf> + '_> {
410 Box::new(self.iter_nodes().filter_map(|(path, node)| {
411 Box::new(self.iter_nodes().filter_map(|(path, node)| {
411 node.entry
412 node.entry
412 .as_ref()
413 .as_ref()
413 .filter(|entry| {
414 .filter(|entry| {
414 entry.is_non_normal() || entry.is_from_other_parent()
415 entry.is_non_normal() || entry.is_from_other_parent()
415 })
416 })
416 .map(|_| path.full_path())
417 .map(|_| path.full_path())
417 }))
418 }))
418 }
419 }
419
420
420 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
421 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
421 // Do nothing, this `DirstateMap` does not have a separate "non normal
422 // Do nothing, this `DirstateMap` does not have a separate "non normal
422 // entries" and "from other parent" sets that need to be recomputed
423 // entries" and "from other parent" sets that need to be recomputed
423 }
424 }
424
425
425 fn iter_non_normal_paths(
426 fn iter_non_normal_paths(
426 &mut self,
427 &mut self,
427 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
428 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
428 self.iter_non_normal_paths_panic()
429 self.iter_non_normal_paths_panic()
429 }
430 }
430
431
431 fn iter_non_normal_paths_panic(
432 fn iter_non_normal_paths_panic(
432 &self,
433 &self,
433 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
434 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
434 Box::new(self.iter_nodes().filter_map(|(path, node)| {
435 Box::new(self.iter_nodes().filter_map(|(path, node)| {
435 node.entry
436 node.entry
436 .as_ref()
437 .as_ref()
437 .filter(|entry| entry.is_non_normal())
438 .filter(|entry| entry.is_non_normal())
438 .map(|_| path.full_path())
439 .map(|_| path.full_path())
439 }))
440 }))
440 }
441 }
441
442
442 fn iter_other_parent_paths(
443 fn iter_other_parent_paths(
443 &mut self,
444 &mut self,
444 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
445 ) -> Box<dyn Iterator<Item = &HgPathBuf> + Send + '_> {
445 Box::new(self.iter_nodes().filter_map(|(path, node)| {
446 Box::new(self.iter_nodes().filter_map(|(path, node)| {
446 node.entry
447 node.entry
447 .as_ref()
448 .as_ref()
448 .filter(|entry| entry.is_from_other_parent())
449 .filter(|entry| entry.is_from_other_parent())
449 .map(|_| path.full_path())
450 .map(|_| path.full_path())
450 }))
451 }))
451 }
452 }
452
453
453 fn has_tracked_dir(
454 fn has_tracked_dir(
454 &mut self,
455 &mut self,
455 directory: &HgPath,
456 directory: &HgPath,
456 ) -> Result<bool, DirstateMapError> {
457 ) -> Result<bool, DirstateMapError> {
457 if let Some(node) = self.get_node(directory) {
458 if let Some(node) = self.get_node(directory) {
458 // A node without a `DirstateEntry` was created to hold child
459 // A node without a `DirstateEntry` was created to hold child
459 // nodes, and is therefore a directory.
460 // nodes, and is therefore a directory.
460 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
461 Ok(node.entry.is_none() && node.tracked_descendants_count > 0)
461 } else {
462 } else {
462 Ok(false)
463 Ok(false)
463 }
464 }
464 }
465 }
465
466
466 fn has_dir(
467 fn has_dir(
467 &mut self,
468 &mut self,
468 directory: &HgPath,
469 directory: &HgPath,
469 ) -> Result<bool, DirstateMapError> {
470 ) -> Result<bool, DirstateMapError> {
470 if let Some(node) = self.get_node(directory) {
471 if let Some(node) = self.get_node(directory) {
471 // A node without a `DirstateEntry` was created to hold child
472 // A node without a `DirstateEntry` was created to hold child
472 // nodes, and is therefore a directory.
473 // nodes, and is therefore a directory.
473 Ok(node.entry.is_none())
474 Ok(node.entry.is_none())
474 } else {
475 } else {
475 Ok(false)
476 Ok(false)
476 }
477 }
477 }
478 }
478
479
479 fn parents(
480 fn parents(
480 &mut self,
481 &mut self,
481 file_contents: &[u8],
482 file_contents: &[u8],
482 ) -> Result<&DirstateParents, DirstateError> {
483 ) -> Result<&DirstateParents, DirstateError> {
483 if self.parents.is_none() {
484 if self.parents.is_none() {
484 let parents = if !file_contents.is_empty() {
485 let parents = if !file_contents.is_empty() {
485 parse_dirstate_parents(file_contents)?.clone()
486 parse_dirstate_parents(file_contents)?.clone()
486 } else {
487 } else {
487 DirstateParents {
488 DirstateParents {
488 p1: NULL_NODE,
489 p1: NULL_NODE,
489 p2: NULL_NODE,
490 p2: NULL_NODE,
490 }
491 }
491 };
492 };
492 self.parents = Some(parents);
493 self.parents = Some(parents);
493 }
494 }
494 Ok(self.parents.as_ref().unwrap())
495 Ok(self.parents.as_ref().unwrap())
495 }
496 }
496
497
497 fn set_parents(&mut self, parents: &DirstateParents) {
498 fn set_parents(&mut self, parents: &DirstateParents) {
498 self.parents = Some(parents.clone());
499 self.parents = Some(parents.clone());
499 self.dirty_parents = true;
500 self.dirty_parents = true;
500 }
501 }
501
502
503 #[timed]
502 fn read<'a>(
504 fn read<'a>(
503 &mut self,
505 &mut self,
504 file_contents: &'a [u8],
506 file_contents: &'a [u8],
505 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
507 ) -> Result<Option<&'a DirstateParents>, DirstateError> {
506 if file_contents.is_empty() {
508 if file_contents.is_empty() {
507 return Ok(None);
509 return Ok(None);
508 }
510 }
509
511
510 let parents = parse_dirstate_entries(
512 let parents = parse_dirstate_entries(
511 file_contents,
513 file_contents,
512 |path, entry, copy_source| {
514 |path, entry, copy_source| {
513 self.add_file_node(
515 self.add_file_node(
514 path,
516 path,
515 *entry,
517 *entry,
516 Some(copy_source.map(HgPath::to_owned)),
518 Some(copy_source.map(HgPath::to_owned)),
517 )
519 )
518 },
520 },
519 )?;
521 )?;
520
522
521 if !self.dirty_parents {
523 if !self.dirty_parents {
522 self.set_parents(parents);
524 self.set_parents(parents);
523 }
525 }
524
526
525 Ok(Some(parents))
527 Ok(Some(parents))
526 }
528 }
527
529
528 fn pack(
530 fn pack(
529 &mut self,
531 &mut self,
530 parents: DirstateParents,
532 parents: DirstateParents,
531 now: Timestamp,
533 now: Timestamp,
532 ) -> Result<Vec<u8>, DirstateError> {
534 ) -> Result<Vec<u8>, DirstateError> {
533 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
535 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
534 // reallocations
536 // reallocations
535 let mut size = parents.as_bytes().len();
537 let mut size = parents.as_bytes().len();
536 for (path, node) in self.iter_nodes() {
538 for (path, node) in self.iter_nodes() {
537 if node.entry.is_some() {
539 if node.entry.is_some() {
538 size += packed_entry_size(
540 size += packed_entry_size(
539 path.full_path(),
541 path.full_path(),
540 node.copy_source.as_ref(),
542 node.copy_source.as_ref(),
541 )
543 )
542 }
544 }
543 }
545 }
544
546
545 let mut packed = Vec::with_capacity(size);
547 let mut packed = Vec::with_capacity(size);
546 packed.extend(parents.as_bytes());
548 packed.extend(parents.as_bytes());
547
549
548 let now: i32 = now.0.try_into().expect("time overflow");
550 let now: i32 = now.0.try_into().expect("time overflow");
549 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
551 for (path, opt_entry, copy_source) in self.iter_node_data_mut() {
550 if let Some(entry) = opt_entry {
552 if let Some(entry) = opt_entry {
551 clear_ambiguous_mtime(entry, now);
553 clear_ambiguous_mtime(entry, now);
552 pack_entry(
554 pack_entry(
553 path.full_path(),
555 path.full_path(),
554 entry,
556 entry,
555 copy_source.as_ref(),
557 copy_source.as_ref(),
556 &mut packed,
558 &mut packed,
557 );
559 );
558 }
560 }
559 }
561 }
560 self.dirty_parents = false;
562 self.dirty_parents = false;
561 Ok(packed)
563 Ok(packed)
562 }
564 }
563
565
564 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
566 fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> {
565 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
567 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
566 // needs to be recomputed
568 // needs to be recomputed
567 Ok(())
569 Ok(())
568 }
570 }
569
571
570 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
572 fn set_dirs(&mut self) -> Result<(), DirstateMapError> {
571 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
573 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
572 // to be recomputed
574 // to be recomputed
573 Ok(())
575 Ok(())
574 }
576 }
575
577
576 fn status<'a>(
578 fn status<'a>(
577 &'a mut self,
579 &'a mut self,
578 matcher: &'a (dyn Matcher + Sync),
580 matcher: &'a (dyn Matcher + Sync),
579 root_dir: PathBuf,
581 root_dir: PathBuf,
580 ignore_files: Vec<PathBuf>,
582 ignore_files: Vec<PathBuf>,
581 options: StatusOptions,
583 options: StatusOptions,
582 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
584 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
583 {
585 {
584 super::status::status(self, matcher, root_dir, ignore_files, options)
586 super::status::status(self, matcher, root_dir, ignore_files, options)
585 }
587 }
586
588
587 fn copy_map_len(&self) -> usize {
589 fn copy_map_len(&self) -> usize {
588 self.nodes_with_copy_source_count
590 self.nodes_with_copy_source_count
589 }
591 }
590
592
591 fn copy_map_iter(&self) -> CopyMapIter<'_> {
593 fn copy_map_iter(&self) -> CopyMapIter<'_> {
592 Box::new(self.iter_nodes().filter_map(|(path, node)| {
594 Box::new(self.iter_nodes().filter_map(|(path, node)| {
593 node.copy_source
595 node.copy_source
594 .as_ref()
596 .as_ref()
595 .map(|copy_source| (path.full_path(), copy_source))
597 .map(|copy_source| (path.full_path(), copy_source))
596 }))
598 }))
597 }
599 }
598
600
599 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
601 fn copy_map_contains_key(&self, key: &HgPath) -> bool {
600 if let Some(node) = self.get_node(key) {
602 if let Some(node) = self.get_node(key) {
601 node.copy_source.is_some()
603 node.copy_source.is_some()
602 } else {
604 } else {
603 false
605 false
604 }
606 }
605 }
607 }
606
608
607 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
609 fn copy_map_get(&self, key: &HgPath) -> Option<&HgPathBuf> {
608 self.get_node(key)?.copy_source.as_ref()
610 self.get_node(key)?.copy_source.as_ref()
609 }
611 }
610
612
611 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
613 fn copy_map_remove(&mut self, key: &HgPath) -> Option<HgPathBuf> {
612 let count = &mut self.nodes_with_copy_source_count;
614 let count = &mut self.nodes_with_copy_source_count;
613 Self::get_node_mut(&mut self.root, key).and_then(|node| {
615 Self::get_node_mut(&mut self.root, key).and_then(|node| {
614 if node.copy_source.is_some() {
616 if node.copy_source.is_some() {
615 *count -= 1
617 *count -= 1
616 }
618 }
617 node.copy_source.take()
619 node.copy_source.take()
618 })
620 })
619 }
621 }
620
622
621 fn copy_map_insert(
623 fn copy_map_insert(
622 &mut self,
624 &mut self,
623 key: HgPathBuf,
625 key: HgPathBuf,
624 value: HgPathBuf,
626 value: HgPathBuf,
625 ) -> Option<HgPathBuf> {
627 ) -> Option<HgPathBuf> {
626 let node = Self::get_or_insert_node(&mut self.root, &key);
628 let node = Self::get_or_insert_node(&mut self.root, &key);
627 if node.copy_source.is_none() {
629 if node.copy_source.is_none() {
628 self.nodes_with_copy_source_count += 1
630 self.nodes_with_copy_source_count += 1
629 }
631 }
630 node.copy_source.replace(value)
632 node.copy_source.replace(value)
631 }
633 }
632
634
633 fn len(&self) -> usize {
635 fn len(&self) -> usize {
634 self.nodes_with_entry_count
636 self.nodes_with_entry_count
635 }
637 }
636
638
637 fn contains_key(&self, key: &HgPath) -> bool {
639 fn contains_key(&self, key: &HgPath) -> bool {
638 self.get(key).is_some()
640 self.get(key).is_some()
639 }
641 }
640
642
641 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
643 fn get(&self, key: &HgPath) -> Option<&DirstateEntry> {
642 self.get_node(key)?.entry.as_ref()
644 self.get_node(key)?.entry.as_ref()
643 }
645 }
644
646
645 fn iter(&self) -> StateMapIter<'_> {
647 fn iter(&self) -> StateMapIter<'_> {
646 Box::new(self.iter_nodes().filter_map(|(path, node)| {
648 Box::new(self.iter_nodes().filter_map(|(path, node)| {
647 node.entry.as_ref().map(|entry| (path.full_path(), entry))
649 node.entry.as_ref().map(|entry| (path.full_path(), entry))
648 }))
650 }))
649 }
651 }
650 }
652 }
@@ -1,426 +1,428 b''
1 use crate::dirstate::status::IgnoreFnType;
1 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate_tree::dirstate_map::ChildNodes;
2 use crate::dirstate_tree::dirstate_map::ChildNodes;
3 use crate::dirstate_tree::dirstate_map::DirstateMap;
3 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::Node;
4 use crate::dirstate_tree::dirstate_map::Node;
5 use crate::matchers::get_ignore_function;
5 use crate::matchers::get_ignore_function;
6 use crate::matchers::Matcher;
6 use crate::matchers::Matcher;
7 use crate::utils::files::get_bytes_from_os_string;
7 use crate::utils::files::get_bytes_from_os_string;
8 use crate::utils::hg_path::HgPath;
8 use crate::utils::hg_path::HgPath;
9 use crate::BadMatch;
9 use crate::BadMatch;
10 use crate::DirstateStatus;
10 use crate::DirstateStatus;
11 use crate::EntryState;
11 use crate::EntryState;
12 use crate::HgPathBuf;
12 use crate::HgPathBuf;
13 use crate::PatternFileWarning;
13 use crate::PatternFileWarning;
14 use crate::StatusError;
14 use crate::StatusError;
15 use crate::StatusOptions;
15 use crate::StatusOptions;
16 use micro_timer::timed;
16 use rayon::prelude::*;
17 use rayon::prelude::*;
17 use std::borrow::Cow;
18 use std::borrow::Cow;
18 use std::io;
19 use std::io;
19 use std::path::Path;
20 use std::path::Path;
20 use std::path::PathBuf;
21 use std::path::PathBuf;
21 use std::sync::Mutex;
22 use std::sync::Mutex;
22
23
23 /// Returns the status of the working directory compared to its parent
24 /// Returns the status of the working directory compared to its parent
24 /// changeset.
25 /// changeset.
25 ///
26 ///
26 /// This algorithm is based on traversing the filesystem tree (`fs` in function
27 /// This algorithm is based on traversing the filesystem tree (`fs` in function
27 /// and variable names) and dirstate tree at the same time. The core of this
28 /// and variable names) and dirstate tree at the same time. The core of this
28 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
29 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
29 /// and its use of `itertools::merge_join_by`. When reaching a path that only
30 /// and its use of `itertools::merge_join_by`. When reaching a path that only
30 /// exists in one of the two trees, depending on information requested by
31 /// exists in one of the two trees, depending on information requested by
31 /// `options` we may need to traverse the remaining subtree.
32 /// `options` we may need to traverse the remaining subtree.
33 #[timed]
32 pub fn status<'tree>(
34 pub fn status<'tree>(
33 dmap: &'tree mut DirstateMap,
35 dmap: &'tree mut DirstateMap,
34 matcher: &(dyn Matcher + Sync),
36 matcher: &(dyn Matcher + Sync),
35 root_dir: PathBuf,
37 root_dir: PathBuf,
36 ignore_files: Vec<PathBuf>,
38 ignore_files: Vec<PathBuf>,
37 options: StatusOptions,
39 options: StatusOptions,
38 ) -> Result<(DirstateStatus<'tree>, Vec<PatternFileWarning>), StatusError> {
40 ) -> Result<(DirstateStatus<'tree>, Vec<PatternFileWarning>), StatusError> {
39 let (ignore_fn, warnings): (IgnoreFnType, _) =
41 let (ignore_fn, warnings): (IgnoreFnType, _) =
40 if options.list_ignored || options.list_unknown {
42 if options.list_ignored || options.list_unknown {
41 get_ignore_function(ignore_files, &root_dir)?
43 get_ignore_function(ignore_files, &root_dir)?
42 } else {
44 } else {
43 (Box::new(|&_| true), vec![])
45 (Box::new(|&_| true), vec![])
44 };
46 };
45
47
46 let common = StatusCommon {
48 let common = StatusCommon {
47 options,
49 options,
48 matcher,
50 matcher,
49 ignore_fn,
51 ignore_fn,
50 outcome: Mutex::new(DirstateStatus::default()),
52 outcome: Mutex::new(DirstateStatus::default()),
51 };
53 };
52 let is_at_repo_root = true;
54 let is_at_repo_root = true;
53 let hg_path = HgPath::new("");
55 let hg_path = HgPath::new("");
54 let has_ignored_ancestor = false;
56 let has_ignored_ancestor = false;
55 common.traverse_fs_directory_and_dirstate(
57 common.traverse_fs_directory_and_dirstate(
56 has_ignored_ancestor,
58 has_ignored_ancestor,
57 &mut dmap.root,
59 &mut dmap.root,
58 hg_path,
60 hg_path,
59 &root_dir,
61 &root_dir,
60 is_at_repo_root,
62 is_at_repo_root,
61 );
63 );
62 Ok((common.outcome.into_inner().unwrap(), warnings))
64 Ok((common.outcome.into_inner().unwrap(), warnings))
63 }
65 }
64
66
65 /// Bag of random things needed by various parts of the algorithm. Reduces the
67 /// Bag of random things needed by various parts of the algorithm. Reduces the
66 /// number of parameters passed to functions.
68 /// number of parameters passed to functions.
67 struct StatusCommon<'tree, 'a> {
69 struct StatusCommon<'tree, 'a> {
68 options: StatusOptions,
70 options: StatusOptions,
69 matcher: &'a (dyn Matcher + Sync),
71 matcher: &'a (dyn Matcher + Sync),
70 ignore_fn: IgnoreFnType<'a>,
72 ignore_fn: IgnoreFnType<'a>,
71 outcome: Mutex<DirstateStatus<'tree>>,
73 outcome: Mutex<DirstateStatus<'tree>>,
72 }
74 }
73
75
74 impl<'tree, 'a> StatusCommon<'tree, 'a> {
76 impl<'tree, 'a> StatusCommon<'tree, 'a> {
75 fn read_dir(
77 fn read_dir(
76 &self,
78 &self,
77 hg_path: &HgPath,
79 hg_path: &HgPath,
78 fs_path: &Path,
80 fs_path: &Path,
79 is_at_repo_root: bool,
81 is_at_repo_root: bool,
80 ) -> Result<Vec<DirEntry>, ()> {
82 ) -> Result<Vec<DirEntry>, ()> {
81 DirEntry::read_dir(fs_path, is_at_repo_root).map_err(|error| {
83 DirEntry::read_dir(fs_path, is_at_repo_root).map_err(|error| {
82 let errno = error.raw_os_error().expect("expected real OS error");
84 let errno = error.raw_os_error().expect("expected real OS error");
83 self.outcome
85 self.outcome
84 .lock()
86 .lock()
85 .unwrap()
87 .unwrap()
86 .bad
88 .bad
87 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
89 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
88 })
90 })
89 }
91 }
90
92
91 fn traverse_fs_directory_and_dirstate(
93 fn traverse_fs_directory_and_dirstate(
92 &self,
94 &self,
93 has_ignored_ancestor: bool,
95 has_ignored_ancestor: bool,
94 dirstate_nodes: &'tree mut ChildNodes,
96 dirstate_nodes: &'tree mut ChildNodes,
95 directory_hg_path: &'tree HgPath,
97 directory_hg_path: &'tree HgPath,
96 directory_fs_path: &Path,
98 directory_fs_path: &Path,
97 is_at_repo_root: bool,
99 is_at_repo_root: bool,
98 ) {
100 ) {
99 let mut fs_entries = if let Ok(entries) = self.read_dir(
101 let mut fs_entries = if let Ok(entries) = self.read_dir(
100 directory_hg_path,
102 directory_hg_path,
101 directory_fs_path,
103 directory_fs_path,
102 is_at_repo_root,
104 is_at_repo_root,
103 ) {
105 ) {
104 entries
106 entries
105 } else {
107 } else {
106 return;
108 return;
107 };
109 };
108
110
109 // `merge_join_by` requires both its input iterators to be sorted:
111 // `merge_join_by` requires both its input iterators to be sorted:
110
112
111 //
113 //
112 // * `BTreeMap` iterates according to keys’ ordering by definition
114 // * `BTreeMap` iterates according to keys’ ordering by definition
113
115
114 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
116 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
115 // https://github.com/rust-lang/rust/issues/34162
117 // https://github.com/rust-lang/rust/issues/34162
116 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
118 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
117
119
118 itertools::merge_join_by(
120 itertools::merge_join_by(
119 dirstate_nodes,
121 dirstate_nodes,
120 &fs_entries,
122 &fs_entries,
121 |(full_path, _node), fs_entry| {
123 |(full_path, _node), fs_entry| {
122 full_path.base_name().cmp(&fs_entry.base_name)
124 full_path.base_name().cmp(&fs_entry.base_name)
123 },
125 },
124 )
126 )
125 .par_bridge()
127 .par_bridge()
126 .for_each(|pair| {
128 .for_each(|pair| {
127 use itertools::EitherOrBoth::*;
129 use itertools::EitherOrBoth::*;
128 match pair {
130 match pair {
129 Both((hg_path, dirstate_node), fs_entry) => {
131 Both((hg_path, dirstate_node), fs_entry) => {
130 self.traverse_fs_and_dirstate(
132 self.traverse_fs_and_dirstate(
131 fs_entry,
133 fs_entry,
132 hg_path.full_path(),
134 hg_path.full_path(),
133 dirstate_node,
135 dirstate_node,
134 has_ignored_ancestor,
136 has_ignored_ancestor,
135 );
137 );
136 }
138 }
137 Left((hg_path, dirstate_node)) => self.traverse_dirstate_only(
139 Left((hg_path, dirstate_node)) => self.traverse_dirstate_only(
138 hg_path.full_path(),
140 hg_path.full_path(),
139 dirstate_node,
141 dirstate_node,
140 ),
142 ),
141 Right(fs_entry) => self.traverse_fs_only(
143 Right(fs_entry) => self.traverse_fs_only(
142 has_ignored_ancestor,
144 has_ignored_ancestor,
143 directory_hg_path,
145 directory_hg_path,
144 fs_entry,
146 fs_entry,
145 ),
147 ),
146 }
148 }
147 })
149 })
148 }
150 }
149
151
150 fn traverse_fs_and_dirstate(
152 fn traverse_fs_and_dirstate(
151 &self,
153 &self,
152 fs_entry: &DirEntry,
154 fs_entry: &DirEntry,
153 hg_path: &'tree HgPath,
155 hg_path: &'tree HgPath,
154 dirstate_node: &'tree mut Node,
156 dirstate_node: &'tree mut Node,
155 has_ignored_ancestor: bool,
157 has_ignored_ancestor: bool,
156 ) {
158 ) {
157 let file_type = fs_entry.metadata.file_type();
159 let file_type = fs_entry.metadata.file_type();
158 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
160 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
159 if !file_or_symlink {
161 if !file_or_symlink {
160 // If we previously had a file here, it was removed (with
162 // If we previously had a file here, it was removed (with
161 // `hg rm` or similar) or deleted before it could be
163 // `hg rm` or similar) or deleted before it could be
162 // replaced by a directory or something else.
164 // replaced by a directory or something else.
163 self.mark_removed_or_deleted_if_file(
165 self.mark_removed_or_deleted_if_file(
164 hg_path,
166 hg_path,
165 dirstate_node.state(),
167 dirstate_node.state(),
166 );
168 );
167 }
169 }
168 if file_type.is_dir() {
170 if file_type.is_dir() {
169 if self.options.collect_traversed_dirs {
171 if self.options.collect_traversed_dirs {
170 self.outcome.lock().unwrap().traversed.push(hg_path.into())
172 self.outcome.lock().unwrap().traversed.push(hg_path.into())
171 }
173 }
172 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
174 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
173 let is_at_repo_root = false;
175 let is_at_repo_root = false;
174 self.traverse_fs_directory_and_dirstate(
176 self.traverse_fs_directory_and_dirstate(
175 is_ignored,
177 is_ignored,
176 &mut dirstate_node.children,
178 &mut dirstate_node.children,
177 hg_path,
179 hg_path,
178 &fs_entry.full_path,
180 &fs_entry.full_path,
179 is_at_repo_root,
181 is_at_repo_root,
180 );
182 );
181 } else {
183 } else {
182 if file_or_symlink && self.matcher.matches(hg_path) {
184 if file_or_symlink && self.matcher.matches(hg_path) {
183 let full_path = Cow::from(hg_path);
185 let full_path = Cow::from(hg_path);
184 if let Some(entry) = &dirstate_node.entry {
186 if let Some(entry) = &dirstate_node.entry {
185 match entry.state {
187 match entry.state {
186 EntryState::Added => {
188 EntryState::Added => {
187 self.outcome.lock().unwrap().added.push(full_path)
189 self.outcome.lock().unwrap().added.push(full_path)
188 }
190 }
189 EntryState::Removed => self
191 EntryState::Removed => self
190 .outcome
192 .outcome
191 .lock()
193 .lock()
192 .unwrap()
194 .unwrap()
193 .removed
195 .removed
194 .push(full_path),
196 .push(full_path),
195 EntryState::Merged => self
197 EntryState::Merged => self
196 .outcome
198 .outcome
197 .lock()
199 .lock()
198 .unwrap()
200 .unwrap()
199 .modified
201 .modified
200 .push(full_path),
202 .push(full_path),
201 EntryState::Normal => {
203 EntryState::Normal => {
202 self.handle_normal_file(
204 self.handle_normal_file(
203 full_path,
205 full_path,
204 dirstate_node,
206 dirstate_node,
205 entry,
207 entry,
206 fs_entry,
208 fs_entry,
207 );
209 );
208 }
210 }
209 // This variant is not used in DirstateMap
211 // This variant is not used in DirstateMap
210 // nodes
212 // nodes
211 EntryState::Unknown => unreachable!(),
213 EntryState::Unknown => unreachable!(),
212 }
214 }
213 } else {
215 } else {
214 // `node.entry.is_none()` indicates a "directory"
216 // `node.entry.is_none()` indicates a "directory"
215 // node, but the filesystem has a file
217 // node, but the filesystem has a file
216 self.mark_unknown_or_ignored(
218 self.mark_unknown_or_ignored(
217 has_ignored_ancestor,
219 has_ignored_ancestor,
218 full_path,
220 full_path,
219 )
221 )
220 }
222 }
221 }
223 }
222
224
223 for (child_hg_path, child_node) in &mut dirstate_node.children {
225 for (child_hg_path, child_node) in &mut dirstate_node.children {
224 self.traverse_dirstate_only(
226 self.traverse_dirstate_only(
225 child_hg_path.full_path(),
227 child_hg_path.full_path(),
226 child_node,
228 child_node,
227 )
229 )
228 }
230 }
229 }
231 }
230 }
232 }
231
233
232 /// A file with `EntryState::Normal` in the dirstate was found in the
234 /// A file with `EntryState::Normal` in the dirstate was found in the
233 /// filesystem
235 /// filesystem
234 fn handle_normal_file(
236 fn handle_normal_file(
235 &self,
237 &self,
236 full_path: Cow<'tree, HgPath>,
238 full_path: Cow<'tree, HgPath>,
237 dirstate_node: &Node,
239 dirstate_node: &Node,
238 entry: &crate::DirstateEntry,
240 entry: &crate::DirstateEntry,
239 fs_entry: &DirEntry,
241 fs_entry: &DirEntry,
240 ) {
242 ) {
241 // Keep the low 31 bits
243 // Keep the low 31 bits
242 fn truncate_u64(value: u64) -> i32 {
244 fn truncate_u64(value: u64) -> i32 {
243 (value & 0x7FFF_FFFF) as i32
245 (value & 0x7FFF_FFFF) as i32
244 }
246 }
245 fn truncate_i64(value: i64) -> i32 {
247 fn truncate_i64(value: i64) -> i32 {
246 (value & 0x7FFF_FFFF) as i32
248 (value & 0x7FFF_FFFF) as i32
247 }
249 }
248
250
249 let mode_changed = || {
251 let mode_changed = || {
250 self.options.check_exec && entry.mode_changed(&fs_entry.metadata)
252 self.options.check_exec && entry.mode_changed(&fs_entry.metadata)
251 };
253 };
252 let size_changed = entry.size != truncate_u64(fs_entry.metadata.len());
254 let size_changed = entry.size != truncate_u64(fs_entry.metadata.len());
253 if entry.size >= 0
255 if entry.size >= 0
254 && size_changed
256 && size_changed
255 && fs_entry.metadata.file_type().is_symlink()
257 && fs_entry.metadata.file_type().is_symlink()
256 {
258 {
257 // issue6456: Size returned may be longer due to encryption
259 // issue6456: Size returned may be longer due to encryption
258 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
260 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
259 self.outcome.lock().unwrap().unsure.push(full_path)
261 self.outcome.lock().unwrap().unsure.push(full_path)
260 } else if dirstate_node.copy_source.is_some()
262 } else if dirstate_node.copy_source.is_some()
261 || entry.is_from_other_parent()
263 || entry.is_from_other_parent()
262 || (entry.size >= 0 && (size_changed || mode_changed()))
264 || (entry.size >= 0 && (size_changed || mode_changed()))
263 {
265 {
264 self.outcome.lock().unwrap().modified.push(full_path)
266 self.outcome.lock().unwrap().modified.push(full_path)
265 } else {
267 } else {
266 let mtime = mtime_seconds(&fs_entry.metadata);
268 let mtime = mtime_seconds(&fs_entry.metadata);
267 if truncate_i64(mtime) != entry.mtime
269 if truncate_i64(mtime) != entry.mtime
268 || mtime == self.options.last_normal_time
270 || mtime == self.options.last_normal_time
269 {
271 {
270 self.outcome.lock().unwrap().unsure.push(full_path)
272 self.outcome.lock().unwrap().unsure.push(full_path)
271 } else if self.options.list_clean {
273 } else if self.options.list_clean {
272 self.outcome.lock().unwrap().clean.push(full_path)
274 self.outcome.lock().unwrap().clean.push(full_path)
273 }
275 }
274 }
276 }
275 }
277 }
276
278
277 /// A node in the dirstate tree has no corresponding filesystem entry
279 /// A node in the dirstate tree has no corresponding filesystem entry
278 fn traverse_dirstate_only(
280 fn traverse_dirstate_only(
279 &self,
281 &self,
280 hg_path: &'tree HgPath,
282 hg_path: &'tree HgPath,
281 dirstate_node: &'tree mut Node,
283 dirstate_node: &'tree mut Node,
282 ) {
284 ) {
283 self.mark_removed_or_deleted_if_file(hg_path, dirstate_node.state());
285 self.mark_removed_or_deleted_if_file(hg_path, dirstate_node.state());
284 dirstate_node.children.par_iter_mut().for_each(
286 dirstate_node.children.par_iter_mut().for_each(
285 |(child_hg_path, child_node)| {
287 |(child_hg_path, child_node)| {
286 self.traverse_dirstate_only(
288 self.traverse_dirstate_only(
287 child_hg_path.full_path(),
289 child_hg_path.full_path(),
288 child_node,
290 child_node,
289 )
291 )
290 },
292 },
291 )
293 )
292 }
294 }
293
295
294 /// A node in the dirstate tree has no corresponding *file* on the
296 /// A node in the dirstate tree has no corresponding *file* on the
295 /// filesystem
297 /// filesystem
296 ///
298 ///
297 /// Does nothing on a "directory" node
299 /// Does nothing on a "directory" node
298 fn mark_removed_or_deleted_if_file(
300 fn mark_removed_or_deleted_if_file(
299 &self,
301 &self,
300 hg_path: &'tree HgPath,
302 hg_path: &'tree HgPath,
301 dirstate_node_state: Option<EntryState>,
303 dirstate_node_state: Option<EntryState>,
302 ) {
304 ) {
303 if let Some(state) = dirstate_node_state {
305 if let Some(state) = dirstate_node_state {
304 if self.matcher.matches(hg_path) {
306 if self.matcher.matches(hg_path) {
305 if let EntryState::Removed = state {
307 if let EntryState::Removed = state {
306 self.outcome.lock().unwrap().removed.push(hg_path.into())
308 self.outcome.lock().unwrap().removed.push(hg_path.into())
307 } else {
309 } else {
308 self.outcome.lock().unwrap().deleted.push(hg_path.into())
310 self.outcome.lock().unwrap().deleted.push(hg_path.into())
309 }
311 }
310 }
312 }
311 }
313 }
312 }
314 }
313
315
314 /// Something in the filesystem has no corresponding dirstate node
316 /// Something in the filesystem has no corresponding dirstate node
315 fn traverse_fs_only(
317 fn traverse_fs_only(
316 &self,
318 &self,
317 has_ignored_ancestor: bool,
319 has_ignored_ancestor: bool,
318 directory_hg_path: &HgPath,
320 directory_hg_path: &HgPath,
319 fs_entry: &DirEntry,
321 fs_entry: &DirEntry,
320 ) {
322 ) {
321 let hg_path = directory_hg_path.join(&fs_entry.base_name);
323 let hg_path = directory_hg_path.join(&fs_entry.base_name);
322 let file_type = fs_entry.metadata.file_type();
324 let file_type = fs_entry.metadata.file_type();
323 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
325 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
324 if file_type.is_dir() {
326 if file_type.is_dir() {
325 let is_ignored =
327 let is_ignored =
326 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
328 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
327 let traverse_children = if is_ignored {
329 let traverse_children = if is_ignored {
328 // Descendants of an ignored directory are all ignored
330 // Descendants of an ignored directory are all ignored
329 self.options.list_ignored
331 self.options.list_ignored
330 } else {
332 } else {
331 // Descendants of an unknown directory may be either unknown or
333 // Descendants of an unknown directory may be either unknown or
332 // ignored
334 // ignored
333 self.options.list_unknown || self.options.list_ignored
335 self.options.list_unknown || self.options.list_ignored
334 };
336 };
335 if traverse_children {
337 if traverse_children {
336 let is_at_repo_root = false;
338 let is_at_repo_root = false;
337 if let Ok(children_fs_entries) = self.read_dir(
339 if let Ok(children_fs_entries) = self.read_dir(
338 &hg_path,
340 &hg_path,
339 &fs_entry.full_path,
341 &fs_entry.full_path,
340 is_at_repo_root,
342 is_at_repo_root,
341 ) {
343 ) {
342 children_fs_entries.par_iter().for_each(|child_fs_entry| {
344 children_fs_entries.par_iter().for_each(|child_fs_entry| {
343 self.traverse_fs_only(
345 self.traverse_fs_only(
344 is_ignored,
346 is_ignored,
345 &hg_path,
347 &hg_path,
346 child_fs_entry,
348 child_fs_entry,
347 )
349 )
348 })
350 })
349 }
351 }
350 }
352 }
351 if self.options.collect_traversed_dirs {
353 if self.options.collect_traversed_dirs {
352 self.outcome.lock().unwrap().traversed.push(hg_path.into())
354 self.outcome.lock().unwrap().traversed.push(hg_path.into())
353 }
355 }
354 } else if file_or_symlink && self.matcher.matches(&hg_path) {
356 } else if file_or_symlink && self.matcher.matches(&hg_path) {
355 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path.into())
357 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path.into())
356 }
358 }
357 }
359 }
358
360
359 fn mark_unknown_or_ignored(
361 fn mark_unknown_or_ignored(
360 &self,
362 &self,
361 has_ignored_ancestor: bool,
363 has_ignored_ancestor: bool,
362 hg_path: Cow<'tree, HgPath>,
364 hg_path: Cow<'tree, HgPath>,
363 ) {
365 ) {
364 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
366 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
365 if is_ignored {
367 if is_ignored {
366 if self.options.list_ignored {
368 if self.options.list_ignored {
367 self.outcome.lock().unwrap().ignored.push(hg_path)
369 self.outcome.lock().unwrap().ignored.push(hg_path)
368 }
370 }
369 } else {
371 } else {
370 if self.options.list_unknown {
372 if self.options.list_unknown {
371 self.outcome.lock().unwrap().unknown.push(hg_path)
373 self.outcome.lock().unwrap().unknown.push(hg_path)
372 }
374 }
373 }
375 }
374 }
376 }
375 }
377 }
376
378
377 #[cfg(unix)] // TODO
379 #[cfg(unix)] // TODO
378 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
380 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
379 // Going through `Metadata::modified()` would be portable, but would take
381 // Going through `Metadata::modified()` would be portable, but would take
380 // care to construct a `SystemTime` value with sub-second precision just
382 // care to construct a `SystemTime` value with sub-second precision just
381 // for us to throw that away here.
383 // for us to throw that away here.
382 use std::os::unix::fs::MetadataExt;
384 use std::os::unix::fs::MetadataExt;
383 metadata.mtime()
385 metadata.mtime()
384 }
386 }
385
387
386 struct DirEntry {
388 struct DirEntry {
387 base_name: HgPathBuf,
389 base_name: HgPathBuf,
388 full_path: PathBuf,
390 full_path: PathBuf,
389 metadata: std::fs::Metadata,
391 metadata: std::fs::Metadata,
390 }
392 }
391
393
392 impl DirEntry {
394 impl DirEntry {
393 /// Returns **unsorted** entries in the given directory, with name and
395 /// Returns **unsorted** entries in the given directory, with name and
394 /// metadata.
396 /// metadata.
395 ///
397 ///
396 /// If a `.hg` sub-directory is encountered:
398 /// If a `.hg` sub-directory is encountered:
397 ///
399 ///
398 /// * At the repository root, ignore that sub-directory
400 /// * At the repository root, ignore that sub-directory
399 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
401 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
400 /// list instead.
402 /// list instead.
401 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
403 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
402 let mut results = Vec::new();
404 let mut results = Vec::new();
403 for entry in path.read_dir()? {
405 for entry in path.read_dir()? {
404 let entry = entry?;
406 let entry = entry?;
405 let metadata = entry.metadata()?;
407 let metadata = entry.metadata()?;
406 let name = get_bytes_from_os_string(entry.file_name());
408 let name = get_bytes_from_os_string(entry.file_name());
407 // FIXME don't do this when cached
409 // FIXME don't do this when cached
408 if name == b".hg" {
410 if name == b".hg" {
409 if is_at_repo_root {
411 if is_at_repo_root {
410 // Skip the repo’s own .hg (might be a symlink)
412 // Skip the repo’s own .hg (might be a symlink)
411 continue;
413 continue;
412 } else if metadata.is_dir() {
414 } else if metadata.is_dir() {
413 // A .hg sub-directory at another location means a subrepo,
415 // A .hg sub-directory at another location means a subrepo,
414 // skip it entirely.
416 // skip it entirely.
415 return Ok(Vec::new());
417 return Ok(Vec::new());
416 }
418 }
417 }
419 }
418 results.push(DirEntry {
420 results.push(DirEntry {
419 base_name: name.into(),
421 base_name: name.into(),
420 full_path: entry.path(),
422 full_path: entry.path(),
421 metadata,
423 metadata,
422 })
424 })
423 }
425 }
424 Ok(results)
426 Ok(results)
425 }
427 }
426 }
428 }
General Comments 0
You need to be logged in to leave comments. Login now