##// END OF EJS Templates
dirstate-v2: Drop cached read_dir results after .hgignore changes...
Simon Sapin -
r48268:c657beac default
parent child Browse files
Show More
@@ -1,1117 +1,1130 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use micro_timer::timed;
3 use std::borrow::Cow;
3 use std::borrow::Cow;
4 use std::convert::TryInto;
4 use std::convert::TryInto;
5 use std::path::PathBuf;
5 use std::path::PathBuf;
6
6
7 use super::on_disk;
7 use super::on_disk;
8 use super::on_disk::DirstateV2ParseError;
8 use super::on_disk::DirstateV2ParseError;
9 use super::path_with_basename::WithBasename;
9 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
10 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
11 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
12 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::parsers::Timestamp;
13 use crate::dirstate::parsers::Timestamp;
14 use crate::matchers::Matcher;
14 use crate::matchers::Matcher;
15 use crate::utils::hg_path::{HgPath, HgPathBuf};
15 use crate::utils::hg_path::{HgPath, HgPathBuf};
16 use crate::CopyMapIter;
16 use crate::CopyMapIter;
17 use crate::DirstateEntry;
17 use crate::DirstateEntry;
18 use crate::DirstateError;
18 use crate::DirstateError;
19 use crate::DirstateParents;
19 use crate::DirstateParents;
20 use crate::DirstateStatus;
20 use crate::DirstateStatus;
21 use crate::EntryState;
21 use crate::EntryState;
22 use crate::FastHashMap;
22 use crate::FastHashMap;
23 use crate::PatternFileWarning;
23 use crate::PatternFileWarning;
24 use crate::StateMapIter;
24 use crate::StateMapIter;
25 use crate::StatusError;
25 use crate::StatusError;
26 use crate::StatusOptions;
26 use crate::StatusOptions;
27
27
28 pub struct DirstateMap<'on_disk> {
28 pub struct DirstateMap<'on_disk> {
29 /// Contents of the `.hg/dirstate` file
29 /// Contents of the `.hg/dirstate` file
30 pub(super) on_disk: &'on_disk [u8],
30 pub(super) on_disk: &'on_disk [u8],
31
31
32 pub(super) root: ChildNodes<'on_disk>,
32 pub(super) root: ChildNodes<'on_disk>,
33
33
34 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
34 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
35 pub(super) nodes_with_entry_count: u32,
35 pub(super) nodes_with_entry_count: u32,
36
36
37 /// Number of nodes anywhere in the tree that have
37 /// Number of nodes anywhere in the tree that have
38 /// `.copy_source.is_some()`.
38 /// `.copy_source.is_some()`.
39 pub(super) nodes_with_copy_source_count: u32,
39 pub(super) nodes_with_copy_source_count: u32,
40
40
41 /// See on_disk::Header
41 /// See on_disk::Header
42 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
42 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
43 }
43 }
44
44
45 /// Using a plain `HgPathBuf` of the full path from the repository root as a
45 /// Using a plain `HgPathBuf` of the full path from the repository root as a
46 /// map key would also work: all paths in a given map have the same parent
46 /// map key would also work: all paths in a given map have the same parent
47 /// path, so comparing full paths gives the same result as comparing base
47 /// path, so comparing full paths gives the same result as comparing base
48 /// names. However `HashMap` would waste time always re-hashing the same
48 /// names. However `HashMap` would waste time always re-hashing the same
49 /// string prefix.
49 /// string prefix.
50 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
50 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
51
51
52 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
52 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
53 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
53 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
54 pub(super) enum BorrowedPath<'tree, 'on_disk> {
54 pub(super) enum BorrowedPath<'tree, 'on_disk> {
55 InMemory(&'tree HgPathBuf),
55 InMemory(&'tree HgPathBuf),
56 OnDisk(&'on_disk HgPath),
56 OnDisk(&'on_disk HgPath),
57 }
57 }
58
58
59 pub(super) enum ChildNodes<'on_disk> {
59 pub(super) enum ChildNodes<'on_disk> {
60 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
60 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
61 OnDisk(&'on_disk [on_disk::Node]),
61 OnDisk(&'on_disk [on_disk::Node]),
62 }
62 }
63
63
64 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
64 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
65 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
65 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
66 OnDisk(&'on_disk [on_disk::Node]),
66 OnDisk(&'on_disk [on_disk::Node]),
67 }
67 }
68
68
69 pub(super) enum NodeRef<'tree, 'on_disk> {
69 pub(super) enum NodeRef<'tree, 'on_disk> {
70 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
70 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
71 OnDisk(&'on_disk on_disk::Node),
71 OnDisk(&'on_disk on_disk::Node),
72 }
72 }
73
73
74 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
74 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
75 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
75 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
76 match *self {
76 match *self {
77 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
77 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
78 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
78 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
79 }
79 }
80 }
80 }
81 }
81 }
82
82
83 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
83 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
84 type Target = HgPath;
84 type Target = HgPath;
85
85
86 fn deref(&self) -> &HgPath {
86 fn deref(&self) -> &HgPath {
87 match *self {
87 match *self {
88 BorrowedPath::InMemory(in_memory) => in_memory,
88 BorrowedPath::InMemory(in_memory) => in_memory,
89 BorrowedPath::OnDisk(on_disk) => on_disk,
89 BorrowedPath::OnDisk(on_disk) => on_disk,
90 }
90 }
91 }
91 }
92 }
92 }
93
93
94 impl Default for ChildNodes<'_> {
94 impl Default for ChildNodes<'_> {
95 fn default() -> Self {
95 fn default() -> Self {
96 ChildNodes::InMemory(Default::default())
96 ChildNodes::InMemory(Default::default())
97 }
97 }
98 }
98 }
99
99
100 impl<'on_disk> ChildNodes<'on_disk> {
100 impl<'on_disk> ChildNodes<'on_disk> {
101 pub(super) fn as_ref<'tree>(
101 pub(super) fn as_ref<'tree>(
102 &'tree self,
102 &'tree self,
103 ) -> ChildNodesRef<'tree, 'on_disk> {
103 ) -> ChildNodesRef<'tree, 'on_disk> {
104 match self {
104 match self {
105 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
105 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
106 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
106 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
107 }
107 }
108 }
108 }
109
109
110 pub(super) fn is_empty(&self) -> bool {
110 pub(super) fn is_empty(&self) -> bool {
111 match self {
111 match self {
112 ChildNodes::InMemory(nodes) => nodes.is_empty(),
112 ChildNodes::InMemory(nodes) => nodes.is_empty(),
113 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
113 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
114 }
114 }
115 }
115 }
116
116
117 pub(super) fn make_mut(
117 pub(super) fn make_mut(
118 &mut self,
118 &mut self,
119 on_disk: &'on_disk [u8],
119 on_disk: &'on_disk [u8],
120 ) -> Result<
120 ) -> Result<
121 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
121 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
122 DirstateV2ParseError,
122 DirstateV2ParseError,
123 > {
123 > {
124 match self {
124 match self {
125 ChildNodes::InMemory(nodes) => Ok(nodes),
125 ChildNodes::InMemory(nodes) => Ok(nodes),
126 ChildNodes::OnDisk(nodes) => {
126 ChildNodes::OnDisk(nodes) => {
127 let nodes = nodes
127 let nodes = nodes
128 .iter()
128 .iter()
129 .map(|node| {
129 .map(|node| {
130 Ok((
130 Ok((
131 node.path(on_disk)?,
131 node.path(on_disk)?,
132 node.to_in_memory_node(on_disk)?,
132 node.to_in_memory_node(on_disk)?,
133 ))
133 ))
134 })
134 })
135 .collect::<Result<_, _>>()?;
135 .collect::<Result<_, _>>()?;
136 *self = ChildNodes::InMemory(nodes);
136 *self = ChildNodes::InMemory(nodes);
137 match self {
137 match self {
138 ChildNodes::InMemory(nodes) => Ok(nodes),
138 ChildNodes::InMemory(nodes) => Ok(nodes),
139 ChildNodes::OnDisk(_) => unreachable!(),
139 ChildNodes::OnDisk(_) => unreachable!(),
140 }
140 }
141 }
141 }
142 }
142 }
143 }
143 }
144 }
144 }
145
145
146 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
146 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
147 pub(super) fn get(
147 pub(super) fn get(
148 &self,
148 &self,
149 base_name: &HgPath,
149 base_name: &HgPath,
150 on_disk: &'on_disk [u8],
150 on_disk: &'on_disk [u8],
151 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
151 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
152 match self {
152 match self {
153 ChildNodesRef::InMemory(nodes) => Ok(nodes
153 ChildNodesRef::InMemory(nodes) => Ok(nodes
154 .get_key_value(base_name)
154 .get_key_value(base_name)
155 .map(|(k, v)| NodeRef::InMemory(k, v))),
155 .map(|(k, v)| NodeRef::InMemory(k, v))),
156 ChildNodesRef::OnDisk(nodes) => {
156 ChildNodesRef::OnDisk(nodes) => {
157 let mut parse_result = Ok(());
157 let mut parse_result = Ok(());
158 let search_result = nodes.binary_search_by(|node| {
158 let search_result = nodes.binary_search_by(|node| {
159 match node.base_name(on_disk) {
159 match node.base_name(on_disk) {
160 Ok(node_base_name) => node_base_name.cmp(base_name),
160 Ok(node_base_name) => node_base_name.cmp(base_name),
161 Err(e) => {
161 Err(e) => {
162 parse_result = Err(e);
162 parse_result = Err(e);
163 // Dummy comparison result, `search_result` won’t
163 // Dummy comparison result, `search_result` won’t
164 // be used since `parse_result` is an error
164 // be used since `parse_result` is an error
165 std::cmp::Ordering::Equal
165 std::cmp::Ordering::Equal
166 }
166 }
167 }
167 }
168 });
168 });
169 parse_result.map(|()| {
169 parse_result.map(|()| {
170 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
170 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
171 })
171 })
172 }
172 }
173 }
173 }
174 }
174 }
175
175
176 /// Iterate in undefined order
176 /// Iterate in undefined order
177 pub(super) fn iter(
177 pub(super) fn iter(
178 &self,
178 &self,
179 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
179 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
180 match self {
180 match self {
181 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
181 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
182 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
182 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
183 ),
183 ),
184 ChildNodesRef::OnDisk(nodes) => {
184 ChildNodesRef::OnDisk(nodes) => {
185 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
185 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
186 }
186 }
187 }
187 }
188 }
188 }
189
189
190 /// Iterate in parallel in undefined order
190 /// Iterate in parallel in undefined order
191 pub(super) fn par_iter(
191 pub(super) fn par_iter(
192 &self,
192 &self,
193 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
193 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
194 {
194 {
195 use rayon::prelude::*;
195 use rayon::prelude::*;
196 match self {
196 match self {
197 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
197 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
198 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
198 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
199 ),
199 ),
200 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
200 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
201 nodes.par_iter().map(NodeRef::OnDisk),
201 nodes.par_iter().map(NodeRef::OnDisk),
202 ),
202 ),
203 }
203 }
204 }
204 }
205
205
206 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
206 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
207 match self {
207 match self {
208 ChildNodesRef::InMemory(nodes) => {
208 ChildNodesRef::InMemory(nodes) => {
209 let mut vec: Vec<_> = nodes
209 let mut vec: Vec<_> = nodes
210 .iter()
210 .iter()
211 .map(|(k, v)| NodeRef::InMemory(k, v))
211 .map(|(k, v)| NodeRef::InMemory(k, v))
212 .collect();
212 .collect();
213 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
213 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
214 match node {
214 match node {
215 NodeRef::InMemory(path, _node) => path.base_name(),
215 NodeRef::InMemory(path, _node) => path.base_name(),
216 NodeRef::OnDisk(_) => unreachable!(),
216 NodeRef::OnDisk(_) => unreachable!(),
217 }
217 }
218 }
218 }
219 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
219 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
220 // value: https://github.com/rust-lang/rust/issues/34162
220 // value: https://github.com/rust-lang/rust/issues/34162
221 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
221 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
222 vec
222 vec
223 }
223 }
224 ChildNodesRef::OnDisk(nodes) => {
224 ChildNodesRef::OnDisk(nodes) => {
225 // Nodes on disk are already sorted
225 // Nodes on disk are already sorted
226 nodes.iter().map(NodeRef::OnDisk).collect()
226 nodes.iter().map(NodeRef::OnDisk).collect()
227 }
227 }
228 }
228 }
229 }
229 }
230 }
230 }
231
231
232 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
232 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
233 pub(super) fn full_path(
233 pub(super) fn full_path(
234 &self,
234 &self,
235 on_disk: &'on_disk [u8],
235 on_disk: &'on_disk [u8],
236 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
236 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
237 match self {
237 match self {
238 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
238 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
239 NodeRef::OnDisk(node) => node.full_path(on_disk),
239 NodeRef::OnDisk(node) => node.full_path(on_disk),
240 }
240 }
241 }
241 }
242
242
243 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
243 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
244 /// HgPath>` detached from `'tree`
244 /// HgPath>` detached from `'tree`
245 pub(super) fn full_path_borrowed(
245 pub(super) fn full_path_borrowed(
246 &self,
246 &self,
247 on_disk: &'on_disk [u8],
247 on_disk: &'on_disk [u8],
248 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
248 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
249 match self {
249 match self {
250 NodeRef::InMemory(path, _node) => match path.full_path() {
250 NodeRef::InMemory(path, _node) => match path.full_path() {
251 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
251 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
252 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
252 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
253 },
253 },
254 NodeRef::OnDisk(node) => {
254 NodeRef::OnDisk(node) => {
255 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
255 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
256 }
256 }
257 }
257 }
258 }
258 }
259
259
260 pub(super) fn base_name(
260 pub(super) fn base_name(
261 &self,
261 &self,
262 on_disk: &'on_disk [u8],
262 on_disk: &'on_disk [u8],
263 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
263 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
264 match self {
264 match self {
265 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
265 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
266 NodeRef::OnDisk(node) => node.base_name(on_disk),
266 NodeRef::OnDisk(node) => node.base_name(on_disk),
267 }
267 }
268 }
268 }
269
269
270 pub(super) fn children(
270 pub(super) fn children(
271 &self,
271 &self,
272 on_disk: &'on_disk [u8],
272 on_disk: &'on_disk [u8],
273 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
273 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
274 match self {
274 match self {
275 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
275 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
276 NodeRef::OnDisk(node) => {
276 NodeRef::OnDisk(node) => {
277 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
277 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
278 }
278 }
279 }
279 }
280 }
280 }
281
281
282 pub(super) fn has_copy_source(&self) -> bool {
282 pub(super) fn has_copy_source(&self) -> bool {
283 match self {
283 match self {
284 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
284 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
285 NodeRef::OnDisk(node) => node.has_copy_source(),
285 NodeRef::OnDisk(node) => node.has_copy_source(),
286 }
286 }
287 }
287 }
288
288
289 pub(super) fn copy_source(
289 pub(super) fn copy_source(
290 &self,
290 &self,
291 on_disk: &'on_disk [u8],
291 on_disk: &'on_disk [u8],
292 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
292 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
293 match self {
293 match self {
294 NodeRef::InMemory(_path, node) => {
294 NodeRef::InMemory(_path, node) => {
295 Ok(node.copy_source.as_ref().map(|s| &**s))
295 Ok(node.copy_source.as_ref().map(|s| &**s))
296 }
296 }
297 NodeRef::OnDisk(node) => node.copy_source(on_disk),
297 NodeRef::OnDisk(node) => node.copy_source(on_disk),
298 }
298 }
299 }
299 }
300
300
301 pub(super) fn entry(
301 pub(super) fn entry(
302 &self,
302 &self,
303 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
303 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
304 match self {
304 match self {
305 NodeRef::InMemory(_path, node) => {
305 NodeRef::InMemory(_path, node) => {
306 Ok(node.data.as_entry().copied())
306 Ok(node.data.as_entry().copied())
307 }
307 }
308 NodeRef::OnDisk(node) => node.entry(),
308 NodeRef::OnDisk(node) => node.entry(),
309 }
309 }
310 }
310 }
311
311
312 pub(super) fn state(
312 pub(super) fn state(
313 &self,
313 &self,
314 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
314 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
315 match self {
315 match self {
316 NodeRef::InMemory(_path, node) => {
316 NodeRef::InMemory(_path, node) => {
317 Ok(node.data.as_entry().map(|entry| entry.state))
317 Ok(node.data.as_entry().map(|entry| entry.state))
318 }
318 }
319 NodeRef::OnDisk(node) => node.state(),
319 NodeRef::OnDisk(node) => node.state(),
320 }
320 }
321 }
321 }
322
322
323 pub(super) fn cached_directory_mtime(
323 pub(super) fn cached_directory_mtime(
324 &self,
324 &self,
325 ) -> Option<&'tree on_disk::Timestamp> {
325 ) -> Option<&'tree on_disk::Timestamp> {
326 match self {
326 match self {
327 NodeRef::InMemory(_path, node) => match &node.data {
327 NodeRef::InMemory(_path, node) => match &node.data {
328 NodeData::CachedDirectory { mtime } => Some(mtime),
328 NodeData::CachedDirectory { mtime } => Some(mtime),
329 _ => None,
329 _ => None,
330 },
330 },
331 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
331 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
332 }
332 }
333 }
333 }
334
334
335 pub(super) fn tracked_descendants_count(&self) -> u32 {
335 pub(super) fn tracked_descendants_count(&self) -> u32 {
336 match self {
336 match self {
337 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
337 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
338 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
338 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
339 }
339 }
340 }
340 }
341 }
341 }
342
342
343 /// Represents a file or a directory
343 /// Represents a file or a directory
344 #[derive(Default)]
344 #[derive(Default)]
345 pub(super) struct Node<'on_disk> {
345 pub(super) struct Node<'on_disk> {
346 pub(super) data: NodeData,
346 pub(super) data: NodeData,
347
347
348 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
348 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
349
349
350 pub(super) children: ChildNodes<'on_disk>,
350 pub(super) children: ChildNodes<'on_disk>,
351
351
352 /// How many (non-inclusive) descendants of this node are tracked files
352 /// How many (non-inclusive) descendants of this node are tracked files
353 pub(super) tracked_descendants_count: u32,
353 pub(super) tracked_descendants_count: u32,
354 }
354 }
355
355
356 pub(super) enum NodeData {
356 pub(super) enum NodeData {
357 Entry(DirstateEntry),
357 Entry(DirstateEntry),
358 CachedDirectory { mtime: on_disk::Timestamp },
358 CachedDirectory { mtime: on_disk::Timestamp },
359 None,
359 None,
360 }
360 }
361
361
362 impl Default for NodeData {
362 impl Default for NodeData {
363 fn default() -> Self {
363 fn default() -> Self {
364 NodeData::None
364 NodeData::None
365 }
365 }
366 }
366 }
367
367
368 impl NodeData {
368 impl NodeData {
369 fn has_entry(&self) -> bool {
369 fn has_entry(&self) -> bool {
370 match self {
370 match self {
371 NodeData::Entry(_) => true,
371 NodeData::Entry(_) => true,
372 _ => false,
372 _ => false,
373 }
373 }
374 }
374 }
375
375
376 fn as_entry(&self) -> Option<&DirstateEntry> {
376 fn as_entry(&self) -> Option<&DirstateEntry> {
377 match self {
377 match self {
378 NodeData::Entry(entry) => Some(entry),
378 NodeData::Entry(entry) => Some(entry),
379 _ => None,
379 _ => None,
380 }
380 }
381 }
381 }
382 }
382 }
383
383
384 impl<'on_disk> DirstateMap<'on_disk> {
384 impl<'on_disk> DirstateMap<'on_disk> {
385 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
385 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
386 Self {
386 Self {
387 on_disk,
387 on_disk,
388 root: ChildNodes::default(),
388 root: ChildNodes::default(),
389 nodes_with_entry_count: 0,
389 nodes_with_entry_count: 0,
390 nodes_with_copy_source_count: 0,
390 nodes_with_copy_source_count: 0,
391 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
391 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
392 }
392 }
393 }
393 }
394
394
395 #[timed]
395 #[timed]
396 pub fn new_v2(
396 pub fn new_v2(
397 on_disk: &'on_disk [u8],
397 on_disk: &'on_disk [u8],
398 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
398 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
399 Ok(on_disk::read(on_disk)?)
399 Ok(on_disk::read(on_disk)?)
400 }
400 }
401
401
402 #[timed]
402 #[timed]
403 pub fn new_v1(
403 pub fn new_v1(
404 on_disk: &'on_disk [u8],
404 on_disk: &'on_disk [u8],
405 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
405 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
406 let mut map = Self::empty(on_disk);
406 let mut map = Self::empty(on_disk);
407 if map.on_disk.is_empty() {
407 if map.on_disk.is_empty() {
408 return Ok((map, None));
408 return Ok((map, None));
409 }
409 }
410
410
411 let parents = parse_dirstate_entries(
411 let parents = parse_dirstate_entries(
412 map.on_disk,
412 map.on_disk,
413 |path, entry, copy_source| {
413 |path, entry, copy_source| {
414 let tracked = entry.state.is_tracked();
414 let tracked = entry.state.is_tracked();
415 let node = Self::get_or_insert_node(
415 let node = Self::get_or_insert_node(
416 map.on_disk,
416 map.on_disk,
417 &mut map.root,
417 &mut map.root,
418 path,
418 path,
419 WithBasename::to_cow_borrowed,
419 WithBasename::to_cow_borrowed,
420 |ancestor| {
420 |ancestor| {
421 if tracked {
421 if tracked {
422 ancestor.tracked_descendants_count += 1
422 ancestor.tracked_descendants_count += 1
423 }
423 }
424 },
424 },
425 )?;
425 )?;
426 assert!(
426 assert!(
427 !node.data.has_entry(),
427 !node.data.has_entry(),
428 "duplicate dirstate entry in read"
428 "duplicate dirstate entry in read"
429 );
429 );
430 assert!(
430 assert!(
431 node.copy_source.is_none(),
431 node.copy_source.is_none(),
432 "duplicate dirstate entry in read"
432 "duplicate dirstate entry in read"
433 );
433 );
434 node.data = NodeData::Entry(*entry);
434 node.data = NodeData::Entry(*entry);
435 node.copy_source = copy_source.map(Cow::Borrowed);
435 node.copy_source = copy_source.map(Cow::Borrowed);
436 map.nodes_with_entry_count += 1;
436 map.nodes_with_entry_count += 1;
437 if copy_source.is_some() {
437 if copy_source.is_some() {
438 map.nodes_with_copy_source_count += 1
438 map.nodes_with_copy_source_count += 1
439 }
439 }
440 Ok(())
440 Ok(())
441 },
441 },
442 )?;
442 )?;
443 let parents = Some(parents.clone());
443 let parents = Some(parents.clone());
444
444
445 Ok((map, parents))
445 Ok((map, parents))
446 }
446 }
447
447
448 fn get_node<'tree>(
448 fn get_node<'tree>(
449 &'tree self,
449 &'tree self,
450 path: &HgPath,
450 path: &HgPath,
451 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
451 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
452 let mut children = self.root.as_ref();
452 let mut children = self.root.as_ref();
453 let mut components = path.components();
453 let mut components = path.components();
454 let mut component =
454 let mut component =
455 components.next().expect("expected at least one components");
455 components.next().expect("expected at least one components");
456 loop {
456 loop {
457 if let Some(child) = children.get(component, self.on_disk)? {
457 if let Some(child) = children.get(component, self.on_disk)? {
458 if let Some(next_component) = components.next() {
458 if let Some(next_component) = components.next() {
459 component = next_component;
459 component = next_component;
460 children = child.children(self.on_disk)?;
460 children = child.children(self.on_disk)?;
461 } else {
461 } else {
462 return Ok(Some(child));
462 return Ok(Some(child));
463 }
463 }
464 } else {
464 } else {
465 return Ok(None);
465 return Ok(None);
466 }
466 }
467 }
467 }
468 }
468 }
469
469
470 /// Returns a mutable reference to the node at `path` if it exists
470 /// Returns a mutable reference to the node at `path` if it exists
471 ///
471 ///
472 /// This takes `root` instead of `&mut self` so that callers can mutate
472 /// This takes `root` instead of `&mut self` so that callers can mutate
473 /// other fields while the returned borrow is still valid
473 /// other fields while the returned borrow is still valid
474 fn get_node_mut<'tree>(
474 fn get_node_mut<'tree>(
475 on_disk: &'on_disk [u8],
475 on_disk: &'on_disk [u8],
476 root: &'tree mut ChildNodes<'on_disk>,
476 root: &'tree mut ChildNodes<'on_disk>,
477 path: &HgPath,
477 path: &HgPath,
478 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
478 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
479 let mut children = root;
479 let mut children = root;
480 let mut components = path.components();
480 let mut components = path.components();
481 let mut component =
481 let mut component =
482 components.next().expect("expected at least one components");
482 components.next().expect("expected at least one components");
483 loop {
483 loop {
484 if let Some(child) = children.make_mut(on_disk)?.get_mut(component)
484 if let Some(child) = children.make_mut(on_disk)?.get_mut(component)
485 {
485 {
486 if let Some(next_component) = components.next() {
486 if let Some(next_component) = components.next() {
487 component = next_component;
487 component = next_component;
488 children = &mut child.children;
488 children = &mut child.children;
489 } else {
489 } else {
490 return Ok(Some(child));
490 return Ok(Some(child));
491 }
491 }
492 } else {
492 } else {
493 return Ok(None);
493 return Ok(None);
494 }
494 }
495 }
495 }
496 }
496 }
497
497
498 pub(super) fn get_or_insert<'tree, 'path>(
499 &'tree mut self,
500 path: &HgPath,
501 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
502 Self::get_or_insert_node(
503 self.on_disk,
504 &mut self.root,
505 path,
506 WithBasename::to_cow_owned,
507 |_| {},
508 )
509 }
510
498 pub(super) fn get_or_insert_node<'tree, 'path>(
511 pub(super) fn get_or_insert_node<'tree, 'path>(
499 on_disk: &'on_disk [u8],
512 on_disk: &'on_disk [u8],
500 root: &'tree mut ChildNodes<'on_disk>,
513 root: &'tree mut ChildNodes<'on_disk>,
501 path: &'path HgPath,
514 path: &'path HgPath,
502 to_cow: impl Fn(
515 to_cow: impl Fn(
503 WithBasename<&'path HgPath>,
516 WithBasename<&'path HgPath>,
504 ) -> WithBasename<Cow<'on_disk, HgPath>>,
517 ) -> WithBasename<Cow<'on_disk, HgPath>>,
505 mut each_ancestor: impl FnMut(&mut Node),
518 mut each_ancestor: impl FnMut(&mut Node),
506 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
519 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
507 let mut child_nodes = root;
520 let mut child_nodes = root;
508 let mut inclusive_ancestor_paths =
521 let mut inclusive_ancestor_paths =
509 WithBasename::inclusive_ancestors_of(path);
522 WithBasename::inclusive_ancestors_of(path);
510 let mut ancestor_path = inclusive_ancestor_paths
523 let mut ancestor_path = inclusive_ancestor_paths
511 .next()
524 .next()
512 .expect("expected at least one inclusive ancestor");
525 .expect("expected at least one inclusive ancestor");
513 loop {
526 loop {
514 // TODO: can we avoid allocating an owned key in cases where the
527 // TODO: can we avoid allocating an owned key in cases where the
515 // map already contains that key, without introducing double
528 // map already contains that key, without introducing double
516 // lookup?
529 // lookup?
517 let child_node = child_nodes
530 let child_node = child_nodes
518 .make_mut(on_disk)?
531 .make_mut(on_disk)?
519 .entry(to_cow(ancestor_path))
532 .entry(to_cow(ancestor_path))
520 .or_default();
533 .or_default();
521 if let Some(next) = inclusive_ancestor_paths.next() {
534 if let Some(next) = inclusive_ancestor_paths.next() {
522 each_ancestor(child_node);
535 each_ancestor(child_node);
523 ancestor_path = next;
536 ancestor_path = next;
524 child_nodes = &mut child_node.children;
537 child_nodes = &mut child_node.children;
525 } else {
538 } else {
526 return Ok(child_node);
539 return Ok(child_node);
527 }
540 }
528 }
541 }
529 }
542 }
530
543
531 fn add_or_remove_file(
544 fn add_or_remove_file(
532 &mut self,
545 &mut self,
533 path: &HgPath,
546 path: &HgPath,
534 old_state: EntryState,
547 old_state: EntryState,
535 new_entry: DirstateEntry,
548 new_entry: DirstateEntry,
536 ) -> Result<(), DirstateV2ParseError> {
549 ) -> Result<(), DirstateV2ParseError> {
537 let tracked_count_increment =
550 let tracked_count_increment =
538 match (old_state.is_tracked(), new_entry.state.is_tracked()) {
551 match (old_state.is_tracked(), new_entry.state.is_tracked()) {
539 (false, true) => 1,
552 (false, true) => 1,
540 (true, false) => -1,
553 (true, false) => -1,
541 _ => 0,
554 _ => 0,
542 };
555 };
543
556
544 let node = Self::get_or_insert_node(
557 let node = Self::get_or_insert_node(
545 self.on_disk,
558 self.on_disk,
546 &mut self.root,
559 &mut self.root,
547 path,
560 path,
548 WithBasename::to_cow_owned,
561 WithBasename::to_cow_owned,
549 |ancestor| {
562 |ancestor| {
550 // We can’t use `+= increment` because the counter is unsigned,
563 // We can’t use `+= increment` because the counter is unsigned,
551 // and we want debug builds to detect accidental underflow
564 // and we want debug builds to detect accidental underflow
552 // through zero
565 // through zero
553 match tracked_count_increment {
566 match tracked_count_increment {
554 1 => ancestor.tracked_descendants_count += 1,
567 1 => ancestor.tracked_descendants_count += 1,
555 -1 => ancestor.tracked_descendants_count -= 1,
568 -1 => ancestor.tracked_descendants_count -= 1,
556 _ => {}
569 _ => {}
557 }
570 }
558 },
571 },
559 )?;
572 )?;
560 if !node.data.has_entry() {
573 if !node.data.has_entry() {
561 self.nodes_with_entry_count += 1
574 self.nodes_with_entry_count += 1
562 }
575 }
563 node.data = NodeData::Entry(new_entry);
576 node.data = NodeData::Entry(new_entry);
564 Ok(())
577 Ok(())
565 }
578 }
566
579
567 fn iter_nodes<'tree>(
580 fn iter_nodes<'tree>(
568 &'tree self,
581 &'tree self,
569 ) -> impl Iterator<
582 ) -> impl Iterator<
570 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
583 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
571 > + 'tree {
584 > + 'tree {
572 // Depth first tree traversal.
585 // Depth first tree traversal.
573 //
586 //
574 // If we could afford internal iteration and recursion,
587 // If we could afford internal iteration and recursion,
575 // this would look like:
588 // this would look like:
576 //
589 //
577 // ```
590 // ```
578 // fn traverse_children(
591 // fn traverse_children(
579 // children: &ChildNodes,
592 // children: &ChildNodes,
580 // each: &mut impl FnMut(&Node),
593 // each: &mut impl FnMut(&Node),
581 // ) {
594 // ) {
582 // for child in children.values() {
595 // for child in children.values() {
583 // traverse_children(&child.children, each);
596 // traverse_children(&child.children, each);
584 // each(child);
597 // each(child);
585 // }
598 // }
586 // }
599 // }
587 // ```
600 // ```
588 //
601 //
589 // However we want an external iterator and therefore can’t use the
602 // However we want an external iterator and therefore can’t use the
590 // call stack. Use an explicit stack instead:
603 // call stack. Use an explicit stack instead:
591 let mut stack = Vec::new();
604 let mut stack = Vec::new();
592 let mut iter = self.root.as_ref().iter();
605 let mut iter = self.root.as_ref().iter();
593 std::iter::from_fn(move || {
606 std::iter::from_fn(move || {
594 while let Some(child_node) = iter.next() {
607 while let Some(child_node) = iter.next() {
595 let children = match child_node.children(self.on_disk) {
608 let children = match child_node.children(self.on_disk) {
596 Ok(children) => children,
609 Ok(children) => children,
597 Err(error) => return Some(Err(error)),
610 Err(error) => return Some(Err(error)),
598 };
611 };
599 // Pseudo-recursion
612 // Pseudo-recursion
600 let new_iter = children.iter();
613 let new_iter = children.iter();
601 let old_iter = std::mem::replace(&mut iter, new_iter);
614 let old_iter = std::mem::replace(&mut iter, new_iter);
602 stack.push((child_node, old_iter));
615 stack.push((child_node, old_iter));
603 }
616 }
604 // Found the end of a `children.iter()` iterator.
617 // Found the end of a `children.iter()` iterator.
605 if let Some((child_node, next_iter)) = stack.pop() {
618 if let Some((child_node, next_iter)) = stack.pop() {
606 // "Return" from pseudo-recursion by restoring state from the
619 // "Return" from pseudo-recursion by restoring state from the
607 // explicit stack
620 // explicit stack
608 iter = next_iter;
621 iter = next_iter;
609
622
610 Some(Ok(child_node))
623 Some(Ok(child_node))
611 } else {
624 } else {
612 // Reached the bottom of the stack, we’re done
625 // Reached the bottom of the stack, we’re done
613 None
626 None
614 }
627 }
615 })
628 })
616 }
629 }
617
630
618 fn clear_known_ambiguous_mtimes(
631 fn clear_known_ambiguous_mtimes(
619 &mut self,
632 &mut self,
620 paths: &[impl AsRef<HgPath>],
633 paths: &[impl AsRef<HgPath>],
621 ) -> Result<(), DirstateV2ParseError> {
634 ) -> Result<(), DirstateV2ParseError> {
622 for path in paths {
635 for path in paths {
623 if let Some(node) = Self::get_node_mut(
636 if let Some(node) = Self::get_node_mut(
624 self.on_disk,
637 self.on_disk,
625 &mut self.root,
638 &mut self.root,
626 path.as_ref(),
639 path.as_ref(),
627 )? {
640 )? {
628 if let NodeData::Entry(entry) = &mut node.data {
641 if let NodeData::Entry(entry) = &mut node.data {
629 entry.clear_mtime();
642 entry.clear_mtime();
630 }
643 }
631 }
644 }
632 }
645 }
633 Ok(())
646 Ok(())
634 }
647 }
635
648
636 /// Return a faillilble iterator of full paths of nodes that have an
649 /// Return a faillilble iterator of full paths of nodes that have an
637 /// `entry` for which the given `predicate` returns true.
650 /// `entry` for which the given `predicate` returns true.
638 ///
651 ///
639 /// Fallibility means that each iterator item is a `Result`, which may
652 /// Fallibility means that each iterator item is a `Result`, which may
640 /// indicate a parse error of the on-disk dirstate-v2 format. Such errors
653 /// indicate a parse error of the on-disk dirstate-v2 format. Such errors
641 /// should only happen if Mercurial is buggy or a repository is corrupted.
654 /// should only happen if Mercurial is buggy or a repository is corrupted.
642 fn filter_full_paths<'tree>(
655 fn filter_full_paths<'tree>(
643 &'tree self,
656 &'tree self,
644 predicate: impl Fn(&DirstateEntry) -> bool + 'tree,
657 predicate: impl Fn(&DirstateEntry) -> bool + 'tree,
645 ) -> impl Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + 'tree
658 ) -> impl Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + 'tree
646 {
659 {
647 filter_map_results(self.iter_nodes(), move |node| {
660 filter_map_results(self.iter_nodes(), move |node| {
648 if let Some(entry) = node.entry()? {
661 if let Some(entry) = node.entry()? {
649 if predicate(&entry) {
662 if predicate(&entry) {
650 return Ok(Some(node.full_path(self.on_disk)?));
663 return Ok(Some(node.full_path(self.on_disk)?));
651 }
664 }
652 }
665 }
653 Ok(None)
666 Ok(None)
654 })
667 })
655 }
668 }
656 }
669 }
657
670
658 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
671 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
659 ///
672 ///
660 /// The callback is only called for incoming `Ok` values. Errors are passed
673 /// The callback is only called for incoming `Ok` values. Errors are passed
661 /// through as-is. In order to let it use the `?` operator the callback is
674 /// through as-is. In order to let it use the `?` operator the callback is
662 /// expected to return a `Result` of `Option`, instead of an `Option` of
675 /// expected to return a `Result` of `Option`, instead of an `Option` of
663 /// `Result`.
676 /// `Result`.
664 fn filter_map_results<'a, I, F, A, B, E>(
677 fn filter_map_results<'a, I, F, A, B, E>(
665 iter: I,
678 iter: I,
666 f: F,
679 f: F,
667 ) -> impl Iterator<Item = Result<B, E>> + 'a
680 ) -> impl Iterator<Item = Result<B, E>> + 'a
668 where
681 where
669 I: Iterator<Item = Result<A, E>> + 'a,
682 I: Iterator<Item = Result<A, E>> + 'a,
670 F: Fn(A) -> Result<Option<B>, E> + 'a,
683 F: Fn(A) -> Result<Option<B>, E> + 'a,
671 {
684 {
672 iter.filter_map(move |result| match result {
685 iter.filter_map(move |result| match result {
673 Ok(node) => f(node).transpose(),
686 Ok(node) => f(node).transpose(),
674 Err(e) => Some(Err(e)),
687 Err(e) => Some(Err(e)),
675 })
688 })
676 }
689 }
677
690
678 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
691 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
679 fn clear(&mut self) {
692 fn clear(&mut self) {
680 self.root = Default::default();
693 self.root = Default::default();
681 self.nodes_with_entry_count = 0;
694 self.nodes_with_entry_count = 0;
682 self.nodes_with_copy_source_count = 0;
695 self.nodes_with_copy_source_count = 0;
683 }
696 }
684
697
685 fn add_file(
698 fn add_file(
686 &mut self,
699 &mut self,
687 filename: &HgPath,
700 filename: &HgPath,
688 old_state: EntryState,
701 old_state: EntryState,
689 entry: DirstateEntry,
702 entry: DirstateEntry,
690 ) -> Result<(), DirstateError> {
703 ) -> Result<(), DirstateError> {
691 Ok(self.add_or_remove_file(filename, old_state, entry)?)
704 Ok(self.add_or_remove_file(filename, old_state, entry)?)
692 }
705 }
693
706
694 fn remove_file(
707 fn remove_file(
695 &mut self,
708 &mut self,
696 filename: &HgPath,
709 filename: &HgPath,
697 old_state: EntryState,
710 old_state: EntryState,
698 size: i32,
711 size: i32,
699 ) -> Result<(), DirstateError> {
712 ) -> Result<(), DirstateError> {
700 let entry = DirstateEntry {
713 let entry = DirstateEntry {
701 state: EntryState::Removed,
714 state: EntryState::Removed,
702 mode: 0,
715 mode: 0,
703 size,
716 size,
704 mtime: 0,
717 mtime: 0,
705 };
718 };
706 Ok(self.add_or_remove_file(filename, old_state, entry)?)
719 Ok(self.add_or_remove_file(filename, old_state, entry)?)
707 }
720 }
708
721
709 fn drop_file(
722 fn drop_file(
710 &mut self,
723 &mut self,
711 filename: &HgPath,
724 filename: &HgPath,
712 old_state: EntryState,
725 old_state: EntryState,
713 ) -> Result<bool, DirstateError> {
726 ) -> Result<bool, DirstateError> {
714 struct Dropped {
727 struct Dropped {
715 was_tracked: bool,
728 was_tracked: bool,
716 had_entry: bool,
729 had_entry: bool,
717 had_copy_source: bool,
730 had_copy_source: bool,
718 }
731 }
719
732
720 /// If this returns `Ok(Some((dropped, removed)))`, then
733 /// If this returns `Ok(Some((dropped, removed)))`, then
721 ///
734 ///
722 /// * `dropped` is about the leaf node that was at `filename`
735 /// * `dropped` is about the leaf node that was at `filename`
723 /// * `removed` is whether this particular level of recursion just
736 /// * `removed` is whether this particular level of recursion just
724 /// removed a node in `nodes`.
737 /// removed a node in `nodes`.
725 fn recur<'on_disk>(
738 fn recur<'on_disk>(
726 on_disk: &'on_disk [u8],
739 on_disk: &'on_disk [u8],
727 nodes: &mut ChildNodes<'on_disk>,
740 nodes: &mut ChildNodes<'on_disk>,
728 path: &HgPath,
741 path: &HgPath,
729 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
742 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
730 let (first_path_component, rest_of_path) =
743 let (first_path_component, rest_of_path) =
731 path.split_first_component();
744 path.split_first_component();
732 let node = if let Some(node) =
745 let node = if let Some(node) =
733 nodes.make_mut(on_disk)?.get_mut(first_path_component)
746 nodes.make_mut(on_disk)?.get_mut(first_path_component)
734 {
747 {
735 node
748 node
736 } else {
749 } else {
737 return Ok(None);
750 return Ok(None);
738 };
751 };
739 let dropped;
752 let dropped;
740 if let Some(rest) = rest_of_path {
753 if let Some(rest) = rest_of_path {
741 if let Some((d, removed)) =
754 if let Some((d, removed)) =
742 recur(on_disk, &mut node.children, rest)?
755 recur(on_disk, &mut node.children, rest)?
743 {
756 {
744 dropped = d;
757 dropped = d;
745 if dropped.was_tracked {
758 if dropped.was_tracked {
746 node.tracked_descendants_count -= 1;
759 node.tracked_descendants_count -= 1;
747 }
760 }
748
761
749 // Directory caches must be invalidated when removing a
762 // Directory caches must be invalidated when removing a
750 // child node
763 // child node
751 if removed {
764 if removed {
752 if let NodeData::CachedDirectory { .. } = &node.data {
765 if let NodeData::CachedDirectory { .. } = &node.data {
753 node.data = NodeData::None
766 node.data = NodeData::None
754 }
767 }
755 }
768 }
756 } else {
769 } else {
757 return Ok(None);
770 return Ok(None);
758 }
771 }
759 } else {
772 } else {
760 let had_entry = node.data.has_entry();
773 let had_entry = node.data.has_entry();
761 if had_entry {
774 if had_entry {
762 node.data = NodeData::None
775 node.data = NodeData::None
763 }
776 }
764 dropped = Dropped {
777 dropped = Dropped {
765 was_tracked: node
778 was_tracked: node
766 .data
779 .data
767 .as_entry()
780 .as_entry()
768 .map_or(false, |entry| entry.state.is_tracked()),
781 .map_or(false, |entry| entry.state.is_tracked()),
769 had_entry,
782 had_entry,
770 had_copy_source: node.copy_source.take().is_some(),
783 had_copy_source: node.copy_source.take().is_some(),
771 };
784 };
772 }
785 }
773 // After recursion, for both leaf (rest_of_path is None) nodes and
786 // After recursion, for both leaf (rest_of_path is None) nodes and
774 // parent nodes, remove a node if it just became empty.
787 // parent nodes, remove a node if it just became empty.
775 let remove = !node.data.has_entry()
788 let remove = !node.data.has_entry()
776 && node.copy_source.is_none()
789 && node.copy_source.is_none()
777 && node.children.is_empty();
790 && node.children.is_empty();
778 if remove {
791 if remove {
779 nodes.make_mut(on_disk)?.remove(first_path_component);
792 nodes.make_mut(on_disk)?.remove(first_path_component);
780 }
793 }
781 Ok(Some((dropped, remove)))
794 Ok(Some((dropped, remove)))
782 }
795 }
783
796
784 if let Some((dropped, _removed)) =
797 if let Some((dropped, _removed)) =
785 recur(self.on_disk, &mut self.root, filename)?
798 recur(self.on_disk, &mut self.root, filename)?
786 {
799 {
787 if dropped.had_entry {
800 if dropped.had_entry {
788 self.nodes_with_entry_count -= 1
801 self.nodes_with_entry_count -= 1
789 }
802 }
790 if dropped.had_copy_source {
803 if dropped.had_copy_source {
791 self.nodes_with_copy_source_count -= 1
804 self.nodes_with_copy_source_count -= 1
792 }
805 }
793 Ok(dropped.had_entry)
806 Ok(dropped.had_entry)
794 } else {
807 } else {
795 debug_assert!(!old_state.is_tracked());
808 debug_assert!(!old_state.is_tracked());
796 Ok(false)
809 Ok(false)
797 }
810 }
798 }
811 }
799
812
800 fn clear_ambiguous_times(
813 fn clear_ambiguous_times(
801 &mut self,
814 &mut self,
802 filenames: Vec<HgPathBuf>,
815 filenames: Vec<HgPathBuf>,
803 now: i32,
816 now: i32,
804 ) -> Result<(), DirstateV2ParseError> {
817 ) -> Result<(), DirstateV2ParseError> {
805 for filename in filenames {
818 for filename in filenames {
806 if let Some(node) =
819 if let Some(node) =
807 Self::get_node_mut(self.on_disk, &mut self.root, &filename)?
820 Self::get_node_mut(self.on_disk, &mut self.root, &filename)?
808 {
821 {
809 if let NodeData::Entry(entry) = &mut node.data {
822 if let NodeData::Entry(entry) = &mut node.data {
810 entry.clear_ambiguous_mtime(now);
823 entry.clear_ambiguous_mtime(now);
811 }
824 }
812 }
825 }
813 }
826 }
814 Ok(())
827 Ok(())
815 }
828 }
816
829
817 fn non_normal_entries_contains(
830 fn non_normal_entries_contains(
818 &mut self,
831 &mut self,
819 key: &HgPath,
832 key: &HgPath,
820 ) -> Result<bool, DirstateV2ParseError> {
833 ) -> Result<bool, DirstateV2ParseError> {
821 Ok(if let Some(node) = self.get_node(key)? {
834 Ok(if let Some(node) = self.get_node(key)? {
822 node.entry()?.map_or(false, |entry| entry.is_non_normal())
835 node.entry()?.map_or(false, |entry| entry.is_non_normal())
823 } else {
836 } else {
824 false
837 false
825 })
838 })
826 }
839 }
827
840
828 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
841 fn non_normal_entries_remove(&mut self, _key: &HgPath) {
829 // Do nothing, this `DirstateMap` does not have a separate "non normal
842 // Do nothing, this `DirstateMap` does not have a separate "non normal
830 // entries" set that need to be kept up to date
843 // entries" set that need to be kept up to date
831 }
844 }
832
845
833 fn non_normal_or_other_parent_paths(
846 fn non_normal_or_other_parent_paths(
834 &mut self,
847 &mut self,
835 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
848 ) -> Box<dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + '_>
836 {
849 {
837 Box::new(self.filter_full_paths(|entry| {
850 Box::new(self.filter_full_paths(|entry| {
838 entry.is_non_normal() || entry.is_from_other_parent()
851 entry.is_non_normal() || entry.is_from_other_parent()
839 }))
852 }))
840 }
853 }
841
854
842 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
855 fn set_non_normal_other_parent_entries(&mut self, _force: bool) {
843 // Do nothing, this `DirstateMap` does not have a separate "non normal
856 // Do nothing, this `DirstateMap` does not have a separate "non normal
844 // entries" and "from other parent" sets that need to be recomputed
857 // entries" and "from other parent" sets that need to be recomputed
845 }
858 }
846
859
847 fn iter_non_normal_paths(
860 fn iter_non_normal_paths(
848 &mut self,
861 &mut self,
849 ) -> Box<
862 ) -> Box<
850 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
863 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
851 > {
864 > {
852 self.iter_non_normal_paths_panic()
865 self.iter_non_normal_paths_panic()
853 }
866 }
854
867
855 fn iter_non_normal_paths_panic(
868 fn iter_non_normal_paths_panic(
856 &self,
869 &self,
857 ) -> Box<
870 ) -> Box<
858 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
871 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
859 > {
872 > {
860 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
873 Box::new(self.filter_full_paths(|entry| entry.is_non_normal()))
861 }
874 }
862
875
863 fn iter_other_parent_paths(
876 fn iter_other_parent_paths(
864 &mut self,
877 &mut self,
865 ) -> Box<
878 ) -> Box<
866 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
879 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>> + Send + '_,
867 > {
880 > {
868 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
881 Box::new(self.filter_full_paths(|entry| entry.is_from_other_parent()))
869 }
882 }
870
883
871 fn has_tracked_dir(
884 fn has_tracked_dir(
872 &mut self,
885 &mut self,
873 directory: &HgPath,
886 directory: &HgPath,
874 ) -> Result<bool, DirstateError> {
887 ) -> Result<bool, DirstateError> {
875 if let Some(node) = self.get_node(directory)? {
888 if let Some(node) = self.get_node(directory)? {
876 // A node without a `DirstateEntry` was created to hold child
889 // A node without a `DirstateEntry` was created to hold child
877 // nodes, and is therefore a directory.
890 // nodes, and is therefore a directory.
878 let state = node.state()?;
891 let state = node.state()?;
879 Ok(state.is_none() && node.tracked_descendants_count() > 0)
892 Ok(state.is_none() && node.tracked_descendants_count() > 0)
880 } else {
893 } else {
881 Ok(false)
894 Ok(false)
882 }
895 }
883 }
896 }
884
897
885 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
898 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
886 if let Some(node) = self.get_node(directory)? {
899 if let Some(node) = self.get_node(directory)? {
887 // A node without a `DirstateEntry` was created to hold child
900 // A node without a `DirstateEntry` was created to hold child
888 // nodes, and is therefore a directory.
901 // nodes, and is therefore a directory.
889 Ok(node.state()?.is_none())
902 Ok(node.state()?.is_none())
890 } else {
903 } else {
891 Ok(false)
904 Ok(false)
892 }
905 }
893 }
906 }
894
907
895 #[timed]
908 #[timed]
896 fn pack_v1(
909 fn pack_v1(
897 &mut self,
910 &mut self,
898 parents: DirstateParents,
911 parents: DirstateParents,
899 now: Timestamp,
912 now: Timestamp,
900 ) -> Result<Vec<u8>, DirstateError> {
913 ) -> Result<Vec<u8>, DirstateError> {
901 let now: i32 = now.0.try_into().expect("time overflow");
914 let now: i32 = now.0.try_into().expect("time overflow");
902 let mut ambiguous_mtimes = Vec::new();
915 let mut ambiguous_mtimes = Vec::new();
903 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
916 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
904 // reallocations
917 // reallocations
905 let mut size = parents.as_bytes().len();
918 let mut size = parents.as_bytes().len();
906 for node in self.iter_nodes() {
919 for node in self.iter_nodes() {
907 let node = node?;
920 let node = node?;
908 if let Some(entry) = node.entry()? {
921 if let Some(entry) = node.entry()? {
909 size += packed_entry_size(
922 size += packed_entry_size(
910 node.full_path(self.on_disk)?,
923 node.full_path(self.on_disk)?,
911 node.copy_source(self.on_disk)?,
924 node.copy_source(self.on_disk)?,
912 );
925 );
913 if entry.mtime_is_ambiguous(now) {
926 if entry.mtime_is_ambiguous(now) {
914 ambiguous_mtimes.push(
927 ambiguous_mtimes.push(
915 node.full_path_borrowed(self.on_disk)?
928 node.full_path_borrowed(self.on_disk)?
916 .detach_from_tree(),
929 .detach_from_tree(),
917 )
930 )
918 }
931 }
919 }
932 }
920 }
933 }
921 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
934 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
922
935
923 let mut packed = Vec::with_capacity(size);
936 let mut packed = Vec::with_capacity(size);
924 packed.extend(parents.as_bytes());
937 packed.extend(parents.as_bytes());
925
938
926 for node in self.iter_nodes() {
939 for node in self.iter_nodes() {
927 let node = node?;
940 let node = node?;
928 if let Some(entry) = node.entry()? {
941 if let Some(entry) = node.entry()? {
929 pack_entry(
942 pack_entry(
930 node.full_path(self.on_disk)?,
943 node.full_path(self.on_disk)?,
931 &entry,
944 &entry,
932 node.copy_source(self.on_disk)?,
945 node.copy_source(self.on_disk)?,
933 &mut packed,
946 &mut packed,
934 );
947 );
935 }
948 }
936 }
949 }
937 Ok(packed)
950 Ok(packed)
938 }
951 }
939
952
940 #[timed]
953 #[timed]
941 fn pack_v2(
954 fn pack_v2(
942 &mut self,
955 &mut self,
943 parents: DirstateParents,
956 parents: DirstateParents,
944 now: Timestamp,
957 now: Timestamp,
945 ) -> Result<Vec<u8>, DirstateError> {
958 ) -> Result<Vec<u8>, DirstateError> {
946 // TODO: how do we want to handle this in 2038?
959 // TODO: how do we want to handle this in 2038?
947 let now: i32 = now.0.try_into().expect("time overflow");
960 let now: i32 = now.0.try_into().expect("time overflow");
948 let mut paths = Vec::new();
961 let mut paths = Vec::new();
949 for node in self.iter_nodes() {
962 for node in self.iter_nodes() {
950 let node = node?;
963 let node = node?;
951 if let Some(entry) = node.entry()? {
964 if let Some(entry) = node.entry()? {
952 if entry.mtime_is_ambiguous(now) {
965 if entry.mtime_is_ambiguous(now) {
953 paths.push(
966 paths.push(
954 node.full_path_borrowed(self.on_disk)?
967 node.full_path_borrowed(self.on_disk)?
955 .detach_from_tree(),
968 .detach_from_tree(),
956 )
969 )
957 }
970 }
958 }
971 }
959 }
972 }
960 // Borrow of `self` ends here since we collect cloned paths
973 // Borrow of `self` ends here since we collect cloned paths
961
974
962 self.clear_known_ambiguous_mtimes(&paths)?;
975 self.clear_known_ambiguous_mtimes(&paths)?;
963
976
964 on_disk::write(self, parents)
977 on_disk::write(self, parents)
965 }
978 }
966
979
967 fn set_all_dirs(&mut self) -> Result<(), DirstateError> {
980 fn set_all_dirs(&mut self) -> Result<(), DirstateError> {
968 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
981 // Do nothing, this `DirstateMap` does not a separate `all_dirs` that
969 // needs to be recomputed
982 // needs to be recomputed
970 Ok(())
983 Ok(())
971 }
984 }
972
985
973 fn set_dirs(&mut self) -> Result<(), DirstateError> {
986 fn set_dirs(&mut self) -> Result<(), DirstateError> {
974 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
987 // Do nothing, this `DirstateMap` does not a separate `dirs` that needs
975 // to be recomputed
988 // to be recomputed
976 Ok(())
989 Ok(())
977 }
990 }
978
991
979 fn status<'a>(
992 fn status<'a>(
980 &'a mut self,
993 &'a mut self,
981 matcher: &'a (dyn Matcher + Sync),
994 matcher: &'a (dyn Matcher + Sync),
982 root_dir: PathBuf,
995 root_dir: PathBuf,
983 ignore_files: Vec<PathBuf>,
996 ignore_files: Vec<PathBuf>,
984 options: StatusOptions,
997 options: StatusOptions,
985 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
998 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
986 {
999 {
987 super::status::status(self, matcher, root_dir, ignore_files, options)
1000 super::status::status(self, matcher, root_dir, ignore_files, options)
988 }
1001 }
989
1002
990 fn copy_map_len(&self) -> usize {
1003 fn copy_map_len(&self) -> usize {
991 self.nodes_with_copy_source_count as usize
1004 self.nodes_with_copy_source_count as usize
992 }
1005 }
993
1006
994 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1007 fn copy_map_iter(&self) -> CopyMapIter<'_> {
995 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1008 Box::new(filter_map_results(self.iter_nodes(), move |node| {
996 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1009 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
997 Some((node.full_path(self.on_disk)?, source))
1010 Some((node.full_path(self.on_disk)?, source))
998 } else {
1011 } else {
999 None
1012 None
1000 })
1013 })
1001 }))
1014 }))
1002 }
1015 }
1003
1016
1004 fn copy_map_contains_key(
1017 fn copy_map_contains_key(
1005 &self,
1018 &self,
1006 key: &HgPath,
1019 key: &HgPath,
1007 ) -> Result<bool, DirstateV2ParseError> {
1020 ) -> Result<bool, DirstateV2ParseError> {
1008 Ok(if let Some(node) = self.get_node(key)? {
1021 Ok(if let Some(node) = self.get_node(key)? {
1009 node.has_copy_source()
1022 node.has_copy_source()
1010 } else {
1023 } else {
1011 false
1024 false
1012 })
1025 })
1013 }
1026 }
1014
1027
1015 fn copy_map_get(
1028 fn copy_map_get(
1016 &self,
1029 &self,
1017 key: &HgPath,
1030 key: &HgPath,
1018 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1031 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1019 if let Some(node) = self.get_node(key)? {
1032 if let Some(node) = self.get_node(key)? {
1020 if let Some(source) = node.copy_source(self.on_disk)? {
1033 if let Some(source) = node.copy_source(self.on_disk)? {
1021 return Ok(Some(source));
1034 return Ok(Some(source));
1022 }
1035 }
1023 }
1036 }
1024 Ok(None)
1037 Ok(None)
1025 }
1038 }
1026
1039
1027 fn copy_map_remove(
1040 fn copy_map_remove(
1028 &mut self,
1041 &mut self,
1029 key: &HgPath,
1042 key: &HgPath,
1030 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1043 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1031 let count = &mut self.nodes_with_copy_source_count;
1044 let count = &mut self.nodes_with_copy_source_count;
1032 Ok(
1045 Ok(
1033 Self::get_node_mut(self.on_disk, &mut self.root, key)?.and_then(
1046 Self::get_node_mut(self.on_disk, &mut self.root, key)?.and_then(
1034 |node| {
1047 |node| {
1035 if node.copy_source.is_some() {
1048 if node.copy_source.is_some() {
1036 *count -= 1
1049 *count -= 1
1037 }
1050 }
1038 node.copy_source.take().map(Cow::into_owned)
1051 node.copy_source.take().map(Cow::into_owned)
1039 },
1052 },
1040 ),
1053 ),
1041 )
1054 )
1042 }
1055 }
1043
1056
1044 fn copy_map_insert(
1057 fn copy_map_insert(
1045 &mut self,
1058 &mut self,
1046 key: HgPathBuf,
1059 key: HgPathBuf,
1047 value: HgPathBuf,
1060 value: HgPathBuf,
1048 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1061 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1049 let node = Self::get_or_insert_node(
1062 let node = Self::get_or_insert_node(
1050 self.on_disk,
1063 self.on_disk,
1051 &mut self.root,
1064 &mut self.root,
1052 &key,
1065 &key,
1053 WithBasename::to_cow_owned,
1066 WithBasename::to_cow_owned,
1054 |_ancestor| {},
1067 |_ancestor| {},
1055 )?;
1068 )?;
1056 if node.copy_source.is_none() {
1069 if node.copy_source.is_none() {
1057 self.nodes_with_copy_source_count += 1
1070 self.nodes_with_copy_source_count += 1
1058 }
1071 }
1059 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1072 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1060 }
1073 }
1061
1074
1062 fn len(&self) -> usize {
1075 fn len(&self) -> usize {
1063 self.nodes_with_entry_count as usize
1076 self.nodes_with_entry_count as usize
1064 }
1077 }
1065
1078
1066 fn contains_key(
1079 fn contains_key(
1067 &self,
1080 &self,
1068 key: &HgPath,
1081 key: &HgPath,
1069 ) -> Result<bool, DirstateV2ParseError> {
1082 ) -> Result<bool, DirstateV2ParseError> {
1070 Ok(self.get(key)?.is_some())
1083 Ok(self.get(key)?.is_some())
1071 }
1084 }
1072
1085
1073 fn get(
1086 fn get(
1074 &self,
1087 &self,
1075 key: &HgPath,
1088 key: &HgPath,
1076 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1089 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1077 Ok(if let Some(node) = self.get_node(key)? {
1090 Ok(if let Some(node) = self.get_node(key)? {
1078 node.entry()?
1091 node.entry()?
1079 } else {
1092 } else {
1080 None
1093 None
1081 })
1094 })
1082 }
1095 }
1083
1096
1084 fn iter(&self) -> StateMapIter<'_> {
1097 fn iter(&self) -> StateMapIter<'_> {
1085 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1098 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1086 Ok(if let Some(entry) = node.entry()? {
1099 Ok(if let Some(entry) = node.entry()? {
1087 Some((node.full_path(self.on_disk)?, entry))
1100 Some((node.full_path(self.on_disk)?, entry))
1088 } else {
1101 } else {
1089 None
1102 None
1090 })
1103 })
1091 }))
1104 }))
1092 }
1105 }
1093
1106
1094 fn iter_directories(
1107 fn iter_directories(
1095 &self,
1108 &self,
1096 ) -> Box<
1109 ) -> Box<
1097 dyn Iterator<
1110 dyn Iterator<
1098 Item = Result<
1111 Item = Result<
1099 (&HgPath, Option<Timestamp>),
1112 (&HgPath, Option<Timestamp>),
1100 DirstateV2ParseError,
1113 DirstateV2ParseError,
1101 >,
1114 >,
1102 > + Send
1115 > + Send
1103 + '_,
1116 + '_,
1104 > {
1117 > {
1105 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1118 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1106 Ok(if node.state()?.is_none() {
1119 Ok(if node.state()?.is_none() {
1107 Some((
1120 Some((
1108 node.full_path(self.on_disk)?,
1121 node.full_path(self.on_disk)?,
1109 node.cached_directory_mtime()
1122 node.cached_directory_mtime()
1110 .map(|mtime| Timestamp(mtime.seconds())),
1123 .map(|mtime| Timestamp(mtime.seconds())),
1111 ))
1124 ))
1112 } else {
1125 } else {
1113 None
1126 None
1114 })
1127 })
1115 }))
1128 }))
1116 }
1129 }
1117 }
1130 }
@@ -1,705 +1,728 b''
1 use crate::dirstate::status::IgnoreFnType;
1 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate_tree::dirstate_map::BorrowedPath;
2 use crate::dirstate_tree::dirstate_map::BorrowedPath;
3 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
3 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 use crate::dirstate_tree::dirstate_map::NodeData;
5 use crate::dirstate_tree::dirstate_map::NodeData;
6 use crate::dirstate_tree::dirstate_map::NodeRef;
6 use crate::dirstate_tree::dirstate_map::NodeRef;
7 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
7 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
8 use crate::dirstate_tree::on_disk::Timestamp;
8 use crate::dirstate_tree::on_disk::Timestamp;
9 use crate::dirstate_tree::path_with_basename::WithBasename;
10 use crate::matchers::get_ignore_function;
9 use crate::matchers::get_ignore_function;
11 use crate::matchers::Matcher;
10 use crate::matchers::Matcher;
12 use crate::utils::files::get_bytes_from_os_string;
11 use crate::utils::files::get_bytes_from_os_string;
13 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPath;
15 use crate::BadMatch;
14 use crate::BadMatch;
16 use crate::DirstateStatus;
15 use crate::DirstateStatus;
17 use crate::EntryState;
16 use crate::EntryState;
18 use crate::HgPathBuf;
17 use crate::HgPathBuf;
19 use crate::PatternFileWarning;
18 use crate::PatternFileWarning;
20 use crate::StatusError;
19 use crate::StatusError;
21 use crate::StatusOptions;
20 use crate::StatusOptions;
22 use micro_timer::timed;
21 use micro_timer::timed;
23 use rayon::prelude::*;
22 use rayon::prelude::*;
24 use sha1::{Digest, Sha1};
23 use sha1::{Digest, Sha1};
25 use std::borrow::Cow;
24 use std::borrow::Cow;
26 use std::io;
25 use std::io;
27 use std::path::Path;
26 use std::path::Path;
28 use std::path::PathBuf;
27 use std::path::PathBuf;
29 use std::sync::Mutex;
28 use std::sync::Mutex;
30 use std::time::SystemTime;
29 use std::time::SystemTime;
31
30
32 /// Returns the status of the working directory compared to its parent
31 /// Returns the status of the working directory compared to its parent
33 /// changeset.
32 /// changeset.
34 ///
33 ///
35 /// This algorithm is based on traversing the filesystem tree (`fs` in function
34 /// This algorithm is based on traversing the filesystem tree (`fs` in function
36 /// and variable names) and dirstate tree at the same time. The core of this
35 /// and variable names) and dirstate tree at the same time. The core of this
37 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
36 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
38 /// and its use of `itertools::merge_join_by`. When reaching a path that only
37 /// and its use of `itertools::merge_join_by`. When reaching a path that only
39 /// exists in one of the two trees, depending on information requested by
38 /// exists in one of the two trees, depending on information requested by
40 /// `options` we may need to traverse the remaining subtree.
39 /// `options` we may need to traverse the remaining subtree.
41 #[timed]
40 #[timed]
42 pub fn status<'tree, 'on_disk: 'tree>(
41 pub fn status<'tree, 'on_disk: 'tree>(
43 dmap: &'tree mut DirstateMap<'on_disk>,
42 dmap: &'tree mut DirstateMap<'on_disk>,
44 matcher: &(dyn Matcher + Sync),
43 matcher: &(dyn Matcher + Sync),
45 root_dir: PathBuf,
44 root_dir: PathBuf,
46 ignore_files: Vec<PathBuf>,
45 ignore_files: Vec<PathBuf>,
47 options: StatusOptions,
46 options: StatusOptions,
48 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
47 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
49 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
48 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
50 if options.list_ignored || options.list_unknown {
49 if options.list_ignored || options.list_unknown {
51 let mut hasher = Sha1::new();
50 let mut hasher = Sha1::new();
52 let (ignore_fn, warnings) = get_ignore_function(
51 let (ignore_fn, warnings) = get_ignore_function(
53 ignore_files,
52 ignore_files,
54 &root_dir,
53 &root_dir,
55 &mut |pattern_bytes| hasher.update(pattern_bytes),
54 &mut |pattern_bytes| hasher.update(pattern_bytes),
56 )?;
55 )?;
57 let new_hash = *hasher.finalize().as_ref();
56 let new_hash = *hasher.finalize().as_ref();
58 let changed = new_hash != dmap.ignore_patterns_hash;
57 let changed = new_hash != dmap.ignore_patterns_hash;
59 dmap.ignore_patterns_hash = new_hash;
58 dmap.ignore_patterns_hash = new_hash;
60 (ignore_fn, warnings, Some(changed))
59 (ignore_fn, warnings, Some(changed))
61 } else {
60 } else {
62 (Box::new(|&_| true), vec![], None)
61 (Box::new(|&_| true), vec![], None)
63 };
62 };
64
63
65 let common = StatusCommon {
64 let common = StatusCommon {
66 dmap,
65 dmap,
67 options,
66 options,
68 matcher,
67 matcher,
69 ignore_fn,
68 ignore_fn,
70 outcome: Default::default(),
69 outcome: Default::default(),
71 ignore_patterns_have_changed: patterns_changed,
70 ignore_patterns_have_changed: patterns_changed,
72 new_cachable_directories: Default::default(),
71 new_cachable_directories: Default::default(),
72 outated_cached_directories: Default::default(),
73 filesystem_time_at_status_start: filesystem_now(&root_dir).ok(),
73 filesystem_time_at_status_start: filesystem_now(&root_dir).ok(),
74 };
74 };
75 let is_at_repo_root = true;
75 let is_at_repo_root = true;
76 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
76 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
77 let has_ignored_ancestor = false;
77 let has_ignored_ancestor = false;
78 let root_cached_mtime = None;
78 let root_cached_mtime = None;
79 let root_dir_metadata = None;
79 let root_dir_metadata = None;
80 // If the path we have for the repository root is a symlink, do follow it.
80 // If the path we have for the repository root is a symlink, do follow it.
81 // (As opposed to symlinks within the working directory which are not
81 // (As opposed to symlinks within the working directory which are not
82 // followed, using `std::fs::symlink_metadata`.)
82 // followed, using `std::fs::symlink_metadata`.)
83 common.traverse_fs_directory_and_dirstate(
83 common.traverse_fs_directory_and_dirstate(
84 has_ignored_ancestor,
84 has_ignored_ancestor,
85 dmap.root.as_ref(),
85 dmap.root.as_ref(),
86 hg_path,
86 hg_path,
87 &root_dir,
87 &root_dir,
88 root_dir_metadata,
88 root_dir_metadata,
89 root_cached_mtime,
89 root_cached_mtime,
90 is_at_repo_root,
90 is_at_repo_root,
91 )?;
91 )?;
92 let mut outcome = common.outcome.into_inner().unwrap();
92 let mut outcome = common.outcome.into_inner().unwrap();
93 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
93 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
94 let outdated = common.outated_cached_directories.into_inner().unwrap();
94
95
95 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
96 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
97 || !outdated.is_empty()
96 || !new_cachable.is_empty();
98 || !new_cachable.is_empty();
97
99
100 // Remove outdated mtimes before adding new mtimes, in case a given
101 // directory is both
102 for path in &outdated {
103 let node = dmap.get_or_insert(path)?;
104 if let NodeData::CachedDirectory { .. } = &node.data {
105 node.data = NodeData::None
106 }
107 }
98 for (path, mtime) in &new_cachable {
108 for (path, mtime) in &new_cachable {
99 let node = DirstateMap::get_or_insert_node(
109 let node = dmap.get_or_insert(path)?;
100 dmap.on_disk,
101 &mut dmap.root,
102 path,
103 WithBasename::to_cow_owned,
104 |_| {},
105 )?;
106 match &node.data {
110 match &node.data {
107 NodeData::Entry(_) => {} // Don’t overwrite an entry
111 NodeData::Entry(_) => {} // Don’t overwrite an entry
108 NodeData::CachedDirectory { .. } | NodeData::None => {
112 NodeData::CachedDirectory { .. } | NodeData::None => {
109 node.data = NodeData::CachedDirectory { mtime: *mtime }
113 node.data = NodeData::CachedDirectory { mtime: *mtime }
110 }
114 }
111 }
115 }
112 }
116 }
113
117
114 Ok((outcome, warnings))
118 Ok((outcome, warnings))
115 }
119 }
116
120
117 /// Bag of random things needed by various parts of the algorithm. Reduces the
121 /// Bag of random things needed by various parts of the algorithm. Reduces the
118 /// number of parameters passed to functions.
122 /// number of parameters passed to functions.
119 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
123 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
120 dmap: &'tree DirstateMap<'on_disk>,
124 dmap: &'tree DirstateMap<'on_disk>,
121 options: StatusOptions,
125 options: StatusOptions,
122 matcher: &'a (dyn Matcher + Sync),
126 matcher: &'a (dyn Matcher + Sync),
123 ignore_fn: IgnoreFnType<'a>,
127 ignore_fn: IgnoreFnType<'a>,
124 outcome: Mutex<DirstateStatus<'on_disk>>,
128 outcome: Mutex<DirstateStatus<'on_disk>>,
125 new_cachable_directories: Mutex<Vec<(Cow<'on_disk, HgPath>, Timestamp)>>,
129 new_cachable_directories: Mutex<Vec<(Cow<'on_disk, HgPath>, Timestamp)>>,
130 outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
126
131
127 /// Whether ignore files like `.hgignore` have changed since the previous
132 /// Whether ignore files like `.hgignore` have changed since the previous
128 /// time a `status()` call wrote their hash to the dirstate. `None` means
133 /// time a `status()` call wrote their hash to the dirstate. `None` means
129 /// we don’t know as this run doesn’t list either ignored or uknown files
134 /// we don’t know as this run doesn’t list either ignored or uknown files
130 /// and therefore isn’t reading `.hgignore`.
135 /// and therefore isn’t reading `.hgignore`.
131 ignore_patterns_have_changed: Option<bool>,
136 ignore_patterns_have_changed: Option<bool>,
132
137
133 /// The current time at the start of the `status()` algorithm, as measured
138 /// The current time at the start of the `status()` algorithm, as measured
134 /// and possibly truncated by the filesystem.
139 /// and possibly truncated by the filesystem.
135 filesystem_time_at_status_start: Option<SystemTime>,
140 filesystem_time_at_status_start: Option<SystemTime>,
136 }
141 }
137
142
138 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
143 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
139 fn read_dir(
144 fn read_dir(
140 &self,
145 &self,
141 hg_path: &HgPath,
146 hg_path: &HgPath,
142 fs_path: &Path,
147 fs_path: &Path,
143 is_at_repo_root: bool,
148 is_at_repo_root: bool,
144 ) -> Result<Vec<DirEntry>, ()> {
149 ) -> Result<Vec<DirEntry>, ()> {
145 DirEntry::read_dir(fs_path, is_at_repo_root)
150 DirEntry::read_dir(fs_path, is_at_repo_root)
146 .map_err(|error| self.io_error(error, hg_path))
151 .map_err(|error| self.io_error(error, hg_path))
147 }
152 }
148
153
149 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
154 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
150 let errno = error.raw_os_error().expect("expected real OS error");
155 let errno = error.raw_os_error().expect("expected real OS error");
151 self.outcome
156 self.outcome
152 .lock()
157 .lock()
153 .unwrap()
158 .unwrap()
154 .bad
159 .bad
155 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
160 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
156 }
161 }
157
162
163 fn check_for_outdated_directory_cache(
164 &self,
165 dirstate_node: &NodeRef<'tree, 'on_disk>,
166 ) -> Result<(), DirstateV2ParseError> {
167 if self.ignore_patterns_have_changed == Some(true)
168 && dirstate_node.cached_directory_mtime().is_some()
169 {
170 self.outated_cached_directories.lock().unwrap().push(
171 dirstate_node
172 .full_path_borrowed(self.dmap.on_disk)?
173 .detach_from_tree(),
174 )
175 }
176 Ok(())
177 }
178
158 /// If this returns true, we can get accurate results by only using
179 /// If this returns true, we can get accurate results by only using
159 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
180 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
160 /// need to call `read_dir`.
181 /// need to call `read_dir`.
161 fn can_skip_fs_readdir(
182 fn can_skip_fs_readdir(
162 &self,
183 &self,
163 directory_metadata: Option<&std::fs::Metadata>,
184 directory_metadata: Option<&std::fs::Metadata>,
164 cached_directory_mtime: Option<&Timestamp>,
185 cached_directory_mtime: Option<&Timestamp>,
165 ) -> bool {
186 ) -> bool {
166 if !self.options.list_unknown && !self.options.list_ignored {
187 if !self.options.list_unknown && !self.options.list_ignored {
167 // All states that we care about listing have corresponding
188 // All states that we care about listing have corresponding
168 // dirstate entries.
189 // dirstate entries.
169 // This happens for example with `hg status -mard`.
190 // This happens for example with `hg status -mard`.
170 return true;
191 return true;
171 }
192 }
172 if let Some(cached_mtime) = cached_directory_mtime {
193 if let Some(cached_mtime) = cached_directory_mtime {
173 // The dirstate contains a cached mtime for this directory, set by
194 // The dirstate contains a cached mtime for this directory, set by
174 // a previous run of the `status` algorithm which found this
195 // a previous run of the `status` algorithm which found this
175 // directory eligible for `read_dir` caching.
196 // directory eligible for `read_dir` caching.
176 if let Some(meta) = directory_metadata {
197 if let Some(meta) = directory_metadata {
177 if let Ok(current_mtime) = meta.modified() {
198 if let Ok(current_mtime) = meta.modified() {
178 if current_mtime == cached_mtime.into() {
199 if current_mtime == cached_mtime.into() {
179 // The mtime of that directory has not changed since
200 // The mtime of that directory has not changed since
180 // then, which means that the
201 // then, which means that the
181 // results of `read_dir` should also
202 // results of `read_dir` should also
182 // be unchanged.
203 // be unchanged.
183 return true;
204 return true;
184 }
205 }
185 }
206 }
186 }
207 }
187 }
208 }
188 false
209 false
189 }
210 }
190
211
191 /// Returns whether the filesystem directory was found to have any entry
212 /// Returns whether the filesystem directory was found to have any entry
192 /// that does not have a corresponding dirstate tree node.
213 /// that does not have a corresponding dirstate tree node.
193 fn traverse_fs_directory_and_dirstate(
214 fn traverse_fs_directory_and_dirstate(
194 &self,
215 &self,
195 has_ignored_ancestor: bool,
216 has_ignored_ancestor: bool,
196 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
217 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
197 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
218 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
198 directory_fs_path: &Path,
219 directory_fs_path: &Path,
199 directory_metadata: Option<&std::fs::Metadata>,
220 directory_metadata: Option<&std::fs::Metadata>,
200 cached_directory_mtime: Option<&Timestamp>,
221 cached_directory_mtime: Option<&Timestamp>,
201 is_at_repo_root: bool,
222 is_at_repo_root: bool,
202 ) -> Result<bool, DirstateV2ParseError> {
223 ) -> Result<bool, DirstateV2ParseError> {
203 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
224 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
204 {
225 {
205 dirstate_nodes
226 dirstate_nodes
206 .par_iter()
227 .par_iter()
207 .map(|dirstate_node| {
228 .map(|dirstate_node| {
208 let fs_path = directory_fs_path.join(get_path_from_bytes(
229 let fs_path = directory_fs_path.join(get_path_from_bytes(
209 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
230 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
210 ));
231 ));
211 match std::fs::symlink_metadata(&fs_path) {
232 match std::fs::symlink_metadata(&fs_path) {
212 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
233 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
213 &fs_path,
234 &fs_path,
214 &fs_metadata,
235 &fs_metadata,
215 dirstate_node,
236 dirstate_node,
216 has_ignored_ancestor,
237 has_ignored_ancestor,
217 ),
238 ),
218 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
239 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
219 self.traverse_dirstate_only(dirstate_node)
240 self.traverse_dirstate_only(dirstate_node)
220 }
241 }
221 Err(error) => {
242 Err(error) => {
222 let hg_path =
243 let hg_path =
223 dirstate_node.full_path(self.dmap.on_disk)?;
244 dirstate_node.full_path(self.dmap.on_disk)?;
224 Ok(self.io_error(error, hg_path))
245 Ok(self.io_error(error, hg_path))
225 }
246 }
226 }
247 }
227 })
248 })
228 .collect::<Result<_, _>>()?;
249 .collect::<Result<_, _>>()?;
229
250
230 // Conservatively don’t let the caller assume that there aren’t
251 // Conservatively don’t let the caller assume that there aren’t
231 // any, since we don’t know.
252 // any, since we don’t know.
232 let directory_has_any_fs_only_entry = true;
253 let directory_has_any_fs_only_entry = true;
233
254
234 return Ok(directory_has_any_fs_only_entry);
255 return Ok(directory_has_any_fs_only_entry);
235 }
256 }
236
257
237 let mut fs_entries = if let Ok(entries) = self.read_dir(
258 let mut fs_entries = if let Ok(entries) = self.read_dir(
238 directory_hg_path,
259 directory_hg_path,
239 directory_fs_path,
260 directory_fs_path,
240 is_at_repo_root,
261 is_at_repo_root,
241 ) {
262 ) {
242 entries
263 entries
243 } else {
264 } else {
244 // Treat an unreadable directory (typically because of insufficient
265 // Treat an unreadable directory (typically because of insufficient
245 // permissions) like an empty directory. `self.read_dir` has
266 // permissions) like an empty directory. `self.read_dir` has
246 // already called `self.io_error` so a warning will be emitted.
267 // already called `self.io_error` so a warning will be emitted.
247 Vec::new()
268 Vec::new()
248 };
269 };
249
270
250 // `merge_join_by` requires both its input iterators to be sorted:
271 // `merge_join_by` requires both its input iterators to be sorted:
251
272
252 let dirstate_nodes = dirstate_nodes.sorted();
273 let dirstate_nodes = dirstate_nodes.sorted();
253 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
274 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
254 // https://github.com/rust-lang/rust/issues/34162
275 // https://github.com/rust-lang/rust/issues/34162
255 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
276 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
256
277
257 // Propagate here any error that would happen inside the comparison
278 // Propagate here any error that would happen inside the comparison
258 // callback below
279 // callback below
259 for dirstate_node in &dirstate_nodes {
280 for dirstate_node in &dirstate_nodes {
260 dirstate_node.base_name(self.dmap.on_disk)?;
281 dirstate_node.base_name(self.dmap.on_disk)?;
261 }
282 }
262 itertools::merge_join_by(
283 itertools::merge_join_by(
263 dirstate_nodes,
284 dirstate_nodes,
264 &fs_entries,
285 &fs_entries,
265 |dirstate_node, fs_entry| {
286 |dirstate_node, fs_entry| {
266 // This `unwrap` never panics because we already propagated
287 // This `unwrap` never panics because we already propagated
267 // those errors above
288 // those errors above
268 dirstate_node
289 dirstate_node
269 .base_name(self.dmap.on_disk)
290 .base_name(self.dmap.on_disk)
270 .unwrap()
291 .unwrap()
271 .cmp(&fs_entry.base_name)
292 .cmp(&fs_entry.base_name)
272 },
293 },
273 )
294 )
274 .par_bridge()
295 .par_bridge()
275 .map(|pair| {
296 .map(|pair| {
276 use itertools::EitherOrBoth::*;
297 use itertools::EitherOrBoth::*;
277 let is_fs_only = pair.is_right();
298 let is_fs_only = pair.is_right();
278 match pair {
299 match pair {
279 Both(dirstate_node, fs_entry) => self
300 Both(dirstate_node, fs_entry) => self
280 .traverse_fs_and_dirstate(
301 .traverse_fs_and_dirstate(
281 &fs_entry.full_path,
302 &fs_entry.full_path,
282 &fs_entry.metadata,
303 &fs_entry.metadata,
283 dirstate_node,
304 dirstate_node,
284 has_ignored_ancestor,
305 has_ignored_ancestor,
285 )?,
306 )?,
286 Left(dirstate_node) => {
307 Left(dirstate_node) => {
287 self.traverse_dirstate_only(dirstate_node)?
308 self.traverse_dirstate_only(dirstate_node)?
288 }
309 }
289 Right(fs_entry) => self.traverse_fs_only(
310 Right(fs_entry) => self.traverse_fs_only(
290 has_ignored_ancestor,
311 has_ignored_ancestor,
291 directory_hg_path,
312 directory_hg_path,
292 fs_entry,
313 fs_entry,
293 ),
314 ),
294 }
315 }
295 Ok(is_fs_only)
316 Ok(is_fs_only)
296 })
317 })
297 .try_reduce(|| false, |a, b| Ok(a || b))
318 .try_reduce(|| false, |a, b| Ok(a || b))
298 }
319 }
299
320
300 fn traverse_fs_and_dirstate(
321 fn traverse_fs_and_dirstate(
301 &self,
322 &self,
302 fs_path: &Path,
323 fs_path: &Path,
303 fs_metadata: &std::fs::Metadata,
324 fs_metadata: &std::fs::Metadata,
304 dirstate_node: NodeRef<'tree, 'on_disk>,
325 dirstate_node: NodeRef<'tree, 'on_disk>,
305 has_ignored_ancestor: bool,
326 has_ignored_ancestor: bool,
306 ) -> Result<(), DirstateV2ParseError> {
327 ) -> Result<(), DirstateV2ParseError> {
328 self.check_for_outdated_directory_cache(&dirstate_node)?;
307 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
329 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
308 let file_type = fs_metadata.file_type();
330 let file_type = fs_metadata.file_type();
309 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
331 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
310 if !file_or_symlink {
332 if !file_or_symlink {
311 // If we previously had a file here, it was removed (with
333 // If we previously had a file here, it was removed (with
312 // `hg rm` or similar) or deleted before it could be
334 // `hg rm` or similar) or deleted before it could be
313 // replaced by a directory or something else.
335 // replaced by a directory or something else.
314 self.mark_removed_or_deleted_if_file(
336 self.mark_removed_or_deleted_if_file(
315 &hg_path,
337 &hg_path,
316 dirstate_node.state()?,
338 dirstate_node.state()?,
317 );
339 );
318 }
340 }
319 if file_type.is_dir() {
341 if file_type.is_dir() {
320 if self.options.collect_traversed_dirs {
342 if self.options.collect_traversed_dirs {
321 self.outcome
343 self.outcome
322 .lock()
344 .lock()
323 .unwrap()
345 .unwrap()
324 .traversed
346 .traversed
325 .push(hg_path.detach_from_tree())
347 .push(hg_path.detach_from_tree())
326 }
348 }
327 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
349 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
328 let is_at_repo_root = false;
350 let is_at_repo_root = false;
329 let directory_has_any_fs_only_entry = self
351 let directory_has_any_fs_only_entry = self
330 .traverse_fs_directory_and_dirstate(
352 .traverse_fs_directory_and_dirstate(
331 is_ignored,
353 is_ignored,
332 dirstate_node.children(self.dmap.on_disk)?,
354 dirstate_node.children(self.dmap.on_disk)?,
333 hg_path,
355 hg_path,
334 fs_path,
356 fs_path,
335 Some(fs_metadata),
357 Some(fs_metadata),
336 dirstate_node.cached_directory_mtime(),
358 dirstate_node.cached_directory_mtime(),
337 is_at_repo_root,
359 is_at_repo_root,
338 )?;
360 )?;
339 self.maybe_save_directory_mtime(
361 self.maybe_save_directory_mtime(
340 directory_has_any_fs_only_entry,
362 directory_has_any_fs_only_entry,
341 fs_metadata,
363 fs_metadata,
342 dirstate_node,
364 dirstate_node,
343 )?
365 )?
344 } else {
366 } else {
345 if file_or_symlink && self.matcher.matches(hg_path) {
367 if file_or_symlink && self.matcher.matches(hg_path) {
346 if let Some(state) = dirstate_node.state()? {
368 if let Some(state) = dirstate_node.state()? {
347 match state {
369 match state {
348 EntryState::Added => self
370 EntryState::Added => self
349 .outcome
371 .outcome
350 .lock()
372 .lock()
351 .unwrap()
373 .unwrap()
352 .added
374 .added
353 .push(hg_path.detach_from_tree()),
375 .push(hg_path.detach_from_tree()),
354 EntryState::Removed => self
376 EntryState::Removed => self
355 .outcome
377 .outcome
356 .lock()
378 .lock()
357 .unwrap()
379 .unwrap()
358 .removed
380 .removed
359 .push(hg_path.detach_from_tree()),
381 .push(hg_path.detach_from_tree()),
360 EntryState::Merged => self
382 EntryState::Merged => self
361 .outcome
383 .outcome
362 .lock()
384 .lock()
363 .unwrap()
385 .unwrap()
364 .modified
386 .modified
365 .push(hg_path.detach_from_tree()),
387 .push(hg_path.detach_from_tree()),
366 EntryState::Normal => self
388 EntryState::Normal => self
367 .handle_normal_file(&dirstate_node, fs_metadata)?,
389 .handle_normal_file(&dirstate_node, fs_metadata)?,
368 // This variant is not used in DirstateMap
390 // This variant is not used in DirstateMap
369 // nodes
391 // nodes
370 EntryState::Unknown => unreachable!(),
392 EntryState::Unknown => unreachable!(),
371 }
393 }
372 } else {
394 } else {
373 // `node.entry.is_none()` indicates a "directory"
395 // `node.entry.is_none()` indicates a "directory"
374 // node, but the filesystem has a file
396 // node, but the filesystem has a file
375 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path)
397 self.mark_unknown_or_ignored(has_ignored_ancestor, hg_path)
376 }
398 }
377 }
399 }
378
400
379 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
401 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
380 {
402 {
381 self.traverse_dirstate_only(child_node)?
403 self.traverse_dirstate_only(child_node)?
382 }
404 }
383 }
405 }
384 Ok(())
406 Ok(())
385 }
407 }
386
408
387 fn maybe_save_directory_mtime(
409 fn maybe_save_directory_mtime(
388 &self,
410 &self,
389 directory_has_any_fs_only_entry: bool,
411 directory_has_any_fs_only_entry: bool,
390 directory_metadata: &std::fs::Metadata,
412 directory_metadata: &std::fs::Metadata,
391 dirstate_node: NodeRef<'tree, 'on_disk>,
413 dirstate_node: NodeRef<'tree, 'on_disk>,
392 ) -> Result<(), DirstateV2ParseError> {
414 ) -> Result<(), DirstateV2ParseError> {
393 if !directory_has_any_fs_only_entry {
415 if !directory_has_any_fs_only_entry {
394 // All filesystem directory entries from `read_dir` have a
416 // All filesystem directory entries from `read_dir` have a
395 // corresponding node in the dirstate, so we can reconstitute the
417 // corresponding node in the dirstate, so we can reconstitute the
396 // names of those entries without calling `read_dir` again.
418 // names of those entries without calling `read_dir` again.
397 if let (Some(status_start), Ok(directory_mtime)) = (
419 if let (Some(status_start), Ok(directory_mtime)) = (
398 &self.filesystem_time_at_status_start,
420 &self.filesystem_time_at_status_start,
399 directory_metadata.modified(),
421 directory_metadata.modified(),
400 ) {
422 ) {
401 // Although the Rust standard library’s `SystemTime` type
423 // Although the Rust standard library’s `SystemTime` type
402 // has nanosecond precision, the times reported for a
424 // has nanosecond precision, the times reported for a
403 // directory’s (or file’s) modified time may have lower
425 // directory’s (or file’s) modified time may have lower
404 // resolution based on the filesystem (for example ext3
426 // resolution based on the filesystem (for example ext3
405 // only stores integer seconds), kernel (see
427 // only stores integer seconds), kernel (see
406 // https://stackoverflow.com/a/14393315/1162888), etc.
428 // https://stackoverflow.com/a/14393315/1162888), etc.
407 if &directory_mtime >= status_start {
429 if &directory_mtime >= status_start {
408 // The directory was modified too recently, don’t cache its
430 // The directory was modified too recently, don’t cache its
409 // `read_dir` results.
431 // `read_dir` results.
410 //
432 //
411 // A timeline like this is possible:
433 // A timeline like this is possible:
412 //
434 //
413 // 1. A change to this directory (direct child was
435 // 1. A change to this directory (direct child was
414 // added or removed) cause its mtime to be set
436 // added or removed) cause its mtime to be set
415 // (possibly truncated) to `directory_mtime`
437 // (possibly truncated) to `directory_mtime`
416 // 2. This `status` algorithm calls `read_dir`
438 // 2. This `status` algorithm calls `read_dir`
417 // 3. An other change is made to the same directory is
439 // 3. An other change is made to the same directory is
418 // made so that calling `read_dir` agin would give
440 // made so that calling `read_dir` agin would give
419 // different results, but soon enough after 1. that
441 // different results, but soon enough after 1. that
420 // the mtime stays the same
442 // the mtime stays the same
421 //
443 //
422 // On a system where the time resolution poor, this
444 // On a system where the time resolution poor, this
423 // scenario is not unlikely if all three steps are caused
445 // scenario is not unlikely if all three steps are caused
424 // by the same script.
446 // by the same script.
425 } else {
447 } else {
426 // We’ve observed (through `status_start`) that time has
448 // We’ve observed (through `status_start`) that time has
427 // “progressed” since `directory_mtime`, so any further
449 // “progressed” since `directory_mtime`, so any further
428 // change to this directory is extremely likely to cause a
450 // change to this directory is extremely likely to cause a
429 // different mtime.
451 // different mtime.
430 //
452 //
431 // Having the same mtime again is not entirely impossible
453 // Having the same mtime again is not entirely impossible
432 // since the system clock is not monotonous. It could jump
454 // since the system clock is not monotonous. It could jump
433 // backward to some point before `directory_mtime`, then a
455 // backward to some point before `directory_mtime`, then a
434 // directory change could potentially happen during exactly
456 // directory change could potentially happen during exactly
435 // the wrong tick.
457 // the wrong tick.
436 //
458 //
437 // We deem this scenario (unlike the previous one) to be
459 // We deem this scenario (unlike the previous one) to be
438 // unlikely enough in practice.
460 // unlikely enough in practice.
439 let timestamp = directory_mtime.into();
461 let timestamp = directory_mtime.into();
440 let cached = dirstate_node.cached_directory_mtime();
462 let cached = dirstate_node.cached_directory_mtime();
441 if cached != Some(&timestamp) {
463 if cached != Some(&timestamp) {
442 let hg_path = dirstate_node
464 let hg_path = dirstate_node
443 .full_path_borrowed(self.dmap.on_disk)?
465 .full_path_borrowed(self.dmap.on_disk)?
444 .detach_from_tree();
466 .detach_from_tree();
445 self.new_cachable_directories
467 self.new_cachable_directories
446 .lock()
468 .lock()
447 .unwrap()
469 .unwrap()
448 .push((hg_path, timestamp))
470 .push((hg_path, timestamp))
449 }
471 }
450 }
472 }
451 }
473 }
452 }
474 }
453 Ok(())
475 Ok(())
454 }
476 }
455
477
456 /// A file with `EntryState::Normal` in the dirstate was found in the
478 /// A file with `EntryState::Normal` in the dirstate was found in the
457 /// filesystem
479 /// filesystem
458 fn handle_normal_file(
480 fn handle_normal_file(
459 &self,
481 &self,
460 dirstate_node: &NodeRef<'tree, 'on_disk>,
482 dirstate_node: &NodeRef<'tree, 'on_disk>,
461 fs_metadata: &std::fs::Metadata,
483 fs_metadata: &std::fs::Metadata,
462 ) -> Result<(), DirstateV2ParseError> {
484 ) -> Result<(), DirstateV2ParseError> {
463 // Keep the low 31 bits
485 // Keep the low 31 bits
464 fn truncate_u64(value: u64) -> i32 {
486 fn truncate_u64(value: u64) -> i32 {
465 (value & 0x7FFF_FFFF) as i32
487 (value & 0x7FFF_FFFF) as i32
466 }
488 }
467 fn truncate_i64(value: i64) -> i32 {
489 fn truncate_i64(value: i64) -> i32 {
468 (value & 0x7FFF_FFFF) as i32
490 (value & 0x7FFF_FFFF) as i32
469 }
491 }
470
492
471 let entry = dirstate_node
493 let entry = dirstate_node
472 .entry()?
494 .entry()?
473 .expect("handle_normal_file called with entry-less node");
495 .expect("handle_normal_file called with entry-less node");
474 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
496 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
475 let mode_changed =
497 let mode_changed =
476 || self.options.check_exec && entry.mode_changed(fs_metadata);
498 || self.options.check_exec && entry.mode_changed(fs_metadata);
477 let size_changed = entry.size != truncate_u64(fs_metadata.len());
499 let size_changed = entry.size != truncate_u64(fs_metadata.len());
478 if entry.size >= 0
500 if entry.size >= 0
479 && size_changed
501 && size_changed
480 && fs_metadata.file_type().is_symlink()
502 && fs_metadata.file_type().is_symlink()
481 {
503 {
482 // issue6456: Size returned may be longer due to encryption
504 // issue6456: Size returned may be longer due to encryption
483 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
505 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
484 self.outcome
506 self.outcome
485 .lock()
507 .lock()
486 .unwrap()
508 .unwrap()
487 .unsure
509 .unsure
488 .push(hg_path.detach_from_tree())
510 .push(hg_path.detach_from_tree())
489 } else if dirstate_node.has_copy_source()
511 } else if dirstate_node.has_copy_source()
490 || entry.is_from_other_parent()
512 || entry.is_from_other_parent()
491 || (entry.size >= 0 && (size_changed || mode_changed()))
513 || (entry.size >= 0 && (size_changed || mode_changed()))
492 {
514 {
493 self.outcome
515 self.outcome
494 .lock()
516 .lock()
495 .unwrap()
517 .unwrap()
496 .modified
518 .modified
497 .push(hg_path.detach_from_tree())
519 .push(hg_path.detach_from_tree())
498 } else {
520 } else {
499 let mtime = mtime_seconds(fs_metadata);
521 let mtime = mtime_seconds(fs_metadata);
500 if truncate_i64(mtime) != entry.mtime
522 if truncate_i64(mtime) != entry.mtime
501 || mtime == self.options.last_normal_time
523 || mtime == self.options.last_normal_time
502 {
524 {
503 self.outcome
525 self.outcome
504 .lock()
526 .lock()
505 .unwrap()
527 .unwrap()
506 .unsure
528 .unsure
507 .push(hg_path.detach_from_tree())
529 .push(hg_path.detach_from_tree())
508 } else if self.options.list_clean {
530 } else if self.options.list_clean {
509 self.outcome
531 self.outcome
510 .lock()
532 .lock()
511 .unwrap()
533 .unwrap()
512 .clean
534 .clean
513 .push(hg_path.detach_from_tree())
535 .push(hg_path.detach_from_tree())
514 }
536 }
515 }
537 }
516 Ok(())
538 Ok(())
517 }
539 }
518
540
519 /// A node in the dirstate tree has no corresponding filesystem entry
541 /// A node in the dirstate tree has no corresponding filesystem entry
520 fn traverse_dirstate_only(
542 fn traverse_dirstate_only(
521 &self,
543 &self,
522 dirstate_node: NodeRef<'tree, 'on_disk>,
544 dirstate_node: NodeRef<'tree, 'on_disk>,
523 ) -> Result<(), DirstateV2ParseError> {
545 ) -> Result<(), DirstateV2ParseError> {
546 self.check_for_outdated_directory_cache(&dirstate_node)?;
524 self.mark_removed_or_deleted_if_file(
547 self.mark_removed_or_deleted_if_file(
525 &dirstate_node.full_path_borrowed(self.dmap.on_disk)?,
548 &dirstate_node.full_path_borrowed(self.dmap.on_disk)?,
526 dirstate_node.state()?,
549 dirstate_node.state()?,
527 );
550 );
528 dirstate_node
551 dirstate_node
529 .children(self.dmap.on_disk)?
552 .children(self.dmap.on_disk)?
530 .par_iter()
553 .par_iter()
531 .map(|child_node| self.traverse_dirstate_only(child_node))
554 .map(|child_node| self.traverse_dirstate_only(child_node))
532 .collect()
555 .collect()
533 }
556 }
534
557
535 /// A node in the dirstate tree has no corresponding *file* on the
558 /// A node in the dirstate tree has no corresponding *file* on the
536 /// filesystem
559 /// filesystem
537 ///
560 ///
538 /// Does nothing on a "directory" node
561 /// Does nothing on a "directory" node
539 fn mark_removed_or_deleted_if_file(
562 fn mark_removed_or_deleted_if_file(
540 &self,
563 &self,
541 hg_path: &BorrowedPath<'tree, 'on_disk>,
564 hg_path: &BorrowedPath<'tree, 'on_disk>,
542 dirstate_node_state: Option<EntryState>,
565 dirstate_node_state: Option<EntryState>,
543 ) {
566 ) {
544 if let Some(state) = dirstate_node_state {
567 if let Some(state) = dirstate_node_state {
545 if self.matcher.matches(hg_path) {
568 if self.matcher.matches(hg_path) {
546 if let EntryState::Removed = state {
569 if let EntryState::Removed = state {
547 self.outcome
570 self.outcome
548 .lock()
571 .lock()
549 .unwrap()
572 .unwrap()
550 .removed
573 .removed
551 .push(hg_path.detach_from_tree())
574 .push(hg_path.detach_from_tree())
552 } else {
575 } else {
553 self.outcome
576 self.outcome
554 .lock()
577 .lock()
555 .unwrap()
578 .unwrap()
556 .deleted
579 .deleted
557 .push(hg_path.detach_from_tree())
580 .push(hg_path.detach_from_tree())
558 }
581 }
559 }
582 }
560 }
583 }
561 }
584 }
562
585
563 /// Something in the filesystem has no corresponding dirstate node
586 /// Something in the filesystem has no corresponding dirstate node
564 fn traverse_fs_only(
587 fn traverse_fs_only(
565 &self,
588 &self,
566 has_ignored_ancestor: bool,
589 has_ignored_ancestor: bool,
567 directory_hg_path: &HgPath,
590 directory_hg_path: &HgPath,
568 fs_entry: &DirEntry,
591 fs_entry: &DirEntry,
569 ) {
592 ) {
570 let hg_path = directory_hg_path.join(&fs_entry.base_name);
593 let hg_path = directory_hg_path.join(&fs_entry.base_name);
571 let file_type = fs_entry.metadata.file_type();
594 let file_type = fs_entry.metadata.file_type();
572 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
595 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
573 if file_type.is_dir() {
596 if file_type.is_dir() {
574 let is_ignored =
597 let is_ignored =
575 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
598 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
576 let traverse_children = if is_ignored {
599 let traverse_children = if is_ignored {
577 // Descendants of an ignored directory are all ignored
600 // Descendants of an ignored directory are all ignored
578 self.options.list_ignored
601 self.options.list_ignored
579 } else {
602 } else {
580 // Descendants of an unknown directory may be either unknown or
603 // Descendants of an unknown directory may be either unknown or
581 // ignored
604 // ignored
582 self.options.list_unknown || self.options.list_ignored
605 self.options.list_unknown || self.options.list_ignored
583 };
606 };
584 if traverse_children {
607 if traverse_children {
585 let is_at_repo_root = false;
608 let is_at_repo_root = false;
586 if let Ok(children_fs_entries) = self.read_dir(
609 if let Ok(children_fs_entries) = self.read_dir(
587 &hg_path,
610 &hg_path,
588 &fs_entry.full_path,
611 &fs_entry.full_path,
589 is_at_repo_root,
612 is_at_repo_root,
590 ) {
613 ) {
591 children_fs_entries.par_iter().for_each(|child_fs_entry| {
614 children_fs_entries.par_iter().for_each(|child_fs_entry| {
592 self.traverse_fs_only(
615 self.traverse_fs_only(
593 is_ignored,
616 is_ignored,
594 &hg_path,
617 &hg_path,
595 child_fs_entry,
618 child_fs_entry,
596 )
619 )
597 })
620 })
598 }
621 }
599 }
622 }
600 if self.options.collect_traversed_dirs {
623 if self.options.collect_traversed_dirs {
601 self.outcome.lock().unwrap().traversed.push(hg_path.into())
624 self.outcome.lock().unwrap().traversed.push(hg_path.into())
602 }
625 }
603 } else if file_or_symlink && self.matcher.matches(&hg_path) {
626 } else if file_or_symlink && self.matcher.matches(&hg_path) {
604 self.mark_unknown_or_ignored(
627 self.mark_unknown_or_ignored(
605 has_ignored_ancestor,
628 has_ignored_ancestor,
606 &BorrowedPath::InMemory(&hg_path),
629 &BorrowedPath::InMemory(&hg_path),
607 )
630 )
608 }
631 }
609 }
632 }
610
633
611 fn mark_unknown_or_ignored(
634 fn mark_unknown_or_ignored(
612 &self,
635 &self,
613 has_ignored_ancestor: bool,
636 has_ignored_ancestor: bool,
614 hg_path: &BorrowedPath<'_, 'on_disk>,
637 hg_path: &BorrowedPath<'_, 'on_disk>,
615 ) {
638 ) {
616 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
639 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
617 if is_ignored {
640 if is_ignored {
618 if self.options.list_ignored {
641 if self.options.list_ignored {
619 self.outcome
642 self.outcome
620 .lock()
643 .lock()
621 .unwrap()
644 .unwrap()
622 .ignored
645 .ignored
623 .push(hg_path.detach_from_tree())
646 .push(hg_path.detach_from_tree())
624 }
647 }
625 } else {
648 } else {
626 if self.options.list_unknown {
649 if self.options.list_unknown {
627 self.outcome
650 self.outcome
628 .lock()
651 .lock()
629 .unwrap()
652 .unwrap()
630 .unknown
653 .unknown
631 .push(hg_path.detach_from_tree())
654 .push(hg_path.detach_from_tree())
632 }
655 }
633 }
656 }
634 }
657 }
635 }
658 }
636
659
637 #[cfg(unix)] // TODO
660 #[cfg(unix)] // TODO
638 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
661 fn mtime_seconds(metadata: &std::fs::Metadata) -> i64 {
639 // Going through `Metadata::modified()` would be portable, but would take
662 // Going through `Metadata::modified()` would be portable, but would take
640 // care to construct a `SystemTime` value with sub-second precision just
663 // care to construct a `SystemTime` value with sub-second precision just
641 // for us to throw that away here.
664 // for us to throw that away here.
642 use std::os::unix::fs::MetadataExt;
665 use std::os::unix::fs::MetadataExt;
643 metadata.mtime()
666 metadata.mtime()
644 }
667 }
645
668
646 struct DirEntry {
669 struct DirEntry {
647 base_name: HgPathBuf,
670 base_name: HgPathBuf,
648 full_path: PathBuf,
671 full_path: PathBuf,
649 metadata: std::fs::Metadata,
672 metadata: std::fs::Metadata,
650 }
673 }
651
674
652 impl DirEntry {
675 impl DirEntry {
653 /// Returns **unsorted** entries in the given directory, with name and
676 /// Returns **unsorted** entries in the given directory, with name and
654 /// metadata.
677 /// metadata.
655 ///
678 ///
656 /// If a `.hg` sub-directory is encountered:
679 /// If a `.hg` sub-directory is encountered:
657 ///
680 ///
658 /// * At the repository root, ignore that sub-directory
681 /// * At the repository root, ignore that sub-directory
659 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
682 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
660 /// list instead.
683 /// list instead.
661 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
684 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
662 let mut results = Vec::new();
685 let mut results = Vec::new();
663 for entry in path.read_dir()? {
686 for entry in path.read_dir()? {
664 let entry = entry?;
687 let entry = entry?;
665 let metadata = entry.metadata()?;
688 let metadata = entry.metadata()?;
666 let name = get_bytes_from_os_string(entry.file_name());
689 let name = get_bytes_from_os_string(entry.file_name());
667 // FIXME don't do this when cached
690 // FIXME don't do this when cached
668 if name == b".hg" {
691 if name == b".hg" {
669 if is_at_repo_root {
692 if is_at_repo_root {
670 // Skip the repo’s own .hg (might be a symlink)
693 // Skip the repo’s own .hg (might be a symlink)
671 continue;
694 continue;
672 } else if metadata.is_dir() {
695 } else if metadata.is_dir() {
673 // A .hg sub-directory at another location means a subrepo,
696 // A .hg sub-directory at another location means a subrepo,
674 // skip it entirely.
697 // skip it entirely.
675 return Ok(Vec::new());
698 return Ok(Vec::new());
676 }
699 }
677 }
700 }
678 results.push(DirEntry {
701 results.push(DirEntry {
679 base_name: name.into(),
702 base_name: name.into(),
680 full_path: entry.path(),
703 full_path: entry.path(),
681 metadata,
704 metadata,
682 })
705 })
683 }
706 }
684 Ok(results)
707 Ok(results)
685 }
708 }
686 }
709 }
687
710
688 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
711 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
689 /// of the give repository.
712 /// of the give repository.
690 ///
713 ///
691 /// This is similar to `SystemTime::now()`, with the result truncated to the
714 /// This is similar to `SystemTime::now()`, with the result truncated to the
692 /// same time resolution as other files’ modification times. Using `.hg`
715 /// same time resolution as other files’ modification times. Using `.hg`
693 /// instead of the system’s default temporary directory (such as `/tmp`) makes
716 /// instead of the system’s default temporary directory (such as `/tmp`) makes
694 /// it more likely the temporary file is in the same disk partition as contents
717 /// it more likely the temporary file is in the same disk partition as contents
695 /// of the working directory, which can matter since different filesystems may
718 /// of the working directory, which can matter since different filesystems may
696 /// store timestamps with different resolutions.
719 /// store timestamps with different resolutions.
697 ///
720 ///
698 /// This may fail, typically if we lack write permissions. In that case we
721 /// This may fail, typically if we lack write permissions. In that case we
699 /// should continue the `status()` algoritm anyway and consider the current
722 /// should continue the `status()` algoritm anyway and consider the current
700 /// date/time to be unknown.
723 /// date/time to be unknown.
701 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
724 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
702 tempfile::tempfile_in(repo_root.join(".hg"))?
725 tempfile::tempfile_in(repo_root.join(".hg"))?
703 .metadata()?
726 .metadata()?
704 .modified()
727 .modified()
705 }
728 }
General Comments 0
You need to be logged in to leave comments. Login now