##// END OF EJS Templates
rust-status: error on non-existent files in file_set...
Spencer Baugh -
r51756:26407210 default
parent child Browse files
Show More
@@ -1,1929 +1,1937 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use std::borrow::Cow;
2 use std::borrow::Cow;
3 use std::path::PathBuf;
3 use std::path::PathBuf;
4
4
5 use super::on_disk;
5 use super::on_disk;
6 use super::on_disk::DirstateV2ParseError;
6 use super::on_disk::DirstateV2ParseError;
7 use super::owning::OwningDirstateMap;
7 use super::owning::OwningDirstateMap;
8 use super::path_with_basename::WithBasename;
8 use super::path_with_basename::WithBasename;
9 use crate::dirstate::parsers::pack_entry;
9 use crate::dirstate::parsers::pack_entry;
10 use crate::dirstate::parsers::packed_entry_size;
10 use crate::dirstate::parsers::packed_entry_size;
11 use crate::dirstate::parsers::parse_dirstate_entries;
11 use crate::dirstate::parsers::parse_dirstate_entries;
12 use crate::dirstate::CopyMapIter;
12 use crate::dirstate::CopyMapIter;
13 use crate::dirstate::DirstateV2Data;
13 use crate::dirstate::DirstateV2Data;
14 use crate::dirstate::ParentFileData;
14 use crate::dirstate::ParentFileData;
15 use crate::dirstate::StateMapIter;
15 use crate::dirstate::StateMapIter;
16 use crate::dirstate::TruncatedTimestamp;
16 use crate::dirstate::TruncatedTimestamp;
17 use crate::matchers::Matcher;
17 use crate::matchers::Matcher;
18 use crate::utils::filter_map_results;
18 use crate::utils::filter_map_results;
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::DirstateEntry;
20 use crate::DirstateEntry;
21 use crate::DirstateError;
21 use crate::DirstateError;
22 use crate::DirstateMapError;
22 use crate::DirstateMapError;
23 use crate::DirstateParents;
23 use crate::DirstateParents;
24 use crate::DirstateStatus;
24 use crate::DirstateStatus;
25 use crate::FastHashbrownMap as FastHashMap;
25 use crate::FastHashbrownMap as FastHashMap;
26 use crate::PatternFileWarning;
26 use crate::PatternFileWarning;
27 use crate::StatusError;
27 use crate::StatusError;
28 use crate::StatusOptions;
28 use crate::StatusOptions;
29
29
30 /// Append to an existing data file if the amount of unreachable data (not used
30 /// Append to an existing data file if the amount of unreachable data (not used
31 /// anymore) is less than this fraction of the total amount of existing data.
31 /// anymore) is less than this fraction of the total amount of existing data.
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33
33
34 #[derive(Debug, PartialEq, Eq)]
34 #[derive(Debug, PartialEq, Eq)]
35 /// Version of the on-disk format
35 /// Version of the on-disk format
36 pub enum DirstateVersion {
36 pub enum DirstateVersion {
37 V1,
37 V1,
38 V2,
38 V2,
39 }
39 }
40
40
41 #[derive(Debug, PartialEq, Eq)]
41 #[derive(Debug, PartialEq, Eq)]
42 pub enum DirstateMapWriteMode {
42 pub enum DirstateMapWriteMode {
43 Auto,
43 Auto,
44 ForceNewDataFile,
44 ForceNewDataFile,
45 ForceAppend,
45 ForceAppend,
46 }
46 }
47
47
48 #[derive(Debug)]
48 #[derive(Debug)]
49 pub struct DirstateMap<'on_disk> {
49 pub struct DirstateMap<'on_disk> {
50 /// Contents of the `.hg/dirstate` file
50 /// Contents of the `.hg/dirstate` file
51 pub(super) on_disk: &'on_disk [u8],
51 pub(super) on_disk: &'on_disk [u8],
52
52
53 pub(super) root: ChildNodes<'on_disk>,
53 pub(super) root: ChildNodes<'on_disk>,
54
54
55 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
55 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
56 pub(super) nodes_with_entry_count: u32,
56 pub(super) nodes_with_entry_count: u32,
57
57
58 /// Number of nodes anywhere in the tree that have
58 /// Number of nodes anywhere in the tree that have
59 /// `.copy_source.is_some()`.
59 /// `.copy_source.is_some()`.
60 pub(super) nodes_with_copy_source_count: u32,
60 pub(super) nodes_with_copy_source_count: u32,
61
61
62 /// See on_disk::Header
62 /// See on_disk::Header
63 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
63 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
64
64
65 /// How many bytes of `on_disk` are not used anymore
65 /// How many bytes of `on_disk` are not used anymore
66 pub(super) unreachable_bytes: u32,
66 pub(super) unreachable_bytes: u32,
67
67
68 /// Size of the data used to first load this `DirstateMap`. Used in case
68 /// Size of the data used to first load this `DirstateMap`. Used in case
69 /// we need to write some new metadata, but no new data on disk,
69 /// we need to write some new metadata, but no new data on disk,
70 /// as well as to detect writes that have happened in another process
70 /// as well as to detect writes that have happened in another process
71 /// since first read.
71 /// since first read.
72 pub(super) old_data_size: usize,
72 pub(super) old_data_size: usize,
73
73
74 /// UUID used when first loading this `DirstateMap`. Used to check if
74 /// UUID used when first loading this `DirstateMap`. Used to check if
75 /// the UUID has been changed by another process since first read.
75 /// the UUID has been changed by another process since first read.
76 /// Can be `None` if using dirstate v1 or if it's a brand new dirstate.
76 /// Can be `None` if using dirstate v1 or if it's a brand new dirstate.
77 pub(super) old_uuid: Option<Vec<u8>>,
77 pub(super) old_uuid: Option<Vec<u8>>,
78
78
79 /// Identity of the dirstate file (for dirstate-v1) or the docket file
79 /// Identity of the dirstate file (for dirstate-v1) or the docket file
80 /// (v2). Used to detect if the file has changed from another process.
80 /// (v2). Used to detect if the file has changed from another process.
81 /// Since it's always written atomically, we can compare the inode to
81 /// Since it's always written atomically, we can compare the inode to
82 /// check the file identity.
82 /// check the file identity.
83 ///
83 ///
84 /// TODO On non-Unix systems, something like hashing is a possibility?
84 /// TODO On non-Unix systems, something like hashing is a possibility?
85 pub(super) identity: Option<u64>,
85 pub(super) identity: Option<u64>,
86
86
87 pub(super) dirstate_version: DirstateVersion,
87 pub(super) dirstate_version: DirstateVersion,
88
88
89 /// Controlled by config option `devel.dirstate.v2.data_update_mode`
89 /// Controlled by config option `devel.dirstate.v2.data_update_mode`
90 pub(super) write_mode: DirstateMapWriteMode,
90 pub(super) write_mode: DirstateMapWriteMode,
91 }
91 }
92
92
93 /// Using a plain `HgPathBuf` of the full path from the repository root as a
93 /// Using a plain `HgPathBuf` of the full path from the repository root as a
94 /// map key would also work: all paths in a given map have the same parent
94 /// map key would also work: all paths in a given map have the same parent
95 /// path, so comparing full paths gives the same result as comparing base
95 /// path, so comparing full paths gives the same result as comparing base
96 /// names. However `HashMap` would waste time always re-hashing the same
96 /// names. However `HashMap` would waste time always re-hashing the same
97 /// string prefix.
97 /// string prefix.
98 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
98 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
99
99
100 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
100 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
101 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
101 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
102 #[derive(Debug)]
102 #[derive(Debug)]
103 pub(super) enum BorrowedPath<'tree, 'on_disk> {
103 pub(super) enum BorrowedPath<'tree, 'on_disk> {
104 InMemory(&'tree HgPathBuf),
104 InMemory(&'tree HgPathBuf),
105 OnDisk(&'on_disk HgPath),
105 OnDisk(&'on_disk HgPath),
106 }
106 }
107
107
108 #[derive(Debug)]
108 #[derive(Debug)]
109 pub(super) enum ChildNodes<'on_disk> {
109 pub(super) enum ChildNodes<'on_disk> {
110 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
110 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
111 OnDisk(&'on_disk [on_disk::Node]),
111 OnDisk(&'on_disk [on_disk::Node]),
112 }
112 }
113
113
114 #[derive(Debug)]
114 #[derive(Debug)]
115 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
115 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
116 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
116 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
117 OnDisk(&'on_disk [on_disk::Node]),
117 OnDisk(&'on_disk [on_disk::Node]),
118 }
118 }
119
119
120 #[derive(Debug)]
120 #[derive(Debug)]
121 pub(super) enum NodeRef<'tree, 'on_disk> {
121 pub(super) enum NodeRef<'tree, 'on_disk> {
122 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
122 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
123 OnDisk(&'on_disk on_disk::Node),
123 OnDisk(&'on_disk on_disk::Node),
124 }
124 }
125
125
126 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
126 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
127 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
127 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
128 match *self {
128 match *self {
129 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
129 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
130 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
130 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
131 }
131 }
132 }
132 }
133 }
133 }
134
134
135 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
135 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
136 type Target = HgPath;
136 type Target = HgPath;
137
137
138 fn deref(&self) -> &HgPath {
138 fn deref(&self) -> &HgPath {
139 match *self {
139 match *self {
140 BorrowedPath::InMemory(in_memory) => in_memory,
140 BorrowedPath::InMemory(in_memory) => in_memory,
141 BorrowedPath::OnDisk(on_disk) => on_disk,
141 BorrowedPath::OnDisk(on_disk) => on_disk,
142 }
142 }
143 }
143 }
144 }
144 }
145
145
146 impl Default for ChildNodes<'_> {
146 impl Default for ChildNodes<'_> {
147 fn default() -> Self {
147 fn default() -> Self {
148 ChildNodes::InMemory(Default::default())
148 ChildNodes::InMemory(Default::default())
149 }
149 }
150 }
150 }
151
151
152 impl<'on_disk> ChildNodes<'on_disk> {
152 impl<'on_disk> ChildNodes<'on_disk> {
153 pub(super) fn as_ref<'tree>(
153 pub(super) fn as_ref<'tree>(
154 &'tree self,
154 &'tree self,
155 ) -> ChildNodesRef<'tree, 'on_disk> {
155 ) -> ChildNodesRef<'tree, 'on_disk> {
156 match self {
156 match self {
157 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
157 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
158 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
158 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
159 }
159 }
160 }
160 }
161
161
162 pub(super) fn is_empty(&self) -> bool {
162 pub(super) fn is_empty(&self) -> bool {
163 match self {
163 match self {
164 ChildNodes::InMemory(nodes) => nodes.is_empty(),
164 ChildNodes::InMemory(nodes) => nodes.is_empty(),
165 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
165 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
166 }
166 }
167 }
167 }
168
168
169 fn make_mut(
169 fn make_mut(
170 &mut self,
170 &mut self,
171 on_disk: &'on_disk [u8],
171 on_disk: &'on_disk [u8],
172 unreachable_bytes: &mut u32,
172 unreachable_bytes: &mut u32,
173 ) -> Result<
173 ) -> Result<
174 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
174 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
175 DirstateV2ParseError,
175 DirstateV2ParseError,
176 > {
176 > {
177 match self {
177 match self {
178 ChildNodes::InMemory(nodes) => Ok(nodes),
178 ChildNodes::InMemory(nodes) => Ok(nodes),
179 ChildNodes::OnDisk(nodes) => {
179 ChildNodes::OnDisk(nodes) => {
180 *unreachable_bytes +=
180 *unreachable_bytes +=
181 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
181 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
182 let nodes = nodes
182 let nodes = nodes
183 .iter()
183 .iter()
184 .map(|node| {
184 .map(|node| {
185 Ok((
185 Ok((
186 node.path(on_disk)?,
186 node.path(on_disk)?,
187 node.to_in_memory_node(on_disk)?,
187 node.to_in_memory_node(on_disk)?,
188 ))
188 ))
189 })
189 })
190 .collect::<Result<_, _>>()?;
190 .collect::<Result<_, _>>()?;
191 *self = ChildNodes::InMemory(nodes);
191 *self = ChildNodes::InMemory(nodes);
192 match self {
192 match self {
193 ChildNodes::InMemory(nodes) => Ok(nodes),
193 ChildNodes::InMemory(nodes) => Ok(nodes),
194 ChildNodes::OnDisk(_) => unreachable!(),
194 ChildNodes::OnDisk(_) => unreachable!(),
195 }
195 }
196 }
196 }
197 }
197 }
198 }
198 }
199 }
199 }
200
200
201 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
201 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
202 pub(super) fn get(
202 pub(super) fn get(
203 &self,
203 &self,
204 base_name: &HgPath,
204 base_name: &HgPath,
205 on_disk: &'on_disk [u8],
205 on_disk: &'on_disk [u8],
206 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
206 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
207 match self {
207 match self {
208 ChildNodesRef::InMemory(nodes) => Ok(nodes
208 ChildNodesRef::InMemory(nodes) => Ok(nodes
209 .get_key_value(base_name)
209 .get_key_value(base_name)
210 .map(|(k, v)| NodeRef::InMemory(k, v))),
210 .map(|(k, v)| NodeRef::InMemory(k, v))),
211 ChildNodesRef::OnDisk(nodes) => {
211 ChildNodesRef::OnDisk(nodes) => {
212 let mut parse_result = Ok(());
212 let mut parse_result = Ok(());
213 let search_result = nodes.binary_search_by(|node| {
213 let search_result = nodes.binary_search_by(|node| {
214 match node.base_name(on_disk) {
214 match node.base_name(on_disk) {
215 Ok(node_base_name) => node_base_name.cmp(base_name),
215 Ok(node_base_name) => node_base_name.cmp(base_name),
216 Err(e) => {
216 Err(e) => {
217 parse_result = Err(e);
217 parse_result = Err(e);
218 // Dummy comparison result, `search_result` won’t
218 // Dummy comparison result, `search_result` won’t
219 // be used since `parse_result` is an error
219 // be used since `parse_result` is an error
220 std::cmp::Ordering::Equal
220 std::cmp::Ordering::Equal
221 }
221 }
222 }
222 }
223 });
223 });
224 parse_result.map(|()| {
224 parse_result.map(|()| {
225 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
225 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
226 })
226 })
227 }
227 }
228 }
228 }
229 }
229 }
230
230
231 /// Iterate in undefined order
231 /// Iterate in undefined order
232 pub(super) fn iter(
232 pub(super) fn iter(
233 &self,
233 &self,
234 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
234 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
235 match self {
235 match self {
236 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
236 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
237 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
237 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
238 ),
238 ),
239 ChildNodesRef::OnDisk(nodes) => {
239 ChildNodesRef::OnDisk(nodes) => {
240 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
240 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
241 }
241 }
242 }
242 }
243 }
243 }
244
244
245 /// Iterate in parallel in undefined order
245 /// Iterate in parallel in undefined order
246 pub(super) fn par_iter(
246 pub(super) fn par_iter(
247 &self,
247 &self,
248 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
248 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
249 {
249 {
250 use rayon::prelude::*;
250 use rayon::prelude::*;
251 match self {
251 match self {
252 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
252 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
253 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
253 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
254 ),
254 ),
255 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
255 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
256 nodes.par_iter().map(NodeRef::OnDisk),
256 nodes.par_iter().map(NodeRef::OnDisk),
257 ),
257 ),
258 }
258 }
259 }
259 }
260
260
261 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
261 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
262 match self {
262 match self {
263 ChildNodesRef::InMemory(nodes) => {
263 ChildNodesRef::InMemory(nodes) => {
264 let mut vec: Vec<_> = nodes
264 let mut vec: Vec<_> = nodes
265 .iter()
265 .iter()
266 .map(|(k, v)| NodeRef::InMemory(k, v))
266 .map(|(k, v)| NodeRef::InMemory(k, v))
267 .collect();
267 .collect();
268 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
268 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
269 match node {
269 match node {
270 NodeRef::InMemory(path, _node) => path.base_name(),
270 NodeRef::InMemory(path, _node) => path.base_name(),
271 NodeRef::OnDisk(_) => unreachable!(),
271 NodeRef::OnDisk(_) => unreachable!(),
272 }
272 }
273 }
273 }
274 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
274 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
275 // value: https://github.com/rust-lang/rust/issues/34162
275 // value: https://github.com/rust-lang/rust/issues/34162
276 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
276 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
277 vec
277 vec
278 }
278 }
279 ChildNodesRef::OnDisk(nodes) => {
279 ChildNodesRef::OnDisk(nodes) => {
280 // Nodes on disk are already sorted
280 // Nodes on disk are already sorted
281 nodes.iter().map(NodeRef::OnDisk).collect()
281 nodes.iter().map(NodeRef::OnDisk).collect()
282 }
282 }
283 }
283 }
284 }
284 }
285 }
285 }
286
286
287 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
287 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
288 pub(super) fn full_path(
288 pub(super) fn full_path(
289 &self,
289 &self,
290 on_disk: &'on_disk [u8],
290 on_disk: &'on_disk [u8],
291 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
291 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
292 match self {
292 match self {
293 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
293 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
294 NodeRef::OnDisk(node) => node.full_path(on_disk),
294 NodeRef::OnDisk(node) => node.full_path(on_disk),
295 }
295 }
296 }
296 }
297
297
298 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
298 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
299 /// HgPath>` detached from `'tree`
299 /// HgPath>` detached from `'tree`
300 pub(super) fn full_path_borrowed(
300 pub(super) fn full_path_borrowed(
301 &self,
301 &self,
302 on_disk: &'on_disk [u8],
302 on_disk: &'on_disk [u8],
303 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
303 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
304 match self {
304 match self {
305 NodeRef::InMemory(path, _node) => match path.full_path() {
305 NodeRef::InMemory(path, _node) => match path.full_path() {
306 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
306 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
307 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
307 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
308 },
308 },
309 NodeRef::OnDisk(node) => {
309 NodeRef::OnDisk(node) => {
310 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
310 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
311 }
311 }
312 }
312 }
313 }
313 }
314
314
315 pub(super) fn base_name(
315 pub(super) fn base_name(
316 &self,
316 &self,
317 on_disk: &'on_disk [u8],
317 on_disk: &'on_disk [u8],
318 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
318 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
319 match self {
319 match self {
320 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
320 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
321 NodeRef::OnDisk(node) => node.base_name(on_disk),
321 NodeRef::OnDisk(node) => node.base_name(on_disk),
322 }
322 }
323 }
323 }
324
324
325 pub(super) fn children(
325 pub(super) fn children(
326 &self,
326 &self,
327 on_disk: &'on_disk [u8],
327 on_disk: &'on_disk [u8],
328 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
328 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
329 match self {
329 match self {
330 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
330 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
331 NodeRef::OnDisk(node) => {
331 NodeRef::OnDisk(node) => {
332 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
332 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
333 }
333 }
334 }
334 }
335 }
335 }
336
336
337 pub(super) fn has_copy_source(&self) -> bool {
337 pub(super) fn has_copy_source(&self) -> bool {
338 match self {
338 match self {
339 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
339 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
340 NodeRef::OnDisk(node) => node.has_copy_source(),
340 NodeRef::OnDisk(node) => node.has_copy_source(),
341 }
341 }
342 }
342 }
343
343
344 pub(super) fn copy_source(
344 pub(super) fn copy_source(
345 &self,
345 &self,
346 on_disk: &'on_disk [u8],
346 on_disk: &'on_disk [u8],
347 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
347 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
348 match self {
348 match self {
349 NodeRef::InMemory(_path, node) => Ok(node.copy_source.as_deref()),
349 NodeRef::InMemory(_path, node) => Ok(node.copy_source.as_deref()),
350 NodeRef::OnDisk(node) => node.copy_source(on_disk),
350 NodeRef::OnDisk(node) => node.copy_source(on_disk),
351 }
351 }
352 }
352 }
353 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
353 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
354 /// HgPath>` detached from `'tree`
354 /// HgPath>` detached from `'tree`
355 pub(super) fn copy_source_borrowed(
355 pub(super) fn copy_source_borrowed(
356 &self,
356 &self,
357 on_disk: &'on_disk [u8],
357 on_disk: &'on_disk [u8],
358 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
358 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
359 {
359 {
360 Ok(match self {
360 Ok(match self {
361 NodeRef::InMemory(_path, node) => {
361 NodeRef::InMemory(_path, node) => {
362 node.copy_source.as_ref().map(|source| match source {
362 node.copy_source.as_ref().map(|source| match source {
363 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
363 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
364 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
364 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
365 })
365 })
366 }
366 }
367 NodeRef::OnDisk(node) => {
367 NodeRef::OnDisk(node) => {
368 node.copy_source(on_disk)?.map(BorrowedPath::OnDisk)
368 node.copy_source(on_disk)?.map(BorrowedPath::OnDisk)
369 }
369 }
370 })
370 })
371 }
371 }
372
372
373 pub(super) fn entry(
373 pub(super) fn entry(
374 &self,
374 &self,
375 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
375 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
376 match self {
376 match self {
377 NodeRef::InMemory(_path, node) => {
377 NodeRef::InMemory(_path, node) => {
378 Ok(node.data.as_entry().copied())
378 Ok(node.data.as_entry().copied())
379 }
379 }
380 NodeRef::OnDisk(node) => node.entry(),
380 NodeRef::OnDisk(node) => node.entry(),
381 }
381 }
382 }
382 }
383
383
384 pub(super) fn cached_directory_mtime(
384 pub(super) fn cached_directory_mtime(
385 &self,
385 &self,
386 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
386 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
387 match self {
387 match self {
388 NodeRef::InMemory(_path, node) => Ok(match node.data {
388 NodeRef::InMemory(_path, node) => Ok(match node.data {
389 NodeData::CachedDirectory { mtime } => Some(mtime),
389 NodeData::CachedDirectory { mtime } => Some(mtime),
390 _ => None,
390 _ => None,
391 }),
391 }),
392 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
392 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
393 }
393 }
394 }
394 }
395
395
396 pub(super) fn descendants_with_entry_count(&self) -> u32 {
396 pub(super) fn descendants_with_entry_count(&self) -> u32 {
397 match self {
397 match self {
398 NodeRef::InMemory(_path, node) => {
398 NodeRef::InMemory(_path, node) => {
399 node.descendants_with_entry_count
399 node.descendants_with_entry_count
400 }
400 }
401 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
401 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
402 }
402 }
403 }
403 }
404
404
405 pub(super) fn tracked_descendants_count(&self) -> u32 {
405 pub(super) fn tracked_descendants_count(&self) -> u32 {
406 match self {
406 match self {
407 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
407 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
408 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
408 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
409 }
409 }
410 }
410 }
411 }
411 }
412
412
413 /// Represents a file or a directory
413 /// Represents a file or a directory
414 #[derive(Default, Debug)]
414 #[derive(Default, Debug)]
415 pub(super) struct Node<'on_disk> {
415 pub(super) struct Node<'on_disk> {
416 pub(super) data: NodeData,
416 pub(super) data: NodeData,
417
417
418 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
418 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
419
419
420 pub(super) children: ChildNodes<'on_disk>,
420 pub(super) children: ChildNodes<'on_disk>,
421
421
422 /// How many (non-inclusive) descendants of this node have an entry.
422 /// How many (non-inclusive) descendants of this node have an entry.
423 pub(super) descendants_with_entry_count: u32,
423 pub(super) descendants_with_entry_count: u32,
424
424
425 /// How many (non-inclusive) descendants of this node have an entry whose
425 /// How many (non-inclusive) descendants of this node have an entry whose
426 /// state is "tracked".
426 /// state is "tracked".
427 pub(super) tracked_descendants_count: u32,
427 pub(super) tracked_descendants_count: u32,
428 }
428 }
429
429
430 #[derive(Debug)]
430 #[derive(Debug)]
431 pub(super) enum NodeData {
431 pub(super) enum NodeData {
432 Entry(DirstateEntry),
432 Entry(DirstateEntry),
433 CachedDirectory { mtime: TruncatedTimestamp },
433 CachedDirectory { mtime: TruncatedTimestamp },
434 None,
434 None,
435 }
435 }
436
436
437 impl Default for NodeData {
437 impl Default for NodeData {
438 fn default() -> Self {
438 fn default() -> Self {
439 NodeData::None
439 NodeData::None
440 }
440 }
441 }
441 }
442
442
443 impl NodeData {
443 impl NodeData {
444 fn has_entry(&self) -> bool {
444 fn has_entry(&self) -> bool {
445 matches!(self, NodeData::Entry(_))
445 matches!(self, NodeData::Entry(_))
446 }
446 }
447
447
448 fn as_entry(&self) -> Option<&DirstateEntry> {
448 fn as_entry(&self) -> Option<&DirstateEntry> {
449 match self {
449 match self {
450 NodeData::Entry(entry) => Some(entry),
450 NodeData::Entry(entry) => Some(entry),
451 _ => None,
451 _ => None,
452 }
452 }
453 }
453 }
454
454
455 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
455 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
456 match self {
456 match self {
457 NodeData::Entry(entry) => Some(entry),
457 NodeData::Entry(entry) => Some(entry),
458 _ => None,
458 _ => None,
459 }
459 }
460 }
460 }
461 }
461 }
462
462
463 impl<'on_disk> DirstateMap<'on_disk> {
463 impl<'on_disk> DirstateMap<'on_disk> {
464 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
464 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
465 Self {
465 Self {
466 on_disk,
466 on_disk,
467 root: ChildNodes::default(),
467 root: ChildNodes::default(),
468 nodes_with_entry_count: 0,
468 nodes_with_entry_count: 0,
469 nodes_with_copy_source_count: 0,
469 nodes_with_copy_source_count: 0,
470 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
470 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
471 unreachable_bytes: 0,
471 unreachable_bytes: 0,
472 old_data_size: 0,
472 old_data_size: 0,
473 old_uuid: None,
473 old_uuid: None,
474 identity: None,
474 identity: None,
475 dirstate_version: DirstateVersion::V1,
475 dirstate_version: DirstateVersion::V1,
476 write_mode: DirstateMapWriteMode::Auto,
476 write_mode: DirstateMapWriteMode::Auto,
477 }
477 }
478 }
478 }
479
479
480 #[logging_timer::time("trace")]
480 #[logging_timer::time("trace")]
481 pub fn new_v2(
481 pub fn new_v2(
482 on_disk: &'on_disk [u8],
482 on_disk: &'on_disk [u8],
483 data_size: usize,
483 data_size: usize,
484 metadata: &[u8],
484 metadata: &[u8],
485 uuid: Vec<u8>,
485 uuid: Vec<u8>,
486 identity: Option<u64>,
486 identity: Option<u64>,
487 ) -> Result<Self, DirstateError> {
487 ) -> Result<Self, DirstateError> {
488 if let Some(data) = on_disk.get(..data_size) {
488 if let Some(data) = on_disk.get(..data_size) {
489 Ok(on_disk::read(data, metadata, uuid, identity)?)
489 Ok(on_disk::read(data, metadata, uuid, identity)?)
490 } else {
490 } else {
491 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
491 Err(DirstateV2ParseError::new("not enough bytes on disk").into())
492 }
492 }
493 }
493 }
494
494
495 #[logging_timer::time("trace")]
495 #[logging_timer::time("trace")]
496 pub fn new_v1(
496 pub fn new_v1(
497 on_disk: &'on_disk [u8],
497 on_disk: &'on_disk [u8],
498 identity: Option<u64>,
498 identity: Option<u64>,
499 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
499 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
500 let mut map = Self::empty(on_disk);
500 let mut map = Self::empty(on_disk);
501 if map.on_disk.is_empty() {
501 if map.on_disk.is_empty() {
502 return Ok((map, None));
502 return Ok((map, None));
503 }
503 }
504
504
505 let parents = parse_dirstate_entries(
505 let parents = parse_dirstate_entries(
506 map.on_disk,
506 map.on_disk,
507 |path, entry, copy_source| {
507 |path, entry, copy_source| {
508 let tracked = entry.tracked();
508 let tracked = entry.tracked();
509 let node = Self::get_or_insert_node_inner(
509 let node = Self::get_or_insert_node_inner(
510 map.on_disk,
510 map.on_disk,
511 &mut map.unreachable_bytes,
511 &mut map.unreachable_bytes,
512 &mut map.root,
512 &mut map.root,
513 path,
513 path,
514 WithBasename::to_cow_borrowed,
514 WithBasename::to_cow_borrowed,
515 |ancestor| {
515 |ancestor| {
516 if tracked {
516 if tracked {
517 ancestor.tracked_descendants_count += 1
517 ancestor.tracked_descendants_count += 1
518 }
518 }
519 ancestor.descendants_with_entry_count += 1
519 ancestor.descendants_with_entry_count += 1
520 },
520 },
521 )?;
521 )?;
522 assert!(
522 assert!(
523 !node.data.has_entry(),
523 !node.data.has_entry(),
524 "duplicate dirstate entry in read"
524 "duplicate dirstate entry in read"
525 );
525 );
526 assert!(
526 assert!(
527 node.copy_source.is_none(),
527 node.copy_source.is_none(),
528 "duplicate dirstate entry in read"
528 "duplicate dirstate entry in read"
529 );
529 );
530 node.data = NodeData::Entry(*entry);
530 node.data = NodeData::Entry(*entry);
531 node.copy_source = copy_source.map(Cow::Borrowed);
531 node.copy_source = copy_source.map(Cow::Borrowed);
532 map.nodes_with_entry_count += 1;
532 map.nodes_with_entry_count += 1;
533 if copy_source.is_some() {
533 if copy_source.is_some() {
534 map.nodes_with_copy_source_count += 1
534 map.nodes_with_copy_source_count += 1
535 }
535 }
536 Ok(())
536 Ok(())
537 },
537 },
538 )?;
538 )?;
539 let parents = Some(*parents);
539 let parents = Some(*parents);
540 map.identity = identity;
540 map.identity = identity;
541
541
542 Ok((map, parents))
542 Ok((map, parents))
543 }
543 }
544
544
545 /// Assuming dirstate-v2 format, returns whether the next write should
545 /// Assuming dirstate-v2 format, returns whether the next write should
546 /// append to the existing data file that contains `self.on_disk` (true),
546 /// append to the existing data file that contains `self.on_disk` (true),
547 /// or create a new data file from scratch (false).
547 /// or create a new data file from scratch (false).
548 pub(super) fn write_should_append(&self) -> bool {
548 pub(super) fn write_should_append(&self) -> bool {
549 match self.write_mode {
549 match self.write_mode {
550 DirstateMapWriteMode::ForceAppend => true,
550 DirstateMapWriteMode::ForceAppend => true,
551 DirstateMapWriteMode::ForceNewDataFile => false,
551 DirstateMapWriteMode::ForceNewDataFile => false,
552 DirstateMapWriteMode::Auto => {
552 DirstateMapWriteMode::Auto => {
553 let ratio =
553 let ratio =
554 self.unreachable_bytes as f32 / self.on_disk.len() as f32;
554 self.unreachable_bytes as f32 / self.on_disk.len() as f32;
555 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
555 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
556 }
556 }
557 }
557 }
558 }
558 }
559
559
560 fn get_node<'tree>(
560 fn get_node<'tree>(
561 &'tree self,
561 &'tree self,
562 path: &HgPath,
562 path: &HgPath,
563 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
563 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
564 let mut children = self.root.as_ref();
564 let mut children = self.root.as_ref();
565 let mut components = path.components();
565 let mut components = path.components();
566 let mut component =
566 let mut component =
567 components.next().expect("expected at least one components");
567 components.next().expect("expected at least one components");
568 loop {
568 loop {
569 if let Some(child) = children.get(component, self.on_disk)? {
569 if let Some(child) = children.get(component, self.on_disk)? {
570 if let Some(next_component) = components.next() {
570 if let Some(next_component) = components.next() {
571 component = next_component;
571 component = next_component;
572 children = child.children(self.on_disk)?;
572 children = child.children(self.on_disk)?;
573 } else {
573 } else {
574 return Ok(Some(child));
574 return Ok(Some(child));
575 }
575 }
576 } else {
576 } else {
577 return Ok(None);
577 return Ok(None);
578 }
578 }
579 }
579 }
580 }
580 }
581
581
582 pub fn has_node(
583 &self,
584 path: &HgPath,
585 ) -> Result<bool, DirstateV2ParseError> {
586 let node = self.get_node(path)?;
587 Ok(node.is_some())
588 }
589
582 /// Returns a mutable reference to the node at `path` if it exists
590 /// Returns a mutable reference to the node at `path` if it exists
583 ///
591 ///
584 /// `each_ancestor` is a callback that is called for each ancestor node
592 /// `each_ancestor` is a callback that is called for each ancestor node
585 /// when descending the tree. It is used to keep the different counters
593 /// when descending the tree. It is used to keep the different counters
586 /// of the `DirstateMap` up-to-date.
594 /// of the `DirstateMap` up-to-date.
587 fn get_node_mut<'tree>(
595 fn get_node_mut<'tree>(
588 &'tree mut self,
596 &'tree mut self,
589 path: &HgPath,
597 path: &HgPath,
590 each_ancestor: impl FnMut(&mut Node),
598 each_ancestor: impl FnMut(&mut Node),
591 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
599 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
592 Self::get_node_mut_inner(
600 Self::get_node_mut_inner(
593 self.on_disk,
601 self.on_disk,
594 &mut self.unreachable_bytes,
602 &mut self.unreachable_bytes,
595 &mut self.root,
603 &mut self.root,
596 path,
604 path,
597 each_ancestor,
605 each_ancestor,
598 )
606 )
599 }
607 }
600
608
601 /// Lower-level version of `get_node_mut`.
609 /// Lower-level version of `get_node_mut`.
602 ///
610 ///
603 /// This takes `root` instead of `&mut self` so that callers can mutate
611 /// This takes `root` instead of `&mut self` so that callers can mutate
604 /// other fields while the returned borrow is still valid.
612 /// other fields while the returned borrow is still valid.
605 ///
613 ///
606 /// `each_ancestor` is a callback that is called for each ancestor node
614 /// `each_ancestor` is a callback that is called for each ancestor node
607 /// when descending the tree. It is used to keep the different counters
615 /// when descending the tree. It is used to keep the different counters
608 /// of the `DirstateMap` up-to-date.
616 /// of the `DirstateMap` up-to-date.
609 fn get_node_mut_inner<'tree>(
617 fn get_node_mut_inner<'tree>(
610 on_disk: &'on_disk [u8],
618 on_disk: &'on_disk [u8],
611 unreachable_bytes: &mut u32,
619 unreachable_bytes: &mut u32,
612 root: &'tree mut ChildNodes<'on_disk>,
620 root: &'tree mut ChildNodes<'on_disk>,
613 path: &HgPath,
621 path: &HgPath,
614 mut each_ancestor: impl FnMut(&mut Node),
622 mut each_ancestor: impl FnMut(&mut Node),
615 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
623 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
616 let mut children = root;
624 let mut children = root;
617 let mut components = path.components();
625 let mut components = path.components();
618 let mut component =
626 let mut component =
619 components.next().expect("expected at least one components");
627 components.next().expect("expected at least one components");
620 loop {
628 loop {
621 if let Some(child) = children
629 if let Some(child) = children
622 .make_mut(on_disk, unreachable_bytes)?
630 .make_mut(on_disk, unreachable_bytes)?
623 .get_mut(component)
631 .get_mut(component)
624 {
632 {
625 if let Some(next_component) = components.next() {
633 if let Some(next_component) = components.next() {
626 each_ancestor(child);
634 each_ancestor(child);
627 component = next_component;
635 component = next_component;
628 children = &mut child.children;
636 children = &mut child.children;
629 } else {
637 } else {
630 return Ok(Some(child));
638 return Ok(Some(child));
631 }
639 }
632 } else {
640 } else {
633 return Ok(None);
641 return Ok(None);
634 }
642 }
635 }
643 }
636 }
644 }
637
645
638 /// Get a mutable reference to the node at `path`, creating it if it does
646 /// Get a mutable reference to the node at `path`, creating it if it does
639 /// not exist.
647 /// not exist.
640 ///
648 ///
641 /// `each_ancestor` is a callback that is called for each ancestor node
649 /// `each_ancestor` is a callback that is called for each ancestor node
642 /// when descending the tree. It is used to keep the different counters
650 /// when descending the tree. It is used to keep the different counters
643 /// of the `DirstateMap` up-to-date.
651 /// of the `DirstateMap` up-to-date.
644 fn get_or_insert_node<'tree, 'path>(
652 fn get_or_insert_node<'tree, 'path>(
645 &'tree mut self,
653 &'tree mut self,
646 path: &'path HgPath,
654 path: &'path HgPath,
647 each_ancestor: impl FnMut(&mut Node),
655 each_ancestor: impl FnMut(&mut Node),
648 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
656 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
649 Self::get_or_insert_node_inner(
657 Self::get_or_insert_node_inner(
650 self.on_disk,
658 self.on_disk,
651 &mut self.unreachable_bytes,
659 &mut self.unreachable_bytes,
652 &mut self.root,
660 &mut self.root,
653 path,
661 path,
654 WithBasename::to_cow_owned,
662 WithBasename::to_cow_owned,
655 each_ancestor,
663 each_ancestor,
656 )
664 )
657 }
665 }
658
666
659 /// Lower-level version of `get_or_insert_node_inner`, which is used when
667 /// Lower-level version of `get_or_insert_node_inner`, which is used when
660 /// parsing disk data to remove allocations for new nodes.
668 /// parsing disk data to remove allocations for new nodes.
661 fn get_or_insert_node_inner<'tree, 'path>(
669 fn get_or_insert_node_inner<'tree, 'path>(
662 on_disk: &'on_disk [u8],
670 on_disk: &'on_disk [u8],
663 unreachable_bytes: &mut u32,
671 unreachable_bytes: &mut u32,
664 root: &'tree mut ChildNodes<'on_disk>,
672 root: &'tree mut ChildNodes<'on_disk>,
665 path: &'path HgPath,
673 path: &'path HgPath,
666 to_cow: impl Fn(
674 to_cow: impl Fn(
667 WithBasename<&'path HgPath>,
675 WithBasename<&'path HgPath>,
668 ) -> WithBasename<Cow<'on_disk, HgPath>>,
676 ) -> WithBasename<Cow<'on_disk, HgPath>>,
669 mut each_ancestor: impl FnMut(&mut Node),
677 mut each_ancestor: impl FnMut(&mut Node),
670 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
678 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
671 let mut child_nodes = root;
679 let mut child_nodes = root;
672 let mut inclusive_ancestor_paths =
680 let mut inclusive_ancestor_paths =
673 WithBasename::inclusive_ancestors_of(path);
681 WithBasename::inclusive_ancestors_of(path);
674 let mut ancestor_path = inclusive_ancestor_paths
682 let mut ancestor_path = inclusive_ancestor_paths
675 .next()
683 .next()
676 .expect("expected at least one inclusive ancestor");
684 .expect("expected at least one inclusive ancestor");
677 loop {
685 loop {
678 let (_, child_node) = child_nodes
686 let (_, child_node) = child_nodes
679 .make_mut(on_disk, unreachable_bytes)?
687 .make_mut(on_disk, unreachable_bytes)?
680 .raw_entry_mut()
688 .raw_entry_mut()
681 .from_key(ancestor_path.base_name())
689 .from_key(ancestor_path.base_name())
682 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
690 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
683 if let Some(next) = inclusive_ancestor_paths.next() {
691 if let Some(next) = inclusive_ancestor_paths.next() {
684 each_ancestor(child_node);
692 each_ancestor(child_node);
685 ancestor_path = next;
693 ancestor_path = next;
686 child_nodes = &mut child_node.children;
694 child_nodes = &mut child_node.children;
687 } else {
695 } else {
688 return Ok(child_node);
696 return Ok(child_node);
689 }
697 }
690 }
698 }
691 }
699 }
692
700
693 #[allow(clippy::too_many_arguments)]
701 #[allow(clippy::too_many_arguments)]
694 fn reset_state(
702 fn reset_state(
695 &mut self,
703 &mut self,
696 filename: &HgPath,
704 filename: &HgPath,
697 old_entry_opt: Option<DirstateEntry>,
705 old_entry_opt: Option<DirstateEntry>,
698 wc_tracked: bool,
706 wc_tracked: bool,
699 p1_tracked: bool,
707 p1_tracked: bool,
700 p2_info: bool,
708 p2_info: bool,
701 has_meaningful_mtime: bool,
709 has_meaningful_mtime: bool,
702 parent_file_data_opt: Option<ParentFileData>,
710 parent_file_data_opt: Option<ParentFileData>,
703 ) -> Result<(), DirstateError> {
711 ) -> Result<(), DirstateError> {
704 let (had_entry, was_tracked) = match old_entry_opt {
712 let (had_entry, was_tracked) = match old_entry_opt {
705 Some(old_entry) => (true, old_entry.tracked()),
713 Some(old_entry) => (true, old_entry.tracked()),
706 None => (false, false),
714 None => (false, false),
707 };
715 };
708 let node = self.get_or_insert_node(filename, |ancestor| {
716 let node = self.get_or_insert_node(filename, |ancestor| {
709 if !had_entry {
717 if !had_entry {
710 ancestor.descendants_with_entry_count += 1;
718 ancestor.descendants_with_entry_count += 1;
711 }
719 }
712 if was_tracked {
720 if was_tracked {
713 if !wc_tracked {
721 if !wc_tracked {
714 ancestor.tracked_descendants_count = ancestor
722 ancestor.tracked_descendants_count = ancestor
715 .tracked_descendants_count
723 .tracked_descendants_count
716 .checked_sub(1)
724 .checked_sub(1)
717 .expect("tracked count to be >= 0");
725 .expect("tracked count to be >= 0");
718 }
726 }
719 } else if wc_tracked {
727 } else if wc_tracked {
720 ancestor.tracked_descendants_count += 1;
728 ancestor.tracked_descendants_count += 1;
721 }
729 }
722 })?;
730 })?;
723
731
724 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
732 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
725 DirstateV2Data {
733 DirstateV2Data {
726 wc_tracked,
734 wc_tracked,
727 p1_tracked,
735 p1_tracked,
728 p2_info,
736 p2_info,
729 mode_size: parent_file_data.mode_size,
737 mode_size: parent_file_data.mode_size,
730 mtime: if has_meaningful_mtime {
738 mtime: if has_meaningful_mtime {
731 parent_file_data.mtime
739 parent_file_data.mtime
732 } else {
740 } else {
733 None
741 None
734 },
742 },
735 ..Default::default()
743 ..Default::default()
736 }
744 }
737 } else {
745 } else {
738 DirstateV2Data {
746 DirstateV2Data {
739 wc_tracked,
747 wc_tracked,
740 p1_tracked,
748 p1_tracked,
741 p2_info,
749 p2_info,
742 ..Default::default()
750 ..Default::default()
743 }
751 }
744 };
752 };
745 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
753 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
746 if !had_entry {
754 if !had_entry {
747 self.nodes_with_entry_count += 1;
755 self.nodes_with_entry_count += 1;
748 }
756 }
749 Ok(())
757 Ok(())
750 }
758 }
751
759
752 fn set_tracked(
760 fn set_tracked(
753 &mut self,
761 &mut self,
754 filename: &HgPath,
762 filename: &HgPath,
755 old_entry_opt: Option<DirstateEntry>,
763 old_entry_opt: Option<DirstateEntry>,
756 ) -> Result<bool, DirstateV2ParseError> {
764 ) -> Result<bool, DirstateV2ParseError> {
757 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
765 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
758 let had_entry = old_entry_opt.is_some();
766 let had_entry = old_entry_opt.is_some();
759 let tracked_count_increment = if was_tracked { 0 } else { 1 };
767 let tracked_count_increment = if was_tracked { 0 } else { 1 };
760 let mut new = false;
768 let mut new = false;
761
769
762 let node = self.get_or_insert_node(filename, |ancestor| {
770 let node = self.get_or_insert_node(filename, |ancestor| {
763 if !had_entry {
771 if !had_entry {
764 ancestor.descendants_with_entry_count += 1;
772 ancestor.descendants_with_entry_count += 1;
765 }
773 }
766
774
767 ancestor.tracked_descendants_count += tracked_count_increment;
775 ancestor.tracked_descendants_count += tracked_count_increment;
768 })?;
776 })?;
769 if let Some(old_entry) = old_entry_opt {
777 if let Some(old_entry) = old_entry_opt {
770 let mut e = old_entry;
778 let mut e = old_entry;
771 if e.tracked() {
779 if e.tracked() {
772 // XXX
780 // XXX
773 // This is probably overkill for more case, but we need this to
781 // This is probably overkill for more case, but we need this to
774 // fully replace the `normallookup` call with `set_tracked`
782 // fully replace the `normallookup` call with `set_tracked`
775 // one. Consider smoothing this in the future.
783 // one. Consider smoothing this in the future.
776 e.set_possibly_dirty();
784 e.set_possibly_dirty();
777 } else {
785 } else {
778 new = true;
786 new = true;
779 e.set_tracked();
787 e.set_tracked();
780 }
788 }
781 node.data = NodeData::Entry(e)
789 node.data = NodeData::Entry(e)
782 } else {
790 } else {
783 node.data = NodeData::Entry(DirstateEntry::new_tracked());
791 node.data = NodeData::Entry(DirstateEntry::new_tracked());
784 self.nodes_with_entry_count += 1;
792 self.nodes_with_entry_count += 1;
785 new = true;
793 new = true;
786 };
794 };
787 Ok(new)
795 Ok(new)
788 }
796 }
789
797
790 /// Set a node as untracked in the dirstate.
798 /// Set a node as untracked in the dirstate.
791 ///
799 ///
792 /// It is the responsibility of the caller to remove the copy source and/or
800 /// It is the responsibility of the caller to remove the copy source and/or
793 /// the entry itself if appropriate.
801 /// the entry itself if appropriate.
794 ///
802 ///
795 /// # Panics
803 /// # Panics
796 ///
804 ///
797 /// Panics if the node does not exist.
805 /// Panics if the node does not exist.
798 fn set_untracked(
806 fn set_untracked(
799 &mut self,
807 &mut self,
800 filename: &HgPath,
808 filename: &HgPath,
801 old_entry: DirstateEntry,
809 old_entry: DirstateEntry,
802 ) -> Result<(), DirstateV2ParseError> {
810 ) -> Result<(), DirstateV2ParseError> {
803 let node = self
811 let node = self
804 .get_node_mut(filename, |ancestor| {
812 .get_node_mut(filename, |ancestor| {
805 ancestor.tracked_descendants_count = ancestor
813 ancestor.tracked_descendants_count = ancestor
806 .tracked_descendants_count
814 .tracked_descendants_count
807 .checked_sub(1)
815 .checked_sub(1)
808 .expect("tracked_descendants_count should be >= 0");
816 .expect("tracked_descendants_count should be >= 0");
809 })?
817 })?
810 .expect("node should exist");
818 .expect("node should exist");
811 let mut new_entry = old_entry;
819 let mut new_entry = old_entry;
812 new_entry.set_untracked();
820 new_entry.set_untracked();
813 node.data = NodeData::Entry(new_entry);
821 node.data = NodeData::Entry(new_entry);
814 Ok(())
822 Ok(())
815 }
823 }
816
824
817 /// Set a node as clean in the dirstate.
825 /// Set a node as clean in the dirstate.
818 ///
826 ///
819 /// It is the responsibility of the caller to remove the copy source.
827 /// It is the responsibility of the caller to remove the copy source.
820 ///
828 ///
821 /// # Panics
829 /// # Panics
822 ///
830 ///
823 /// Panics if the node does not exist.
831 /// Panics if the node does not exist.
824 fn set_clean(
832 fn set_clean(
825 &mut self,
833 &mut self,
826 filename: &HgPath,
834 filename: &HgPath,
827 old_entry: DirstateEntry,
835 old_entry: DirstateEntry,
828 mode: u32,
836 mode: u32,
829 size: u32,
837 size: u32,
830 mtime: TruncatedTimestamp,
838 mtime: TruncatedTimestamp,
831 ) -> Result<(), DirstateError> {
839 ) -> Result<(), DirstateError> {
832 let node = self
840 let node = self
833 .get_node_mut(filename, |ancestor| {
841 .get_node_mut(filename, |ancestor| {
834 if !old_entry.tracked() {
842 if !old_entry.tracked() {
835 ancestor.tracked_descendants_count += 1;
843 ancestor.tracked_descendants_count += 1;
836 }
844 }
837 })?
845 })?
838 .expect("node should exist");
846 .expect("node should exist");
839 let mut new_entry = old_entry;
847 let mut new_entry = old_entry;
840 new_entry.set_clean(mode, size, mtime);
848 new_entry.set_clean(mode, size, mtime);
841 node.data = NodeData::Entry(new_entry);
849 node.data = NodeData::Entry(new_entry);
842 Ok(())
850 Ok(())
843 }
851 }
844
852
845 /// Set a node as possibly dirty in the dirstate.
853 /// Set a node as possibly dirty in the dirstate.
846 ///
854 ///
847 /// # Panics
855 /// # Panics
848 ///
856 ///
849 /// Panics if the node does not exist.
857 /// Panics if the node does not exist.
850 fn set_possibly_dirty(
858 fn set_possibly_dirty(
851 &mut self,
859 &mut self,
852 filename: &HgPath,
860 filename: &HgPath,
853 ) -> Result<(), DirstateError> {
861 ) -> Result<(), DirstateError> {
854 let node = self
862 let node = self
855 .get_node_mut(filename, |_ancestor| {})?
863 .get_node_mut(filename, |_ancestor| {})?
856 .expect("node should exist");
864 .expect("node should exist");
857 let entry = node.data.as_entry_mut().expect("entry should exist");
865 let entry = node.data.as_entry_mut().expect("entry should exist");
858 entry.set_possibly_dirty();
866 entry.set_possibly_dirty();
859 node.data = NodeData::Entry(*entry);
867 node.data = NodeData::Entry(*entry);
860 Ok(())
868 Ok(())
861 }
869 }
862
870
863 /// Clears the cached mtime for the (potential) folder at `path`.
871 /// Clears the cached mtime for the (potential) folder at `path`.
864 pub(super) fn clear_cached_mtime(
872 pub(super) fn clear_cached_mtime(
865 &mut self,
873 &mut self,
866 path: &HgPath,
874 path: &HgPath,
867 ) -> Result<(), DirstateV2ParseError> {
875 ) -> Result<(), DirstateV2ParseError> {
868 let node = match self.get_node_mut(path, |_ancestor| {})? {
876 let node = match self.get_node_mut(path, |_ancestor| {})? {
869 Some(node) => node,
877 Some(node) => node,
870 None => return Ok(()),
878 None => return Ok(()),
871 };
879 };
872 if let NodeData::CachedDirectory { .. } = &node.data {
880 if let NodeData::CachedDirectory { .. } = &node.data {
873 node.data = NodeData::None
881 node.data = NodeData::None
874 }
882 }
875 Ok(())
883 Ok(())
876 }
884 }
877
885
878 /// Sets the cached mtime for the (potential) folder at `path`.
886 /// Sets the cached mtime for the (potential) folder at `path`.
879 pub(super) fn set_cached_mtime(
887 pub(super) fn set_cached_mtime(
880 &mut self,
888 &mut self,
881 path: &HgPath,
889 path: &HgPath,
882 mtime: TruncatedTimestamp,
890 mtime: TruncatedTimestamp,
883 ) -> Result<(), DirstateV2ParseError> {
891 ) -> Result<(), DirstateV2ParseError> {
884 let node = match self.get_node_mut(path, |_ancestor| {})? {
892 let node = match self.get_node_mut(path, |_ancestor| {})? {
885 Some(node) => node,
893 Some(node) => node,
886 None => return Ok(()),
894 None => return Ok(()),
887 };
895 };
888 match &node.data {
896 match &node.data {
889 NodeData::Entry(_) => {} // Don’t overwrite an entry
897 NodeData::Entry(_) => {} // Don’t overwrite an entry
890 NodeData::CachedDirectory { .. } | NodeData::None => {
898 NodeData::CachedDirectory { .. } | NodeData::None => {
891 node.data = NodeData::CachedDirectory { mtime }
899 node.data = NodeData::CachedDirectory { mtime }
892 }
900 }
893 }
901 }
894 Ok(())
902 Ok(())
895 }
903 }
896
904
897 fn iter_nodes<'tree>(
905 fn iter_nodes<'tree>(
898 &'tree self,
906 &'tree self,
899 ) -> impl Iterator<
907 ) -> impl Iterator<
900 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
908 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
901 > + 'tree {
909 > + 'tree {
902 // Depth first tree traversal.
910 // Depth first tree traversal.
903 //
911 //
904 // If we could afford internal iteration and recursion,
912 // If we could afford internal iteration and recursion,
905 // this would look like:
913 // this would look like:
906 //
914 //
907 // ```
915 // ```
908 // fn traverse_children(
916 // fn traverse_children(
909 // children: &ChildNodes,
917 // children: &ChildNodes,
910 // each: &mut impl FnMut(&Node),
918 // each: &mut impl FnMut(&Node),
911 // ) {
919 // ) {
912 // for child in children.values() {
920 // for child in children.values() {
913 // traverse_children(&child.children, each);
921 // traverse_children(&child.children, each);
914 // each(child);
922 // each(child);
915 // }
923 // }
916 // }
924 // }
917 // ```
925 // ```
918 //
926 //
919 // However we want an external iterator and therefore can’t use the
927 // However we want an external iterator and therefore can’t use the
920 // call stack. Use an explicit stack instead:
928 // call stack. Use an explicit stack instead:
921 let mut stack = Vec::new();
929 let mut stack = Vec::new();
922 let mut iter = self.root.as_ref().iter();
930 let mut iter = self.root.as_ref().iter();
923 std::iter::from_fn(move || {
931 std::iter::from_fn(move || {
924 while let Some(child_node) = iter.next() {
932 while let Some(child_node) = iter.next() {
925 let children = match child_node.children(self.on_disk) {
933 let children = match child_node.children(self.on_disk) {
926 Ok(children) => children,
934 Ok(children) => children,
927 Err(error) => return Some(Err(error)),
935 Err(error) => return Some(Err(error)),
928 };
936 };
929 // Pseudo-recursion
937 // Pseudo-recursion
930 let new_iter = children.iter();
938 let new_iter = children.iter();
931 let old_iter = std::mem::replace(&mut iter, new_iter);
939 let old_iter = std::mem::replace(&mut iter, new_iter);
932 stack.push((child_node, old_iter));
940 stack.push((child_node, old_iter));
933 }
941 }
934 // Found the end of a `children.iter()` iterator.
942 // Found the end of a `children.iter()` iterator.
935 if let Some((child_node, next_iter)) = stack.pop() {
943 if let Some((child_node, next_iter)) = stack.pop() {
936 // "Return" from pseudo-recursion by restoring state from the
944 // "Return" from pseudo-recursion by restoring state from the
937 // explicit stack
945 // explicit stack
938 iter = next_iter;
946 iter = next_iter;
939
947
940 Some(Ok(child_node))
948 Some(Ok(child_node))
941 } else {
949 } else {
942 // Reached the bottom of the stack, we’re done
950 // Reached the bottom of the stack, we’re done
943 None
951 None
944 }
952 }
945 })
953 })
946 }
954 }
947
955
948 fn count_dropped_path(unreachable_bytes: &mut u32, path: Cow<HgPath>) {
956 fn count_dropped_path(unreachable_bytes: &mut u32, path: Cow<HgPath>) {
949 if let Cow::Borrowed(path) = path {
957 if let Cow::Borrowed(path) = path {
950 *unreachable_bytes += path.len() as u32
958 *unreachable_bytes += path.len() as u32
951 }
959 }
952 }
960 }
953
961
954 pub(crate) fn set_write_mode(&mut self, write_mode: DirstateMapWriteMode) {
962 pub(crate) fn set_write_mode(&mut self, write_mode: DirstateMapWriteMode) {
955 self.write_mode = write_mode;
963 self.write_mode = write_mode;
956 }
964 }
957 }
965 }
958
966
959 type DebugDirstateTuple<'a> = (&'a HgPath, (u8, i32, i32, i32));
967 type DebugDirstateTuple<'a> = (&'a HgPath, (u8, i32, i32, i32));
960
968
961 impl OwningDirstateMap {
969 impl OwningDirstateMap {
962 pub fn clear(&mut self) {
970 pub fn clear(&mut self) {
963 self.with_dmap_mut(|map| {
971 self.with_dmap_mut(|map| {
964 map.root = Default::default();
972 map.root = Default::default();
965 map.nodes_with_entry_count = 0;
973 map.nodes_with_entry_count = 0;
966 map.nodes_with_copy_source_count = 0;
974 map.nodes_with_copy_source_count = 0;
967 map.unreachable_bytes = map.on_disk.len() as u32;
975 map.unreachable_bytes = map.on_disk.len() as u32;
968 });
976 });
969 }
977 }
970
978
971 pub fn set_tracked(
979 pub fn set_tracked(
972 &mut self,
980 &mut self,
973 filename: &HgPath,
981 filename: &HgPath,
974 ) -> Result<bool, DirstateV2ParseError> {
982 ) -> Result<bool, DirstateV2ParseError> {
975 let old_entry_opt = self.get(filename)?;
983 let old_entry_opt = self.get(filename)?;
976 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
984 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
977 }
985 }
978
986
979 pub fn set_untracked(
987 pub fn set_untracked(
980 &mut self,
988 &mut self,
981 filename: &HgPath,
989 filename: &HgPath,
982 ) -> Result<bool, DirstateError> {
990 ) -> Result<bool, DirstateError> {
983 let old_entry_opt = self.get(filename)?;
991 let old_entry_opt = self.get(filename)?;
984 match old_entry_opt {
992 match old_entry_opt {
985 None => Ok(false),
993 None => Ok(false),
986 Some(old_entry) => {
994 Some(old_entry) => {
987 if !old_entry.tracked() {
995 if !old_entry.tracked() {
988 // `DirstateMap::set_untracked` is not a noop if
996 // `DirstateMap::set_untracked` is not a noop if
989 // already not tracked as it will decrement the
997 // already not tracked as it will decrement the
990 // tracked counters while going down.
998 // tracked counters while going down.
991 return Ok(true);
999 return Ok(true);
992 }
1000 }
993 if old_entry.added() {
1001 if old_entry.added() {
994 // Untracking an "added" entry will just result in a
1002 // Untracking an "added" entry will just result in a
995 // worthless entry (and other parts of the code will
1003 // worthless entry (and other parts of the code will
996 // complain about it), just drop it entirely.
1004 // complain about it), just drop it entirely.
997 self.drop_entry_and_copy_source(filename)?;
1005 self.drop_entry_and_copy_source(filename)?;
998 return Ok(true);
1006 return Ok(true);
999 }
1007 }
1000 if !old_entry.p2_info() {
1008 if !old_entry.p2_info() {
1001 self.copy_map_remove(filename)?;
1009 self.copy_map_remove(filename)?;
1002 }
1010 }
1003
1011
1004 self.with_dmap_mut(|map| {
1012 self.with_dmap_mut(|map| {
1005 map.set_untracked(filename, old_entry)?;
1013 map.set_untracked(filename, old_entry)?;
1006 Ok(true)
1014 Ok(true)
1007 })
1015 })
1008 }
1016 }
1009 }
1017 }
1010 }
1018 }
1011
1019
1012 pub fn set_clean(
1020 pub fn set_clean(
1013 &mut self,
1021 &mut self,
1014 filename: &HgPath,
1022 filename: &HgPath,
1015 mode: u32,
1023 mode: u32,
1016 size: u32,
1024 size: u32,
1017 mtime: TruncatedTimestamp,
1025 mtime: TruncatedTimestamp,
1018 ) -> Result<(), DirstateError> {
1026 ) -> Result<(), DirstateError> {
1019 let old_entry = match self.get(filename)? {
1027 let old_entry = match self.get(filename)? {
1020 None => {
1028 None => {
1021 return Err(
1029 return Err(
1022 DirstateMapError::PathNotFound(filename.into()).into()
1030 DirstateMapError::PathNotFound(filename.into()).into()
1023 )
1031 )
1024 }
1032 }
1025 Some(e) => e,
1033 Some(e) => e,
1026 };
1034 };
1027 self.copy_map_remove(filename)?;
1035 self.copy_map_remove(filename)?;
1028 self.with_dmap_mut(|map| {
1036 self.with_dmap_mut(|map| {
1029 map.set_clean(filename, old_entry, mode, size, mtime)
1037 map.set_clean(filename, old_entry, mode, size, mtime)
1030 })
1038 })
1031 }
1039 }
1032
1040
1033 pub fn set_possibly_dirty(
1041 pub fn set_possibly_dirty(
1034 &mut self,
1042 &mut self,
1035 filename: &HgPath,
1043 filename: &HgPath,
1036 ) -> Result<(), DirstateError> {
1044 ) -> Result<(), DirstateError> {
1037 if self.get(filename)?.is_none() {
1045 if self.get(filename)?.is_none() {
1038 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1046 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1039 }
1047 }
1040 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1048 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1041 }
1049 }
1042
1050
1043 pub fn reset_state(
1051 pub fn reset_state(
1044 &mut self,
1052 &mut self,
1045 filename: &HgPath,
1053 filename: &HgPath,
1046 wc_tracked: bool,
1054 wc_tracked: bool,
1047 p1_tracked: bool,
1055 p1_tracked: bool,
1048 p2_info: bool,
1056 p2_info: bool,
1049 has_meaningful_mtime: bool,
1057 has_meaningful_mtime: bool,
1050 parent_file_data_opt: Option<ParentFileData>,
1058 parent_file_data_opt: Option<ParentFileData>,
1051 ) -> Result<(), DirstateError> {
1059 ) -> Result<(), DirstateError> {
1052 if !(p1_tracked || p2_info || wc_tracked) {
1060 if !(p1_tracked || p2_info || wc_tracked) {
1053 self.drop_entry_and_copy_source(filename)?;
1061 self.drop_entry_and_copy_source(filename)?;
1054 return Ok(());
1062 return Ok(());
1055 }
1063 }
1056 self.copy_map_remove(filename)?;
1064 self.copy_map_remove(filename)?;
1057 let old_entry_opt = self.get(filename)?;
1065 let old_entry_opt = self.get(filename)?;
1058 self.with_dmap_mut(|map| {
1066 self.with_dmap_mut(|map| {
1059 map.reset_state(
1067 map.reset_state(
1060 filename,
1068 filename,
1061 old_entry_opt,
1069 old_entry_opt,
1062 wc_tracked,
1070 wc_tracked,
1063 p1_tracked,
1071 p1_tracked,
1064 p2_info,
1072 p2_info,
1065 has_meaningful_mtime,
1073 has_meaningful_mtime,
1066 parent_file_data_opt,
1074 parent_file_data_opt,
1067 )
1075 )
1068 })
1076 })
1069 }
1077 }
1070
1078
1071 pub fn drop_entry_and_copy_source(
1079 pub fn drop_entry_and_copy_source(
1072 &mut self,
1080 &mut self,
1073 filename: &HgPath,
1081 filename: &HgPath,
1074 ) -> Result<(), DirstateError> {
1082 ) -> Result<(), DirstateError> {
1075 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1083 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1076 struct Dropped {
1084 struct Dropped {
1077 was_tracked: bool,
1085 was_tracked: bool,
1078 had_entry: bool,
1086 had_entry: bool,
1079 had_copy_source: bool,
1087 had_copy_source: bool,
1080 }
1088 }
1081
1089
1082 /// If this returns `Ok(Some((dropped, removed)))`, then
1090 /// If this returns `Ok(Some((dropped, removed)))`, then
1083 ///
1091 ///
1084 /// * `dropped` is about the leaf node that was at `filename`
1092 /// * `dropped` is about the leaf node that was at `filename`
1085 /// * `removed` is whether this particular level of recursion just
1093 /// * `removed` is whether this particular level of recursion just
1086 /// removed a node in `nodes`.
1094 /// removed a node in `nodes`.
1087 fn recur<'on_disk>(
1095 fn recur<'on_disk>(
1088 on_disk: &'on_disk [u8],
1096 on_disk: &'on_disk [u8],
1089 unreachable_bytes: &mut u32,
1097 unreachable_bytes: &mut u32,
1090 nodes: &mut ChildNodes<'on_disk>,
1098 nodes: &mut ChildNodes<'on_disk>,
1091 path: &HgPath,
1099 path: &HgPath,
1092 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1100 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1093 let (first_path_component, rest_of_path) =
1101 let (first_path_component, rest_of_path) =
1094 path.split_first_component();
1102 path.split_first_component();
1095 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1103 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1096 let node = if let Some(node) = nodes.get_mut(first_path_component)
1104 let node = if let Some(node) = nodes.get_mut(first_path_component)
1097 {
1105 {
1098 node
1106 node
1099 } else {
1107 } else {
1100 return Ok(None);
1108 return Ok(None);
1101 };
1109 };
1102 let dropped;
1110 let dropped;
1103 if let Some(rest) = rest_of_path {
1111 if let Some(rest) = rest_of_path {
1104 if let Some((d, removed)) = recur(
1112 if let Some((d, removed)) = recur(
1105 on_disk,
1113 on_disk,
1106 unreachable_bytes,
1114 unreachable_bytes,
1107 &mut node.children,
1115 &mut node.children,
1108 rest,
1116 rest,
1109 )? {
1117 )? {
1110 dropped = d;
1118 dropped = d;
1111 if dropped.had_entry {
1119 if dropped.had_entry {
1112 node.descendants_with_entry_count = node
1120 node.descendants_with_entry_count = node
1113 .descendants_with_entry_count
1121 .descendants_with_entry_count
1114 .checked_sub(1)
1122 .checked_sub(1)
1115 .expect(
1123 .expect(
1116 "descendants_with_entry_count should be >= 0",
1124 "descendants_with_entry_count should be >= 0",
1117 );
1125 );
1118 }
1126 }
1119 if dropped.was_tracked {
1127 if dropped.was_tracked {
1120 node.tracked_descendants_count = node
1128 node.tracked_descendants_count = node
1121 .tracked_descendants_count
1129 .tracked_descendants_count
1122 .checked_sub(1)
1130 .checked_sub(1)
1123 .expect(
1131 .expect(
1124 "tracked_descendants_count should be >= 0",
1132 "tracked_descendants_count should be >= 0",
1125 );
1133 );
1126 }
1134 }
1127
1135
1128 // Directory caches must be invalidated when removing a
1136 // Directory caches must be invalidated when removing a
1129 // child node
1137 // child node
1130 if removed {
1138 if removed {
1131 if let NodeData::CachedDirectory { .. } = &node.data {
1139 if let NodeData::CachedDirectory { .. } = &node.data {
1132 node.data = NodeData::None
1140 node.data = NodeData::None
1133 }
1141 }
1134 }
1142 }
1135 } else {
1143 } else {
1136 return Ok(None);
1144 return Ok(None);
1137 }
1145 }
1138 } else {
1146 } else {
1139 let entry = node.data.as_entry();
1147 let entry = node.data.as_entry();
1140 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1148 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1141 let had_entry = entry.is_some();
1149 let had_entry = entry.is_some();
1142 if had_entry {
1150 if had_entry {
1143 node.data = NodeData::None
1151 node.data = NodeData::None
1144 }
1152 }
1145 let mut had_copy_source = false;
1153 let mut had_copy_source = false;
1146 if let Some(source) = &node.copy_source {
1154 if let Some(source) = &node.copy_source {
1147 DirstateMap::count_dropped_path(
1155 DirstateMap::count_dropped_path(
1148 unreachable_bytes,
1156 unreachable_bytes,
1149 Cow::Borrowed(source),
1157 Cow::Borrowed(source),
1150 );
1158 );
1151 had_copy_source = true;
1159 had_copy_source = true;
1152 node.copy_source = None
1160 node.copy_source = None
1153 }
1161 }
1154 dropped = Dropped {
1162 dropped = Dropped {
1155 was_tracked,
1163 was_tracked,
1156 had_entry,
1164 had_entry,
1157 had_copy_source,
1165 had_copy_source,
1158 };
1166 };
1159 }
1167 }
1160 // After recursion, for both leaf (rest_of_path is None) nodes and
1168 // After recursion, for both leaf (rest_of_path is None) nodes and
1161 // parent nodes, remove a node if it just became empty.
1169 // parent nodes, remove a node if it just became empty.
1162 let remove = !node.data.has_entry()
1170 let remove = !node.data.has_entry()
1163 && node.copy_source.is_none()
1171 && node.copy_source.is_none()
1164 && node.children.is_empty();
1172 && node.children.is_empty();
1165 if remove {
1173 if remove {
1166 let (key, _) =
1174 let (key, _) =
1167 nodes.remove_entry(first_path_component).unwrap();
1175 nodes.remove_entry(first_path_component).unwrap();
1168 DirstateMap::count_dropped_path(
1176 DirstateMap::count_dropped_path(
1169 unreachable_bytes,
1177 unreachable_bytes,
1170 Cow::Borrowed(key.full_path()),
1178 Cow::Borrowed(key.full_path()),
1171 )
1179 )
1172 }
1180 }
1173 Ok(Some((dropped, remove)))
1181 Ok(Some((dropped, remove)))
1174 }
1182 }
1175
1183
1176 self.with_dmap_mut(|map| {
1184 self.with_dmap_mut(|map| {
1177 if let Some((dropped, _removed)) = recur(
1185 if let Some((dropped, _removed)) = recur(
1178 map.on_disk,
1186 map.on_disk,
1179 &mut map.unreachable_bytes,
1187 &mut map.unreachable_bytes,
1180 &mut map.root,
1188 &mut map.root,
1181 filename,
1189 filename,
1182 )? {
1190 )? {
1183 if dropped.had_entry {
1191 if dropped.had_entry {
1184 map.nodes_with_entry_count = map
1192 map.nodes_with_entry_count = map
1185 .nodes_with_entry_count
1193 .nodes_with_entry_count
1186 .checked_sub(1)
1194 .checked_sub(1)
1187 .expect("nodes_with_entry_count should be >= 0");
1195 .expect("nodes_with_entry_count should be >= 0");
1188 }
1196 }
1189 if dropped.had_copy_source {
1197 if dropped.had_copy_source {
1190 map.nodes_with_copy_source_count = map
1198 map.nodes_with_copy_source_count = map
1191 .nodes_with_copy_source_count
1199 .nodes_with_copy_source_count
1192 .checked_sub(1)
1200 .checked_sub(1)
1193 .expect("nodes_with_copy_source_count should be >= 0");
1201 .expect("nodes_with_copy_source_count should be >= 0");
1194 }
1202 }
1195 } else {
1203 } else {
1196 debug_assert!(!was_tracked);
1204 debug_assert!(!was_tracked);
1197 }
1205 }
1198 Ok(())
1206 Ok(())
1199 })
1207 })
1200 }
1208 }
1201
1209
1202 pub fn has_tracked_dir(
1210 pub fn has_tracked_dir(
1203 &mut self,
1211 &mut self,
1204 directory: &HgPath,
1212 directory: &HgPath,
1205 ) -> Result<bool, DirstateError> {
1213 ) -> Result<bool, DirstateError> {
1206 self.with_dmap_mut(|map| {
1214 self.with_dmap_mut(|map| {
1207 if let Some(node) = map.get_node(directory)? {
1215 if let Some(node) = map.get_node(directory)? {
1208 // A node without a `DirstateEntry` was created to hold child
1216 // A node without a `DirstateEntry` was created to hold child
1209 // nodes, and is therefore a directory.
1217 // nodes, and is therefore a directory.
1210 let is_dir = node.entry()?.is_none();
1218 let is_dir = node.entry()?.is_none();
1211 Ok(is_dir && node.tracked_descendants_count() > 0)
1219 Ok(is_dir && node.tracked_descendants_count() > 0)
1212 } else {
1220 } else {
1213 Ok(false)
1221 Ok(false)
1214 }
1222 }
1215 })
1223 })
1216 }
1224 }
1217
1225
1218 pub fn has_dir(
1226 pub fn has_dir(
1219 &mut self,
1227 &mut self,
1220 directory: &HgPath,
1228 directory: &HgPath,
1221 ) -> Result<bool, DirstateError> {
1229 ) -> Result<bool, DirstateError> {
1222 self.with_dmap_mut(|map| {
1230 self.with_dmap_mut(|map| {
1223 if let Some(node) = map.get_node(directory)? {
1231 if let Some(node) = map.get_node(directory)? {
1224 // A node without a `DirstateEntry` was created to hold child
1232 // A node without a `DirstateEntry` was created to hold child
1225 // nodes, and is therefore a directory.
1233 // nodes, and is therefore a directory.
1226 let is_dir = node.entry()?.is_none();
1234 let is_dir = node.entry()?.is_none();
1227 Ok(is_dir && node.descendants_with_entry_count() > 0)
1235 Ok(is_dir && node.descendants_with_entry_count() > 0)
1228 } else {
1236 } else {
1229 Ok(false)
1237 Ok(false)
1230 }
1238 }
1231 })
1239 })
1232 }
1240 }
1233
1241
1234 #[logging_timer::time("trace")]
1242 #[logging_timer::time("trace")]
1235 pub fn pack_v1(
1243 pub fn pack_v1(
1236 &self,
1244 &self,
1237 parents: DirstateParents,
1245 parents: DirstateParents,
1238 ) -> Result<Vec<u8>, DirstateError> {
1246 ) -> Result<Vec<u8>, DirstateError> {
1239 let map = self.get_map();
1247 let map = self.get_map();
1240 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1248 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1241 // reallocations
1249 // reallocations
1242 let mut size = parents.as_bytes().len();
1250 let mut size = parents.as_bytes().len();
1243 for node in map.iter_nodes() {
1251 for node in map.iter_nodes() {
1244 let node = node?;
1252 let node = node?;
1245 if node.entry()?.is_some() {
1253 if node.entry()?.is_some() {
1246 size += packed_entry_size(
1254 size += packed_entry_size(
1247 node.full_path(map.on_disk)?,
1255 node.full_path(map.on_disk)?,
1248 node.copy_source(map.on_disk)?,
1256 node.copy_source(map.on_disk)?,
1249 );
1257 );
1250 }
1258 }
1251 }
1259 }
1252
1260
1253 let mut packed = Vec::with_capacity(size);
1261 let mut packed = Vec::with_capacity(size);
1254 packed.extend(parents.as_bytes());
1262 packed.extend(parents.as_bytes());
1255
1263
1256 for node in map.iter_nodes() {
1264 for node in map.iter_nodes() {
1257 let node = node?;
1265 let node = node?;
1258 if let Some(entry) = node.entry()? {
1266 if let Some(entry) = node.entry()? {
1259 pack_entry(
1267 pack_entry(
1260 node.full_path(map.on_disk)?,
1268 node.full_path(map.on_disk)?,
1261 &entry,
1269 &entry,
1262 node.copy_source(map.on_disk)?,
1270 node.copy_source(map.on_disk)?,
1263 &mut packed,
1271 &mut packed,
1264 );
1272 );
1265 }
1273 }
1266 }
1274 }
1267 Ok(packed)
1275 Ok(packed)
1268 }
1276 }
1269
1277
1270 /// Returns new data and metadata together with whether that data should be
1278 /// Returns new data and metadata together with whether that data should be
1271 /// appended to the existing data file whose content is at
1279 /// appended to the existing data file whose content is at
1272 /// `map.on_disk` (true), instead of written to a new data file
1280 /// `map.on_disk` (true), instead of written to a new data file
1273 /// (false), and the previous size of data on disk.
1281 /// (false), and the previous size of data on disk.
1274 #[logging_timer::time("trace")]
1282 #[logging_timer::time("trace")]
1275 pub fn pack_v2(
1283 pub fn pack_v2(
1276 &self,
1284 &self,
1277 write_mode: DirstateMapWriteMode,
1285 write_mode: DirstateMapWriteMode,
1278 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1286 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool, usize), DirstateError>
1279 {
1287 {
1280 let map = self.get_map();
1288 let map = self.get_map();
1281 on_disk::write(map, write_mode)
1289 on_disk::write(map, write_mode)
1282 }
1290 }
1283
1291
1284 /// `callback` allows the caller to process and do something with the
1292 /// `callback` allows the caller to process and do something with the
1285 /// results of the status. This is needed to do so efficiently (i.e.
1293 /// results of the status. This is needed to do so efficiently (i.e.
1286 /// without cloning the `DirstateStatus` object with its paths) because
1294 /// without cloning the `DirstateStatus` object with its paths) because
1287 /// we need to borrow from `Self`.
1295 /// we need to borrow from `Self`.
1288 pub fn with_status<R>(
1296 pub fn with_status<R>(
1289 &mut self,
1297 &mut self,
1290 matcher: &(dyn Matcher + Sync),
1298 matcher: &(dyn Matcher + Sync),
1291 root_dir: PathBuf,
1299 root_dir: PathBuf,
1292 ignore_files: Vec<PathBuf>,
1300 ignore_files: Vec<PathBuf>,
1293 options: StatusOptions,
1301 options: StatusOptions,
1294 callback: impl for<'r> FnOnce(
1302 callback: impl for<'r> FnOnce(
1295 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1303 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1296 ) -> R,
1304 ) -> R,
1297 ) -> R {
1305 ) -> R {
1298 self.with_dmap_mut(|map| {
1306 self.with_dmap_mut(|map| {
1299 callback(super::status::status(
1307 callback(super::status::status(
1300 map,
1308 map,
1301 matcher,
1309 matcher,
1302 root_dir,
1310 root_dir,
1303 ignore_files,
1311 ignore_files,
1304 options,
1312 options,
1305 ))
1313 ))
1306 })
1314 })
1307 }
1315 }
1308
1316
1309 pub fn copy_map_len(&self) -> usize {
1317 pub fn copy_map_len(&self) -> usize {
1310 let map = self.get_map();
1318 let map = self.get_map();
1311 map.nodes_with_copy_source_count as usize
1319 map.nodes_with_copy_source_count as usize
1312 }
1320 }
1313
1321
1314 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1322 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1315 let map = self.get_map();
1323 let map = self.get_map();
1316 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1324 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1317 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1325 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1318 Some((node.full_path(map.on_disk)?, source))
1326 Some((node.full_path(map.on_disk)?, source))
1319 } else {
1327 } else {
1320 None
1328 None
1321 })
1329 })
1322 }))
1330 }))
1323 }
1331 }
1324
1332
1325 pub fn copy_map_contains_key(
1333 pub fn copy_map_contains_key(
1326 &self,
1334 &self,
1327 key: &HgPath,
1335 key: &HgPath,
1328 ) -> Result<bool, DirstateV2ParseError> {
1336 ) -> Result<bool, DirstateV2ParseError> {
1329 let map = self.get_map();
1337 let map = self.get_map();
1330 Ok(if let Some(node) = map.get_node(key)? {
1338 Ok(if let Some(node) = map.get_node(key)? {
1331 node.has_copy_source()
1339 node.has_copy_source()
1332 } else {
1340 } else {
1333 false
1341 false
1334 })
1342 })
1335 }
1343 }
1336
1344
1337 pub fn copy_map_get(
1345 pub fn copy_map_get(
1338 &self,
1346 &self,
1339 key: &HgPath,
1347 key: &HgPath,
1340 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1348 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1341 let map = self.get_map();
1349 let map = self.get_map();
1342 if let Some(node) = map.get_node(key)? {
1350 if let Some(node) = map.get_node(key)? {
1343 if let Some(source) = node.copy_source(map.on_disk)? {
1351 if let Some(source) = node.copy_source(map.on_disk)? {
1344 return Ok(Some(source));
1352 return Ok(Some(source));
1345 }
1353 }
1346 }
1354 }
1347 Ok(None)
1355 Ok(None)
1348 }
1356 }
1349
1357
1350 pub fn copy_map_remove(
1358 pub fn copy_map_remove(
1351 &mut self,
1359 &mut self,
1352 key: &HgPath,
1360 key: &HgPath,
1353 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1361 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1354 self.with_dmap_mut(|map| {
1362 self.with_dmap_mut(|map| {
1355 let count = &mut map.nodes_with_copy_source_count;
1363 let count = &mut map.nodes_with_copy_source_count;
1356 let unreachable_bytes = &mut map.unreachable_bytes;
1364 let unreachable_bytes = &mut map.unreachable_bytes;
1357 Ok(DirstateMap::get_node_mut_inner(
1365 Ok(DirstateMap::get_node_mut_inner(
1358 map.on_disk,
1366 map.on_disk,
1359 unreachable_bytes,
1367 unreachable_bytes,
1360 &mut map.root,
1368 &mut map.root,
1361 key,
1369 key,
1362 |_ancestor| {},
1370 |_ancestor| {},
1363 )?
1371 )?
1364 .and_then(|node| {
1372 .and_then(|node| {
1365 if let Some(source) = &node.copy_source {
1373 if let Some(source) = &node.copy_source {
1366 *count = count
1374 *count = count
1367 .checked_sub(1)
1375 .checked_sub(1)
1368 .expect("nodes_with_copy_source_count should be >= 0");
1376 .expect("nodes_with_copy_source_count should be >= 0");
1369 DirstateMap::count_dropped_path(
1377 DirstateMap::count_dropped_path(
1370 unreachable_bytes,
1378 unreachable_bytes,
1371 Cow::Borrowed(source),
1379 Cow::Borrowed(source),
1372 );
1380 );
1373 }
1381 }
1374 node.copy_source.take().map(Cow::into_owned)
1382 node.copy_source.take().map(Cow::into_owned)
1375 }))
1383 }))
1376 })
1384 })
1377 }
1385 }
1378
1386
1379 pub fn copy_map_insert(
1387 pub fn copy_map_insert(
1380 &mut self,
1388 &mut self,
1381 key: &HgPath,
1389 key: &HgPath,
1382 value: &HgPath,
1390 value: &HgPath,
1383 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1391 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1384 self.with_dmap_mut(|map| {
1392 self.with_dmap_mut(|map| {
1385 let node = map.get_or_insert_node(key, |_ancestor| {})?;
1393 let node = map.get_or_insert_node(key, |_ancestor| {})?;
1386 let had_copy_source = node.copy_source.is_none();
1394 let had_copy_source = node.copy_source.is_none();
1387 let old = node
1395 let old = node
1388 .copy_source
1396 .copy_source
1389 .replace(value.to_owned().into())
1397 .replace(value.to_owned().into())
1390 .map(Cow::into_owned);
1398 .map(Cow::into_owned);
1391 if had_copy_source {
1399 if had_copy_source {
1392 map.nodes_with_copy_source_count += 1
1400 map.nodes_with_copy_source_count += 1
1393 }
1401 }
1394 Ok(old)
1402 Ok(old)
1395 })
1403 })
1396 }
1404 }
1397
1405
1398 pub fn len(&self) -> usize {
1406 pub fn len(&self) -> usize {
1399 let map = self.get_map();
1407 let map = self.get_map();
1400 map.nodes_with_entry_count as usize
1408 map.nodes_with_entry_count as usize
1401 }
1409 }
1402
1410
1403 pub fn is_empty(&self) -> bool {
1411 pub fn is_empty(&self) -> bool {
1404 self.len() == 0
1412 self.len() == 0
1405 }
1413 }
1406
1414
1407 pub fn contains_key(
1415 pub fn contains_key(
1408 &self,
1416 &self,
1409 key: &HgPath,
1417 key: &HgPath,
1410 ) -> Result<bool, DirstateV2ParseError> {
1418 ) -> Result<bool, DirstateV2ParseError> {
1411 Ok(self.get(key)?.is_some())
1419 Ok(self.get(key)?.is_some())
1412 }
1420 }
1413
1421
1414 pub fn get(
1422 pub fn get(
1415 &self,
1423 &self,
1416 key: &HgPath,
1424 key: &HgPath,
1417 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1425 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1418 let map = self.get_map();
1426 let map = self.get_map();
1419 Ok(if let Some(node) = map.get_node(key)? {
1427 Ok(if let Some(node) = map.get_node(key)? {
1420 node.entry()?
1428 node.entry()?
1421 } else {
1429 } else {
1422 None
1430 None
1423 })
1431 })
1424 }
1432 }
1425
1433
1426 pub fn iter(&self) -> StateMapIter<'_> {
1434 pub fn iter(&self) -> StateMapIter<'_> {
1427 let map = self.get_map();
1435 let map = self.get_map();
1428 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1436 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1429 Ok(if let Some(entry) = node.entry()? {
1437 Ok(if let Some(entry) = node.entry()? {
1430 Some((node.full_path(map.on_disk)?, entry))
1438 Some((node.full_path(map.on_disk)?, entry))
1431 } else {
1439 } else {
1432 None
1440 None
1433 })
1441 })
1434 }))
1442 }))
1435 }
1443 }
1436
1444
1437 pub fn iter_tracked_dirs(
1445 pub fn iter_tracked_dirs(
1438 &mut self,
1446 &mut self,
1439 ) -> Result<
1447 ) -> Result<
1440 Box<
1448 Box<
1441 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1449 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1442 + Send
1450 + Send
1443 + '_,
1451 + '_,
1444 >,
1452 >,
1445 DirstateError,
1453 DirstateError,
1446 > {
1454 > {
1447 let map = self.get_map();
1455 let map = self.get_map();
1448 let on_disk = map.on_disk;
1456 let on_disk = map.on_disk;
1449 Ok(Box::new(filter_map_results(
1457 Ok(Box::new(filter_map_results(
1450 map.iter_nodes(),
1458 map.iter_nodes(),
1451 move |node| {
1459 move |node| {
1452 Ok(if node.tracked_descendants_count() > 0 {
1460 Ok(if node.tracked_descendants_count() > 0 {
1453 Some(node.full_path(on_disk)?)
1461 Some(node.full_path(on_disk)?)
1454 } else {
1462 } else {
1455 None
1463 None
1456 })
1464 })
1457 },
1465 },
1458 )))
1466 )))
1459 }
1467 }
1460
1468
1461 /// Only public because it needs to be exposed to the Python layer.
1469 /// Only public because it needs to be exposed to the Python layer.
1462 /// It is not the full `setparents` logic, only the parts that mutate the
1470 /// It is not the full `setparents` logic, only the parts that mutate the
1463 /// entries.
1471 /// entries.
1464 pub fn setparents_fixup(
1472 pub fn setparents_fixup(
1465 &mut self,
1473 &mut self,
1466 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1474 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1467 // XXX
1475 // XXX
1468 // All the copying and re-querying is quite inefficient, but this is
1476 // All the copying and re-querying is quite inefficient, but this is
1469 // still a lot better than doing it from Python.
1477 // still a lot better than doing it from Python.
1470 //
1478 //
1471 // The better solution is to develop a mechanism for `iter_mut`,
1479 // The better solution is to develop a mechanism for `iter_mut`,
1472 // which will be a lot more involved: we're dealing with a lazy,
1480 // which will be a lot more involved: we're dealing with a lazy,
1473 // append-mostly, tree-like data structure. This will do for now.
1481 // append-mostly, tree-like data structure. This will do for now.
1474 let mut copies = vec![];
1482 let mut copies = vec![];
1475 let mut files_with_p2_info = vec![];
1483 let mut files_with_p2_info = vec![];
1476 for res in self.iter() {
1484 for res in self.iter() {
1477 let (path, entry) = res?;
1485 let (path, entry) = res?;
1478 if entry.p2_info() {
1486 if entry.p2_info() {
1479 files_with_p2_info.push(path.to_owned())
1487 files_with_p2_info.push(path.to_owned())
1480 }
1488 }
1481 }
1489 }
1482 self.with_dmap_mut(|map| {
1490 self.with_dmap_mut(|map| {
1483 for path in files_with_p2_info.iter() {
1491 for path in files_with_p2_info.iter() {
1484 let node = map.get_or_insert_node(path, |_| {})?;
1492 let node = map.get_or_insert_node(path, |_| {})?;
1485 let entry =
1493 let entry =
1486 node.data.as_entry_mut().expect("entry should exist");
1494 node.data.as_entry_mut().expect("entry should exist");
1487 entry.drop_merge_data();
1495 entry.drop_merge_data();
1488 if let Some(source) = node.copy_source.take().as_deref() {
1496 if let Some(source) = node.copy_source.take().as_deref() {
1489 copies.push((path.to_owned(), source.to_owned()));
1497 copies.push((path.to_owned(), source.to_owned()));
1490 }
1498 }
1491 }
1499 }
1492 Ok(copies)
1500 Ok(copies)
1493 })
1501 })
1494 }
1502 }
1495
1503
1496 pub fn debug_iter(
1504 pub fn debug_iter(
1497 &self,
1505 &self,
1498 all: bool,
1506 all: bool,
1499 ) -> Box<
1507 ) -> Box<
1500 dyn Iterator<Item = Result<DebugDirstateTuple, DirstateV2ParseError>>
1508 dyn Iterator<Item = Result<DebugDirstateTuple, DirstateV2ParseError>>
1501 + Send
1509 + Send
1502 + '_,
1510 + '_,
1503 > {
1511 > {
1504 let map = self.get_map();
1512 let map = self.get_map();
1505 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1513 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1506 let debug_tuple = if let Some(entry) = node.entry()? {
1514 let debug_tuple = if let Some(entry) = node.entry()? {
1507 entry.debug_tuple()
1515 entry.debug_tuple()
1508 } else if !all {
1516 } else if !all {
1509 return Ok(None);
1517 return Ok(None);
1510 } else if let Some(mtime) = node.cached_directory_mtime()? {
1518 } else if let Some(mtime) = node.cached_directory_mtime()? {
1511 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1519 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1512 } else {
1520 } else {
1513 (b' ', 0, -1, -1)
1521 (b' ', 0, -1, -1)
1514 };
1522 };
1515 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1523 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1516 }))
1524 }))
1517 }
1525 }
1518 }
1526 }
1519 #[cfg(test)]
1527 #[cfg(test)]
1520 mod tests {
1528 mod tests {
1521 use super::*;
1529 use super::*;
1522
1530
1523 /// Shortcut to return tracked descendants of a path.
1531 /// Shortcut to return tracked descendants of a path.
1524 /// Panics if the path does not exist.
1532 /// Panics if the path does not exist.
1525 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1533 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1526 let path = dbg!(HgPath::new(path));
1534 let path = dbg!(HgPath::new(path));
1527 let node = map.get_map().get_node(path);
1535 let node = map.get_map().get_node(path);
1528 node.unwrap().unwrap().tracked_descendants_count()
1536 node.unwrap().unwrap().tracked_descendants_count()
1529 }
1537 }
1530
1538
1531 /// Shortcut to return descendants with an entry.
1539 /// Shortcut to return descendants with an entry.
1532 /// Panics if the path does not exist.
1540 /// Panics if the path does not exist.
1533 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1541 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1534 let path = dbg!(HgPath::new(path));
1542 let path = dbg!(HgPath::new(path));
1535 let node = map.get_map().get_node(path);
1543 let node = map.get_map().get_node(path);
1536 node.unwrap().unwrap().descendants_with_entry_count()
1544 node.unwrap().unwrap().descendants_with_entry_count()
1537 }
1545 }
1538
1546
1539 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1547 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1540 let path = dbg!(HgPath::new(path));
1548 let path = dbg!(HgPath::new(path));
1541 let node = map.get_map().get_node(path);
1549 let node = map.get_map().get_node(path);
1542 assert!(node.unwrap().is_none());
1550 assert!(node.unwrap().is_none());
1543 }
1551 }
1544
1552
1545 /// Shortcut for path creation in tests
1553 /// Shortcut for path creation in tests
1546 fn p(b: &[u8]) -> &HgPath {
1554 fn p(b: &[u8]) -> &HgPath {
1547 HgPath::new(b)
1555 HgPath::new(b)
1548 }
1556 }
1549
1557
1550 /// Test the very simple case a single tracked file
1558 /// Test the very simple case a single tracked file
1551 #[test]
1559 #[test]
1552 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1560 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1553 let mut map = OwningDirstateMap::new_empty(vec![]);
1561 let mut map = OwningDirstateMap::new_empty(vec![]);
1554 assert_eq!(map.len(), 0);
1562 assert_eq!(map.len(), 0);
1555
1563
1556 map.set_tracked(p(b"some/nested/path"))?;
1564 map.set_tracked(p(b"some/nested/path"))?;
1557
1565
1558 assert_eq!(map.len(), 1);
1566 assert_eq!(map.len(), 1);
1559 assert_eq!(tracked_descendants(&map, b"some"), 1);
1567 assert_eq!(tracked_descendants(&map, b"some"), 1);
1560 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1568 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1561 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1569 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1562
1570
1563 map.set_untracked(p(b"some/nested/path"))?;
1571 map.set_untracked(p(b"some/nested/path"))?;
1564 assert_eq!(map.len(), 0);
1572 assert_eq!(map.len(), 0);
1565 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1573 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1566
1574
1567 Ok(())
1575 Ok(())
1568 }
1576 }
1569
1577
1570 /// Test the simple case of all tracked, but multiple files
1578 /// Test the simple case of all tracked, but multiple files
1571 #[test]
1579 #[test]
1572 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1580 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1573 let mut map = OwningDirstateMap::new_empty(vec![]);
1581 let mut map = OwningDirstateMap::new_empty(vec![]);
1574
1582
1575 map.set_tracked(p(b"some/nested/path"))?;
1583 map.set_tracked(p(b"some/nested/path"))?;
1576 map.set_tracked(p(b"some/nested/file"))?;
1584 map.set_tracked(p(b"some/nested/file"))?;
1577 // one layer without any files to test deletion cascade
1585 // one layer without any files to test deletion cascade
1578 map.set_tracked(p(b"some/other/nested/path"))?;
1586 map.set_tracked(p(b"some/other/nested/path"))?;
1579 map.set_tracked(p(b"root_file"))?;
1587 map.set_tracked(p(b"root_file"))?;
1580 map.set_tracked(p(b"some/file"))?;
1588 map.set_tracked(p(b"some/file"))?;
1581 map.set_tracked(p(b"some/file2"))?;
1589 map.set_tracked(p(b"some/file2"))?;
1582 map.set_tracked(p(b"some/file3"))?;
1590 map.set_tracked(p(b"some/file3"))?;
1583
1591
1584 assert_eq!(map.len(), 7);
1592 assert_eq!(map.len(), 7);
1585 assert_eq!(tracked_descendants(&map, b"some"), 6);
1593 assert_eq!(tracked_descendants(&map, b"some"), 6);
1586 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1594 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1587 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1595 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1588 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1596 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1589 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1597 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1590
1598
1591 map.set_untracked(p(b"some/nested/path"))?;
1599 map.set_untracked(p(b"some/nested/path"))?;
1592 assert_eq!(map.len(), 6);
1600 assert_eq!(map.len(), 6);
1593 assert_eq!(tracked_descendants(&map, b"some"), 5);
1601 assert_eq!(tracked_descendants(&map, b"some"), 5);
1594 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1602 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1595 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1603 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1596 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1604 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1597
1605
1598 map.set_untracked(p(b"some/nested/file"))?;
1606 map.set_untracked(p(b"some/nested/file"))?;
1599 assert_eq!(map.len(), 5);
1607 assert_eq!(map.len(), 5);
1600 assert_eq!(tracked_descendants(&map, b"some"), 4);
1608 assert_eq!(tracked_descendants(&map, b"some"), 4);
1601 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1609 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1602 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1610 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1603 assert_does_not_exist(&map, b"some_nested");
1611 assert_does_not_exist(&map, b"some_nested");
1604
1612
1605 map.set_untracked(p(b"some/other/nested/path"))?;
1613 map.set_untracked(p(b"some/other/nested/path"))?;
1606 assert_eq!(map.len(), 4);
1614 assert_eq!(map.len(), 4);
1607 assert_eq!(tracked_descendants(&map, b"some"), 3);
1615 assert_eq!(tracked_descendants(&map, b"some"), 3);
1608 assert_does_not_exist(&map, b"some/other");
1616 assert_does_not_exist(&map, b"some/other");
1609
1617
1610 map.set_untracked(p(b"root_file"))?;
1618 map.set_untracked(p(b"root_file"))?;
1611 assert_eq!(map.len(), 3);
1619 assert_eq!(map.len(), 3);
1612 assert_eq!(tracked_descendants(&map, b"some"), 3);
1620 assert_eq!(tracked_descendants(&map, b"some"), 3);
1613 assert_does_not_exist(&map, b"root_file");
1621 assert_does_not_exist(&map, b"root_file");
1614
1622
1615 map.set_untracked(p(b"some/file"))?;
1623 map.set_untracked(p(b"some/file"))?;
1616 assert_eq!(map.len(), 2);
1624 assert_eq!(map.len(), 2);
1617 assert_eq!(tracked_descendants(&map, b"some"), 2);
1625 assert_eq!(tracked_descendants(&map, b"some"), 2);
1618 assert_does_not_exist(&map, b"some/file");
1626 assert_does_not_exist(&map, b"some/file");
1619
1627
1620 map.set_untracked(p(b"some/file2"))?;
1628 map.set_untracked(p(b"some/file2"))?;
1621 assert_eq!(map.len(), 1);
1629 assert_eq!(map.len(), 1);
1622 assert_eq!(tracked_descendants(&map, b"some"), 1);
1630 assert_eq!(tracked_descendants(&map, b"some"), 1);
1623 assert_does_not_exist(&map, b"some/file2");
1631 assert_does_not_exist(&map, b"some/file2");
1624
1632
1625 map.set_untracked(p(b"some/file3"))?;
1633 map.set_untracked(p(b"some/file3"))?;
1626 assert_eq!(map.len(), 0);
1634 assert_eq!(map.len(), 0);
1627 assert_does_not_exist(&map, b"some/file3");
1635 assert_does_not_exist(&map, b"some/file3");
1628
1636
1629 Ok(())
1637 Ok(())
1630 }
1638 }
1631
1639
1632 /// Check with a mix of tracked and non-tracked items
1640 /// Check with a mix of tracked and non-tracked items
1633 #[test]
1641 #[test]
1634 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1642 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1635 let mut map = OwningDirstateMap::new_empty(vec![]);
1643 let mut map = OwningDirstateMap::new_empty(vec![]);
1636
1644
1637 // A file that was just added
1645 // A file that was just added
1638 map.set_tracked(p(b"some/nested/path"))?;
1646 map.set_tracked(p(b"some/nested/path"))?;
1639 // This has no information, the dirstate should ignore it
1647 // This has no information, the dirstate should ignore it
1640 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1648 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1641 assert_does_not_exist(&map, b"some/file");
1649 assert_does_not_exist(&map, b"some/file");
1642
1650
1643 // A file that was removed
1651 // A file that was removed
1644 map.reset_state(
1652 map.reset_state(
1645 p(b"some/nested/file"),
1653 p(b"some/nested/file"),
1646 false,
1654 false,
1647 true,
1655 true,
1648 false,
1656 false,
1649 false,
1657 false,
1650 None,
1658 None,
1651 )?;
1659 )?;
1652 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1660 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1653 // Only present in p2
1661 // Only present in p2
1654 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1662 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1655 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1663 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1656 // A file that was merged
1664 // A file that was merged
1657 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1665 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1658 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1666 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1659 // A file that is added, with info from p2
1667 // A file that is added, with info from p2
1660 // XXX is that actually possible?
1668 // XXX is that actually possible?
1661 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1669 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1662 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1670 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1663 // A clean file
1671 // A clean file
1664 // One layer without any files to test deletion cascade
1672 // One layer without any files to test deletion cascade
1665 map.reset_state(
1673 map.reset_state(
1666 p(b"some/other/nested/path"),
1674 p(b"some/other/nested/path"),
1667 true,
1675 true,
1668 true,
1676 true,
1669 false,
1677 false,
1670 false,
1678 false,
1671 None,
1679 None,
1672 )?;
1680 )?;
1673 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1681 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1674
1682
1675 assert_eq!(map.len(), 6);
1683 assert_eq!(map.len(), 6);
1676 assert_eq!(tracked_descendants(&map, b"some"), 3);
1684 assert_eq!(tracked_descendants(&map, b"some"), 3);
1677 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1685 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1678 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1686 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1679 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1687 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1680 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1688 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1681 assert_eq!(
1689 assert_eq!(
1682 descendants_with_an_entry(&map, b"some/other/nested/path"),
1690 descendants_with_an_entry(&map, b"some/other/nested/path"),
1683 0
1691 0
1684 );
1692 );
1685 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1693 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1686 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1694 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1687
1695
1688 // might as well check this
1696 // might as well check this
1689 map.set_untracked(p(b"path/does/not/exist"))?;
1697 map.set_untracked(p(b"path/does/not/exist"))?;
1690 assert_eq!(map.len(), 6);
1698 assert_eq!(map.len(), 6);
1691
1699
1692 map.set_untracked(p(b"some/other/nested/path"))?;
1700 map.set_untracked(p(b"some/other/nested/path"))?;
1693 // It is set untracked but not deleted since it held other information
1701 // It is set untracked but not deleted since it held other information
1694 assert_eq!(map.len(), 6);
1702 assert_eq!(map.len(), 6);
1695 assert_eq!(tracked_descendants(&map, b"some"), 2);
1703 assert_eq!(tracked_descendants(&map, b"some"), 2);
1696 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1704 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1697 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1705 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1698 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1706 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1699 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1707 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1700 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1708 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1701
1709
1702 map.set_untracked(p(b"some/nested/path"))?;
1710 map.set_untracked(p(b"some/nested/path"))?;
1703 // It is set untracked *and* deleted since it was only added
1711 // It is set untracked *and* deleted since it was only added
1704 assert_eq!(map.len(), 5);
1712 assert_eq!(map.len(), 5);
1705 assert_eq!(tracked_descendants(&map, b"some"), 1);
1713 assert_eq!(tracked_descendants(&map, b"some"), 1);
1706 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1714 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1707 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1715 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1708 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1716 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1709 assert_does_not_exist(&map, b"some/nested/path");
1717 assert_does_not_exist(&map, b"some/nested/path");
1710
1718
1711 map.set_untracked(p(b"root_file"))?;
1719 map.set_untracked(p(b"root_file"))?;
1712 // Untracked but not deleted
1720 // Untracked but not deleted
1713 assert_eq!(map.len(), 5);
1721 assert_eq!(map.len(), 5);
1714 assert!(map.get(p(b"root_file"))?.is_some());
1722 assert!(map.get(p(b"root_file"))?.is_some());
1715
1723
1716 map.set_untracked(p(b"some/file2"))?;
1724 map.set_untracked(p(b"some/file2"))?;
1717 assert_eq!(map.len(), 5);
1725 assert_eq!(map.len(), 5);
1718 assert_eq!(tracked_descendants(&map, b"some"), 0);
1726 assert_eq!(tracked_descendants(&map, b"some"), 0);
1719 assert!(map.get(p(b"some/file2"))?.is_some());
1727 assert!(map.get(p(b"some/file2"))?.is_some());
1720
1728
1721 map.set_untracked(p(b"some/file3"))?;
1729 map.set_untracked(p(b"some/file3"))?;
1722 assert_eq!(map.len(), 5);
1730 assert_eq!(map.len(), 5);
1723 assert_eq!(tracked_descendants(&map, b"some"), 0);
1731 assert_eq!(tracked_descendants(&map, b"some"), 0);
1724 assert!(map.get(p(b"some/file3"))?.is_some());
1732 assert!(map.get(p(b"some/file3"))?.is_some());
1725
1733
1726 Ok(())
1734 Ok(())
1727 }
1735 }
1728
1736
1729 /// Check that copies counter is correctly updated
1737 /// Check that copies counter is correctly updated
1730 #[test]
1738 #[test]
1731 fn test_copy_source() -> Result<(), DirstateError> {
1739 fn test_copy_source() -> Result<(), DirstateError> {
1732 let mut map = OwningDirstateMap::new_empty(vec![]);
1740 let mut map = OwningDirstateMap::new_empty(vec![]);
1733
1741
1734 // Clean file
1742 // Clean file
1735 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1743 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1736 // Merged file
1744 // Merged file
1737 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1745 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1738 // Removed file
1746 // Removed file
1739 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1747 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1740 // Added file
1748 // Added file
1741 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1749 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1742 // Add copy
1750 // Add copy
1743 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1751 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1744 assert_eq!(map.copy_map_len(), 1);
1752 assert_eq!(map.copy_map_len(), 1);
1745
1753
1746 // Copy override
1754 // Copy override
1747 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1755 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1748 assert_eq!(map.copy_map_len(), 1);
1756 assert_eq!(map.copy_map_len(), 1);
1749
1757
1750 // Multiple copies
1758 // Multiple copies
1751 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1759 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1752 assert_eq!(map.copy_map_len(), 2);
1760 assert_eq!(map.copy_map_len(), 2);
1753
1761
1754 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1762 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1755 assert_eq!(map.copy_map_len(), 3);
1763 assert_eq!(map.copy_map_len(), 3);
1756
1764
1757 // Added, so the entry is completely removed
1765 // Added, so the entry is completely removed
1758 map.set_untracked(p(b"files/added"))?;
1766 map.set_untracked(p(b"files/added"))?;
1759 assert_does_not_exist(&map, b"files/added");
1767 assert_does_not_exist(&map, b"files/added");
1760 assert_eq!(map.copy_map_len(), 2);
1768 assert_eq!(map.copy_map_len(), 2);
1761
1769
1762 // Removed, so the entry is kept around, so is its copy
1770 // Removed, so the entry is kept around, so is its copy
1763 map.set_untracked(p(b"removed"))?;
1771 map.set_untracked(p(b"removed"))?;
1764 assert!(map.get(p(b"removed"))?.is_some());
1772 assert!(map.get(p(b"removed"))?.is_some());
1765 assert_eq!(map.copy_map_len(), 2);
1773 assert_eq!(map.copy_map_len(), 2);
1766
1774
1767 // Clean, so the entry is kept around, but not its copy
1775 // Clean, so the entry is kept around, but not its copy
1768 map.set_untracked(p(b"files/clean"))?;
1776 map.set_untracked(p(b"files/clean"))?;
1769 assert!(map.get(p(b"files/clean"))?.is_some());
1777 assert!(map.get(p(b"files/clean"))?.is_some());
1770 assert_eq!(map.copy_map_len(), 1);
1778 assert_eq!(map.copy_map_len(), 1);
1771
1779
1772 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1780 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1773 assert_eq!(map.copy_map_len(), 2);
1781 assert_eq!(map.copy_map_len(), 2);
1774
1782
1775 // Info from p2, so its copy source info is kept around
1783 // Info from p2, so its copy source info is kept around
1776 map.set_untracked(p(b"files/from_p2"))?;
1784 map.set_untracked(p(b"files/from_p2"))?;
1777 assert!(map.get(p(b"files/from_p2"))?.is_some());
1785 assert!(map.get(p(b"files/from_p2"))?.is_some());
1778 assert_eq!(map.copy_map_len(), 2);
1786 assert_eq!(map.copy_map_len(), 2);
1779
1787
1780 Ok(())
1788 Ok(())
1781 }
1789 }
1782
1790
1783 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1791 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1784 /// does not actually come from the disk, but it's opaque to the code being
1792 /// does not actually come from the disk, but it's opaque to the code being
1785 /// tested.
1793 /// tested.
1786 #[test]
1794 #[test]
1787 fn test_on_disk() -> Result<(), DirstateError> {
1795 fn test_on_disk() -> Result<(), DirstateError> {
1788 // First let's create some data to put "on disk"
1796 // First let's create some data to put "on disk"
1789 let mut map = OwningDirstateMap::new_empty(vec![]);
1797 let mut map = OwningDirstateMap::new_empty(vec![]);
1790
1798
1791 // A file that was just added
1799 // A file that was just added
1792 map.set_tracked(p(b"some/nested/added"))?;
1800 map.set_tracked(p(b"some/nested/added"))?;
1793 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1801 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1794
1802
1795 // A file that was removed
1803 // A file that was removed
1796 map.reset_state(
1804 map.reset_state(
1797 p(b"some/nested/removed"),
1805 p(b"some/nested/removed"),
1798 false,
1806 false,
1799 true,
1807 true,
1800 false,
1808 false,
1801 false,
1809 false,
1802 None,
1810 None,
1803 )?;
1811 )?;
1804 // Only present in p2
1812 // Only present in p2
1805 map.reset_state(
1813 map.reset_state(
1806 p(b"other/p2_info_only"),
1814 p(b"other/p2_info_only"),
1807 false,
1815 false,
1808 false,
1816 false,
1809 true,
1817 true,
1810 false,
1818 false,
1811 None,
1819 None,
1812 )?;
1820 )?;
1813 map.copy_map_insert(
1821 map.copy_map_insert(
1814 p(b"other/p2_info_only"),
1822 p(b"other/p2_info_only"),
1815 p(b"other/p2_info_copy_source"),
1823 p(b"other/p2_info_copy_source"),
1816 )?;
1824 )?;
1817 // A file that was merged
1825 // A file that was merged
1818 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1826 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1819 // A file that is added, with info from p2
1827 // A file that is added, with info from p2
1820 // XXX is that actually possible?
1828 // XXX is that actually possible?
1821 map.reset_state(
1829 map.reset_state(
1822 p(b"other/added_with_p2"),
1830 p(b"other/added_with_p2"),
1823 true,
1831 true,
1824 false,
1832 false,
1825 true,
1833 true,
1826 false,
1834 false,
1827 None,
1835 None,
1828 )?;
1836 )?;
1829 // One layer without any files to test deletion cascade
1837 // One layer without any files to test deletion cascade
1830 // A clean file
1838 // A clean file
1831 map.reset_state(
1839 map.reset_state(
1832 p(b"some/other/nested/clean"),
1840 p(b"some/other/nested/clean"),
1833 true,
1841 true,
1834 true,
1842 true,
1835 false,
1843 false,
1836 false,
1844 false,
1837 None,
1845 None,
1838 )?;
1846 )?;
1839
1847
1840 let (packed, metadata, _should_append, _old_data_size) =
1848 let (packed, metadata, _should_append, _old_data_size) =
1841 map.pack_v2(DirstateMapWriteMode::ForceNewDataFile)?;
1849 map.pack_v2(DirstateMapWriteMode::ForceNewDataFile)?;
1842 let packed_len = packed.len();
1850 let packed_len = packed.len();
1843 assert!(packed_len > 0);
1851 assert!(packed_len > 0);
1844
1852
1845 // Recreate "from disk"
1853 // Recreate "from disk"
1846 let mut map = OwningDirstateMap::new_v2(
1854 let mut map = OwningDirstateMap::new_v2(
1847 packed,
1855 packed,
1848 packed_len,
1856 packed_len,
1849 metadata.as_bytes(),
1857 metadata.as_bytes(),
1850 vec![],
1858 vec![],
1851 None,
1859 None,
1852 )?;
1860 )?;
1853
1861
1854 // Check that everything is accounted for
1862 // Check that everything is accounted for
1855 assert!(map.contains_key(p(b"some/nested/added"))?);
1863 assert!(map.contains_key(p(b"some/nested/added"))?);
1856 assert!(map.contains_key(p(b"some/nested/removed"))?);
1864 assert!(map.contains_key(p(b"some/nested/removed"))?);
1857 assert!(map.contains_key(p(b"merged"))?);
1865 assert!(map.contains_key(p(b"merged"))?);
1858 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1866 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1859 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1867 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1860 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1868 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1861 assert_eq!(
1869 assert_eq!(
1862 map.copy_map_get(p(b"some/nested/added"))?,
1870 map.copy_map_get(p(b"some/nested/added"))?,
1863 Some(p(b"added_copy_source"))
1871 Some(p(b"added_copy_source"))
1864 );
1872 );
1865 assert_eq!(
1873 assert_eq!(
1866 map.copy_map_get(p(b"other/p2_info_only"))?,
1874 map.copy_map_get(p(b"other/p2_info_only"))?,
1867 Some(p(b"other/p2_info_copy_source"))
1875 Some(p(b"other/p2_info_copy_source"))
1868 );
1876 );
1869 assert_eq!(tracked_descendants(&map, b"some"), 2);
1877 assert_eq!(tracked_descendants(&map, b"some"), 2);
1870 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1878 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1871 assert_eq!(tracked_descendants(&map, b"other"), 1);
1879 assert_eq!(tracked_descendants(&map, b"other"), 1);
1872 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1880 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1873 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1881 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1874 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1882 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1875 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1883 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1876 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1884 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1877 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1885 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1878 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1886 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1879 assert_eq!(map.len(), 6);
1887 assert_eq!(map.len(), 6);
1880 assert_eq!(map.get_map().unreachable_bytes, 0);
1888 assert_eq!(map.get_map().unreachable_bytes, 0);
1881 assert_eq!(map.copy_map_len(), 2);
1889 assert_eq!(map.copy_map_len(), 2);
1882
1890
1883 // Shouldn't change anything since it's already not tracked
1891 // Shouldn't change anything since it's already not tracked
1884 map.set_untracked(p(b"some/nested/removed"))?;
1892 map.set_untracked(p(b"some/nested/removed"))?;
1885 assert_eq!(map.get_map().unreachable_bytes, 0);
1893 assert_eq!(map.get_map().unreachable_bytes, 0);
1886
1894
1887 if let ChildNodes::InMemory(_) = map.get_map().root {
1895 if let ChildNodes::InMemory(_) = map.get_map().root {
1888 panic!("root should not have been mutated")
1896 panic!("root should not have been mutated")
1889 }
1897 }
1890 // We haven't mutated enough (nothing, actually), we should still be in
1898 // We haven't mutated enough (nothing, actually), we should still be in
1891 // the append strategy
1899 // the append strategy
1892 assert!(map.get_map().write_should_append());
1900 assert!(map.get_map().write_should_append());
1893
1901
1894 // But this mutates the structure, so there should be unreachable_bytes
1902 // But this mutates the structure, so there should be unreachable_bytes
1895 assert!(map.set_untracked(p(b"some/nested/added"))?);
1903 assert!(map.set_untracked(p(b"some/nested/added"))?);
1896 let unreachable_bytes = map.get_map().unreachable_bytes;
1904 let unreachable_bytes = map.get_map().unreachable_bytes;
1897 assert!(unreachable_bytes > 0);
1905 assert!(unreachable_bytes > 0);
1898
1906
1899 if let ChildNodes::OnDisk(_) = map.get_map().root {
1907 if let ChildNodes::OnDisk(_) = map.get_map().root {
1900 panic!("root should have been mutated")
1908 panic!("root should have been mutated")
1901 }
1909 }
1902
1910
1903 // This should not mutate the structure either, since `root` has
1911 // This should not mutate the structure either, since `root` has
1904 // already been mutated along with its direct children.
1912 // already been mutated along with its direct children.
1905 map.set_untracked(p(b"merged"))?;
1913 map.set_untracked(p(b"merged"))?;
1906 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1914 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1907
1915
1908 if let NodeRef::InMemory(_, _) =
1916 if let NodeRef::InMemory(_, _) =
1909 map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap()
1917 map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap()
1910 {
1918 {
1911 panic!("'other/added_with_p2' should not have been mutated")
1919 panic!("'other/added_with_p2' should not have been mutated")
1912 }
1920 }
1913 // But this should, since it's in a different path
1921 // But this should, since it's in a different path
1914 // than `<root>some/nested/add`
1922 // than `<root>some/nested/add`
1915 map.set_untracked(p(b"other/added_with_p2"))?;
1923 map.set_untracked(p(b"other/added_with_p2"))?;
1916 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1924 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1917
1925
1918 if let NodeRef::OnDisk(_) =
1926 if let NodeRef::OnDisk(_) =
1919 map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap()
1927 map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap()
1920 {
1928 {
1921 panic!("'other/added_with_p2' should have been mutated")
1929 panic!("'other/added_with_p2' should have been mutated")
1922 }
1930 }
1923
1931
1924 // We have rewritten most of the tree, we should create a new file
1932 // We have rewritten most of the tree, we should create a new file
1925 assert!(!map.get_map().write_should_append());
1933 assert!(!map.get_map().write_should_append());
1926
1934
1927 Ok(())
1935 Ok(())
1928 }
1936 }
1929 }
1937 }
@@ -1,1013 +1,1026 b''
1 use crate::dirstate::entry::TruncatedTimestamp;
1 use crate::dirstate::entry::TruncatedTimestamp;
2 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate::status::IgnoreFnType;
3 use crate::dirstate::status::StatusPath;
3 use crate::dirstate::status::StatusPath;
4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
6 use crate::dirstate_tree::dirstate_map::DirstateMap;
6 use crate::dirstate_tree::dirstate_map::DirstateMap;
7 use crate::dirstate_tree::dirstate_map::DirstateVersion;
7 use crate::dirstate_tree::dirstate_map::DirstateVersion;
8 use crate::dirstate_tree::dirstate_map::NodeRef;
8 use crate::dirstate_tree::dirstate_map::NodeRef;
9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
10 use crate::matchers::get_ignore_function;
10 use crate::matchers::get_ignore_function;
11 use crate::matchers::Matcher;
11 use crate::matchers::Matcher;
12 use crate::utils::files::get_bytes_from_os_string;
12 use crate::utils::files::get_bytes_from_os_string;
13 use crate::utils::files::get_bytes_from_path;
13 use crate::utils::files::get_bytes_from_path;
14 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::files::get_path_from_bytes;
15 use crate::utils::hg_path::hg_path_to_path_buf;
15 use crate::utils::hg_path::HgPath;
16 use crate::utils::hg_path::HgPath;
16 use crate::BadMatch;
17 use crate::BadMatch;
17 use crate::BadType;
18 use crate::BadType;
18 use crate::DirstateStatus;
19 use crate::DirstateStatus;
19 use crate::HgPathCow;
20 use crate::HgPathCow;
20 use crate::PatternFileWarning;
21 use crate::PatternFileWarning;
21 use crate::StatusError;
22 use crate::StatusError;
22 use crate::StatusOptions;
23 use crate::StatusOptions;
23 use once_cell::sync::OnceCell;
24 use once_cell::sync::OnceCell;
24 use rayon::prelude::*;
25 use rayon::prelude::*;
25 use sha1::{Digest, Sha1};
26 use sha1::{Digest, Sha1};
26 use std::borrow::Cow;
27 use std::borrow::Cow;
27 use std::io;
28 use std::io;
28 use std::os::unix::prelude::FileTypeExt;
29 use std::os::unix::prelude::FileTypeExt;
29 use std::path::Path;
30 use std::path::Path;
30 use std::path::PathBuf;
31 use std::path::PathBuf;
31 use std::sync::Mutex;
32 use std::sync::Mutex;
32 use std::time::SystemTime;
33 use std::time::SystemTime;
33
34
34 /// Returns the status of the working directory compared to its parent
35 /// Returns the status of the working directory compared to its parent
35 /// changeset.
36 /// changeset.
36 ///
37 ///
37 /// This algorithm is based on traversing the filesystem tree (`fs` in function
38 /// This algorithm is based on traversing the filesystem tree (`fs` in function
38 /// and variable names) and dirstate tree at the same time. The core of this
39 /// and variable names) and dirstate tree at the same time. The core of this
39 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
40 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
40 /// and its use of `itertools::merge_join_by`. When reaching a path that only
41 /// and its use of `itertools::merge_join_by`. When reaching a path that only
41 /// exists in one of the two trees, depending on information requested by
42 /// exists in one of the two trees, depending on information requested by
42 /// `options` we may need to traverse the remaining subtree.
43 /// `options` we may need to traverse the remaining subtree.
43 #[logging_timer::time("trace")]
44 #[logging_timer::time("trace")]
44 pub fn status<'dirstate>(
45 pub fn status<'dirstate>(
45 dmap: &'dirstate mut DirstateMap,
46 dmap: &'dirstate mut DirstateMap,
46 matcher: &(dyn Matcher + Sync),
47 matcher: &(dyn Matcher + Sync),
47 root_dir: PathBuf,
48 root_dir: PathBuf,
48 ignore_files: Vec<PathBuf>,
49 ignore_files: Vec<PathBuf>,
49 options: StatusOptions,
50 options: StatusOptions,
50 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
51 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
51 {
52 {
52 // Also cap for a Python caller of this function, but don't complain if
53 // Also cap for a Python caller of this function, but don't complain if
53 // the global threadpool has already been set since this code path is also
54 // the global threadpool has already been set since this code path is also
54 // being used by `rhg`, which calls this early.
55 // being used by `rhg`, which calls this early.
55 let _ = crate::utils::cap_default_rayon_threads();
56 let _ = crate::utils::cap_default_rayon_threads();
56
57
57 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
58 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
58 if options.list_ignored || options.list_unknown {
59 if options.list_ignored || options.list_unknown {
59 let (ignore_fn, warnings, changed) = match dmap.dirstate_version {
60 let (ignore_fn, warnings, changed) = match dmap.dirstate_version {
60 DirstateVersion::V1 => {
61 DirstateVersion::V1 => {
61 let (ignore_fn, warnings) = get_ignore_function(
62 let (ignore_fn, warnings) = get_ignore_function(
62 ignore_files,
63 ignore_files,
63 &root_dir,
64 &root_dir,
64 &mut |_source, _pattern_bytes| {},
65 &mut |_source, _pattern_bytes| {},
65 )?;
66 )?;
66 (ignore_fn, warnings, None)
67 (ignore_fn, warnings, None)
67 }
68 }
68 DirstateVersion::V2 => {
69 DirstateVersion::V2 => {
69 let mut hasher = Sha1::new();
70 let mut hasher = Sha1::new();
70 let (ignore_fn, warnings) = get_ignore_function(
71 let (ignore_fn, warnings) = get_ignore_function(
71 ignore_files,
72 ignore_files,
72 &root_dir,
73 &root_dir,
73 &mut |source, pattern_bytes| {
74 &mut |source, pattern_bytes| {
74 // If inside the repo, use the relative version to
75 // If inside the repo, use the relative version to
75 // make it deterministic inside tests.
76 // make it deterministic inside tests.
76 // The performance hit should be negligible.
77 // The performance hit should be negligible.
77 let source = source
78 let source = source
78 .strip_prefix(&root_dir)
79 .strip_prefix(&root_dir)
79 .unwrap_or(source);
80 .unwrap_or(source);
80 let source = get_bytes_from_path(source);
81 let source = get_bytes_from_path(source);
81
82
82 let mut subhasher = Sha1::new();
83 let mut subhasher = Sha1::new();
83 subhasher.update(pattern_bytes);
84 subhasher.update(pattern_bytes);
84 let patterns_hash = subhasher.finalize();
85 let patterns_hash = subhasher.finalize();
85
86
86 hasher.update(source);
87 hasher.update(source);
87 hasher.update(b" ");
88 hasher.update(b" ");
88 hasher.update(patterns_hash);
89 hasher.update(patterns_hash);
89 hasher.update(b"\n");
90 hasher.update(b"\n");
90 },
91 },
91 )?;
92 )?;
92 let new_hash = *hasher.finalize().as_ref();
93 let new_hash = *hasher.finalize().as_ref();
93 let changed = new_hash != dmap.ignore_patterns_hash;
94 let changed = new_hash != dmap.ignore_patterns_hash;
94 dmap.ignore_patterns_hash = new_hash;
95 dmap.ignore_patterns_hash = new_hash;
95 (ignore_fn, warnings, Some(changed))
96 (ignore_fn, warnings, Some(changed))
96 }
97 }
97 };
98 };
98 (ignore_fn, warnings, changed)
99 (ignore_fn, warnings, changed)
99 } else {
100 } else {
100 (Box::new(|&_| true), vec![], None)
101 (Box::new(|&_| true), vec![], None)
101 };
102 };
102
103
103 let filesystem_time_at_status_start =
104 let filesystem_time_at_status_start =
104 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
105 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
105
106
106 // If the repository is under the current directory, prefer using a
107 // If the repository is under the current directory, prefer using a
107 // relative path, so the kernel needs to traverse fewer directory in every
108 // relative path, so the kernel needs to traverse fewer directory in every
108 // call to `read_dir` or `symlink_metadata`.
109 // call to `read_dir` or `symlink_metadata`.
109 // This is effective in the common case where the current directory is the
110 // This is effective in the common case where the current directory is the
110 // repository root.
111 // repository root.
111
112
112 // TODO: Better yet would be to use libc functions like `openat` and
113 // TODO: Better yet would be to use libc functions like `openat` and
113 // `fstatat` to remove such repeated traversals entirely, but the standard
114 // `fstatat` to remove such repeated traversals entirely, but the standard
114 // library does not provide APIs based on those.
115 // library does not provide APIs based on those.
115 // Maybe with a crate like https://crates.io/crates/openat instead?
116 // Maybe with a crate like https://crates.io/crates/openat instead?
116 let root_dir = if let Some(relative) = std::env::current_dir()
117 let root_dir = if let Some(relative) = std::env::current_dir()
117 .ok()
118 .ok()
118 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
119 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
119 {
120 {
120 relative
121 relative
121 } else {
122 } else {
122 &root_dir
123 &root_dir
123 };
124 };
124
125
125 let outcome = DirstateStatus {
126 let outcome = DirstateStatus {
126 filesystem_time_at_status_start,
127 filesystem_time_at_status_start,
127 ..Default::default()
128 ..Default::default()
128 };
129 };
129 let common = StatusCommon {
130 let common = StatusCommon {
130 dmap,
131 dmap,
131 options,
132 options,
132 matcher,
133 matcher,
133 ignore_fn,
134 ignore_fn,
134 outcome: Mutex::new(outcome),
135 outcome: Mutex::new(outcome),
135 ignore_patterns_have_changed: patterns_changed,
136 ignore_patterns_have_changed: patterns_changed,
136 new_cacheable_directories: Default::default(),
137 new_cacheable_directories: Default::default(),
137 outdated_cached_directories: Default::default(),
138 outdated_cached_directories: Default::default(),
138 filesystem_time_at_status_start,
139 filesystem_time_at_status_start,
139 };
140 };
140 let is_at_repo_root = true;
141 let is_at_repo_root = true;
141 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
142 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
142 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
143 let has_ignored_ancestor = HasIgnoredAncestor::create(None, hg_path);
143 let root_cached_mtime = None;
144 let root_cached_mtime = None;
144 // If the path we have for the repository root is a symlink, do follow it.
145 // If the path we have for the repository root is a symlink, do follow it.
145 // (As opposed to symlinks within the working directory which are not
146 // (As opposed to symlinks within the working directory which are not
146 // followed, using `std::fs::symlink_metadata`.)
147 // followed, using `std::fs::symlink_metadata`.)
147 common.traverse_fs_directory_and_dirstate(
148 common.traverse_fs_directory_and_dirstate(
148 &has_ignored_ancestor,
149 &has_ignored_ancestor,
149 dmap.root.as_ref(),
150 dmap.root.as_ref(),
150 hg_path,
151 hg_path,
151 &DirEntry {
152 &DirEntry {
152 hg_path: Cow::Borrowed(HgPath::new(b"")),
153 hg_path: Cow::Borrowed(HgPath::new(b"")),
153 fs_path: Cow::Borrowed(root_dir),
154 fs_path: Cow::Borrowed(root_dir),
154 symlink_metadata: None,
155 symlink_metadata: None,
155 file_type: FakeFileType::Directory,
156 file_type: FakeFileType::Directory,
156 },
157 },
157 root_cached_mtime,
158 root_cached_mtime,
158 is_at_repo_root,
159 is_at_repo_root,
159 )?;
160 )?;
161 if let Some(file_set) = common.matcher.file_set() {
162 for file in file_set {
163 if !file.is_empty() && !dmap.has_node(file)? {
164 let path = hg_path_to_path_buf(file)?;
165 if let io::Result::Err(error) =
166 root_dir.join(path).symlink_metadata()
167 {
168 common.io_error(error, file)
169 }
170 }
171 }
172 }
160 let mut outcome = common.outcome.into_inner().unwrap();
173 let mut outcome = common.outcome.into_inner().unwrap();
161 let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
174 let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
162 let outdated = common.outdated_cached_directories.into_inner().unwrap();
175 let outdated = common.outdated_cached_directories.into_inner().unwrap();
163
176
164 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
177 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
165 || !outdated.is_empty()
178 || !outdated.is_empty()
166 || (!new_cacheable.is_empty()
179 || (!new_cacheable.is_empty()
167 && dmap.dirstate_version == DirstateVersion::V2);
180 && dmap.dirstate_version == DirstateVersion::V2);
168
181
169 // Remove outdated mtimes before adding new mtimes, in case a given
182 // Remove outdated mtimes before adding new mtimes, in case a given
170 // directory is both
183 // directory is both
171 for path in &outdated {
184 for path in &outdated {
172 dmap.clear_cached_mtime(path)?;
185 dmap.clear_cached_mtime(path)?;
173 }
186 }
174 for (path, mtime) in &new_cacheable {
187 for (path, mtime) in &new_cacheable {
175 dmap.set_cached_mtime(path, *mtime)?;
188 dmap.set_cached_mtime(path, *mtime)?;
176 }
189 }
177
190
178 Ok((outcome, warnings))
191 Ok((outcome, warnings))
179 }
192 }
180
193
181 /// Bag of random things needed by various parts of the algorithm. Reduces the
194 /// Bag of random things needed by various parts of the algorithm. Reduces the
182 /// number of parameters passed to functions.
195 /// number of parameters passed to functions.
183 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
196 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
184 dmap: &'tree DirstateMap<'on_disk>,
197 dmap: &'tree DirstateMap<'on_disk>,
185 options: StatusOptions,
198 options: StatusOptions,
186 matcher: &'a (dyn Matcher + Sync),
199 matcher: &'a (dyn Matcher + Sync),
187 ignore_fn: IgnoreFnType<'a>,
200 ignore_fn: IgnoreFnType<'a>,
188 outcome: Mutex<DirstateStatus<'on_disk>>,
201 outcome: Mutex<DirstateStatus<'on_disk>>,
189 /// New timestamps of directories to be used for caching their readdirs
202 /// New timestamps of directories to be used for caching their readdirs
190 new_cacheable_directories:
203 new_cacheable_directories:
191 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
204 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
192 /// Used to invalidate the readdir cache of directories
205 /// Used to invalidate the readdir cache of directories
193 outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
206 outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
194
207
195 /// Whether ignore files like `.hgignore` have changed since the previous
208 /// Whether ignore files like `.hgignore` have changed since the previous
196 /// time a `status()` call wrote their hash to the dirstate. `None` means
209 /// time a `status()` call wrote their hash to the dirstate. `None` means
197 /// we don’t know as this run doesn’t list either ignored or uknown files
210 /// we don’t know as this run doesn’t list either ignored or uknown files
198 /// and therefore isn’t reading `.hgignore`.
211 /// and therefore isn’t reading `.hgignore`.
199 ignore_patterns_have_changed: Option<bool>,
212 ignore_patterns_have_changed: Option<bool>,
200
213
201 /// The current time at the start of the `status()` algorithm, as measured
214 /// The current time at the start of the `status()` algorithm, as measured
202 /// and possibly truncated by the filesystem.
215 /// and possibly truncated by the filesystem.
203 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
216 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
204 }
217 }
205
218
206 enum Outcome {
219 enum Outcome {
207 Modified,
220 Modified,
208 Added,
221 Added,
209 Removed,
222 Removed,
210 Deleted,
223 Deleted,
211 Clean,
224 Clean,
212 Ignored,
225 Ignored,
213 Unknown,
226 Unknown,
214 Unsure,
227 Unsure,
215 }
228 }
216
229
217 /// Lazy computation of whether a given path has a hgignored
230 /// Lazy computation of whether a given path has a hgignored
218 /// ancestor.
231 /// ancestor.
219 struct HasIgnoredAncestor<'a> {
232 struct HasIgnoredAncestor<'a> {
220 /// `path` and `parent` constitute the inputs to the computation,
233 /// `path` and `parent` constitute the inputs to the computation,
221 /// `cache` stores the outcome.
234 /// `cache` stores the outcome.
222 path: &'a HgPath,
235 path: &'a HgPath,
223 parent: Option<&'a HasIgnoredAncestor<'a>>,
236 parent: Option<&'a HasIgnoredAncestor<'a>>,
224 cache: OnceCell<bool>,
237 cache: OnceCell<bool>,
225 }
238 }
226
239
227 impl<'a> HasIgnoredAncestor<'a> {
240 impl<'a> HasIgnoredAncestor<'a> {
228 fn create(
241 fn create(
229 parent: Option<&'a HasIgnoredAncestor<'a>>,
242 parent: Option<&'a HasIgnoredAncestor<'a>>,
230 path: &'a HgPath,
243 path: &'a HgPath,
231 ) -> HasIgnoredAncestor<'a> {
244 ) -> HasIgnoredAncestor<'a> {
232 Self {
245 Self {
233 path,
246 path,
234 parent,
247 parent,
235 cache: OnceCell::new(),
248 cache: OnceCell::new(),
236 }
249 }
237 }
250 }
238
251
239 fn force<'b>(&self, ignore_fn: &IgnoreFnType<'b>) -> bool {
252 fn force<'b>(&self, ignore_fn: &IgnoreFnType<'b>) -> bool {
240 match self.parent {
253 match self.parent {
241 None => false,
254 None => false,
242 Some(parent) => {
255 Some(parent) => {
243 *(self.cache.get_or_init(|| {
256 *(self.cache.get_or_init(|| {
244 parent.force(ignore_fn) || ignore_fn(self.path)
257 parent.force(ignore_fn) || ignore_fn(self.path)
245 }))
258 }))
246 }
259 }
247 }
260 }
248 }
261 }
249 }
262 }
250
263
251 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
264 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
252 fn push_outcome(
265 fn push_outcome(
253 &self,
266 &self,
254 which: Outcome,
267 which: Outcome,
255 dirstate_node: &NodeRef<'tree, 'on_disk>,
268 dirstate_node: &NodeRef<'tree, 'on_disk>,
256 ) -> Result<(), DirstateV2ParseError> {
269 ) -> Result<(), DirstateV2ParseError> {
257 let path = dirstate_node
270 let path = dirstate_node
258 .full_path_borrowed(self.dmap.on_disk)?
271 .full_path_borrowed(self.dmap.on_disk)?
259 .detach_from_tree();
272 .detach_from_tree();
260 let copy_source = if self.options.list_copies {
273 let copy_source = if self.options.list_copies {
261 dirstate_node
274 dirstate_node
262 .copy_source_borrowed(self.dmap.on_disk)?
275 .copy_source_borrowed(self.dmap.on_disk)?
263 .map(|source| source.detach_from_tree())
276 .map(|source| source.detach_from_tree())
264 } else {
277 } else {
265 None
278 None
266 };
279 };
267 self.push_outcome_common(which, path, copy_source);
280 self.push_outcome_common(which, path, copy_source);
268 Ok(())
281 Ok(())
269 }
282 }
270
283
271 fn push_outcome_without_copy_source(
284 fn push_outcome_without_copy_source(
272 &self,
285 &self,
273 which: Outcome,
286 which: Outcome,
274 path: &BorrowedPath<'_, 'on_disk>,
287 path: &BorrowedPath<'_, 'on_disk>,
275 ) {
288 ) {
276 self.push_outcome_common(which, path.detach_from_tree(), None)
289 self.push_outcome_common(which, path.detach_from_tree(), None)
277 }
290 }
278
291
279 fn push_outcome_common(
292 fn push_outcome_common(
280 &self,
293 &self,
281 which: Outcome,
294 which: Outcome,
282 path: HgPathCow<'on_disk>,
295 path: HgPathCow<'on_disk>,
283 copy_source: Option<HgPathCow<'on_disk>>,
296 copy_source: Option<HgPathCow<'on_disk>>,
284 ) {
297 ) {
285 let mut outcome = self.outcome.lock().unwrap();
298 let mut outcome = self.outcome.lock().unwrap();
286 let vec = match which {
299 let vec = match which {
287 Outcome::Modified => &mut outcome.modified,
300 Outcome::Modified => &mut outcome.modified,
288 Outcome::Added => &mut outcome.added,
301 Outcome::Added => &mut outcome.added,
289 Outcome::Removed => &mut outcome.removed,
302 Outcome::Removed => &mut outcome.removed,
290 Outcome::Deleted => &mut outcome.deleted,
303 Outcome::Deleted => &mut outcome.deleted,
291 Outcome::Clean => &mut outcome.clean,
304 Outcome::Clean => &mut outcome.clean,
292 Outcome::Ignored => &mut outcome.ignored,
305 Outcome::Ignored => &mut outcome.ignored,
293 Outcome::Unknown => &mut outcome.unknown,
306 Outcome::Unknown => &mut outcome.unknown,
294 Outcome::Unsure => &mut outcome.unsure,
307 Outcome::Unsure => &mut outcome.unsure,
295 };
308 };
296 vec.push(StatusPath { path, copy_source });
309 vec.push(StatusPath { path, copy_source });
297 }
310 }
298
311
299 fn read_dir(
312 fn read_dir(
300 &self,
313 &self,
301 hg_path: &HgPath,
314 hg_path: &HgPath,
302 fs_path: &Path,
315 fs_path: &Path,
303 is_at_repo_root: bool,
316 is_at_repo_root: bool,
304 ) -> Result<Vec<DirEntry>, ()> {
317 ) -> Result<Vec<DirEntry>, ()> {
305 DirEntry::read_dir(fs_path, is_at_repo_root)
318 DirEntry::read_dir(fs_path, is_at_repo_root)
306 .map_err(|error| self.io_error(error, hg_path))
319 .map_err(|error| self.io_error(error, hg_path))
307 }
320 }
308
321
309 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
322 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
310 let errno = error.raw_os_error().expect("expected real OS error");
323 let errno = error.raw_os_error().expect("expected real OS error");
311 self.outcome
324 self.outcome
312 .lock()
325 .lock()
313 .unwrap()
326 .unwrap()
314 .bad
327 .bad
315 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
328 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
316 }
329 }
317
330
318 fn check_for_outdated_directory_cache(
331 fn check_for_outdated_directory_cache(
319 &self,
332 &self,
320 dirstate_node: &NodeRef<'tree, 'on_disk>,
333 dirstate_node: &NodeRef<'tree, 'on_disk>,
321 ) -> Result<bool, DirstateV2ParseError> {
334 ) -> Result<bool, DirstateV2ParseError> {
322 if self.ignore_patterns_have_changed == Some(true)
335 if self.ignore_patterns_have_changed == Some(true)
323 && dirstate_node.cached_directory_mtime()?.is_some()
336 && dirstate_node.cached_directory_mtime()?.is_some()
324 {
337 {
325 self.outdated_cached_directories.lock().unwrap().push(
338 self.outdated_cached_directories.lock().unwrap().push(
326 dirstate_node
339 dirstate_node
327 .full_path_borrowed(self.dmap.on_disk)?
340 .full_path_borrowed(self.dmap.on_disk)?
328 .detach_from_tree(),
341 .detach_from_tree(),
329 );
342 );
330 return Ok(true);
343 return Ok(true);
331 }
344 }
332 Ok(false)
345 Ok(false)
333 }
346 }
334
347
335 /// If this returns true, we can get accurate results by only using
348 /// If this returns true, we can get accurate results by only using
336 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
349 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
337 /// need to call `read_dir`.
350 /// need to call `read_dir`.
338 fn can_skip_fs_readdir(
351 fn can_skip_fs_readdir(
339 &self,
352 &self,
340 directory_entry: &DirEntry,
353 directory_entry: &DirEntry,
341 cached_directory_mtime: Option<TruncatedTimestamp>,
354 cached_directory_mtime: Option<TruncatedTimestamp>,
342 ) -> bool {
355 ) -> bool {
343 if !self.options.list_unknown && !self.options.list_ignored {
356 if !self.options.list_unknown && !self.options.list_ignored {
344 // All states that we care about listing have corresponding
357 // All states that we care about listing have corresponding
345 // dirstate entries.
358 // dirstate entries.
346 // This happens for example with `hg status -mard`.
359 // This happens for example with `hg status -mard`.
347 return true;
360 return true;
348 }
361 }
349 if !self.options.list_ignored
362 if !self.options.list_ignored
350 && self.ignore_patterns_have_changed == Some(false)
363 && self.ignore_patterns_have_changed == Some(false)
351 {
364 {
352 if let Some(cached_mtime) = cached_directory_mtime {
365 if let Some(cached_mtime) = cached_directory_mtime {
353 // The dirstate contains a cached mtime for this directory, set
366 // The dirstate contains a cached mtime for this directory, set
354 // by a previous run of the `status` algorithm which found this
367 // by a previous run of the `status` algorithm which found this
355 // directory eligible for `read_dir` caching.
368 // directory eligible for `read_dir` caching.
356 if let Ok(meta) = directory_entry.symlink_metadata() {
369 if let Ok(meta) = directory_entry.symlink_metadata() {
357 if cached_mtime
370 if cached_mtime
358 .likely_equal_to_mtime_of(&meta)
371 .likely_equal_to_mtime_of(&meta)
359 .unwrap_or(false)
372 .unwrap_or(false)
360 {
373 {
361 // The mtime of that directory has not changed
374 // The mtime of that directory has not changed
362 // since then, which means that the results of
375 // since then, which means that the results of
363 // `read_dir` should also be unchanged.
376 // `read_dir` should also be unchanged.
364 return true;
377 return true;
365 }
378 }
366 }
379 }
367 }
380 }
368 }
381 }
369 false
382 false
370 }
383 }
371
384
372 /// Returns whether all child entries of the filesystem directory have a
385 /// Returns whether all child entries of the filesystem directory have a
373 /// corresponding dirstate node or are ignored.
386 /// corresponding dirstate node or are ignored.
374 fn traverse_fs_directory_and_dirstate<'ancestor>(
387 fn traverse_fs_directory_and_dirstate<'ancestor>(
375 &self,
388 &self,
376 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
389 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
377 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
390 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
378 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
391 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
379 directory_entry: &DirEntry,
392 directory_entry: &DirEntry,
380 cached_directory_mtime: Option<TruncatedTimestamp>,
393 cached_directory_mtime: Option<TruncatedTimestamp>,
381 is_at_repo_root: bool,
394 is_at_repo_root: bool,
382 ) -> Result<bool, DirstateV2ParseError> {
395 ) -> Result<bool, DirstateV2ParseError> {
383 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
396 if self.can_skip_fs_readdir(directory_entry, cached_directory_mtime) {
384 dirstate_nodes
397 dirstate_nodes
385 .par_iter()
398 .par_iter()
386 .map(|dirstate_node| {
399 .map(|dirstate_node| {
387 let fs_path = &directory_entry.fs_path;
400 let fs_path = &directory_entry.fs_path;
388 let fs_path = fs_path.join(get_path_from_bytes(
401 let fs_path = fs_path.join(get_path_from_bytes(
389 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
402 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
390 ));
403 ));
391 match std::fs::symlink_metadata(&fs_path) {
404 match std::fs::symlink_metadata(&fs_path) {
392 Ok(fs_metadata) => {
405 Ok(fs_metadata) => {
393 let file_type = fs_metadata.file_type().into();
406 let file_type = fs_metadata.file_type().into();
394 let entry = DirEntry {
407 let entry = DirEntry {
395 hg_path: Cow::Borrowed(
408 hg_path: Cow::Borrowed(
396 dirstate_node
409 dirstate_node
397 .full_path(self.dmap.on_disk)?,
410 .full_path(self.dmap.on_disk)?,
398 ),
411 ),
399 fs_path: Cow::Borrowed(&fs_path),
412 fs_path: Cow::Borrowed(&fs_path),
400 symlink_metadata: Some(fs_metadata),
413 symlink_metadata: Some(fs_metadata),
401 file_type,
414 file_type,
402 };
415 };
403 self.traverse_fs_and_dirstate(
416 self.traverse_fs_and_dirstate(
404 &entry,
417 &entry,
405 dirstate_node,
418 dirstate_node,
406 has_ignored_ancestor,
419 has_ignored_ancestor,
407 )
420 )
408 }
421 }
409 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
422 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
410 self.traverse_dirstate_only(dirstate_node)
423 self.traverse_dirstate_only(dirstate_node)
411 }
424 }
412 Err(error) => {
425 Err(error) => {
413 let hg_path =
426 let hg_path =
414 dirstate_node.full_path(self.dmap.on_disk)?;
427 dirstate_node.full_path(self.dmap.on_disk)?;
415 self.io_error(error, hg_path);
428 self.io_error(error, hg_path);
416 Ok(())
429 Ok(())
417 }
430 }
418 }
431 }
419 })
432 })
420 .collect::<Result<_, _>>()?;
433 .collect::<Result<_, _>>()?;
421
434
422 // We don’t know, so conservatively say this isn’t the case
435 // We don’t know, so conservatively say this isn’t the case
423 let children_all_have_dirstate_node_or_are_ignored = false;
436 let children_all_have_dirstate_node_or_are_ignored = false;
424
437
425 return Ok(children_all_have_dirstate_node_or_are_ignored);
438 return Ok(children_all_have_dirstate_node_or_are_ignored);
426 }
439 }
427
440
428 let readdir_succeeded;
441 let readdir_succeeded;
429 let mut fs_entries = if let Ok(entries) = self.read_dir(
442 let mut fs_entries = if let Ok(entries) = self.read_dir(
430 directory_hg_path,
443 directory_hg_path,
431 &directory_entry.fs_path,
444 &directory_entry.fs_path,
432 is_at_repo_root,
445 is_at_repo_root,
433 ) {
446 ) {
434 readdir_succeeded = true;
447 readdir_succeeded = true;
435 entries
448 entries
436 } else {
449 } else {
437 // Treat an unreadable directory (typically because of insufficient
450 // Treat an unreadable directory (typically because of insufficient
438 // permissions) like an empty directory. `self.read_dir` has
451 // permissions) like an empty directory. `self.read_dir` has
439 // already called `self.io_error` so a warning will be emitted.
452 // already called `self.io_error` so a warning will be emitted.
440 // We still need to remember that there was an error so that we
453 // We still need to remember that there was an error so that we
441 // know not to cache this result.
454 // know not to cache this result.
442 readdir_succeeded = false;
455 readdir_succeeded = false;
443 Vec::new()
456 Vec::new()
444 };
457 };
445
458
446 // `merge_join_by` requires both its input iterators to be sorted:
459 // `merge_join_by` requires both its input iterators to be sorted:
447
460
448 let dirstate_nodes = dirstate_nodes.sorted();
461 let dirstate_nodes = dirstate_nodes.sorted();
449 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
462 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
450 // https://github.com/rust-lang/rust/issues/34162
463 // https://github.com/rust-lang/rust/issues/34162
451 fs_entries.sort_unstable_by(|e1, e2| e1.hg_path.cmp(&e2.hg_path));
464 fs_entries.sort_unstable_by(|e1, e2| e1.hg_path.cmp(&e2.hg_path));
452
465
453 // Propagate here any error that would happen inside the comparison
466 // Propagate here any error that would happen inside the comparison
454 // callback below
467 // callback below
455 for dirstate_node in &dirstate_nodes {
468 for dirstate_node in &dirstate_nodes {
456 dirstate_node.base_name(self.dmap.on_disk)?;
469 dirstate_node.base_name(self.dmap.on_disk)?;
457 }
470 }
458 itertools::merge_join_by(
471 itertools::merge_join_by(
459 dirstate_nodes,
472 dirstate_nodes,
460 &fs_entries,
473 &fs_entries,
461 |dirstate_node, fs_entry| {
474 |dirstate_node, fs_entry| {
462 // This `unwrap` never panics because we already propagated
475 // This `unwrap` never panics because we already propagated
463 // those errors above
476 // those errors above
464 dirstate_node
477 dirstate_node
465 .base_name(self.dmap.on_disk)
478 .base_name(self.dmap.on_disk)
466 .unwrap()
479 .unwrap()
467 .cmp(&fs_entry.hg_path)
480 .cmp(&fs_entry.hg_path)
468 },
481 },
469 )
482 )
470 .par_bridge()
483 .par_bridge()
471 .map(|pair| {
484 .map(|pair| {
472 use itertools::EitherOrBoth::*;
485 use itertools::EitherOrBoth::*;
473 let has_dirstate_node_or_is_ignored = match pair {
486 let has_dirstate_node_or_is_ignored = match pair {
474 Both(dirstate_node, fs_entry) => {
487 Both(dirstate_node, fs_entry) => {
475 self.traverse_fs_and_dirstate(
488 self.traverse_fs_and_dirstate(
476 fs_entry,
489 fs_entry,
477 dirstate_node,
490 dirstate_node,
478 has_ignored_ancestor,
491 has_ignored_ancestor,
479 )?;
492 )?;
480 true
493 true
481 }
494 }
482 Left(dirstate_node) => {
495 Left(dirstate_node) => {
483 self.traverse_dirstate_only(dirstate_node)?;
496 self.traverse_dirstate_only(dirstate_node)?;
484 true
497 true
485 }
498 }
486 Right(fs_entry) => self.traverse_fs_only(
499 Right(fs_entry) => self.traverse_fs_only(
487 has_ignored_ancestor.force(&self.ignore_fn),
500 has_ignored_ancestor.force(&self.ignore_fn),
488 directory_hg_path,
501 directory_hg_path,
489 fs_entry,
502 fs_entry,
490 ),
503 ),
491 };
504 };
492 Ok(has_dirstate_node_or_is_ignored)
505 Ok(has_dirstate_node_or_is_ignored)
493 })
506 })
494 .try_reduce(|| true, |a, b| Ok(a && b))
507 .try_reduce(|| true, |a, b| Ok(a && b))
495 .map(|res| res && readdir_succeeded)
508 .map(|res| res && readdir_succeeded)
496 }
509 }
497
510
498 fn traverse_fs_and_dirstate<'ancestor>(
511 fn traverse_fs_and_dirstate<'ancestor>(
499 &self,
512 &self,
500 fs_entry: &DirEntry,
513 fs_entry: &DirEntry,
501 dirstate_node: NodeRef<'tree, 'on_disk>,
514 dirstate_node: NodeRef<'tree, 'on_disk>,
502 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
515 has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
503 ) -> Result<(), DirstateV2ParseError> {
516 ) -> Result<(), DirstateV2ParseError> {
504 let outdated_dircache =
517 let outdated_dircache =
505 self.check_for_outdated_directory_cache(&dirstate_node)?;
518 self.check_for_outdated_directory_cache(&dirstate_node)?;
506 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
519 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
507 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
520 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
508 if !file_or_symlink {
521 if !file_or_symlink {
509 // If we previously had a file here, it was removed (with
522 // If we previously had a file here, it was removed (with
510 // `hg rm` or similar) or deleted before it could be
523 // `hg rm` or similar) or deleted before it could be
511 // replaced by a directory or something else.
524 // replaced by a directory or something else.
512 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
525 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
513 }
526 }
514 if let Some(bad_type) = fs_entry.is_bad() {
527 if let Some(bad_type) = fs_entry.is_bad() {
515 if self.matcher.exact_match(hg_path) {
528 if self.matcher.exact_match(hg_path) {
516 let path = dirstate_node.full_path(self.dmap.on_disk)?;
529 let path = dirstate_node.full_path(self.dmap.on_disk)?;
517 self.outcome.lock().unwrap().bad.push((
530 self.outcome.lock().unwrap().bad.push((
518 path.to_owned().into(),
531 path.to_owned().into(),
519 BadMatch::BadType(bad_type),
532 BadMatch::BadType(bad_type),
520 ))
533 ))
521 }
534 }
522 }
535 }
523 if fs_entry.is_dir() {
536 if fs_entry.is_dir() {
524 if self.options.collect_traversed_dirs {
537 if self.options.collect_traversed_dirs {
525 self.outcome
538 self.outcome
526 .lock()
539 .lock()
527 .unwrap()
540 .unwrap()
528 .traversed
541 .traversed
529 .push(hg_path.detach_from_tree())
542 .push(hg_path.detach_from_tree())
530 }
543 }
531 let is_ignored = HasIgnoredAncestor::create(
544 let is_ignored = HasIgnoredAncestor::create(
532 Some(has_ignored_ancestor),
545 Some(has_ignored_ancestor),
533 hg_path,
546 hg_path,
534 );
547 );
535 let is_at_repo_root = false;
548 let is_at_repo_root = false;
536 let children_all_have_dirstate_node_or_are_ignored = self
549 let children_all_have_dirstate_node_or_are_ignored = self
537 .traverse_fs_directory_and_dirstate(
550 .traverse_fs_directory_and_dirstate(
538 &is_ignored,
551 &is_ignored,
539 dirstate_node.children(self.dmap.on_disk)?,
552 dirstate_node.children(self.dmap.on_disk)?,
540 hg_path,
553 hg_path,
541 fs_entry,
554 fs_entry,
542 dirstate_node.cached_directory_mtime()?,
555 dirstate_node.cached_directory_mtime()?,
543 is_at_repo_root,
556 is_at_repo_root,
544 )?;
557 )?;
545 self.maybe_save_directory_mtime(
558 self.maybe_save_directory_mtime(
546 children_all_have_dirstate_node_or_are_ignored,
559 children_all_have_dirstate_node_or_are_ignored,
547 fs_entry,
560 fs_entry,
548 dirstate_node,
561 dirstate_node,
549 outdated_dircache,
562 outdated_dircache,
550 )?
563 )?
551 } else {
564 } else {
552 if file_or_symlink && self.matcher.matches(hg_path) {
565 if file_or_symlink && self.matcher.matches(hg_path) {
553 if let Some(entry) = dirstate_node.entry()? {
566 if let Some(entry) = dirstate_node.entry()? {
554 if !entry.any_tracked() {
567 if !entry.any_tracked() {
555 // Forward-compat if we start tracking unknown/ignored
568 // Forward-compat if we start tracking unknown/ignored
556 // files for caching reasons
569 // files for caching reasons
557 self.mark_unknown_or_ignored(
570 self.mark_unknown_or_ignored(
558 has_ignored_ancestor.force(&self.ignore_fn),
571 has_ignored_ancestor.force(&self.ignore_fn),
559 hg_path,
572 hg_path,
560 );
573 );
561 }
574 }
562 if entry.added() {
575 if entry.added() {
563 self.push_outcome(Outcome::Added, &dirstate_node)?;
576 self.push_outcome(Outcome::Added, &dirstate_node)?;
564 } else if entry.removed() {
577 } else if entry.removed() {
565 self.push_outcome(Outcome::Removed, &dirstate_node)?;
578 self.push_outcome(Outcome::Removed, &dirstate_node)?;
566 } else if entry.modified() {
579 } else if entry.modified() {
567 self.push_outcome(Outcome::Modified, &dirstate_node)?;
580 self.push_outcome(Outcome::Modified, &dirstate_node)?;
568 } else {
581 } else {
569 self.handle_normal_file(&dirstate_node, fs_entry)?;
582 self.handle_normal_file(&dirstate_node, fs_entry)?;
570 }
583 }
571 } else {
584 } else {
572 // `node.entry.is_none()` indicates a "directory"
585 // `node.entry.is_none()` indicates a "directory"
573 // node, but the filesystem has a file
586 // node, but the filesystem has a file
574 self.mark_unknown_or_ignored(
587 self.mark_unknown_or_ignored(
575 has_ignored_ancestor.force(&self.ignore_fn),
588 has_ignored_ancestor.force(&self.ignore_fn),
576 hg_path,
589 hg_path,
577 );
590 );
578 }
591 }
579 }
592 }
580
593
581 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
594 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
582 {
595 {
583 self.traverse_dirstate_only(child_node)?
596 self.traverse_dirstate_only(child_node)?
584 }
597 }
585 }
598 }
586 Ok(())
599 Ok(())
587 }
600 }
588
601
589 /// Save directory mtime if applicable.
602 /// Save directory mtime if applicable.
590 ///
603 ///
591 /// `outdated_directory_cache` is `true` if we've just invalidated the
604 /// `outdated_directory_cache` is `true` if we've just invalidated the
592 /// cache for this directory in `check_for_outdated_directory_cache`,
605 /// cache for this directory in `check_for_outdated_directory_cache`,
593 /// which forces the update.
606 /// which forces the update.
594 fn maybe_save_directory_mtime(
607 fn maybe_save_directory_mtime(
595 &self,
608 &self,
596 children_all_have_dirstate_node_or_are_ignored: bool,
609 children_all_have_dirstate_node_or_are_ignored: bool,
597 directory_entry: &DirEntry,
610 directory_entry: &DirEntry,
598 dirstate_node: NodeRef<'tree, 'on_disk>,
611 dirstate_node: NodeRef<'tree, 'on_disk>,
599 outdated_directory_cache: bool,
612 outdated_directory_cache: bool,
600 ) -> Result<(), DirstateV2ParseError> {
613 ) -> Result<(), DirstateV2ParseError> {
601 if !children_all_have_dirstate_node_or_are_ignored {
614 if !children_all_have_dirstate_node_or_are_ignored {
602 return Ok(());
615 return Ok(());
603 }
616 }
604 // All filesystem directory entries from `read_dir` have a
617 // All filesystem directory entries from `read_dir` have a
605 // corresponding node in the dirstate, so we can reconstitute the
618 // corresponding node in the dirstate, so we can reconstitute the
606 // names of those entries without calling `read_dir` again.
619 // names of those entries without calling `read_dir` again.
607
620
608 // TODO: use let-else here and below when available:
621 // TODO: use let-else here and below when available:
609 // https://github.com/rust-lang/rust/issues/87335
622 // https://github.com/rust-lang/rust/issues/87335
610 let status_start = if let Some(status_start) =
623 let status_start = if let Some(status_start) =
611 &self.filesystem_time_at_status_start
624 &self.filesystem_time_at_status_start
612 {
625 {
613 status_start
626 status_start
614 } else {
627 } else {
615 return Ok(());
628 return Ok(());
616 };
629 };
617
630
618 // Although the Rust standard library’s `SystemTime` type
631 // Although the Rust standard library’s `SystemTime` type
619 // has nanosecond precision, the times reported for a
632 // has nanosecond precision, the times reported for a
620 // directory’s (or file’s) modified time may have lower
633 // directory’s (or file’s) modified time may have lower
621 // resolution based on the filesystem (for example ext3
634 // resolution based on the filesystem (for example ext3
622 // only stores integer seconds), kernel (see
635 // only stores integer seconds), kernel (see
623 // https://stackoverflow.com/a/14393315/1162888), etc.
636 // https://stackoverflow.com/a/14393315/1162888), etc.
624 let metadata = match directory_entry.symlink_metadata() {
637 let metadata = match directory_entry.symlink_metadata() {
625 Ok(meta) => meta,
638 Ok(meta) => meta,
626 Err(_) => return Ok(()),
639 Err(_) => return Ok(()),
627 };
640 };
628
641
629 let directory_mtime = match TruncatedTimestamp::for_reliable_mtime_of(
642 let directory_mtime = match TruncatedTimestamp::for_reliable_mtime_of(
630 &metadata,
643 &metadata,
631 status_start,
644 status_start,
632 ) {
645 ) {
633 Ok(Some(directory_mtime)) => directory_mtime,
646 Ok(Some(directory_mtime)) => directory_mtime,
634 Ok(None) => {
647 Ok(None) => {
635 // The directory was modified too recently,
648 // The directory was modified too recently,
636 // don’t cache its `read_dir` results.
649 // don’t cache its `read_dir` results.
637 //
650 //
638 // 1. A change to this directory (direct child was
651 // 1. A change to this directory (direct child was
639 // added or removed) cause its mtime to be set
652 // added or removed) cause its mtime to be set
640 // (possibly truncated) to `directory_mtime`
653 // (possibly truncated) to `directory_mtime`
641 // 2. This `status` algorithm calls `read_dir`
654 // 2. This `status` algorithm calls `read_dir`
642 // 3. An other change is made to the same directory is
655 // 3. An other change is made to the same directory is
643 // made so that calling `read_dir` agin would give
656 // made so that calling `read_dir` agin would give
644 // different results, but soon enough after 1. that
657 // different results, but soon enough after 1. that
645 // the mtime stays the same
658 // the mtime stays the same
646 //
659 //
647 // On a system where the time resolution poor, this
660 // On a system where the time resolution poor, this
648 // scenario is not unlikely if all three steps are caused
661 // scenario is not unlikely if all three steps are caused
649 // by the same script.
662 // by the same script.
650 return Ok(());
663 return Ok(());
651 }
664 }
652 Err(_) => {
665 Err(_) => {
653 // OS/libc does not support mtime?
666 // OS/libc does not support mtime?
654 return Ok(());
667 return Ok(());
655 }
668 }
656 };
669 };
657 // We’ve observed (through `status_start`) that time has
670 // We’ve observed (through `status_start`) that time has
658 // “progressed” since `directory_mtime`, so any further
671 // “progressed” since `directory_mtime`, so any further
659 // change to this directory is extremely likely to cause a
672 // change to this directory is extremely likely to cause a
660 // different mtime.
673 // different mtime.
661 //
674 //
662 // Having the same mtime again is not entirely impossible
675 // Having the same mtime again is not entirely impossible
663 // since the system clock is not monotonous. It could jump
676 // since the system clock is not monotonous. It could jump
664 // backward to some point before `directory_mtime`, then a
677 // backward to some point before `directory_mtime`, then a
665 // directory change could potentially happen during exactly
678 // directory change could potentially happen during exactly
666 // the wrong tick.
679 // the wrong tick.
667 //
680 //
668 // We deem this scenario (unlike the previous one) to be
681 // We deem this scenario (unlike the previous one) to be
669 // unlikely enough in practice.
682 // unlikely enough in practice.
670
683
671 let is_up_to_date = if let Some(cached) =
684 let is_up_to_date = if let Some(cached) =
672 dirstate_node.cached_directory_mtime()?
685 dirstate_node.cached_directory_mtime()?
673 {
686 {
674 !outdated_directory_cache && cached.likely_equal(directory_mtime)
687 !outdated_directory_cache && cached.likely_equal(directory_mtime)
675 } else {
688 } else {
676 false
689 false
677 };
690 };
678 if !is_up_to_date {
691 if !is_up_to_date {
679 let hg_path = dirstate_node
692 let hg_path = dirstate_node
680 .full_path_borrowed(self.dmap.on_disk)?
693 .full_path_borrowed(self.dmap.on_disk)?
681 .detach_from_tree();
694 .detach_from_tree();
682 self.new_cacheable_directories
695 self.new_cacheable_directories
683 .lock()
696 .lock()
684 .unwrap()
697 .unwrap()
685 .push((hg_path, directory_mtime))
698 .push((hg_path, directory_mtime))
686 }
699 }
687 Ok(())
700 Ok(())
688 }
701 }
689
702
690 /// A file that is clean in the dirstate was found in the filesystem
703 /// A file that is clean in the dirstate was found in the filesystem
691 fn handle_normal_file(
704 fn handle_normal_file(
692 &self,
705 &self,
693 dirstate_node: &NodeRef<'tree, 'on_disk>,
706 dirstate_node: &NodeRef<'tree, 'on_disk>,
694 fs_entry: &DirEntry,
707 fs_entry: &DirEntry,
695 ) -> Result<(), DirstateV2ParseError> {
708 ) -> Result<(), DirstateV2ParseError> {
696 // Keep the low 31 bits
709 // Keep the low 31 bits
697 fn truncate_u64(value: u64) -> i32 {
710 fn truncate_u64(value: u64) -> i32 {
698 (value & 0x7FFF_FFFF) as i32
711 (value & 0x7FFF_FFFF) as i32
699 }
712 }
700
713
701 let fs_metadata = match fs_entry.symlink_metadata() {
714 let fs_metadata = match fs_entry.symlink_metadata() {
702 Ok(meta) => meta,
715 Ok(meta) => meta,
703 Err(_) => return Ok(()),
716 Err(_) => return Ok(()),
704 };
717 };
705
718
706 let entry = dirstate_node
719 let entry = dirstate_node
707 .entry()?
720 .entry()?
708 .expect("handle_normal_file called with entry-less node");
721 .expect("handle_normal_file called with entry-less node");
709 let mode_changed =
722 let mode_changed =
710 || self.options.check_exec && entry.mode_changed(&fs_metadata);
723 || self.options.check_exec && entry.mode_changed(&fs_metadata);
711 let size = entry.size();
724 let size = entry.size();
712 let size_changed = size != truncate_u64(fs_metadata.len());
725 let size_changed = size != truncate_u64(fs_metadata.len());
713 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
726 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
714 // issue6456: Size returned may be longer due to encryption
727 // issue6456: Size returned may be longer due to encryption
715 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
728 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
716 self.push_outcome(Outcome::Unsure, dirstate_node)?
729 self.push_outcome(Outcome::Unsure, dirstate_node)?
717 } else if dirstate_node.has_copy_source()
730 } else if dirstate_node.has_copy_source()
718 || entry.is_from_other_parent()
731 || entry.is_from_other_parent()
719 || (size >= 0 && (size_changed || mode_changed()))
732 || (size >= 0 && (size_changed || mode_changed()))
720 {
733 {
721 self.push_outcome(Outcome::Modified, dirstate_node)?
734 self.push_outcome(Outcome::Modified, dirstate_node)?
722 } else {
735 } else {
723 let mtime_looks_clean = if let Some(dirstate_mtime) =
736 let mtime_looks_clean = if let Some(dirstate_mtime) =
724 entry.truncated_mtime()
737 entry.truncated_mtime()
725 {
738 {
726 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
739 let fs_mtime = TruncatedTimestamp::for_mtime_of(&fs_metadata)
727 .expect("OS/libc does not support mtime?");
740 .expect("OS/libc does not support mtime?");
728 // There might be a change in the future if for example the
741 // There might be a change in the future if for example the
729 // internal clock become off while process run, but this is a
742 // internal clock become off while process run, but this is a
730 // case where the issues the user would face
743 // case where the issues the user would face
731 // would be a lot worse and there is nothing we
744 // would be a lot worse and there is nothing we
732 // can really do.
745 // can really do.
733 fs_mtime.likely_equal(dirstate_mtime)
746 fs_mtime.likely_equal(dirstate_mtime)
734 } else {
747 } else {
735 // No mtime in the dirstate entry
748 // No mtime in the dirstate entry
736 false
749 false
737 };
750 };
738 if !mtime_looks_clean {
751 if !mtime_looks_clean {
739 self.push_outcome(Outcome::Unsure, dirstate_node)?
752 self.push_outcome(Outcome::Unsure, dirstate_node)?
740 } else if self.options.list_clean {
753 } else if self.options.list_clean {
741 self.push_outcome(Outcome::Clean, dirstate_node)?
754 self.push_outcome(Outcome::Clean, dirstate_node)?
742 }
755 }
743 }
756 }
744 Ok(())
757 Ok(())
745 }
758 }
746
759
747 /// A node in the dirstate tree has no corresponding filesystem entry
760 /// A node in the dirstate tree has no corresponding filesystem entry
748 fn traverse_dirstate_only(
761 fn traverse_dirstate_only(
749 &self,
762 &self,
750 dirstate_node: NodeRef<'tree, 'on_disk>,
763 dirstate_node: NodeRef<'tree, 'on_disk>,
751 ) -> Result<(), DirstateV2ParseError> {
764 ) -> Result<(), DirstateV2ParseError> {
752 self.check_for_outdated_directory_cache(&dirstate_node)?;
765 self.check_for_outdated_directory_cache(&dirstate_node)?;
753 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
766 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
754 dirstate_node
767 dirstate_node
755 .children(self.dmap.on_disk)?
768 .children(self.dmap.on_disk)?
756 .par_iter()
769 .par_iter()
757 .map(|child_node| self.traverse_dirstate_only(child_node))
770 .map(|child_node| self.traverse_dirstate_only(child_node))
758 .collect()
771 .collect()
759 }
772 }
760
773
761 /// A node in the dirstate tree has no corresponding *file* on the
774 /// A node in the dirstate tree has no corresponding *file* on the
762 /// filesystem
775 /// filesystem
763 ///
776 ///
764 /// Does nothing on a "directory" node
777 /// Does nothing on a "directory" node
765 fn mark_removed_or_deleted_if_file(
778 fn mark_removed_or_deleted_if_file(
766 &self,
779 &self,
767 dirstate_node: &NodeRef<'tree, 'on_disk>,
780 dirstate_node: &NodeRef<'tree, 'on_disk>,
768 ) -> Result<(), DirstateV2ParseError> {
781 ) -> Result<(), DirstateV2ParseError> {
769 if let Some(entry) = dirstate_node.entry()? {
782 if let Some(entry) = dirstate_node.entry()? {
770 if !entry.any_tracked() {
783 if !entry.any_tracked() {
771 // Future-compat for when we start storing ignored and unknown
784 // Future-compat for when we start storing ignored and unknown
772 // files for caching reasons
785 // files for caching reasons
773 return Ok(());
786 return Ok(());
774 }
787 }
775 let path = dirstate_node.full_path(self.dmap.on_disk)?;
788 let path = dirstate_node.full_path(self.dmap.on_disk)?;
776 if self.matcher.matches(path) {
789 if self.matcher.matches(path) {
777 if entry.removed() {
790 if entry.removed() {
778 self.push_outcome(Outcome::Removed, dirstate_node)?
791 self.push_outcome(Outcome::Removed, dirstate_node)?
779 } else {
792 } else {
780 self.push_outcome(Outcome::Deleted, dirstate_node)?
793 self.push_outcome(Outcome::Deleted, dirstate_node)?
781 }
794 }
782 }
795 }
783 }
796 }
784 Ok(())
797 Ok(())
785 }
798 }
786
799
787 /// Something in the filesystem has no corresponding dirstate node
800 /// Something in the filesystem has no corresponding dirstate node
788 ///
801 ///
789 /// Returns whether that path is ignored
802 /// Returns whether that path is ignored
790 fn traverse_fs_only(
803 fn traverse_fs_only(
791 &self,
804 &self,
792 has_ignored_ancestor: bool,
805 has_ignored_ancestor: bool,
793 directory_hg_path: &HgPath,
806 directory_hg_path: &HgPath,
794 fs_entry: &DirEntry,
807 fs_entry: &DirEntry,
795 ) -> bool {
808 ) -> bool {
796 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
809 let hg_path = directory_hg_path.join(&fs_entry.hg_path);
797 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
810 let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
798 if fs_entry.is_dir() {
811 if fs_entry.is_dir() {
799 let is_ignored =
812 let is_ignored =
800 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
813 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
801 let traverse_children = if is_ignored {
814 let traverse_children = if is_ignored {
802 // Descendants of an ignored directory are all ignored
815 // Descendants of an ignored directory are all ignored
803 self.options.list_ignored
816 self.options.list_ignored
804 } else {
817 } else {
805 // Descendants of an unknown directory may be either unknown or
818 // Descendants of an unknown directory may be either unknown or
806 // ignored
819 // ignored
807 self.options.list_unknown || self.options.list_ignored
820 self.options.list_unknown || self.options.list_ignored
808 };
821 };
809 if traverse_children {
822 if traverse_children {
810 let is_at_repo_root = false;
823 let is_at_repo_root = false;
811 if let Ok(children_fs_entries) =
824 if let Ok(children_fs_entries) =
812 self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
825 self.read_dir(&hg_path, &fs_entry.fs_path, is_at_repo_root)
813 {
826 {
814 children_fs_entries.par_iter().for_each(|child_fs_entry| {
827 children_fs_entries.par_iter().for_each(|child_fs_entry| {
815 self.traverse_fs_only(
828 self.traverse_fs_only(
816 is_ignored,
829 is_ignored,
817 &hg_path,
830 &hg_path,
818 child_fs_entry,
831 child_fs_entry,
819 );
832 );
820 })
833 })
821 }
834 }
822 if self.options.collect_traversed_dirs {
835 if self.options.collect_traversed_dirs {
823 self.outcome.lock().unwrap().traversed.push(hg_path.into())
836 self.outcome.lock().unwrap().traversed.push(hg_path.into())
824 }
837 }
825 }
838 }
826 is_ignored
839 is_ignored
827 } else if file_or_symlink {
840 } else if file_or_symlink {
828 if self.matcher.matches(&hg_path) {
841 if self.matcher.matches(&hg_path) {
829 self.mark_unknown_or_ignored(
842 self.mark_unknown_or_ignored(
830 has_ignored_ancestor,
843 has_ignored_ancestor,
831 &BorrowedPath::InMemory(&hg_path),
844 &BorrowedPath::InMemory(&hg_path),
832 )
845 )
833 } else {
846 } else {
834 // We haven’t computed whether this path is ignored. It
847 // We haven’t computed whether this path is ignored. It
835 // might not be, and a future run of status might have a
848 // might not be, and a future run of status might have a
836 // different matcher that matches it. So treat it as not
849 // different matcher that matches it. So treat it as not
837 // ignored. That is, inhibit readdir caching of the parent
850 // ignored. That is, inhibit readdir caching of the parent
838 // directory.
851 // directory.
839 false
852 false
840 }
853 }
841 } else {
854 } else {
842 // This is neither a directory, a plain file, or a symlink.
855 // This is neither a directory, a plain file, or a symlink.
843 // Treat it like an ignored file.
856 // Treat it like an ignored file.
844 true
857 true
845 }
858 }
846 }
859 }
847
860
848 /// Returns whether that path is ignored
861 /// Returns whether that path is ignored
849 fn mark_unknown_or_ignored(
862 fn mark_unknown_or_ignored(
850 &self,
863 &self,
851 has_ignored_ancestor: bool,
864 has_ignored_ancestor: bool,
852 hg_path: &BorrowedPath<'_, 'on_disk>,
865 hg_path: &BorrowedPath<'_, 'on_disk>,
853 ) -> bool {
866 ) -> bool {
854 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
867 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
855 if is_ignored {
868 if is_ignored {
856 if self.options.list_ignored {
869 if self.options.list_ignored {
857 self.push_outcome_without_copy_source(
870 self.push_outcome_without_copy_source(
858 Outcome::Ignored,
871 Outcome::Ignored,
859 hg_path,
872 hg_path,
860 )
873 )
861 }
874 }
862 } else if self.options.list_unknown {
875 } else if self.options.list_unknown {
863 self.push_outcome_without_copy_source(Outcome::Unknown, hg_path)
876 self.push_outcome_without_copy_source(Outcome::Unknown, hg_path)
864 }
877 }
865 is_ignored
878 is_ignored
866 }
879 }
867 }
880 }
868
881
869 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
882 /// Since [`std::fs::FileType`] cannot be built directly, we emulate what we
870 /// care about.
883 /// care about.
871 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
884 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
872 enum FakeFileType {
885 enum FakeFileType {
873 File,
886 File,
874 Directory,
887 Directory,
875 Symlink,
888 Symlink,
876 BadType(BadType),
889 BadType(BadType),
877 }
890 }
878
891
879 impl From<std::fs::FileType> for FakeFileType {
892 impl From<std::fs::FileType> for FakeFileType {
880 fn from(f: std::fs::FileType) -> Self {
893 fn from(f: std::fs::FileType) -> Self {
881 if f.is_dir() {
894 if f.is_dir() {
882 Self::Directory
895 Self::Directory
883 } else if f.is_file() {
896 } else if f.is_file() {
884 Self::File
897 Self::File
885 } else if f.is_symlink() {
898 } else if f.is_symlink() {
886 Self::Symlink
899 Self::Symlink
887 } else if f.is_fifo() {
900 } else if f.is_fifo() {
888 Self::BadType(BadType::FIFO)
901 Self::BadType(BadType::FIFO)
889 } else if f.is_block_device() {
902 } else if f.is_block_device() {
890 Self::BadType(BadType::BlockDevice)
903 Self::BadType(BadType::BlockDevice)
891 } else if f.is_char_device() {
904 } else if f.is_char_device() {
892 Self::BadType(BadType::CharacterDevice)
905 Self::BadType(BadType::CharacterDevice)
893 } else if f.is_socket() {
906 } else if f.is_socket() {
894 Self::BadType(BadType::Socket)
907 Self::BadType(BadType::Socket)
895 } else {
908 } else {
896 Self::BadType(BadType::Unknown)
909 Self::BadType(BadType::Unknown)
897 }
910 }
898 }
911 }
899 }
912 }
900
913
901 struct DirEntry<'a> {
914 struct DirEntry<'a> {
902 /// Path as stored in the dirstate, or just the filename for optimization.
915 /// Path as stored in the dirstate, or just the filename for optimization.
903 hg_path: HgPathCow<'a>,
916 hg_path: HgPathCow<'a>,
904 /// Filesystem path
917 /// Filesystem path
905 fs_path: Cow<'a, Path>,
918 fs_path: Cow<'a, Path>,
906 /// Lazily computed
919 /// Lazily computed
907 symlink_metadata: Option<std::fs::Metadata>,
920 symlink_metadata: Option<std::fs::Metadata>,
908 /// Already computed for ergonomics.
921 /// Already computed for ergonomics.
909 file_type: FakeFileType,
922 file_type: FakeFileType,
910 }
923 }
911
924
912 impl<'a> DirEntry<'a> {
925 impl<'a> DirEntry<'a> {
913 /// Returns **unsorted** entries in the given directory, with name,
926 /// Returns **unsorted** entries in the given directory, with name,
914 /// metadata and file type.
927 /// metadata and file type.
915 ///
928 ///
916 /// If a `.hg` sub-directory is encountered:
929 /// If a `.hg` sub-directory is encountered:
917 ///
930 ///
918 /// * At the repository root, ignore that sub-directory
931 /// * At the repository root, ignore that sub-directory
919 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
932 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
920 /// list instead.
933 /// list instead.
921 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
934 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
922 // `read_dir` returns a "not found" error for the empty path
935 // `read_dir` returns a "not found" error for the empty path
923 let at_cwd = path == Path::new("");
936 let at_cwd = path == Path::new("");
924 let read_dir_path = if at_cwd { Path::new(".") } else { path };
937 let read_dir_path = if at_cwd { Path::new(".") } else { path };
925 let mut results = Vec::new();
938 let mut results = Vec::new();
926 for entry in read_dir_path.read_dir()? {
939 for entry in read_dir_path.read_dir()? {
927 let entry = entry?;
940 let entry = entry?;
928 let file_type = match entry.file_type() {
941 let file_type = match entry.file_type() {
929 Ok(v) => v,
942 Ok(v) => v,
930 Err(e) => {
943 Err(e) => {
931 // race with file deletion?
944 // race with file deletion?
932 if e.kind() == std::io::ErrorKind::NotFound {
945 if e.kind() == std::io::ErrorKind::NotFound {
933 continue;
946 continue;
934 } else {
947 } else {
935 return Err(e);
948 return Err(e);
936 }
949 }
937 }
950 }
938 };
951 };
939 let file_name = entry.file_name();
952 let file_name = entry.file_name();
940 // FIXME don't do this when cached
953 // FIXME don't do this when cached
941 if file_name == ".hg" {
954 if file_name == ".hg" {
942 if is_at_repo_root {
955 if is_at_repo_root {
943 // Skip the repo’s own .hg (might be a symlink)
956 // Skip the repo’s own .hg (might be a symlink)
944 continue;
957 continue;
945 } else if file_type.is_dir() {
958 } else if file_type.is_dir() {
946 // A .hg sub-directory at another location means a subrepo,
959 // A .hg sub-directory at another location means a subrepo,
947 // skip it entirely.
960 // skip it entirely.
948 return Ok(Vec::new());
961 return Ok(Vec::new());
949 }
962 }
950 }
963 }
951 let full_path = if at_cwd {
964 let full_path = if at_cwd {
952 file_name.clone().into()
965 file_name.clone().into()
953 } else {
966 } else {
954 entry.path()
967 entry.path()
955 };
968 };
956 let filename =
969 let filename =
957 Cow::Owned(get_bytes_from_os_string(file_name).into());
970 Cow::Owned(get_bytes_from_os_string(file_name).into());
958 let file_type = FakeFileType::from(file_type);
971 let file_type = FakeFileType::from(file_type);
959 results.push(DirEntry {
972 results.push(DirEntry {
960 hg_path: filename,
973 hg_path: filename,
961 fs_path: Cow::Owned(full_path.to_path_buf()),
974 fs_path: Cow::Owned(full_path.to_path_buf()),
962 symlink_metadata: None,
975 symlink_metadata: None,
963 file_type,
976 file_type,
964 })
977 })
965 }
978 }
966 Ok(results)
979 Ok(results)
967 }
980 }
968
981
969 fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
982 fn symlink_metadata(&self) -> Result<std::fs::Metadata, std::io::Error> {
970 match &self.symlink_metadata {
983 match &self.symlink_metadata {
971 Some(meta) => Ok(meta.clone()),
984 Some(meta) => Ok(meta.clone()),
972 None => std::fs::symlink_metadata(&self.fs_path),
985 None => std::fs::symlink_metadata(&self.fs_path),
973 }
986 }
974 }
987 }
975
988
976 fn is_dir(&self) -> bool {
989 fn is_dir(&self) -> bool {
977 self.file_type == FakeFileType::Directory
990 self.file_type == FakeFileType::Directory
978 }
991 }
979
992
980 fn is_file(&self) -> bool {
993 fn is_file(&self) -> bool {
981 self.file_type == FakeFileType::File
994 self.file_type == FakeFileType::File
982 }
995 }
983
996
984 fn is_symlink(&self) -> bool {
997 fn is_symlink(&self) -> bool {
985 self.file_type == FakeFileType::Symlink
998 self.file_type == FakeFileType::Symlink
986 }
999 }
987
1000
988 fn is_bad(&self) -> Option<BadType> {
1001 fn is_bad(&self) -> Option<BadType> {
989 match self.file_type {
1002 match self.file_type {
990 FakeFileType::BadType(ty) => Some(ty),
1003 FakeFileType::BadType(ty) => Some(ty),
991 _ => None,
1004 _ => None,
992 }
1005 }
993 }
1006 }
994 }
1007 }
995
1008
996 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
1009 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
997 /// of the give repository.
1010 /// of the give repository.
998 ///
1011 ///
999 /// This is similar to `SystemTime::now()`, with the result truncated to the
1012 /// This is similar to `SystemTime::now()`, with the result truncated to the
1000 /// same time resolution as other files’ modification times. Using `.hg`
1013 /// same time resolution as other files’ modification times. Using `.hg`
1001 /// instead of the system’s default temporary directory (such as `/tmp`) makes
1014 /// instead of the system’s default temporary directory (such as `/tmp`) makes
1002 /// it more likely the temporary file is in the same disk partition as contents
1015 /// it more likely the temporary file is in the same disk partition as contents
1003 /// of the working directory, which can matter since different filesystems may
1016 /// of the working directory, which can matter since different filesystems may
1004 /// store timestamps with different resolutions.
1017 /// store timestamps with different resolutions.
1005 ///
1018 ///
1006 /// This may fail, typically if we lack write permissions. In that case we
1019 /// This may fail, typically if we lack write permissions. In that case we
1007 /// should continue the `status()` algoritm anyway and consider the current
1020 /// should continue the `status()` algoritm anyway and consider the current
1008 /// date/time to be unknown.
1021 /// date/time to be unknown.
1009 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
1022 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
1010 tempfile::tempfile_in(repo_root.join(".hg"))?
1023 tempfile::tempfile_in(repo_root.join(".hg"))?
1011 .metadata()?
1024 .metadata()?
1012 .modified()
1025 .modified()
1013 }
1026 }
General Comments 0
You need to be logged in to leave comments. Login now