##// END OF EJS Templates
rust-dirstatemap: add unit tests...
Raphaël Gomès -
r50017:e7b74bb6 default
parent child Browse files
Show More
@@ -1,1435 +1,1849 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use micro_timer::timed;
3 use std::borrow::Cow;
3 use std::borrow::Cow;
4 use std::path::PathBuf;
4 use std::path::PathBuf;
5
5
6 use super::on_disk;
6 use super::on_disk;
7 use super::on_disk::DirstateV2ParseError;
7 use super::on_disk::DirstateV2ParseError;
8 use super::owning::OwningDirstateMap;
8 use super::owning::OwningDirstateMap;
9 use super::path_with_basename::WithBasename;
9 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
10 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
11 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
12 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::CopyMapIter;
13 use crate::dirstate::CopyMapIter;
14 use crate::dirstate::DirstateV2Data;
14 use crate::dirstate::DirstateV2Data;
15 use crate::dirstate::ParentFileData;
15 use crate::dirstate::ParentFileData;
16 use crate::dirstate::StateMapIter;
16 use crate::dirstate::StateMapIter;
17 use crate::dirstate::TruncatedTimestamp;
17 use crate::dirstate::TruncatedTimestamp;
18 use crate::matchers::Matcher;
18 use crate::matchers::Matcher;
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::EntryState;
25 use crate::EntryState;
26 use crate::FastHashbrownMap as FastHashMap;
26 use crate::FastHashbrownMap as FastHashMap;
27 use crate::PatternFileWarning;
27 use crate::PatternFileWarning;
28 use crate::StatusError;
28 use crate::StatusError;
29 use crate::StatusOptions;
29 use crate::StatusOptions;
30
30
31 /// Append to an existing data file if the amount of unreachable data (not used
31 /// Append to an existing data file if the amount of unreachable data (not used
32 /// anymore) is less than this fraction of the total amount of existing data.
32 /// anymore) is less than this fraction of the total amount of existing data.
33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
34
34
35 #[derive(Debug)]
35 #[derive(Debug)]
36 pub struct DirstateMap<'on_disk> {
36 pub struct DirstateMap<'on_disk> {
37 /// Contents of the `.hg/dirstate` file
37 /// Contents of the `.hg/dirstate` file
38 pub(super) on_disk: &'on_disk [u8],
38 pub(super) on_disk: &'on_disk [u8],
39
39
40 pub(super) root: ChildNodes<'on_disk>,
40 pub(super) root: ChildNodes<'on_disk>,
41
41
42 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
42 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
43 pub(super) nodes_with_entry_count: u32,
43 pub(super) nodes_with_entry_count: u32,
44
44
45 /// Number of nodes anywhere in the tree that have
45 /// Number of nodes anywhere in the tree that have
46 /// `.copy_source.is_some()`.
46 /// `.copy_source.is_some()`.
47 pub(super) nodes_with_copy_source_count: u32,
47 pub(super) nodes_with_copy_source_count: u32,
48
48
49 /// See on_disk::Header
49 /// See on_disk::Header
50 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
50 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
51
51
52 /// How many bytes of `on_disk` are not used anymore
52 /// How many bytes of `on_disk` are not used anymore
53 pub(super) unreachable_bytes: u32,
53 pub(super) unreachable_bytes: u32,
54 }
54 }
55
55
56 /// Using a plain `HgPathBuf` of the full path from the repository root as a
56 /// Using a plain `HgPathBuf` of the full path from the repository root as a
57 /// map key would also work: all paths in a given map have the same parent
57 /// map key would also work: all paths in a given map have the same parent
58 /// path, so comparing full paths gives the same result as comparing base
58 /// path, so comparing full paths gives the same result as comparing base
59 /// names. However `HashMap` would waste time always re-hashing the same
59 /// names. However `HashMap` would waste time always re-hashing the same
60 /// string prefix.
60 /// string prefix.
61 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
61 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
62
62
63 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
63 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
64 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
64 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
65 #[derive(Debug)]
65 #[derive(Debug)]
66 pub(super) enum BorrowedPath<'tree, 'on_disk> {
66 pub(super) enum BorrowedPath<'tree, 'on_disk> {
67 InMemory(&'tree HgPathBuf),
67 InMemory(&'tree HgPathBuf),
68 OnDisk(&'on_disk HgPath),
68 OnDisk(&'on_disk HgPath),
69 }
69 }
70
70
71 #[derive(Debug)]
71 #[derive(Debug)]
72 pub(super) enum ChildNodes<'on_disk> {
72 pub(super) enum ChildNodes<'on_disk> {
73 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
73 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
74 OnDisk(&'on_disk [on_disk::Node]),
74 OnDisk(&'on_disk [on_disk::Node]),
75 }
75 }
76
76
77 #[derive(Debug)]
77 #[derive(Debug)]
78 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
78 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
79 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
79 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
80 OnDisk(&'on_disk [on_disk::Node]),
80 OnDisk(&'on_disk [on_disk::Node]),
81 }
81 }
82
82
83 #[derive(Debug)]
83 #[derive(Debug)]
84 pub(super) enum NodeRef<'tree, 'on_disk> {
84 pub(super) enum NodeRef<'tree, 'on_disk> {
85 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
85 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
86 OnDisk(&'on_disk on_disk::Node),
86 OnDisk(&'on_disk on_disk::Node),
87 }
87 }
88
88
89 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
89 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
90 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
90 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
91 match *self {
91 match *self {
92 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
92 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
93 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
93 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
94 }
94 }
95 }
95 }
96 }
96 }
97
97
98 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
98 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
99 type Target = HgPath;
99 type Target = HgPath;
100
100
101 fn deref(&self) -> &HgPath {
101 fn deref(&self) -> &HgPath {
102 match *self {
102 match *self {
103 BorrowedPath::InMemory(in_memory) => in_memory,
103 BorrowedPath::InMemory(in_memory) => in_memory,
104 BorrowedPath::OnDisk(on_disk) => on_disk,
104 BorrowedPath::OnDisk(on_disk) => on_disk,
105 }
105 }
106 }
106 }
107 }
107 }
108
108
109 impl Default for ChildNodes<'_> {
109 impl Default for ChildNodes<'_> {
110 fn default() -> Self {
110 fn default() -> Self {
111 ChildNodes::InMemory(Default::default())
111 ChildNodes::InMemory(Default::default())
112 }
112 }
113 }
113 }
114
114
115 impl<'on_disk> ChildNodes<'on_disk> {
115 impl<'on_disk> ChildNodes<'on_disk> {
116 pub(super) fn as_ref<'tree>(
116 pub(super) fn as_ref<'tree>(
117 &'tree self,
117 &'tree self,
118 ) -> ChildNodesRef<'tree, 'on_disk> {
118 ) -> ChildNodesRef<'tree, 'on_disk> {
119 match self {
119 match self {
120 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
120 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
121 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
121 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
122 }
122 }
123 }
123 }
124
124
125 pub(super) fn is_empty(&self) -> bool {
125 pub(super) fn is_empty(&self) -> bool {
126 match self {
126 match self {
127 ChildNodes::InMemory(nodes) => nodes.is_empty(),
127 ChildNodes::InMemory(nodes) => nodes.is_empty(),
128 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
128 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
129 }
129 }
130 }
130 }
131
131
132 fn make_mut(
132 fn make_mut(
133 &mut self,
133 &mut self,
134 on_disk: &'on_disk [u8],
134 on_disk: &'on_disk [u8],
135 unreachable_bytes: &mut u32,
135 unreachable_bytes: &mut u32,
136 ) -> Result<
136 ) -> Result<
137 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
137 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
138 DirstateV2ParseError,
138 DirstateV2ParseError,
139 > {
139 > {
140 match self {
140 match self {
141 ChildNodes::InMemory(nodes) => Ok(nodes),
141 ChildNodes::InMemory(nodes) => Ok(nodes),
142 ChildNodes::OnDisk(nodes) => {
142 ChildNodes::OnDisk(nodes) => {
143 *unreachable_bytes +=
143 *unreachable_bytes +=
144 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
144 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
145 let nodes = nodes
145 let nodes = nodes
146 .iter()
146 .iter()
147 .map(|node| {
147 .map(|node| {
148 Ok((
148 Ok((
149 node.path(on_disk)?,
149 node.path(on_disk)?,
150 node.to_in_memory_node(on_disk)?,
150 node.to_in_memory_node(on_disk)?,
151 ))
151 ))
152 })
152 })
153 .collect::<Result<_, _>>()?;
153 .collect::<Result<_, _>>()?;
154 *self = ChildNodes::InMemory(nodes);
154 *self = ChildNodes::InMemory(nodes);
155 match self {
155 match self {
156 ChildNodes::InMemory(nodes) => Ok(nodes),
156 ChildNodes::InMemory(nodes) => Ok(nodes),
157 ChildNodes::OnDisk(_) => unreachable!(),
157 ChildNodes::OnDisk(_) => unreachable!(),
158 }
158 }
159 }
159 }
160 }
160 }
161 }
161 }
162 }
162 }
163
163
164 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
164 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
165 pub(super) fn get(
165 pub(super) fn get(
166 &self,
166 &self,
167 base_name: &HgPath,
167 base_name: &HgPath,
168 on_disk: &'on_disk [u8],
168 on_disk: &'on_disk [u8],
169 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
169 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
170 match self {
170 match self {
171 ChildNodesRef::InMemory(nodes) => Ok(nodes
171 ChildNodesRef::InMemory(nodes) => Ok(nodes
172 .get_key_value(base_name)
172 .get_key_value(base_name)
173 .map(|(k, v)| NodeRef::InMemory(k, v))),
173 .map(|(k, v)| NodeRef::InMemory(k, v))),
174 ChildNodesRef::OnDisk(nodes) => {
174 ChildNodesRef::OnDisk(nodes) => {
175 let mut parse_result = Ok(());
175 let mut parse_result = Ok(());
176 let search_result = nodes.binary_search_by(|node| {
176 let search_result = nodes.binary_search_by(|node| {
177 match node.base_name(on_disk) {
177 match node.base_name(on_disk) {
178 Ok(node_base_name) => node_base_name.cmp(base_name),
178 Ok(node_base_name) => node_base_name.cmp(base_name),
179 Err(e) => {
179 Err(e) => {
180 parse_result = Err(e);
180 parse_result = Err(e);
181 // Dummy comparison result, `search_result` won’t
181 // Dummy comparison result, `search_result` won’t
182 // be used since `parse_result` is an error
182 // be used since `parse_result` is an error
183 std::cmp::Ordering::Equal
183 std::cmp::Ordering::Equal
184 }
184 }
185 }
185 }
186 });
186 });
187 parse_result.map(|()| {
187 parse_result.map(|()| {
188 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
188 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
189 })
189 })
190 }
190 }
191 }
191 }
192 }
192 }
193
193
194 /// Iterate in undefined order
194 /// Iterate in undefined order
195 pub(super) fn iter(
195 pub(super) fn iter(
196 &self,
196 &self,
197 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
197 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
198 match self {
198 match self {
199 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
199 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
200 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
200 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
201 ),
201 ),
202 ChildNodesRef::OnDisk(nodes) => {
202 ChildNodesRef::OnDisk(nodes) => {
203 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
203 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
204 }
204 }
205 }
205 }
206 }
206 }
207
207
208 /// Iterate in parallel in undefined order
208 /// Iterate in parallel in undefined order
209 pub(super) fn par_iter(
209 pub(super) fn par_iter(
210 &self,
210 &self,
211 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
211 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
212 {
212 {
213 use rayon::prelude::*;
213 use rayon::prelude::*;
214 match self {
214 match self {
215 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
215 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
216 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
216 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
217 ),
217 ),
218 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
218 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
219 nodes.par_iter().map(NodeRef::OnDisk),
219 nodes.par_iter().map(NodeRef::OnDisk),
220 ),
220 ),
221 }
221 }
222 }
222 }
223
223
224 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
224 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
225 match self {
225 match self {
226 ChildNodesRef::InMemory(nodes) => {
226 ChildNodesRef::InMemory(nodes) => {
227 let mut vec: Vec<_> = nodes
227 let mut vec: Vec<_> = nodes
228 .iter()
228 .iter()
229 .map(|(k, v)| NodeRef::InMemory(k, v))
229 .map(|(k, v)| NodeRef::InMemory(k, v))
230 .collect();
230 .collect();
231 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
231 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
232 match node {
232 match node {
233 NodeRef::InMemory(path, _node) => path.base_name(),
233 NodeRef::InMemory(path, _node) => path.base_name(),
234 NodeRef::OnDisk(_) => unreachable!(),
234 NodeRef::OnDisk(_) => unreachable!(),
235 }
235 }
236 }
236 }
237 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
237 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
238 // value: https://github.com/rust-lang/rust/issues/34162
238 // value: https://github.com/rust-lang/rust/issues/34162
239 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
239 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
240 vec
240 vec
241 }
241 }
242 ChildNodesRef::OnDisk(nodes) => {
242 ChildNodesRef::OnDisk(nodes) => {
243 // Nodes on disk are already sorted
243 // Nodes on disk are already sorted
244 nodes.iter().map(NodeRef::OnDisk).collect()
244 nodes.iter().map(NodeRef::OnDisk).collect()
245 }
245 }
246 }
246 }
247 }
247 }
248 }
248 }
249
249
250 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
250 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
251 pub(super) fn full_path(
251 pub(super) fn full_path(
252 &self,
252 &self,
253 on_disk: &'on_disk [u8],
253 on_disk: &'on_disk [u8],
254 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
254 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
255 match self {
255 match self {
256 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
256 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
257 NodeRef::OnDisk(node) => node.full_path(on_disk),
257 NodeRef::OnDisk(node) => node.full_path(on_disk),
258 }
258 }
259 }
259 }
260
260
261 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
261 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
262 /// HgPath>` detached from `'tree`
262 /// HgPath>` detached from `'tree`
263 pub(super) fn full_path_borrowed(
263 pub(super) fn full_path_borrowed(
264 &self,
264 &self,
265 on_disk: &'on_disk [u8],
265 on_disk: &'on_disk [u8],
266 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
266 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
267 match self {
267 match self {
268 NodeRef::InMemory(path, _node) => match path.full_path() {
268 NodeRef::InMemory(path, _node) => match path.full_path() {
269 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
269 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
270 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
270 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
271 },
271 },
272 NodeRef::OnDisk(node) => {
272 NodeRef::OnDisk(node) => {
273 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
273 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
274 }
274 }
275 }
275 }
276 }
276 }
277
277
278 pub(super) fn base_name(
278 pub(super) fn base_name(
279 &self,
279 &self,
280 on_disk: &'on_disk [u8],
280 on_disk: &'on_disk [u8],
281 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
281 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
282 match self {
282 match self {
283 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
283 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
284 NodeRef::OnDisk(node) => node.base_name(on_disk),
284 NodeRef::OnDisk(node) => node.base_name(on_disk),
285 }
285 }
286 }
286 }
287
287
288 pub(super) fn children(
288 pub(super) fn children(
289 &self,
289 &self,
290 on_disk: &'on_disk [u8],
290 on_disk: &'on_disk [u8],
291 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
291 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
292 match self {
292 match self {
293 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
293 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
294 NodeRef::OnDisk(node) => {
294 NodeRef::OnDisk(node) => {
295 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
295 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
296 }
296 }
297 }
297 }
298 }
298 }
299
299
300 pub(super) fn has_copy_source(&self) -> bool {
300 pub(super) fn has_copy_source(&self) -> bool {
301 match self {
301 match self {
302 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
302 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
303 NodeRef::OnDisk(node) => node.has_copy_source(),
303 NodeRef::OnDisk(node) => node.has_copy_source(),
304 }
304 }
305 }
305 }
306
306
307 pub(super) fn copy_source(
307 pub(super) fn copy_source(
308 &self,
308 &self,
309 on_disk: &'on_disk [u8],
309 on_disk: &'on_disk [u8],
310 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
310 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
311 match self {
311 match self {
312 NodeRef::InMemory(_path, node) => {
312 NodeRef::InMemory(_path, node) => {
313 Ok(node.copy_source.as_ref().map(|s| &**s))
313 Ok(node.copy_source.as_ref().map(|s| &**s))
314 }
314 }
315 NodeRef::OnDisk(node) => node.copy_source(on_disk),
315 NodeRef::OnDisk(node) => node.copy_source(on_disk),
316 }
316 }
317 }
317 }
318 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
318 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
319 /// HgPath>` detached from `'tree`
319 /// HgPath>` detached from `'tree`
320 pub(super) fn copy_source_borrowed(
320 pub(super) fn copy_source_borrowed(
321 &self,
321 &self,
322 on_disk: &'on_disk [u8],
322 on_disk: &'on_disk [u8],
323 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
323 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
324 {
324 {
325 Ok(match self {
325 Ok(match self {
326 NodeRef::InMemory(_path, node) => {
326 NodeRef::InMemory(_path, node) => {
327 node.copy_source.as_ref().map(|source| match source {
327 node.copy_source.as_ref().map(|source| match source {
328 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
328 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
329 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
329 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
330 })
330 })
331 }
331 }
332 NodeRef::OnDisk(node) => node
332 NodeRef::OnDisk(node) => node
333 .copy_source(on_disk)?
333 .copy_source(on_disk)?
334 .map(|source| BorrowedPath::OnDisk(source)),
334 .map(|source| BorrowedPath::OnDisk(source)),
335 })
335 })
336 }
336 }
337
337
338 pub(super) fn entry(
338 pub(super) fn entry(
339 &self,
339 &self,
340 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
340 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
341 match self {
341 match self {
342 NodeRef::InMemory(_path, node) => {
342 NodeRef::InMemory(_path, node) => {
343 Ok(node.data.as_entry().copied())
343 Ok(node.data.as_entry().copied())
344 }
344 }
345 NodeRef::OnDisk(node) => node.entry(),
345 NodeRef::OnDisk(node) => node.entry(),
346 }
346 }
347 }
347 }
348
348
349 pub(super) fn state(
349 pub(super) fn state(
350 &self,
350 &self,
351 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
351 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
352 Ok(self.entry()?.and_then(|e| {
352 Ok(self.entry()?.and_then(|e| {
353 if e.any_tracked() {
353 if e.any_tracked() {
354 Some(e.state())
354 Some(e.state())
355 } else {
355 } else {
356 None
356 None
357 }
357 }
358 }))
358 }))
359 }
359 }
360
360
361 pub(super) fn cached_directory_mtime(
361 pub(super) fn cached_directory_mtime(
362 &self,
362 &self,
363 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
363 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
364 match self {
364 match self {
365 NodeRef::InMemory(_path, node) => Ok(match node.data {
365 NodeRef::InMemory(_path, node) => Ok(match node.data {
366 NodeData::CachedDirectory { mtime } => Some(mtime),
366 NodeData::CachedDirectory { mtime } => Some(mtime),
367 _ => None,
367 _ => None,
368 }),
368 }),
369 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
369 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
370 }
370 }
371 }
371 }
372
372
373 pub(super) fn descendants_with_entry_count(&self) -> u32 {
373 pub(super) fn descendants_with_entry_count(&self) -> u32 {
374 match self {
374 match self {
375 NodeRef::InMemory(_path, node) => {
375 NodeRef::InMemory(_path, node) => {
376 node.descendants_with_entry_count
376 node.descendants_with_entry_count
377 }
377 }
378 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
378 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
379 }
379 }
380 }
380 }
381
381
382 pub(super) fn tracked_descendants_count(&self) -> u32 {
382 pub(super) fn tracked_descendants_count(&self) -> u32 {
383 match self {
383 match self {
384 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
384 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
385 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
385 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
386 }
386 }
387 }
387 }
388 }
388 }
389
389
390 /// Represents a file or a directory
390 /// Represents a file or a directory
391 #[derive(Default, Debug)]
391 #[derive(Default, Debug)]
392 pub(super) struct Node<'on_disk> {
392 pub(super) struct Node<'on_disk> {
393 pub(super) data: NodeData,
393 pub(super) data: NodeData,
394
394
395 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
395 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
396
396
397 pub(super) children: ChildNodes<'on_disk>,
397 pub(super) children: ChildNodes<'on_disk>,
398
398
399 /// How many (non-inclusive) descendants of this node have an entry.
399 /// How many (non-inclusive) descendants of this node have an entry.
400 pub(super) descendants_with_entry_count: u32,
400 pub(super) descendants_with_entry_count: u32,
401
401
402 /// How many (non-inclusive) descendants of this node have an entry whose
402 /// How many (non-inclusive) descendants of this node have an entry whose
403 /// state is "tracked".
403 /// state is "tracked".
404 pub(super) tracked_descendants_count: u32,
404 pub(super) tracked_descendants_count: u32,
405 }
405 }
406
406
407 #[derive(Debug)]
407 #[derive(Debug)]
408 pub(super) enum NodeData {
408 pub(super) enum NodeData {
409 Entry(DirstateEntry),
409 Entry(DirstateEntry),
410 CachedDirectory { mtime: TruncatedTimestamp },
410 CachedDirectory { mtime: TruncatedTimestamp },
411 None,
411 None,
412 }
412 }
413
413
414 impl Default for NodeData {
414 impl Default for NodeData {
415 fn default() -> Self {
415 fn default() -> Self {
416 NodeData::None
416 NodeData::None
417 }
417 }
418 }
418 }
419
419
420 impl NodeData {
420 impl NodeData {
421 fn has_entry(&self) -> bool {
421 fn has_entry(&self) -> bool {
422 match self {
422 match self {
423 NodeData::Entry(_) => true,
423 NodeData::Entry(_) => true,
424 _ => false,
424 _ => false,
425 }
425 }
426 }
426 }
427
427
428 fn as_entry(&self) -> Option<&DirstateEntry> {
428 fn as_entry(&self) -> Option<&DirstateEntry> {
429 match self {
429 match self {
430 NodeData::Entry(entry) => Some(entry),
430 NodeData::Entry(entry) => Some(entry),
431 _ => None,
431 _ => None,
432 }
432 }
433 }
433 }
434
434
435 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
435 fn as_entry_mut(&mut self) -> Option<&mut DirstateEntry> {
436 match self {
436 match self {
437 NodeData::Entry(entry) => Some(entry),
437 NodeData::Entry(entry) => Some(entry),
438 _ => None,
438 _ => None,
439 }
439 }
440 }
440 }
441 }
441 }
442
442
443 impl<'on_disk> DirstateMap<'on_disk> {
443 impl<'on_disk> DirstateMap<'on_disk> {
444 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
444 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
445 Self {
445 Self {
446 on_disk,
446 on_disk,
447 root: ChildNodes::default(),
447 root: ChildNodes::default(),
448 nodes_with_entry_count: 0,
448 nodes_with_entry_count: 0,
449 nodes_with_copy_source_count: 0,
449 nodes_with_copy_source_count: 0,
450 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
450 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
451 unreachable_bytes: 0,
451 unreachable_bytes: 0,
452 }
452 }
453 }
453 }
454
454
455 #[timed]
455 #[timed]
456 pub fn new_v2(
456 pub fn new_v2(
457 on_disk: &'on_disk [u8],
457 on_disk: &'on_disk [u8],
458 data_size: usize,
458 data_size: usize,
459 metadata: &[u8],
459 metadata: &[u8],
460 ) -> Result<Self, DirstateError> {
460 ) -> Result<Self, DirstateError> {
461 if let Some(data) = on_disk.get(..data_size) {
461 if let Some(data) = on_disk.get(..data_size) {
462 Ok(on_disk::read(data, metadata)?)
462 Ok(on_disk::read(data, metadata)?)
463 } else {
463 } else {
464 Err(DirstateV2ParseError.into())
464 Err(DirstateV2ParseError.into())
465 }
465 }
466 }
466 }
467
467
468 #[timed]
468 #[timed]
469 pub fn new_v1(
469 pub fn new_v1(
470 on_disk: &'on_disk [u8],
470 on_disk: &'on_disk [u8],
471 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
471 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
472 let mut map = Self::empty(on_disk);
472 let mut map = Self::empty(on_disk);
473 if map.on_disk.is_empty() {
473 if map.on_disk.is_empty() {
474 return Ok((map, None));
474 return Ok((map, None));
475 }
475 }
476
476
477 let parents = parse_dirstate_entries(
477 let parents = parse_dirstate_entries(
478 map.on_disk,
478 map.on_disk,
479 |path, entry, copy_source| {
479 |path, entry, copy_source| {
480 let tracked = entry.state().is_tracked();
480 let tracked = entry.state().is_tracked();
481 let node = Self::get_or_insert_node(
481 let node = Self::get_or_insert_node(
482 map.on_disk,
482 map.on_disk,
483 &mut map.unreachable_bytes,
483 &mut map.unreachable_bytes,
484 &mut map.root,
484 &mut map.root,
485 path,
485 path,
486 WithBasename::to_cow_borrowed,
486 WithBasename::to_cow_borrowed,
487 |ancestor| {
487 |ancestor| {
488 if tracked {
488 if tracked {
489 ancestor.tracked_descendants_count += 1
489 ancestor.tracked_descendants_count += 1
490 }
490 }
491 ancestor.descendants_with_entry_count += 1
491 ancestor.descendants_with_entry_count += 1
492 },
492 },
493 )?;
493 )?;
494 assert!(
494 assert!(
495 !node.data.has_entry(),
495 !node.data.has_entry(),
496 "duplicate dirstate entry in read"
496 "duplicate dirstate entry in read"
497 );
497 );
498 assert!(
498 assert!(
499 node.copy_source.is_none(),
499 node.copy_source.is_none(),
500 "duplicate dirstate entry in read"
500 "duplicate dirstate entry in read"
501 );
501 );
502 node.data = NodeData::Entry(*entry);
502 node.data = NodeData::Entry(*entry);
503 node.copy_source = copy_source.map(Cow::Borrowed);
503 node.copy_source = copy_source.map(Cow::Borrowed);
504 map.nodes_with_entry_count += 1;
504 map.nodes_with_entry_count += 1;
505 if copy_source.is_some() {
505 if copy_source.is_some() {
506 map.nodes_with_copy_source_count += 1
506 map.nodes_with_copy_source_count += 1
507 }
507 }
508 Ok(())
508 Ok(())
509 },
509 },
510 )?;
510 )?;
511 let parents = Some(parents.clone());
511 let parents = Some(parents.clone());
512
512
513 Ok((map, parents))
513 Ok((map, parents))
514 }
514 }
515
515
516 /// Assuming dirstate-v2 format, returns whether the next write should
516 /// Assuming dirstate-v2 format, returns whether the next write should
517 /// append to the existing data file that contains `self.on_disk` (true),
517 /// append to the existing data file that contains `self.on_disk` (true),
518 /// or create a new data file from scratch (false).
518 /// or create a new data file from scratch (false).
519 pub(super) fn write_should_append(&self) -> bool {
519 pub(super) fn write_should_append(&self) -> bool {
520 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
520 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
521 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
521 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
522 }
522 }
523
523
524 fn get_node<'tree>(
524 fn get_node<'tree>(
525 &'tree self,
525 &'tree self,
526 path: &HgPath,
526 path: &HgPath,
527 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
527 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
528 let mut children = self.root.as_ref();
528 let mut children = self.root.as_ref();
529 let mut components = path.components();
529 let mut components = path.components();
530 let mut component =
530 let mut component =
531 components.next().expect("expected at least one components");
531 components.next().expect("expected at least one components");
532 loop {
532 loop {
533 if let Some(child) = children.get(component, self.on_disk)? {
533 if let Some(child) = children.get(component, self.on_disk)? {
534 if let Some(next_component) = components.next() {
534 if let Some(next_component) = components.next() {
535 component = next_component;
535 component = next_component;
536 children = child.children(self.on_disk)?;
536 children = child.children(self.on_disk)?;
537 } else {
537 } else {
538 return Ok(Some(child));
538 return Ok(Some(child));
539 }
539 }
540 } else {
540 } else {
541 return Ok(None);
541 return Ok(None);
542 }
542 }
543 }
543 }
544 }
544 }
545
545
546 /// Returns a mutable reference to the node at `path` if it exists
546 /// Returns a mutable reference to the node at `path` if it exists
547 ///
547 ///
548 /// This takes `root` instead of `&mut self` so that callers can mutate
548 /// This takes `root` instead of `&mut self` so that callers can mutate
549 /// other fields while the returned borrow is still valid
549 /// other fields while the returned borrow is still valid
550 fn get_node_mut<'tree>(
550 fn get_node_mut<'tree>(
551 on_disk: &'on_disk [u8],
551 on_disk: &'on_disk [u8],
552 unreachable_bytes: &mut u32,
552 unreachable_bytes: &mut u32,
553 root: &'tree mut ChildNodes<'on_disk>,
553 root: &'tree mut ChildNodes<'on_disk>,
554 path: &HgPath,
554 path: &HgPath,
555 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
555 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
556 let mut children = root;
556 let mut children = root;
557 let mut components = path.components();
557 let mut components = path.components();
558 let mut component =
558 let mut component =
559 components.next().expect("expected at least one components");
559 components.next().expect("expected at least one components");
560 loop {
560 loop {
561 if let Some(child) = children
561 if let Some(child) = children
562 .make_mut(on_disk, unreachable_bytes)?
562 .make_mut(on_disk, unreachable_bytes)?
563 .get_mut(component)
563 .get_mut(component)
564 {
564 {
565 if let Some(next_component) = components.next() {
565 if let Some(next_component) = components.next() {
566 component = next_component;
566 component = next_component;
567 children = &mut child.children;
567 children = &mut child.children;
568 } else {
568 } else {
569 return Ok(Some(child));
569 return Ok(Some(child));
570 }
570 }
571 } else {
571 } else {
572 return Ok(None);
572 return Ok(None);
573 }
573 }
574 }
574 }
575 }
575 }
576
576
577 pub(super) fn get_or_insert<'tree, 'path>(
577 pub(super) fn get_or_insert<'tree, 'path>(
578 &'tree mut self,
578 &'tree mut self,
579 path: &HgPath,
579 path: &HgPath,
580 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
580 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
581 Self::get_or_insert_node(
581 Self::get_or_insert_node(
582 self.on_disk,
582 self.on_disk,
583 &mut self.unreachable_bytes,
583 &mut self.unreachable_bytes,
584 &mut self.root,
584 &mut self.root,
585 path,
585 path,
586 WithBasename::to_cow_owned,
586 WithBasename::to_cow_owned,
587 |_| {},
587 |_| {},
588 )
588 )
589 }
589 }
590
590
591 fn get_or_insert_node<'tree, 'path>(
591 fn get_or_insert_node<'tree, 'path>(
592 on_disk: &'on_disk [u8],
592 on_disk: &'on_disk [u8],
593 unreachable_bytes: &mut u32,
593 unreachable_bytes: &mut u32,
594 root: &'tree mut ChildNodes<'on_disk>,
594 root: &'tree mut ChildNodes<'on_disk>,
595 path: &'path HgPath,
595 path: &'path HgPath,
596 to_cow: impl Fn(
596 to_cow: impl Fn(
597 WithBasename<&'path HgPath>,
597 WithBasename<&'path HgPath>,
598 ) -> WithBasename<Cow<'on_disk, HgPath>>,
598 ) -> WithBasename<Cow<'on_disk, HgPath>>,
599 mut each_ancestor: impl FnMut(&mut Node),
599 mut each_ancestor: impl FnMut(&mut Node),
600 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
600 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
601 let mut child_nodes = root;
601 let mut child_nodes = root;
602 let mut inclusive_ancestor_paths =
602 let mut inclusive_ancestor_paths =
603 WithBasename::inclusive_ancestors_of(path);
603 WithBasename::inclusive_ancestors_of(path);
604 let mut ancestor_path = inclusive_ancestor_paths
604 let mut ancestor_path = inclusive_ancestor_paths
605 .next()
605 .next()
606 .expect("expected at least one inclusive ancestor");
606 .expect("expected at least one inclusive ancestor");
607 loop {
607 loop {
608 let (_, child_node) = child_nodes
608 let (_, child_node) = child_nodes
609 .make_mut(on_disk, unreachable_bytes)?
609 .make_mut(on_disk, unreachable_bytes)?
610 .raw_entry_mut()
610 .raw_entry_mut()
611 .from_key(ancestor_path.base_name())
611 .from_key(ancestor_path.base_name())
612 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
612 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
613 if let Some(next) = inclusive_ancestor_paths.next() {
613 if let Some(next) = inclusive_ancestor_paths.next() {
614 each_ancestor(child_node);
614 each_ancestor(child_node);
615 ancestor_path = next;
615 ancestor_path = next;
616 child_nodes = &mut child_node.children;
616 child_nodes = &mut child_node.children;
617 } else {
617 } else {
618 return Ok(child_node);
618 return Ok(child_node);
619 }
619 }
620 }
620 }
621 }
621 }
622
622
623 fn reset_state(
623 fn reset_state(
624 &mut self,
624 &mut self,
625 filename: &HgPath,
625 filename: &HgPath,
626 old_entry_opt: Option<DirstateEntry>,
626 old_entry_opt: Option<DirstateEntry>,
627 wc_tracked: bool,
627 wc_tracked: bool,
628 p1_tracked: bool,
628 p1_tracked: bool,
629 p2_info: bool,
629 p2_info: bool,
630 has_meaningful_mtime: bool,
630 has_meaningful_mtime: bool,
631 parent_file_data_opt: Option<ParentFileData>,
631 parent_file_data_opt: Option<ParentFileData>,
632 ) -> Result<(), DirstateError> {
632 ) -> Result<(), DirstateError> {
633 let (had_entry, was_tracked) = match old_entry_opt {
633 let (had_entry, was_tracked) = match old_entry_opt {
634 Some(old_entry) => (true, old_entry.tracked()),
634 Some(old_entry) => (true, old_entry.tracked()),
635 None => (false, false),
635 None => (false, false),
636 };
636 };
637 let node = Self::get_or_insert_node(
637 let node = Self::get_or_insert_node(
638 self.on_disk,
638 self.on_disk,
639 &mut self.unreachable_bytes,
639 &mut self.unreachable_bytes,
640 &mut self.root,
640 &mut self.root,
641 filename,
641 filename,
642 WithBasename::to_cow_owned,
642 WithBasename::to_cow_owned,
643 |ancestor| {
643 |ancestor| {
644 if !had_entry {
644 if !had_entry {
645 ancestor.descendants_with_entry_count += 1;
645 ancestor.descendants_with_entry_count += 1;
646 }
646 }
647 if was_tracked {
647 if was_tracked {
648 if !wc_tracked {
648 if !wc_tracked {
649 ancestor.tracked_descendants_count = ancestor
649 ancestor.tracked_descendants_count = ancestor
650 .tracked_descendants_count
650 .tracked_descendants_count
651 .checked_sub(1)
651 .checked_sub(1)
652 .expect("tracked count to be >= 0");
652 .expect("tracked count to be >= 0");
653 }
653 }
654 } else {
654 } else {
655 if wc_tracked {
655 if wc_tracked {
656 ancestor.tracked_descendants_count += 1;
656 ancestor.tracked_descendants_count += 1;
657 }
657 }
658 }
658 }
659 },
659 },
660 )?;
660 )?;
661
661
662 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
662 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
663 DirstateV2Data {
663 DirstateV2Data {
664 wc_tracked,
664 wc_tracked,
665 p1_tracked,
665 p1_tracked,
666 p2_info,
666 p2_info,
667 mode_size: parent_file_data.mode_size,
667 mode_size: parent_file_data.mode_size,
668 mtime: if has_meaningful_mtime {
668 mtime: if has_meaningful_mtime {
669 parent_file_data.mtime
669 parent_file_data.mtime
670 } else {
670 } else {
671 None
671 None
672 },
672 },
673 ..Default::default()
673 ..Default::default()
674 }
674 }
675 } else {
675 } else {
676 DirstateV2Data {
676 DirstateV2Data {
677 wc_tracked,
677 wc_tracked,
678 p1_tracked,
678 p1_tracked,
679 p2_info,
679 p2_info,
680 ..Default::default()
680 ..Default::default()
681 }
681 }
682 };
682 };
683 if !had_entry {
683 if !had_entry {
684 self.nodes_with_entry_count += 1;
684 self.nodes_with_entry_count += 1;
685 }
685 }
686 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
686 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
687 Ok(())
687 Ok(())
688 }
688 }
689
689
690 fn set_tracked(
690 fn set_tracked(
691 &mut self,
691 &mut self,
692 filename: &HgPath,
692 filename: &HgPath,
693 old_entry_opt: Option<DirstateEntry>,
693 old_entry_opt: Option<DirstateEntry>,
694 ) -> Result<bool, DirstateV2ParseError> {
694 ) -> Result<bool, DirstateV2ParseError> {
695 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
695 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
696 let had_entry = old_entry_opt.is_some();
696 let had_entry = old_entry_opt.is_some();
697 let tracked_count_increment = if was_tracked { 0 } else { 1 };
697 let tracked_count_increment = if was_tracked { 0 } else { 1 };
698 let mut new = false;
698 let mut new = false;
699
699
700 let node = Self::get_or_insert_node(
700 let node = Self::get_or_insert_node(
701 self.on_disk,
701 self.on_disk,
702 &mut self.unreachable_bytes,
702 &mut self.unreachable_bytes,
703 &mut self.root,
703 &mut self.root,
704 filename,
704 filename,
705 WithBasename::to_cow_owned,
705 WithBasename::to_cow_owned,
706 |ancestor| {
706 |ancestor| {
707 if !had_entry {
707 if !had_entry {
708 ancestor.descendants_with_entry_count += 1;
708 ancestor.descendants_with_entry_count += 1;
709 }
709 }
710
710
711 ancestor.tracked_descendants_count += tracked_count_increment;
711 ancestor.tracked_descendants_count += tracked_count_increment;
712 },
712 },
713 )?;
713 )?;
714 let new_entry = if let Some(old_entry) = old_entry_opt {
714 let new_entry = if let Some(old_entry) = old_entry_opt {
715 let mut e = old_entry.clone();
715 let mut e = old_entry.clone();
716 if e.tracked() {
716 if e.tracked() {
717 // XXX
717 // XXX
718 // This is probably overkill for more case, but we need this to
718 // This is probably overkill for more case, but we need this to
719 // fully replace the `normallookup` call with `set_tracked`
719 // fully replace the `normallookup` call with `set_tracked`
720 // one. Consider smoothing this in the future.
720 // one. Consider smoothing this in the future.
721 e.set_possibly_dirty();
721 e.set_possibly_dirty();
722 } else {
722 } else {
723 new = true;
723 new = true;
724 e.set_tracked();
724 e.set_tracked();
725 }
725 }
726 e
726 e
727 } else {
727 } else {
728 self.nodes_with_entry_count += 1;
728 self.nodes_with_entry_count += 1;
729 new = true;
729 new = true;
730 DirstateEntry::new_tracked()
730 DirstateEntry::new_tracked()
731 };
731 };
732 node.data = NodeData::Entry(new_entry);
732 node.data = NodeData::Entry(new_entry);
733 Ok(new)
733 Ok(new)
734 }
734 }
735
735
736 /// It is the responsibility of the caller to know that there was an entry
736 /// It is the responsibility of the caller to know that there was an entry
737 /// there before. Does not handle the removal of copy source
737 /// there before. Does not handle the removal of copy source
738 fn set_untracked(
738 fn set_untracked(
739 &mut self,
739 &mut self,
740 filename: &HgPath,
740 filename: &HgPath,
741 old_entry: DirstateEntry,
741 old_entry: DirstateEntry,
742 ) -> Result<(), DirstateV2ParseError> {
742 ) -> Result<(), DirstateV2ParseError> {
743 let node = Self::get_or_insert_node(
743 let node = Self::get_or_insert_node(
744 self.on_disk,
744 self.on_disk,
745 &mut self.unreachable_bytes,
745 &mut self.unreachable_bytes,
746 &mut self.root,
746 &mut self.root,
747 filename,
747 filename,
748 WithBasename::to_cow_owned,
748 WithBasename::to_cow_owned,
749 |ancestor| {
749 |ancestor| {
750 ancestor.tracked_descendants_count = ancestor
750 ancestor.tracked_descendants_count = ancestor
751 .tracked_descendants_count
751 .tracked_descendants_count
752 .checked_sub(1)
752 .checked_sub(1)
753 .expect("tracked_descendants_count should be >= 0");
753 .expect("tracked_descendants_count should be >= 0");
754 },
754 },
755 )?;
755 )?;
756 let mut new_entry = old_entry.clone();
756 let mut new_entry = old_entry.clone();
757 new_entry.set_untracked();
757 new_entry.set_untracked();
758 node.data = NodeData::Entry(new_entry);
758 node.data = NodeData::Entry(new_entry);
759 Ok(())
759 Ok(())
760 }
760 }
761
761
762 fn set_clean(
762 fn set_clean(
763 &mut self,
763 &mut self,
764 filename: &HgPath,
764 filename: &HgPath,
765 old_entry: DirstateEntry,
765 old_entry: DirstateEntry,
766 mode: u32,
766 mode: u32,
767 size: u32,
767 size: u32,
768 mtime: TruncatedTimestamp,
768 mtime: TruncatedTimestamp,
769 ) -> Result<(), DirstateError> {
769 ) -> Result<(), DirstateError> {
770 let node = Self::get_or_insert_node(
770 let node = Self::get_or_insert_node(
771 self.on_disk,
771 self.on_disk,
772 &mut self.unreachable_bytes,
772 &mut self.unreachable_bytes,
773 &mut self.root,
773 &mut self.root,
774 filename,
774 filename,
775 WithBasename::to_cow_owned,
775 WithBasename::to_cow_owned,
776 |ancestor| {
776 |ancestor| {
777 if !old_entry.tracked() {
777 if !old_entry.tracked() {
778 ancestor.tracked_descendants_count += 1;
778 ancestor.tracked_descendants_count += 1;
779 }
779 }
780 },
780 },
781 )?;
781 )?;
782 let mut new_entry = old_entry.clone();
782 let mut new_entry = old_entry.clone();
783 new_entry.set_clean(mode, size, mtime);
783 new_entry.set_clean(mode, size, mtime);
784 node.data = NodeData::Entry(new_entry);
784 node.data = NodeData::Entry(new_entry);
785 Ok(())
785 Ok(())
786 }
786 }
787
787
788 fn set_possibly_dirty(
788 fn set_possibly_dirty(
789 &mut self,
789 &mut self,
790 filename: &HgPath,
790 filename: &HgPath,
791 ) -> Result<(), DirstateError> {
791 ) -> Result<(), DirstateError> {
792 let node = Self::get_or_insert_node(
792 let node = Self::get_or_insert_node(
793 self.on_disk,
793 self.on_disk,
794 &mut self.unreachable_bytes,
794 &mut self.unreachable_bytes,
795 &mut self.root,
795 &mut self.root,
796 filename,
796 filename,
797 WithBasename::to_cow_owned,
797 WithBasename::to_cow_owned,
798 |_ancestor| {},
798 |_ancestor| {},
799 )?;
799 )?;
800 let entry = node.data.as_entry_mut().expect("entry should exist");
800 let entry = node.data.as_entry_mut().expect("entry should exist");
801 entry.set_possibly_dirty();
801 entry.set_possibly_dirty();
802 node.data = NodeData::Entry(*entry);
802 node.data = NodeData::Entry(*entry);
803 Ok(())
803 Ok(())
804 }
804 }
805
805
806 fn iter_nodes<'tree>(
806 fn iter_nodes<'tree>(
807 &'tree self,
807 &'tree self,
808 ) -> impl Iterator<
808 ) -> impl Iterator<
809 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
809 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
810 > + 'tree {
810 > + 'tree {
811 // Depth first tree traversal.
811 // Depth first tree traversal.
812 //
812 //
813 // If we could afford internal iteration and recursion,
813 // If we could afford internal iteration and recursion,
814 // this would look like:
814 // this would look like:
815 //
815 //
816 // ```
816 // ```
817 // fn traverse_children(
817 // fn traverse_children(
818 // children: &ChildNodes,
818 // children: &ChildNodes,
819 // each: &mut impl FnMut(&Node),
819 // each: &mut impl FnMut(&Node),
820 // ) {
820 // ) {
821 // for child in children.values() {
821 // for child in children.values() {
822 // traverse_children(&child.children, each);
822 // traverse_children(&child.children, each);
823 // each(child);
823 // each(child);
824 // }
824 // }
825 // }
825 // }
826 // ```
826 // ```
827 //
827 //
828 // However we want an external iterator and therefore can’t use the
828 // However we want an external iterator and therefore can’t use the
829 // call stack. Use an explicit stack instead:
829 // call stack. Use an explicit stack instead:
830 let mut stack = Vec::new();
830 let mut stack = Vec::new();
831 let mut iter = self.root.as_ref().iter();
831 let mut iter = self.root.as_ref().iter();
832 std::iter::from_fn(move || {
832 std::iter::from_fn(move || {
833 while let Some(child_node) = iter.next() {
833 while let Some(child_node) = iter.next() {
834 let children = match child_node.children(self.on_disk) {
834 let children = match child_node.children(self.on_disk) {
835 Ok(children) => children,
835 Ok(children) => children,
836 Err(error) => return Some(Err(error)),
836 Err(error) => return Some(Err(error)),
837 };
837 };
838 // Pseudo-recursion
838 // Pseudo-recursion
839 let new_iter = children.iter();
839 let new_iter = children.iter();
840 let old_iter = std::mem::replace(&mut iter, new_iter);
840 let old_iter = std::mem::replace(&mut iter, new_iter);
841 stack.push((child_node, old_iter));
841 stack.push((child_node, old_iter));
842 }
842 }
843 // Found the end of a `children.iter()` iterator.
843 // Found the end of a `children.iter()` iterator.
844 if let Some((child_node, next_iter)) = stack.pop() {
844 if let Some((child_node, next_iter)) = stack.pop() {
845 // "Return" from pseudo-recursion by restoring state from the
845 // "Return" from pseudo-recursion by restoring state from the
846 // explicit stack
846 // explicit stack
847 iter = next_iter;
847 iter = next_iter;
848
848
849 Some(Ok(child_node))
849 Some(Ok(child_node))
850 } else {
850 } else {
851 // Reached the bottom of the stack, we’re done
851 // Reached the bottom of the stack, we’re done
852 None
852 None
853 }
853 }
854 })
854 })
855 }
855 }
856
856
857 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
857 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
858 if let Cow::Borrowed(path) = path {
858 if let Cow::Borrowed(path) = path {
859 *unreachable_bytes += path.len() as u32
859 *unreachable_bytes += path.len() as u32
860 }
860 }
861 }
861 }
862 }
862 }
863
863
864 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
864 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
865 ///
865 ///
866 /// The callback is only called for incoming `Ok` values. Errors are passed
866 /// The callback is only called for incoming `Ok` values. Errors are passed
867 /// through as-is. In order to let it use the `?` operator the callback is
867 /// through as-is. In order to let it use the `?` operator the callback is
868 /// expected to return a `Result` of `Option`, instead of an `Option` of
868 /// expected to return a `Result` of `Option`, instead of an `Option` of
869 /// `Result`.
869 /// `Result`.
870 fn filter_map_results<'a, I, F, A, B, E>(
870 fn filter_map_results<'a, I, F, A, B, E>(
871 iter: I,
871 iter: I,
872 f: F,
872 f: F,
873 ) -> impl Iterator<Item = Result<B, E>> + 'a
873 ) -> impl Iterator<Item = Result<B, E>> + 'a
874 where
874 where
875 I: Iterator<Item = Result<A, E>> + 'a,
875 I: Iterator<Item = Result<A, E>> + 'a,
876 F: Fn(A) -> Result<Option<B>, E> + 'a,
876 F: Fn(A) -> Result<Option<B>, E> + 'a,
877 {
877 {
878 iter.filter_map(move |result| match result {
878 iter.filter_map(move |result| match result {
879 Ok(node) => f(node).transpose(),
879 Ok(node) => f(node).transpose(),
880 Err(e) => Some(Err(e)),
880 Err(e) => Some(Err(e)),
881 })
881 })
882 }
882 }
883
883
884 impl OwningDirstateMap {
884 impl OwningDirstateMap {
885 pub fn clear(&mut self) {
885 pub fn clear(&mut self) {
886 self.with_dmap_mut(|map| {
886 self.with_dmap_mut(|map| {
887 map.root = Default::default();
887 map.root = Default::default();
888 map.nodes_with_entry_count = 0;
888 map.nodes_with_entry_count = 0;
889 map.nodes_with_copy_source_count = 0;
889 map.nodes_with_copy_source_count = 0;
890 });
890 });
891 }
891 }
892
892
893 pub fn set_tracked(
893 pub fn set_tracked(
894 &mut self,
894 &mut self,
895 filename: &HgPath,
895 filename: &HgPath,
896 ) -> Result<bool, DirstateV2ParseError> {
896 ) -> Result<bool, DirstateV2ParseError> {
897 let old_entry_opt = self.get(filename)?;
897 let old_entry_opt = self.get(filename)?;
898 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
898 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
899 }
899 }
900
900
901 pub fn set_untracked(
901 pub fn set_untracked(
902 &mut self,
902 &mut self,
903 filename: &HgPath,
903 filename: &HgPath,
904 ) -> Result<bool, DirstateError> {
904 ) -> Result<bool, DirstateError> {
905 let old_entry_opt = self.get(filename)?;
905 let old_entry_opt = self.get(filename)?;
906 match old_entry_opt {
906 match old_entry_opt {
907 None => Ok(false),
907 None => Ok(false),
908 Some(old_entry) => {
908 Some(old_entry) => {
909 if !old_entry.tracked() {
909 if !old_entry.tracked() {
910 // `DirstateMap::set_untracked` is not a noop if
910 // `DirstateMap::set_untracked` is not a noop if
911 // already not tracked as it will decrement the
911 // already not tracked as it will decrement the
912 // tracked counters while going down.
912 // tracked counters while going down.
913 return Ok(true);
913 return Ok(true);
914 }
914 }
915 if old_entry.added() {
915 if old_entry.added() {
916 // Untracking an "added" entry will just result in a
916 // Untracking an "added" entry will just result in a
917 // worthless entry (and other parts of the code will
917 // worthless entry (and other parts of the code will
918 // complain about it), just drop it entirely.
918 // complain about it), just drop it entirely.
919 self.drop_entry_and_copy_source(filename)?;
919 self.drop_entry_and_copy_source(filename)?;
920 return Ok(true);
920 return Ok(true);
921 }
921 }
922 if !old_entry.p2_info() {
922 if !old_entry.p2_info() {
923 self.copy_map_remove(filename)?;
923 self.copy_map_remove(filename)?;
924 }
924 }
925
925
926 self.with_dmap_mut(|map| {
926 self.with_dmap_mut(|map| {
927 map.set_untracked(filename, old_entry)?;
927 map.set_untracked(filename, old_entry)?;
928 Ok(true)
928 Ok(true)
929 })
929 })
930 }
930 }
931 }
931 }
932 }
932 }
933
933
934 pub fn set_clean(
934 pub fn set_clean(
935 &mut self,
935 &mut self,
936 filename: &HgPath,
936 filename: &HgPath,
937 mode: u32,
937 mode: u32,
938 size: u32,
938 size: u32,
939 mtime: TruncatedTimestamp,
939 mtime: TruncatedTimestamp,
940 ) -> Result<(), DirstateError> {
940 ) -> Result<(), DirstateError> {
941 let old_entry = match self.get(filename)? {
941 let old_entry = match self.get(filename)? {
942 None => {
942 None => {
943 return Err(
943 return Err(
944 DirstateMapError::PathNotFound(filename.into()).into()
944 DirstateMapError::PathNotFound(filename.into()).into()
945 )
945 )
946 }
946 }
947 Some(e) => e,
947 Some(e) => e,
948 };
948 };
949 self.copy_map_remove(filename)?;
949 self.copy_map_remove(filename)?;
950 self.with_dmap_mut(|map| {
950 self.with_dmap_mut(|map| {
951 map.set_clean(filename, old_entry, mode, size, mtime)
951 map.set_clean(filename, old_entry, mode, size, mtime)
952 })
952 })
953 }
953 }
954
954
955 pub fn set_possibly_dirty(
955 pub fn set_possibly_dirty(
956 &mut self,
956 &mut self,
957 filename: &HgPath,
957 filename: &HgPath,
958 ) -> Result<(), DirstateError> {
958 ) -> Result<(), DirstateError> {
959 if self.get(filename)?.is_none() {
959 if self.get(filename)?.is_none() {
960 return Err(DirstateMapError::PathNotFound(filename.into()).into());
960 return Err(DirstateMapError::PathNotFound(filename.into()).into());
961 }
961 }
962 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
962 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
963 }
963 }
964
964
965 pub fn reset_state(
965 pub fn reset_state(
966 &mut self,
966 &mut self,
967 filename: &HgPath,
967 filename: &HgPath,
968 wc_tracked: bool,
968 wc_tracked: bool,
969 p1_tracked: bool,
969 p1_tracked: bool,
970 p2_info: bool,
970 p2_info: bool,
971 has_meaningful_mtime: bool,
971 has_meaningful_mtime: bool,
972 parent_file_data_opt: Option<ParentFileData>,
972 parent_file_data_opt: Option<ParentFileData>,
973 ) -> Result<(), DirstateError> {
973 ) -> Result<(), DirstateError> {
974 if !(p1_tracked || p2_info || wc_tracked) {
974 if !(p1_tracked || p2_info || wc_tracked) {
975 self.drop_entry_and_copy_source(filename)?;
975 self.drop_entry_and_copy_source(filename)?;
976 return Ok(());
976 return Ok(());
977 }
977 }
978 self.copy_map_remove(filename)?;
978 self.copy_map_remove(filename)?;
979 let old_entry_opt = self.get(filename)?;
979 let old_entry_opt = self.get(filename)?;
980 self.with_dmap_mut(|map| {
980 self.with_dmap_mut(|map| {
981 map.reset_state(
981 map.reset_state(
982 filename,
982 filename,
983 old_entry_opt,
983 old_entry_opt,
984 wc_tracked,
984 wc_tracked,
985 p1_tracked,
985 p1_tracked,
986 p2_info,
986 p2_info,
987 has_meaningful_mtime,
987 has_meaningful_mtime,
988 parent_file_data_opt,
988 parent_file_data_opt,
989 )
989 )
990 })
990 })
991 }
991 }
992
992
993 pub fn drop_entry_and_copy_source(
993 pub fn drop_entry_and_copy_source(
994 &mut self,
994 &mut self,
995 filename: &HgPath,
995 filename: &HgPath,
996 ) -> Result<(), DirstateError> {
996 ) -> Result<(), DirstateError> {
997 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
997 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
998 struct Dropped {
998 struct Dropped {
999 was_tracked: bool,
999 was_tracked: bool,
1000 had_entry: bool,
1000 had_entry: bool,
1001 had_copy_source: bool,
1001 had_copy_source: bool,
1002 }
1002 }
1003
1003
1004 /// If this returns `Ok(Some((dropped, removed)))`, then
1004 /// If this returns `Ok(Some((dropped, removed)))`, then
1005 ///
1005 ///
1006 /// * `dropped` is about the leaf node that was at `filename`
1006 /// * `dropped` is about the leaf node that was at `filename`
1007 /// * `removed` is whether this particular level of recursion just
1007 /// * `removed` is whether this particular level of recursion just
1008 /// removed a node in `nodes`.
1008 /// removed a node in `nodes`.
1009 fn recur<'on_disk>(
1009 fn recur<'on_disk>(
1010 on_disk: &'on_disk [u8],
1010 on_disk: &'on_disk [u8],
1011 unreachable_bytes: &mut u32,
1011 unreachable_bytes: &mut u32,
1012 nodes: &mut ChildNodes<'on_disk>,
1012 nodes: &mut ChildNodes<'on_disk>,
1013 path: &HgPath,
1013 path: &HgPath,
1014 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1014 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1015 let (first_path_component, rest_of_path) =
1015 let (first_path_component, rest_of_path) =
1016 path.split_first_component();
1016 path.split_first_component();
1017 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1017 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1018 let node = if let Some(node) = nodes.get_mut(first_path_component)
1018 let node = if let Some(node) = nodes.get_mut(first_path_component)
1019 {
1019 {
1020 node
1020 node
1021 } else {
1021 } else {
1022 return Ok(None);
1022 return Ok(None);
1023 };
1023 };
1024 let dropped;
1024 let dropped;
1025 if let Some(rest) = rest_of_path {
1025 if let Some(rest) = rest_of_path {
1026 if let Some((d, removed)) = recur(
1026 if let Some((d, removed)) = recur(
1027 on_disk,
1027 on_disk,
1028 unreachable_bytes,
1028 unreachable_bytes,
1029 &mut node.children,
1029 &mut node.children,
1030 rest,
1030 rest,
1031 )? {
1031 )? {
1032 dropped = d;
1032 dropped = d;
1033 if dropped.had_entry {
1033 if dropped.had_entry {
1034 node.descendants_with_entry_count = node
1034 node.descendants_with_entry_count = node
1035 .descendants_with_entry_count
1035 .descendants_with_entry_count
1036 .checked_sub(1)
1036 .checked_sub(1)
1037 .expect(
1037 .expect(
1038 "descendants_with_entry_count should be >= 0",
1038 "descendants_with_entry_count should be >= 0",
1039 );
1039 );
1040 }
1040 }
1041 if dropped.was_tracked {
1041 if dropped.was_tracked {
1042 node.tracked_descendants_count = node
1042 node.tracked_descendants_count = node
1043 .tracked_descendants_count
1043 .tracked_descendants_count
1044 .checked_sub(1)
1044 .checked_sub(1)
1045 .expect(
1045 .expect(
1046 "tracked_descendants_count should be >= 0",
1046 "tracked_descendants_count should be >= 0",
1047 );
1047 );
1048 }
1048 }
1049
1049
1050 // Directory caches must be invalidated when removing a
1050 // Directory caches must be invalidated when removing a
1051 // child node
1051 // child node
1052 if removed {
1052 if removed {
1053 if let NodeData::CachedDirectory { .. } = &node.data {
1053 if let NodeData::CachedDirectory { .. } = &node.data {
1054 node.data = NodeData::None
1054 node.data = NodeData::None
1055 }
1055 }
1056 }
1056 }
1057 } else {
1057 } else {
1058 return Ok(None);
1058 return Ok(None);
1059 }
1059 }
1060 } else {
1060 } else {
1061 let entry = node.data.as_entry();
1061 let entry = node.data.as_entry();
1062 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1062 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1063 let had_entry = entry.is_some();
1063 let had_entry = entry.is_some();
1064 if had_entry {
1064 if had_entry {
1065 node.data = NodeData::None
1065 node.data = NodeData::None
1066 }
1066 }
1067 let mut had_copy_source = false;
1067 let mut had_copy_source = false;
1068 if let Some(source) = &node.copy_source {
1068 if let Some(source) = &node.copy_source {
1069 DirstateMap::count_dropped_path(unreachable_bytes, source);
1069 DirstateMap::count_dropped_path(unreachable_bytes, source);
1070 had_copy_source = true;
1070 had_copy_source = true;
1071 node.copy_source = None
1071 node.copy_source = None
1072 }
1072 }
1073 dropped = Dropped {
1073 dropped = Dropped {
1074 was_tracked,
1074 was_tracked,
1075 had_entry,
1075 had_entry,
1076 had_copy_source,
1076 had_copy_source,
1077 };
1077 };
1078 }
1078 }
1079 // After recursion, for both leaf (rest_of_path is None) nodes and
1079 // After recursion, for both leaf (rest_of_path is None) nodes and
1080 // parent nodes, remove a node if it just became empty.
1080 // parent nodes, remove a node if it just became empty.
1081 let remove = !node.data.has_entry()
1081 let remove = !node.data.has_entry()
1082 && node.copy_source.is_none()
1082 && node.copy_source.is_none()
1083 && node.children.is_empty();
1083 && node.children.is_empty();
1084 if remove {
1084 if remove {
1085 let (key, _) =
1085 let (key, _) =
1086 nodes.remove_entry(first_path_component).unwrap();
1086 nodes.remove_entry(first_path_component).unwrap();
1087 DirstateMap::count_dropped_path(
1087 DirstateMap::count_dropped_path(
1088 unreachable_bytes,
1088 unreachable_bytes,
1089 key.full_path(),
1089 key.full_path(),
1090 )
1090 )
1091 }
1091 }
1092 Ok(Some((dropped, remove)))
1092 Ok(Some((dropped, remove)))
1093 }
1093 }
1094
1094
1095 self.with_dmap_mut(|map| {
1095 self.with_dmap_mut(|map| {
1096 if let Some((dropped, _removed)) = recur(
1096 if let Some((dropped, _removed)) = recur(
1097 map.on_disk,
1097 map.on_disk,
1098 &mut map.unreachable_bytes,
1098 &mut map.unreachable_bytes,
1099 &mut map.root,
1099 &mut map.root,
1100 filename,
1100 filename,
1101 )? {
1101 )? {
1102 if dropped.had_entry {
1102 if dropped.had_entry {
1103 map.nodes_with_entry_count = map
1103 map.nodes_with_entry_count = map
1104 .nodes_with_entry_count
1104 .nodes_with_entry_count
1105 .checked_sub(1)
1105 .checked_sub(1)
1106 .expect("nodes_with_entry_count should be >= 0");
1106 .expect("nodes_with_entry_count should be >= 0");
1107 }
1107 }
1108 if dropped.had_copy_source {
1108 if dropped.had_copy_source {
1109 map.nodes_with_copy_source_count = map
1109 map.nodes_with_copy_source_count = map
1110 .nodes_with_copy_source_count
1110 .nodes_with_copy_source_count
1111 .checked_sub(1)
1111 .checked_sub(1)
1112 .expect("nodes_with_copy_source_count should be >= 0");
1112 .expect("nodes_with_copy_source_count should be >= 0");
1113 }
1113 }
1114 } else {
1114 } else {
1115 debug_assert!(!was_tracked);
1115 debug_assert!(!was_tracked);
1116 }
1116 }
1117 Ok(())
1117 Ok(())
1118 })
1118 })
1119 }
1119 }
1120
1120
1121 pub fn has_tracked_dir(
1121 pub fn has_tracked_dir(
1122 &mut self,
1122 &mut self,
1123 directory: &HgPath,
1123 directory: &HgPath,
1124 ) -> Result<bool, DirstateError> {
1124 ) -> Result<bool, DirstateError> {
1125 self.with_dmap_mut(|map| {
1125 self.with_dmap_mut(|map| {
1126 if let Some(node) = map.get_node(directory)? {
1126 if let Some(node) = map.get_node(directory)? {
1127 // A node without a `DirstateEntry` was created to hold child
1127 // A node without a `DirstateEntry` was created to hold child
1128 // nodes, and is therefore a directory.
1128 // nodes, and is therefore a directory.
1129 let state = node.state()?;
1129 let state = node.state()?;
1130 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1130 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1131 } else {
1131 } else {
1132 Ok(false)
1132 Ok(false)
1133 }
1133 }
1134 })
1134 })
1135 }
1135 }
1136
1136
1137 pub fn has_dir(
1137 pub fn has_dir(
1138 &mut self,
1138 &mut self,
1139 directory: &HgPath,
1139 directory: &HgPath,
1140 ) -> Result<bool, DirstateError> {
1140 ) -> Result<bool, DirstateError> {
1141 self.with_dmap_mut(|map| {
1141 self.with_dmap_mut(|map| {
1142 if let Some(node) = map.get_node(directory)? {
1142 if let Some(node) = map.get_node(directory)? {
1143 // A node without a `DirstateEntry` was created to hold child
1143 // A node without a `DirstateEntry` was created to hold child
1144 // nodes, and is therefore a directory.
1144 // nodes, and is therefore a directory.
1145 let state = node.state()?;
1145 let state = node.state()?;
1146 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1146 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1147 } else {
1147 } else {
1148 Ok(false)
1148 Ok(false)
1149 }
1149 }
1150 })
1150 })
1151 }
1151 }
1152
1152
1153 #[timed]
1153 #[timed]
1154 pub fn pack_v1(
1154 pub fn pack_v1(
1155 &self,
1155 &self,
1156 parents: DirstateParents,
1156 parents: DirstateParents,
1157 ) -> Result<Vec<u8>, DirstateError> {
1157 ) -> Result<Vec<u8>, DirstateError> {
1158 let map = self.get_map();
1158 let map = self.get_map();
1159 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1159 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1160 // reallocations
1160 // reallocations
1161 let mut size = parents.as_bytes().len();
1161 let mut size = parents.as_bytes().len();
1162 for node in map.iter_nodes() {
1162 for node in map.iter_nodes() {
1163 let node = node?;
1163 let node = node?;
1164 if node.entry()?.is_some() {
1164 if node.entry()?.is_some() {
1165 size += packed_entry_size(
1165 size += packed_entry_size(
1166 node.full_path(map.on_disk)?,
1166 node.full_path(map.on_disk)?,
1167 node.copy_source(map.on_disk)?,
1167 node.copy_source(map.on_disk)?,
1168 );
1168 );
1169 }
1169 }
1170 }
1170 }
1171
1171
1172 let mut packed = Vec::with_capacity(size);
1172 let mut packed = Vec::with_capacity(size);
1173 packed.extend(parents.as_bytes());
1173 packed.extend(parents.as_bytes());
1174
1174
1175 for node in map.iter_nodes() {
1175 for node in map.iter_nodes() {
1176 let node = node?;
1176 let node = node?;
1177 if let Some(entry) = node.entry()? {
1177 if let Some(entry) = node.entry()? {
1178 pack_entry(
1178 pack_entry(
1179 node.full_path(map.on_disk)?,
1179 node.full_path(map.on_disk)?,
1180 &entry,
1180 &entry,
1181 node.copy_source(map.on_disk)?,
1181 node.copy_source(map.on_disk)?,
1182 &mut packed,
1182 &mut packed,
1183 );
1183 );
1184 }
1184 }
1185 }
1185 }
1186 Ok(packed)
1186 Ok(packed)
1187 }
1187 }
1188
1188
1189 /// Returns new data and metadata together with whether that data should be
1189 /// Returns new data and metadata together with whether that data should be
1190 /// appended to the existing data file whose content is at
1190 /// appended to the existing data file whose content is at
1191 /// `map.on_disk` (true), instead of written to a new data file
1191 /// `map.on_disk` (true), instead of written to a new data file
1192 /// (false).
1192 /// (false).
1193 #[timed]
1193 #[timed]
1194 pub fn pack_v2(
1194 pub fn pack_v2(
1195 &self,
1195 &self,
1196 can_append: bool,
1196 can_append: bool,
1197 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1197 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1198 let map = self.get_map();
1198 let map = self.get_map();
1199 on_disk::write(map, can_append)
1199 on_disk::write(map, can_append)
1200 }
1200 }
1201
1201
1202 /// `callback` allows the caller to process and do something with the
1202 /// `callback` allows the caller to process and do something with the
1203 /// results of the status. This is needed to do so efficiently (i.e.
1203 /// results of the status. This is needed to do so efficiently (i.e.
1204 /// without cloning the `DirstateStatus` object with its paths) because
1204 /// without cloning the `DirstateStatus` object with its paths) because
1205 /// we need to borrow from `Self`.
1205 /// we need to borrow from `Self`.
1206 pub fn with_status<R>(
1206 pub fn with_status<R>(
1207 &mut self,
1207 &mut self,
1208 matcher: &(dyn Matcher + Sync),
1208 matcher: &(dyn Matcher + Sync),
1209 root_dir: PathBuf,
1209 root_dir: PathBuf,
1210 ignore_files: Vec<PathBuf>,
1210 ignore_files: Vec<PathBuf>,
1211 options: StatusOptions,
1211 options: StatusOptions,
1212 callback: impl for<'r> FnOnce(
1212 callback: impl for<'r> FnOnce(
1213 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1213 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1214 ) -> R,
1214 ) -> R,
1215 ) -> R {
1215 ) -> R {
1216 self.with_dmap_mut(|map| {
1216 self.with_dmap_mut(|map| {
1217 callback(super::status::status(
1217 callback(super::status::status(
1218 map,
1218 map,
1219 matcher,
1219 matcher,
1220 root_dir,
1220 root_dir,
1221 ignore_files,
1221 ignore_files,
1222 options,
1222 options,
1223 ))
1223 ))
1224 })
1224 })
1225 }
1225 }
1226
1226
1227 pub fn copy_map_len(&self) -> usize {
1227 pub fn copy_map_len(&self) -> usize {
1228 let map = self.get_map();
1228 let map = self.get_map();
1229 map.nodes_with_copy_source_count as usize
1229 map.nodes_with_copy_source_count as usize
1230 }
1230 }
1231
1231
1232 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1232 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1233 let map = self.get_map();
1233 let map = self.get_map();
1234 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1234 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1235 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1235 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1236 Some((node.full_path(map.on_disk)?, source))
1236 Some((node.full_path(map.on_disk)?, source))
1237 } else {
1237 } else {
1238 None
1238 None
1239 })
1239 })
1240 }))
1240 }))
1241 }
1241 }
1242
1242
1243 pub fn copy_map_contains_key(
1243 pub fn copy_map_contains_key(
1244 &self,
1244 &self,
1245 key: &HgPath,
1245 key: &HgPath,
1246 ) -> Result<bool, DirstateV2ParseError> {
1246 ) -> Result<bool, DirstateV2ParseError> {
1247 let map = self.get_map();
1247 let map = self.get_map();
1248 Ok(if let Some(node) = map.get_node(key)? {
1248 Ok(if let Some(node) = map.get_node(key)? {
1249 node.has_copy_source()
1249 node.has_copy_source()
1250 } else {
1250 } else {
1251 false
1251 false
1252 })
1252 })
1253 }
1253 }
1254
1254
1255 pub fn copy_map_get(
1255 pub fn copy_map_get(
1256 &self,
1256 &self,
1257 key: &HgPath,
1257 key: &HgPath,
1258 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1258 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1259 let map = self.get_map();
1259 let map = self.get_map();
1260 if let Some(node) = map.get_node(key)? {
1260 if let Some(node) = map.get_node(key)? {
1261 if let Some(source) = node.copy_source(map.on_disk)? {
1261 if let Some(source) = node.copy_source(map.on_disk)? {
1262 return Ok(Some(source));
1262 return Ok(Some(source));
1263 }
1263 }
1264 }
1264 }
1265 Ok(None)
1265 Ok(None)
1266 }
1266 }
1267
1267
1268 pub fn copy_map_remove(
1268 pub fn copy_map_remove(
1269 &mut self,
1269 &mut self,
1270 key: &HgPath,
1270 key: &HgPath,
1271 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1271 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1272 self.with_dmap_mut(|map| {
1272 self.with_dmap_mut(|map| {
1273 let count = &mut map.nodes_with_copy_source_count;
1273 let count = &mut map.nodes_with_copy_source_count;
1274 let unreachable_bytes = &mut map.unreachable_bytes;
1274 let unreachable_bytes = &mut map.unreachable_bytes;
1275 Ok(DirstateMap::get_node_mut(
1275 Ok(DirstateMap::get_node_mut(
1276 map.on_disk,
1276 map.on_disk,
1277 unreachable_bytes,
1277 unreachable_bytes,
1278 &mut map.root,
1278 &mut map.root,
1279 key,
1279 key,
1280 )?
1280 )?
1281 .and_then(|node| {
1281 .and_then(|node| {
1282 if let Some(source) = &node.copy_source {
1282 if let Some(source) = &node.copy_source {
1283 *count -= 1;
1283 *count -= 1;
1284 DirstateMap::count_dropped_path(unreachable_bytes, source);
1284 DirstateMap::count_dropped_path(unreachable_bytes, source);
1285 }
1285 }
1286 node.copy_source.take().map(Cow::into_owned)
1286 node.copy_source.take().map(Cow::into_owned)
1287 }))
1287 }))
1288 })
1288 })
1289 }
1289 }
1290
1290
1291 pub fn copy_map_insert(
1291 pub fn copy_map_insert(
1292 &mut self,
1292 &mut self,
1293 key: &HgPath,
1293 key: &HgPath,
1294 value: &HgPath,
1294 value: &HgPath,
1295 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1295 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1296 self.with_dmap_mut(|map| {
1296 self.with_dmap_mut(|map| {
1297 let node = DirstateMap::get_or_insert_node(
1297 let node = DirstateMap::get_or_insert_node(
1298 map.on_disk,
1298 map.on_disk,
1299 &mut map.unreachable_bytes,
1299 &mut map.unreachable_bytes,
1300 &mut map.root,
1300 &mut map.root,
1301 &key,
1301 &key,
1302 WithBasename::to_cow_owned,
1302 WithBasename::to_cow_owned,
1303 |_ancestor| {},
1303 |_ancestor| {},
1304 )?;
1304 )?;
1305 if node.copy_source.is_none() {
1305 if node.copy_source.is_none() {
1306 map.nodes_with_copy_source_count += 1
1306 map.nodes_with_copy_source_count += 1
1307 }
1307 }
1308 Ok(node
1308 Ok(node
1309 .copy_source
1309 .copy_source
1310 .replace(value.to_owned().into())
1310 .replace(value.to_owned().into())
1311 .map(Cow::into_owned))
1311 .map(Cow::into_owned))
1312 })
1312 })
1313 }
1313 }
1314
1314
1315 pub fn len(&self) -> usize {
1315 pub fn len(&self) -> usize {
1316 let map = self.get_map();
1316 let map = self.get_map();
1317 map.nodes_with_entry_count as usize
1317 map.nodes_with_entry_count as usize
1318 }
1318 }
1319
1319
1320 pub fn contains_key(
1320 pub fn contains_key(
1321 &self,
1321 &self,
1322 key: &HgPath,
1322 key: &HgPath,
1323 ) -> Result<bool, DirstateV2ParseError> {
1323 ) -> Result<bool, DirstateV2ParseError> {
1324 Ok(self.get(key)?.is_some())
1324 Ok(self.get(key)?.is_some())
1325 }
1325 }
1326
1326
1327 pub fn get(
1327 pub fn get(
1328 &self,
1328 &self,
1329 key: &HgPath,
1329 key: &HgPath,
1330 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1330 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1331 let map = self.get_map();
1331 let map = self.get_map();
1332 Ok(if let Some(node) = map.get_node(key)? {
1332 Ok(if let Some(node) = map.get_node(key)? {
1333 node.entry()?
1333 node.entry()?
1334 } else {
1334 } else {
1335 None
1335 None
1336 })
1336 })
1337 }
1337 }
1338
1338
1339 pub fn iter(&self) -> StateMapIter<'_> {
1339 pub fn iter(&self) -> StateMapIter<'_> {
1340 let map = self.get_map();
1340 let map = self.get_map();
1341 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1341 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1342 Ok(if let Some(entry) = node.entry()? {
1342 Ok(if let Some(entry) = node.entry()? {
1343 Some((node.full_path(map.on_disk)?, entry))
1343 Some((node.full_path(map.on_disk)?, entry))
1344 } else {
1344 } else {
1345 None
1345 None
1346 })
1346 })
1347 }))
1347 }))
1348 }
1348 }
1349
1349
1350 pub fn iter_tracked_dirs(
1350 pub fn iter_tracked_dirs(
1351 &mut self,
1351 &mut self,
1352 ) -> Result<
1352 ) -> Result<
1353 Box<
1353 Box<
1354 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1354 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1355 + Send
1355 + Send
1356 + '_,
1356 + '_,
1357 >,
1357 >,
1358 DirstateError,
1358 DirstateError,
1359 > {
1359 > {
1360 let map = self.get_map();
1360 let map = self.get_map();
1361 let on_disk = map.on_disk;
1361 let on_disk = map.on_disk;
1362 Ok(Box::new(filter_map_results(
1362 Ok(Box::new(filter_map_results(
1363 map.iter_nodes(),
1363 map.iter_nodes(),
1364 move |node| {
1364 move |node| {
1365 Ok(if node.tracked_descendants_count() > 0 {
1365 Ok(if node.tracked_descendants_count() > 0 {
1366 Some(node.full_path(on_disk)?)
1366 Some(node.full_path(on_disk)?)
1367 } else {
1367 } else {
1368 None
1368 None
1369 })
1369 })
1370 },
1370 },
1371 )))
1371 )))
1372 }
1372 }
1373
1373
1374 /// Only public because it needs to be exposed to the Python layer.
1374 /// Only public because it needs to be exposed to the Python layer.
1375 /// It is not the full `setparents` logic, only the parts that mutate the
1375 /// It is not the full `setparents` logic, only the parts that mutate the
1376 /// entries.
1376 /// entries.
1377 pub fn setparents_fixup(
1377 pub fn setparents_fixup(
1378 &mut self,
1378 &mut self,
1379 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1379 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1380 // XXX
1380 // XXX
1381 // All the copying and re-querying is quite inefficient, but this is
1381 // All the copying and re-querying is quite inefficient, but this is
1382 // still a lot better than doing it from Python.
1382 // still a lot better than doing it from Python.
1383 //
1383 //
1384 // The better solution is to develop a mechanism for `iter_mut`,
1384 // The better solution is to develop a mechanism for `iter_mut`,
1385 // which will be a lot more involved: we're dealing with a lazy,
1385 // which will be a lot more involved: we're dealing with a lazy,
1386 // append-mostly, tree-like data structure. This will do for now.
1386 // append-mostly, tree-like data structure. This will do for now.
1387 let mut copies = vec![];
1387 let mut copies = vec![];
1388 let mut files_with_p2_info = vec![];
1388 let mut files_with_p2_info = vec![];
1389 for res in self.iter() {
1389 for res in self.iter() {
1390 let (path, entry) = res?;
1390 let (path, entry) = res?;
1391 if entry.p2_info() {
1391 if entry.p2_info() {
1392 files_with_p2_info.push(path.to_owned())
1392 files_with_p2_info.push(path.to_owned())
1393 }
1393 }
1394 }
1394 }
1395 self.with_dmap_mut(|map| {
1395 self.with_dmap_mut(|map| {
1396 for path in files_with_p2_info.iter() {
1396 for path in files_with_p2_info.iter() {
1397 let node = map.get_or_insert(path)?;
1397 let node = map.get_or_insert(path)?;
1398 let entry =
1398 let entry =
1399 node.data.as_entry_mut().expect("entry should exist");
1399 node.data.as_entry_mut().expect("entry should exist");
1400 entry.drop_merge_data();
1400 entry.drop_merge_data();
1401 if let Some(source) = node.copy_source.take().as_deref() {
1401 if let Some(source) = node.copy_source.take().as_deref() {
1402 copies.push((path.to_owned(), source.to_owned()));
1402 copies.push((path.to_owned(), source.to_owned()));
1403 }
1403 }
1404 }
1404 }
1405 Ok(copies)
1405 Ok(copies)
1406 })
1406 })
1407 }
1407 }
1408
1408
1409 pub fn debug_iter(
1409 pub fn debug_iter(
1410 &self,
1410 &self,
1411 all: bool,
1411 all: bool,
1412 ) -> Box<
1412 ) -> Box<
1413 dyn Iterator<
1413 dyn Iterator<
1414 Item = Result<
1414 Item = Result<
1415 (&HgPath, (u8, i32, i32, i32)),
1415 (&HgPath, (u8, i32, i32, i32)),
1416 DirstateV2ParseError,
1416 DirstateV2ParseError,
1417 >,
1417 >,
1418 > + Send
1418 > + Send
1419 + '_,
1419 + '_,
1420 > {
1420 > {
1421 let map = self.get_map();
1421 let map = self.get_map();
1422 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1422 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1423 let debug_tuple = if let Some(entry) = node.entry()? {
1423 let debug_tuple = if let Some(entry) = node.entry()? {
1424 entry.debug_tuple()
1424 entry.debug_tuple()
1425 } else if !all {
1425 } else if !all {
1426 return Ok(None);
1426 return Ok(None);
1427 } else if let Some(mtime) = node.cached_directory_mtime()? {
1427 } else if let Some(mtime) = node.cached_directory_mtime()? {
1428 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1428 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1429 } else {
1429 } else {
1430 (b' ', 0, -1, -1)
1430 (b' ', 0, -1, -1)
1431 };
1431 };
1432 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1432 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1433 }))
1433 }))
1434 }
1434 }
1435 }
1435 }
1436 #[cfg(test)]
1437 mod tests {
1438 use super::*;
1439
1440 /// Shortcut to return tracked descendants of a path.
1441 /// Panics if the path does not exist.
1442 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1443 let path = dbg!(HgPath::new(path));
1444 let node = map.get_map().get_node(path);
1445 node.unwrap().unwrap().tracked_descendants_count()
1446 }
1447
1448 /// Shortcut to return descendants with an entry.
1449 /// Panics if the path does not exist.
1450 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1451 let path = dbg!(HgPath::new(path));
1452 let node = map.get_map().get_node(path);
1453 node.unwrap().unwrap().descendants_with_entry_count()
1454 }
1455
1456 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1457 let path = dbg!(HgPath::new(path));
1458 let node = map.get_map().get_node(path);
1459 assert!(node.unwrap().is_none());
1460 }
1461
1462 /// Shortcut for path creation in tests
1463 fn p(b: &[u8]) -> &HgPath {
1464 HgPath::new(b)
1465 }
1466
1467 /// Test the very simple case a single tracked file
1468 #[test]
1469 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1470 let mut map = OwningDirstateMap::new_empty(vec![]);
1471 assert_eq!(map.len(), 0);
1472
1473 map.set_tracked(p(b"some/nested/path"))?;
1474
1475 assert_eq!(map.len(), 1);
1476 assert_eq!(tracked_descendants(&map, b"some"), 1);
1477 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1478 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1479
1480 map.set_untracked(p(b"some/nested/path"))?;
1481 assert_eq!(map.len(), 0);
1482 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1483
1484 Ok(())
1485 }
1486
1487 /// Test the simple case of all tracked, but multiple files
1488 #[test]
1489 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1490 let mut map = OwningDirstateMap::new_empty(vec![]);
1491
1492 map.set_tracked(p(b"some/nested/path"))?;
1493 map.set_tracked(p(b"some/nested/file"))?;
1494 // one layer without any files to test deletion cascade
1495 map.set_tracked(p(b"some/other/nested/path"))?;
1496 map.set_tracked(p(b"root_file"))?;
1497 map.set_tracked(p(b"some/file"))?;
1498 map.set_tracked(p(b"some/file2"))?;
1499 map.set_tracked(p(b"some/file3"))?;
1500
1501 assert_eq!(map.len(), 7);
1502 assert_eq!(tracked_descendants(&map, b"some"), 6);
1503 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1504 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1505 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1506 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1507
1508 map.set_untracked(p(b"some/nested/path"))?;
1509 assert_eq!(map.len(), 6);
1510 assert_eq!(tracked_descendants(&map, b"some"), 5);
1511 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1512 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1513 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1514
1515 map.set_untracked(p(b"some/nested/file"))?;
1516 assert_eq!(map.len(), 5);
1517 assert_eq!(tracked_descendants(&map, b"some"), 4);
1518 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1519 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1520 assert_does_not_exist(&map, b"some_nested");
1521
1522 map.set_untracked(p(b"some/other/nested/path"))?;
1523 assert_eq!(map.len(), 4);
1524 assert_eq!(tracked_descendants(&map, b"some"), 3);
1525 assert_does_not_exist(&map, b"some/other");
1526
1527 map.set_untracked(p(b"root_file"))?;
1528 assert_eq!(map.len(), 3);
1529 assert_eq!(tracked_descendants(&map, b"some"), 3);
1530 assert_does_not_exist(&map, b"root_file");
1531
1532 map.set_untracked(p(b"some/file"))?;
1533 assert_eq!(map.len(), 2);
1534 assert_eq!(tracked_descendants(&map, b"some"), 2);
1535 assert_does_not_exist(&map, b"some/file");
1536
1537 map.set_untracked(p(b"some/file2"))?;
1538 assert_eq!(map.len(), 1);
1539 assert_eq!(tracked_descendants(&map, b"some"), 1);
1540 assert_does_not_exist(&map, b"some/file2");
1541
1542 map.set_untracked(p(b"some/file3"))?;
1543 assert_eq!(map.len(), 0);
1544 assert_does_not_exist(&map, b"some/file3");
1545
1546 Ok(())
1547 }
1548
1549 /// Check with a mix of tracked and non-tracked items
1550 #[test]
1551 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1552 let mut map = OwningDirstateMap::new_empty(vec![]);
1553
1554 // A file that was just added
1555 map.set_tracked(p(b"some/nested/path"))?;
1556 // This has no information, the dirstate should ignore it
1557 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1558 assert_does_not_exist(&map, b"some/file");
1559
1560 // A file that was removed
1561 map.reset_state(
1562 p(b"some/nested/file"),
1563 false,
1564 true,
1565 false,
1566 false,
1567 None,
1568 )?;
1569 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1570 // Only present in p2
1571 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1572 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1573 // A file that was merged
1574 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1575 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1576 // A file that is added, with info from p2
1577 // XXX is that actually possible?
1578 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1579 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1580 // A clean file
1581 // One layer without any files to test deletion cascade
1582 map.reset_state(
1583 p(b"some/other/nested/path"),
1584 true,
1585 true,
1586 false,
1587 false,
1588 None,
1589 )?;
1590 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1591
1592 assert_eq!(map.len(), 6);
1593 assert_eq!(tracked_descendants(&map, b"some"), 3);
1594 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1595 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1596 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1597 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1598 assert_eq!(
1599 descendants_with_an_entry(&map, b"some/other/nested/path"),
1600 0
1601 );
1602 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1603 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1604
1605 // might as well check this
1606 map.set_untracked(p(b"path/does/not/exist"))?;
1607 assert_eq!(map.len(), 6);
1608
1609 map.set_untracked(p(b"some/other/nested/path"))?;
1610 // It is set untracked but not deleted since it held other information
1611 assert_eq!(map.len(), 6);
1612 assert_eq!(tracked_descendants(&map, b"some"), 2);
1613 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1614 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1615 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1616 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1617 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1618
1619 map.set_untracked(p(b"some/nested/path"))?;
1620 // It is set untracked *and* deleted since it was only added
1621 assert_eq!(map.len(), 5);
1622 assert_eq!(tracked_descendants(&map, b"some"), 1);
1623 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1624 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1625 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1626 assert_does_not_exist(&map, b"some/nested/path");
1627
1628 map.set_untracked(p(b"root_file"))?;
1629 // Untracked but not deleted
1630 assert_eq!(map.len(), 5);
1631 assert!(map.get(p(b"root_file"))?.is_some());
1632
1633 map.set_untracked(p(b"some/file2"))?;
1634 assert_eq!(map.len(), 5);
1635 assert_eq!(tracked_descendants(&map, b"some"), 0);
1636 assert!(map.get(p(b"some/file2"))?.is_some());
1637
1638 map.set_untracked(p(b"some/file3"))?;
1639 assert_eq!(map.len(), 5);
1640 assert_eq!(tracked_descendants(&map, b"some"), 0);
1641 assert!(map.get(p(b"some/file3"))?.is_some());
1642
1643 Ok(())
1644 }
1645
1646 /// Check that copies counter is correctly updated
1647 #[test]
1648 fn test_copy_source() -> Result<(), DirstateError> {
1649 let mut map = OwningDirstateMap::new_empty(vec![]);
1650
1651 // Clean file
1652 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1653 // Merged file
1654 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1655 // Removed file
1656 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1657 // Added file
1658 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1659 // Add copy
1660 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1661 assert_eq!(map.copy_map_len(), 1);
1662
1663 // Copy override
1664 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1665 assert_eq!(map.copy_map_len(), 1);
1666
1667 // Multiple copies
1668 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1669 assert_eq!(map.copy_map_len(), 2);
1670
1671 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1672 assert_eq!(map.copy_map_len(), 3);
1673
1674 // Added, so the entry is completely removed
1675 map.set_untracked(p(b"files/added"))?;
1676 assert_does_not_exist(&map, b"files/added");
1677 assert_eq!(map.copy_map_len(), 2);
1678
1679 // Removed, so the entry is kept around, so is its copy
1680 map.set_untracked(p(b"removed"))?;
1681 assert!(map.get(p(b"removed"))?.is_some());
1682 assert_eq!(map.copy_map_len(), 2);
1683
1684 // Clean, so the entry is kept around, but not its copy
1685 map.set_untracked(p(b"files/clean"))?;
1686 assert!(map.get(p(b"files/clean"))?.is_some());
1687 assert_eq!(map.copy_map_len(), 1);
1688
1689 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1690 assert_eq!(map.copy_map_len(), 2);
1691
1692 // Info from p2, so its copy source info is kept around
1693 map.set_untracked(p(b"files/from_p2"))?;
1694 assert!(map.get(p(b"files/from_p2"))?.is_some());
1695 assert_eq!(map.copy_map_len(), 2);
1696
1697 Ok(())
1698 }
1699
1700 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1701 /// does not actually come from the disk, but it's opaque to the code being
1702 /// tested.
1703 #[test]
1704 fn test_on_disk() -> Result<(), DirstateError> {
1705 // First let's create some data to put "on disk"
1706 let mut map = OwningDirstateMap::new_empty(vec![]);
1707
1708 // A file that was just added
1709 map.set_tracked(p(b"some/nested/added"))?;
1710 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1711
1712 // A file that was removed
1713 map.reset_state(
1714 p(b"some/nested/removed"),
1715 false,
1716 true,
1717 false,
1718 false,
1719 None,
1720 )?;
1721 // Only present in p2
1722 map.reset_state(
1723 p(b"other/p2_info_only"),
1724 false,
1725 false,
1726 true,
1727 false,
1728 None,
1729 )?;
1730 map.copy_map_insert(
1731 p(b"other/p2_info_only"),
1732 p(b"other/p2_info_copy_source"),
1733 )?;
1734 // A file that was merged
1735 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1736 // A file that is added, with info from p2
1737 // XXX is that actually possible?
1738 map.reset_state(
1739 p(b"other/added_with_p2"),
1740 true,
1741 false,
1742 true,
1743 false,
1744 None,
1745 )?;
1746 // One layer without any files to test deletion cascade
1747 // A clean file
1748 map.reset_state(
1749 p(b"some/other/nested/clean"),
1750 true,
1751 true,
1752 false,
1753 false,
1754 None,
1755 )?;
1756
1757 let (packed, metadata, _should_append) = map.pack_v2(false)?;
1758 let packed_len = packed.len();
1759 assert!(packed_len > 0);
1760
1761 // Recreate "from disk"
1762 let mut map = OwningDirstateMap::new_v2(
1763 packed,
1764 packed_len,
1765 metadata.as_bytes(),
1766 )?;
1767
1768 // Check that everything is accounted for
1769 assert!(map.contains_key(p(b"some/nested/added"))?);
1770 assert!(map.contains_key(p(b"some/nested/removed"))?);
1771 assert!(map.contains_key(p(b"merged"))?);
1772 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1773 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1774 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1775 assert_eq!(
1776 map.copy_map_get(p(b"some/nested/added"))?,
1777 Some(p(b"added_copy_source"))
1778 );
1779 assert_eq!(
1780 map.copy_map_get(p(b"other/p2_info_only"))?,
1781 Some(p(b"other/p2_info_copy_source"))
1782 );
1783 assert_eq!(tracked_descendants(&map, b"some"), 2);
1784 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1785 assert_eq!(tracked_descendants(&map, b"other"), 1);
1786 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1787 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1788 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1789 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1790 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1791 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1792 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1793 assert_eq!(map.len(), 6);
1794 assert_eq!(map.get_map().unreachable_bytes, 0);
1795 assert_eq!(map.copy_map_len(), 2);
1796
1797 // Shouldn't change anything since it's already not tracked
1798 map.set_untracked(p(b"some/nested/removed"))?;
1799 assert_eq!(map.get_map().unreachable_bytes, 0);
1800
1801 match map.get_map().root {
1802 ChildNodes::InMemory(_) => {
1803 panic!("root should not have been mutated")
1804 }
1805 _ => (),
1806 }
1807 // We haven't mutated enough (nothing, actually), we should still be in
1808 // the append strategy
1809 assert!(map.get_map().write_should_append());
1810
1811 // But this mutates the structure, so there should be unreachable_bytes
1812 assert!(map.set_untracked(p(b"some/nested/added"))?);
1813 let unreachable_bytes = map.get_map().unreachable_bytes;
1814 assert!(unreachable_bytes > 0);
1815
1816 match map.get_map().root {
1817 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1818 _ => (),
1819 }
1820
1821 // This should not mutate the structure either, since `root` has
1822 // already been mutated along with its direct children.
1823 map.set_untracked(p(b"merged"))?;
1824 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1825
1826 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1827 NodeRef::InMemory(_, _) => {
1828 panic!("'other/added_with_p2' should not have been mutated")
1829 }
1830 _ => (),
1831 }
1832 // But this should, since it's in a different path
1833 // than `<root>some/nested/add`
1834 map.set_untracked(p(b"other/added_with_p2"))?;
1835 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1836
1837 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1838 NodeRef::OnDisk(_) => {
1839 panic!("'other/added_with_p2' should have been mutated")
1840 }
1841 _ => (),
1842 }
1843
1844 // We have rewritten most of the tree, we should create a new file
1845 assert!(!map.get_map().write_should_append());
1846
1847 Ok(())
1848 }
1849 }
General Comments 0
You need to be logged in to leave comments. Login now