Show More
@@ -5,7 +5,6 b'' | |||||
5 | // This software may be used and distributed according to the terms of the |
|
5 | // This software may be used and distributed according to the terms of the | |
6 | // GNU General Public License version 2 or any later version. |
|
6 | // GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | use crate::dirstate::parsers::clear_ambiguous_mtime; |
|
|||
9 | use crate::dirstate::parsers::Timestamp; |
|
8 | use crate::dirstate::parsers::Timestamp; | |
10 | use crate::{ |
|
9 | use crate::{ | |
11 | dirstate::EntryState, |
|
10 | dirstate::EntryState, | |
@@ -166,7 +165,7 b' impl DirstateMap {' | |||||
166 | ) { |
|
165 | ) { | |
167 | for filename in filenames { |
|
166 | for filename in filenames { | |
168 | if let Some(entry) = self.state_map.get_mut(&filename) { |
|
167 | if let Some(entry) = self.state_map.get_mut(&filename) { | |
169 |
if clear_ambiguous_mtime( |
|
168 | if entry.clear_ambiguous_mtime(now) { | |
170 | self.get_non_normal_other_parent_entries() |
|
169 | self.get_non_normal_other_parent_entries() | |
171 | .0 |
|
170 | .0 | |
172 | .insert(filename.to_owned()); |
|
171 | .insert(filename.to_owned()); |
@@ -126,25 +126,31 b' pub fn pack_entry(' | |||||
126 | /// Seconds since the Unix epoch |
|
126 | /// Seconds since the Unix epoch | |
127 | pub struct Timestamp(pub u64); |
|
127 | pub struct Timestamp(pub u64); | |
128 |
|
128 | |||
129 | pub fn clear_ambiguous_mtime( |
|
129 | impl DirstateEntry { | |
130 | entry: &mut DirstateEntry, |
|
130 | pub fn mtime_is_ambiguous(&self, now: i32) -> bool { | |
131 | mtime_now: i32, |
|
131 | self.state == EntryState::Normal && self.mtime == now | |
132 | ) -> bool { |
|
|||
133 | let ambiguous = |
|
|||
134 | entry.state == EntryState::Normal && entry.mtime == mtime_now; |
|
|||
135 | if ambiguous { |
|
|||
136 | // The file was last modified "simultaneously" with the current |
|
|||
137 | // write to dirstate (i.e. within the same second for file- |
|
|||
138 | // systems with a granularity of 1 sec). This commonly happens |
|
|||
139 | // for at least a couple of files on 'update'. |
|
|||
140 | // The user could change the file without changing its size |
|
|||
141 | // within the same second. Invalidate the file's mtime in |
|
|||
142 | // dirstate, forcing future 'status' calls to compare the |
|
|||
143 | // contents of the file if the size is the same. This prevents |
|
|||
144 | // mistakenly treating such files as clean. |
|
|||
145 | entry.mtime = -1; |
|
|||
146 | } |
|
132 | } | |
147 | ambiguous |
|
133 | ||
|
134 | pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool { | |||
|
135 | let ambiguous = self.mtime_is_ambiguous(now); | |||
|
136 | if ambiguous { | |||
|
137 | // The file was last modified "simultaneously" with the current | |||
|
138 | // write to dirstate (i.e. within the same second for file- | |||
|
139 | // systems with a granularity of 1 sec). This commonly happens | |||
|
140 | // for at least a couple of files on 'update'. | |||
|
141 | // The user could change the file without changing its size | |||
|
142 | // within the same second. Invalidate the file's mtime in | |||
|
143 | // dirstate, forcing future 'status' calls to compare the | |||
|
144 | // contents of the file if the size is the same. This prevents | |||
|
145 | // mistakenly treating such files as clean. | |||
|
146 | self.clear_mtime() | |||
|
147 | } | |||
|
148 | ambiguous | |||
|
149 | } | |||
|
150 | ||||
|
151 | pub fn clear_mtime(&mut self) { | |||
|
152 | self.mtime = -1; | |||
|
153 | } | |||
148 | } |
|
154 | } | |
149 |
|
155 | |||
150 | pub fn pack_dirstate( |
|
156 | pub fn pack_dirstate( | |
@@ -170,7 +176,7 b' pub fn pack_dirstate(' | |||||
170 | packed.extend(parents.p2.as_bytes()); |
|
176 | packed.extend(parents.p2.as_bytes()); | |
171 |
|
177 | |||
172 | for (filename, entry) in state_map.iter_mut() { |
|
178 | for (filename, entry) in state_map.iter_mut() { | |
173 |
clear_ambiguous_mtime( |
|
179 | entry.clear_ambiguous_mtime(now); | |
174 | pack_entry( |
|
180 | pack_entry( | |
175 | filename, |
|
181 | filename, | |
176 | entry, |
|
182 | entry, |
@@ -6,7 +6,6 b' use std::path::PathBuf;' | |||||
6 |
|
6 | |||
7 | use super::on_disk; |
|
7 | use super::on_disk; | |
8 | use super::path_with_basename::WithBasename; |
|
8 | use super::path_with_basename::WithBasename; | |
9 | use crate::dirstate::parsers::clear_ambiguous_mtime; |
|
|||
10 | use crate::dirstate::parsers::pack_entry; |
|
9 | use crate::dirstate::parsers::pack_entry; | |
11 | use crate::dirstate::parsers::packed_entry_size; |
|
10 | use crate::dirstate::parsers::packed_entry_size; | |
12 | use crate::dirstate::parsers::parse_dirstate_entries; |
|
11 | use crate::dirstate::parsers::parse_dirstate_entries; | |
@@ -79,13 +78,6 b" impl<'on_disk> Node<'on_disk> {" | |||||
79 | } |
|
78 | } | |
80 | } |
|
79 | } | |
81 |
|
80 | |||
82 | /// `(full_path, entry, copy_source)` |
|
|||
83 | type NodeDataMut<'tree, 'on_disk> = ( |
|
|||
84 | &'tree HgPath, |
|
|||
85 | &'tree mut Option<DirstateEntry>, |
|
|||
86 | &'tree mut Option<Cow<'on_disk, HgPath>>, |
|
|||
87 | ); |
|
|||
88 |
|
||||
89 | impl<'on_disk> DirstateMap<'on_disk> { |
|
81 | impl<'on_disk> DirstateMap<'on_disk> { | |
90 | pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self { |
|
82 | pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self { | |
91 | Self { |
|
83 | Self { | |
@@ -252,7 +244,7 b" impl<'on_disk> DirstateMap<'on_disk> {" | |||||
252 |
|
244 | |||
253 | fn iter_nodes<'a>( |
|
245 | fn iter_nodes<'a>( | |
254 | &'a self, |
|
246 | &'a self, | |
255 | ) -> impl Iterator<Item = (&'a HgPath, &'a Node)> + 'a { |
|
247 | ) -> impl Iterator<Item = (&'a Cow<'on_disk, HgPath>, &'a Node)> + 'a { | |
256 | // Depth first tree traversal. |
|
248 | // Depth first tree traversal. | |
257 | // |
|
249 | // | |
258 | // If we could afford internal iteration and recursion, |
|
250 | // If we could afford internal iteration and recursion, | |
@@ -279,7 +271,7 b" impl<'on_disk> DirstateMap<'on_disk> {" | |||||
279 | // Pseudo-recursion |
|
271 | // Pseudo-recursion | |
280 | let new_iter = child_node.children.iter(); |
|
272 | let new_iter = child_node.children.iter(); | |
281 | let old_iter = std::mem::replace(&mut iter, new_iter); |
|
273 | let old_iter = std::mem::replace(&mut iter, new_iter); | |
282 |
let key = |
|
274 | let key = key.full_path(); | |
283 | stack.push((key, child_node, old_iter)); |
|
275 | stack.push((key, child_node, old_iter)); | |
284 | } |
|
276 | } | |
285 | // Found the end of a `children.iter()` iterator. |
|
277 | // Found the end of a `children.iter()` iterator. | |
@@ -296,42 +288,16 b" impl<'on_disk> DirstateMap<'on_disk> {" | |||||
296 | }) |
|
288 | }) | |
297 | } |
|
289 | } | |
298 |
|
290 | |||
299 | /// Mutable iterator for the `(entry, copy source)` of each node. |
|
291 | fn clear_known_ambiguous_mtimes(&mut self, paths: &[impl AsRef<HgPath>]) { | |
300 | /// |
|
292 | for path in paths { | |
301 | /// It would not be safe to yield mutable references to nodes themeselves |
|
293 | if let Some(node) = | |
302 | /// with `-> impl Iterator<Item = &mut Node>` since child nodes are |
|
294 | Self::get_node_mut(&mut self.root, path.as_ref()) | |
303 | /// reachable from their ancestor nodes, potentially creating multiple |
|
295 | { | |
304 | /// `&mut` references to a given node. |
|
296 | if let Some(entry) = node.entry.as_mut() { | |
305 | fn iter_node_data_mut<'tree>( |
|
297 | entry.clear_mtime(); | |
306 | &'tree mut self, |
|
298 | } | |
307 | ) -> impl Iterator<Item = NodeDataMut<'tree, 'on_disk>> + 'tree { |
|
|||
308 | // Explict stack for pseudo-recursion, see `iter_nodes` above. |
|
|||
309 | let mut stack = Vec::new(); |
|
|||
310 | let mut iter = self.root.iter_mut(); |
|
|||
311 | std::iter::from_fn(move || { |
|
|||
312 | while let Some((key, child_node)) = iter.next() { |
|
|||
313 | // Pseudo-recursion |
|
|||
314 | let data = ( |
|
|||
315 | &**key.full_path(), |
|
|||
316 | &mut child_node.entry, |
|
|||
317 | &mut child_node.copy_source, |
|
|||
318 | ); |
|
|||
319 | let new_iter = child_node.children.iter_mut(); |
|
|||
320 | let old_iter = std::mem::replace(&mut iter, new_iter); |
|
|||
321 | stack.push((data, old_iter)); |
|
|||
322 | } |
|
299 | } | |
323 | // Found the end of a `children.values_mut()` iterator. |
|
300 | } | |
324 | if let Some((data, next_iter)) = stack.pop() { |
|
|||
325 | // "Return" from pseudo-recursion by restoring state from the |
|
|||
326 | // explicit stack |
|
|||
327 | iter = next_iter; |
|
|||
328 |
|
||||
329 | Some(data) |
|
|||
330 | } else { |
|
|||
331 | // Reached the bottom of the stack, we’re done |
|
|||
332 | None |
|
|||
333 | } |
|
|||
334 | }) |
|
|||
335 | } |
|
301 | } | |
336 | } |
|
302 | } | |
337 |
|
303 | |||
@@ -427,7 +393,7 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
427 | for filename in filenames { |
|
393 | for filename in filenames { | |
428 | if let Some(node) = Self::get_node_mut(&mut self.root, &filename) { |
|
394 | if let Some(node) = Self::get_node_mut(&mut self.root, &filename) { | |
429 | if let Some(entry) = node.entry.as_mut() { |
|
395 | if let Some(entry) = node.entry.as_mut() { | |
430 |
clear_ambiguous_mtime( |
|
396 | entry.clear_ambiguous_mtime(now); | |
431 | } |
|
397 | } | |
432 | } |
|
398 | } | |
433 | } |
|
399 | } | |
@@ -453,7 +419,7 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
453 | .filter(|entry| { |
|
419 | .filter(|entry| { | |
454 | entry.is_non_normal() || entry.is_from_other_parent() |
|
420 | entry.is_non_normal() || entry.is_from_other_parent() | |
455 | }) |
|
421 | }) | |
456 | .map(|_| path) |
|
422 | .map(|_| &**path) | |
457 | })) |
|
423 | })) | |
458 | } |
|
424 | } | |
459 |
|
425 | |||
@@ -475,7 +441,7 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
475 | node.entry |
|
441 | node.entry | |
476 | .as_ref() |
|
442 | .as_ref() | |
477 | .filter(|entry| entry.is_non_normal()) |
|
443 | .filter(|entry| entry.is_non_normal()) | |
478 | .map(|_| path) |
|
444 | .map(|_| &**path) | |
479 | })) |
|
445 | })) | |
480 | } |
|
446 | } | |
481 |
|
447 | |||
@@ -486,7 +452,7 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
486 | node.entry |
|
452 | node.entry | |
487 | .as_ref() |
|
453 | .as_ref() | |
488 | .filter(|entry| entry.is_from_other_parent()) |
|
454 | .filter(|entry| entry.is_from_other_parent()) | |
489 | .map(|_| path) |
|
455 | .map(|_| &**path) | |
490 | })) |
|
456 | })) | |
491 | } |
|
457 | } | |
492 |
|
458 | |||
@@ -522,29 +488,33 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
522 | parents: DirstateParents, |
|
488 | parents: DirstateParents, | |
523 | now: Timestamp, |
|
489 | now: Timestamp, | |
524 | ) -> Result<Vec<u8>, DirstateError> { |
|
490 | ) -> Result<Vec<u8>, DirstateError> { | |
|
491 | let now: i32 = now.0.try_into().expect("time overflow"); | |||
|
492 | let mut ambiguous_mtimes = Vec::new(); | |||
525 | // Optizimation (to be measured?): pre-compute size to avoid `Vec` |
|
493 | // Optizimation (to be measured?): pre-compute size to avoid `Vec` | |
526 | // reallocations |
|
494 | // reallocations | |
527 | let mut size = parents.as_bytes().len(); |
|
495 | let mut size = parents.as_bytes().len(); | |
528 | for (path, node) in self.iter_nodes() { |
|
496 | for (path, node) in self.iter_nodes() { | |
529 |
if node.entry |
|
497 | if let Some(entry) = &node.entry { | |
530 | size += packed_entry_size( |
|
498 | size += packed_entry_size( | |
531 | path, |
|
499 | path, | |
532 | node.copy_source.as_ref().map(|p| &**p), |
|
500 | node.copy_source.as_ref().map(|p| &**p), | |
533 | ) |
|
501 | ); | |
|
502 | if entry.mtime_is_ambiguous(now) { | |||
|
503 | ambiguous_mtimes.push(path.clone()) | |||
|
504 | } | |||
534 | } |
|
505 | } | |
535 | } |
|
506 | } | |
|
507 | self.clear_known_ambiguous_mtimes(&ambiguous_mtimes); | |||
536 |
|
508 | |||
537 | let mut packed = Vec::with_capacity(size); |
|
509 | let mut packed = Vec::with_capacity(size); | |
538 | packed.extend(parents.as_bytes()); |
|
510 | packed.extend(parents.as_bytes()); | |
539 |
|
511 | |||
540 | let now: i32 = now.0.try_into().expect("time overflow"); |
|
512 | for (path, node) in self.iter_nodes() { | |
541 | for (path, opt_entry, copy_source) in self.iter_node_data_mut() { |
|
513 | if let Some(entry) = &node.entry { | |
542 | if let Some(entry) = opt_entry { |
|
|||
543 | clear_ambiguous_mtime(entry, now); |
|
|||
544 | pack_entry( |
|
514 | pack_entry( | |
545 | path, |
|
515 | path, | |
546 | entry, |
|
516 | entry, | |
547 | copy_source.as_ref().map(|p| &**p), |
|
517 | node.copy_source.as_ref().map(|p| &**p), | |
548 | &mut packed, |
|
518 | &mut packed, | |
549 | ); |
|
519 | ); | |
550 | } |
|
520 | } | |
@@ -558,7 +528,21 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
558 | parents: DirstateParents, |
|
528 | parents: DirstateParents, | |
559 | now: Timestamp, |
|
529 | now: Timestamp, | |
560 | ) -> Result<Vec<u8>, DirstateError> { |
|
530 | ) -> Result<Vec<u8>, DirstateError> { | |
561 | on_disk::write(self, parents, now) |
|
531 | // TODO: how do we want to handle this in 2038? | |
|
532 | let now: i32 = now.0.try_into().expect("time overflow"); | |||
|
533 | let mut paths = Vec::new(); | |||
|
534 | for (path, node) in self.iter_nodes() { | |||
|
535 | if let Some(entry) = &node.entry { | |||
|
536 | if entry.mtime_is_ambiguous(now) { | |||
|
537 | paths.push(path.clone()) | |||
|
538 | } | |||
|
539 | } | |||
|
540 | } | |||
|
541 | // Borrow of `self` ends here since we collect cloned paths | |||
|
542 | ||||
|
543 | self.clear_known_ambiguous_mtimes(&paths); | |||
|
544 | ||||
|
545 | on_disk::write(self, parents) | |||
562 | } |
|
546 | } | |
563 |
|
547 | |||
564 | fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { |
|
548 | fn set_all_dirs(&mut self) -> Result<(), DirstateMapError> { | |
@@ -592,7 +576,7 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
592 | Box::new(self.iter_nodes().filter_map(|(path, node)| { |
|
576 | Box::new(self.iter_nodes().filter_map(|(path, node)| { | |
593 | node.copy_source |
|
577 | node.copy_source | |
594 | .as_ref() |
|
578 | .as_ref() | |
595 | .map(|copy_source| (path, &**copy_source)) |
|
579 | .map(|copy_source| (&**path, &**copy_source)) | |
596 | })) |
|
580 | })) | |
597 | } |
|
581 | } | |
598 |
|
582 | |||
@@ -649,7 +633,7 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
649 |
|
633 | |||
650 | fn iter(&self) -> StateMapIter<'_> { |
|
634 | fn iter(&self) -> StateMapIter<'_> { | |
651 | Box::new(self.iter_nodes().filter_map(|(path, node)| { |
|
635 | Box::new(self.iter_nodes().filter_map(|(path, node)| { | |
652 | node.entry.as_ref().map(|entry| (path, entry)) |
|
636 | node.entry.as_ref().map(|entry| (&**path, entry)) | |
653 | })) |
|
637 | })) | |
654 | } |
|
638 | } | |
655 | } |
|
639 | } |
@@ -9,8 +9,6 b'' | |||||
9 | //! Nodes in turn contain slices to variable-size paths, and to their own child |
|
9 | //! Nodes in turn contain slices to variable-size paths, and to their own child | |
10 | //! nodes (if any) for nested files and directories. |
|
10 | //! nodes (if any) for nested files and directories. | |
11 |
|
11 | |||
12 | use crate::dirstate::parsers::clear_ambiguous_mtime; |
|
|||
13 | use crate::dirstate::parsers::Timestamp; |
|
|||
14 | use crate::dirstate_tree::dirstate_map::{self, DirstateMap}; |
|
12 | use crate::dirstate_tree::dirstate_map::{self, DirstateMap}; | |
15 | use crate::dirstate_tree::path_with_basename::WithBasename; |
|
13 | use crate::dirstate_tree::path_with_basename::WithBasename; | |
16 | use crate::errors::HgError; |
|
14 | use crate::errors::HgError; | |
@@ -230,11 +228,7 b' where' | |||||
230 | pub(super) fn write( |
|
228 | pub(super) fn write( | |
231 | dirstate_map: &mut DirstateMap, |
|
229 | dirstate_map: &mut DirstateMap, | |
232 | parents: DirstateParents, |
|
230 | parents: DirstateParents, | |
233 | now: Timestamp, |
|
|||
234 | ) -> Result<Vec<u8>, DirstateError> { |
|
231 | ) -> Result<Vec<u8>, DirstateError> { | |
235 | // TODO: how do we want to handle this in 2038? |
|
|||
236 | let now: i32 = now.0.try_into().expect("time overflow"); |
|
|||
237 |
|
||||
238 | let header_len = std::mem::size_of::<Header>(); |
|
232 | let header_len = std::mem::size_of::<Header>(); | |
239 |
|
233 | |||
240 | // This ignores the space for paths, and for nodes without an entry. |
|
234 | // This ignores the space for paths, and for nodes without an entry. | |
@@ -248,7 +242,7 b' pub(super) fn write(' | |||||
248 | // actual offset for the root nodes. |
|
242 | // actual offset for the root nodes. | |
249 | out.resize(header_len, 0_u8); |
|
243 | out.resize(header_len, 0_u8); | |
250 |
|
244 | |||
251 |
let root = write_nodes(&mut dirstate_map.root, |
|
245 | let root = write_nodes(&mut dirstate_map.root, &mut out)?; | |
252 |
|
246 | |||
253 | let header = Header { |
|
247 | let header = Header { | |
254 | marker: *V2_FORMAT_MARKER, |
|
248 | marker: *V2_FORMAT_MARKER, | |
@@ -263,10 +257,8 b' pub(super) fn write(' | |||||
263 | Ok(out) |
|
257 | Ok(out) | |
264 | } |
|
258 | } | |
265 |
|
259 | |||
266 | /// Serialize the dirstate to the `v2` format after clearing ambigous `mtime`s. |
|
|||
267 | fn write_nodes( |
|
260 | fn write_nodes( | |
268 | nodes: &mut dirstate_map::ChildNodes, |
|
261 | nodes: &mut dirstate_map::ChildNodes, | |
269 | now: i32, |
|
|||
270 | out: &mut Vec<u8>, |
|
262 | out: &mut Vec<u8>, | |
271 | ) -> Result<ChildNodes, DirstateError> { |
|
263 | ) -> Result<ChildNodes, DirstateError> { | |
272 | // `dirstate_map::ChildNodes` is a `HashMap` with undefined iteration |
|
264 | // `dirstate_map::ChildNodes` is a `HashMap` with undefined iteration | |
@@ -277,7 +269,7 b' fn write_nodes(' | |||||
277 | let mut on_disk_nodes = Vec::with_capacity(nodes.len()); |
|
269 | let mut on_disk_nodes = Vec::with_capacity(nodes.len()); | |
278 | for (full_path, node) in nodes { |
|
270 | for (full_path, node) in nodes { | |
279 | on_disk_nodes.push(Node { |
|
271 | on_disk_nodes.push(Node { | |
280 |
children: write_nodes(&mut node.children, |
|
272 | children: write_nodes(&mut node.children, out)?, | |
281 | tracked_descendants_count: node.tracked_descendants_count.into(), |
|
273 | tracked_descendants_count: node.tracked_descendants_count.into(), | |
282 | full_path: write_slice::<u8>( |
|
274 | full_path: write_slice::<u8>( | |
283 | full_path.full_path().as_bytes(), |
|
275 | full_path.full_path().as_bytes(), | |
@@ -296,7 +288,6 b' fn write_nodes(' | |||||
296 | } |
|
288 | } | |
297 | }, |
|
289 | }, | |
298 | entry: if let Some(entry) = &mut node.entry { |
|
290 | entry: if let Some(entry) = &mut node.entry { | |
299 | clear_ambiguous_mtime(entry, now); |
|
|||
300 | OptEntry { |
|
291 | OptEntry { | |
301 | state: entry.state.into(), |
|
292 | state: entry.state.into(), | |
302 | mode: entry.mode.into(), |
|
293 | mode: entry.mode.into(), |
General Comments 0
You need to be logged in to leave comments.
Login now