##// END OF EJS Templates
rust: use `entry.tracked()` directly...
Raphaël Gomès -
r50027:3f5e207f default
parent child Browse files
Show More
@@ -1,1903 +1,1903 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.tracked();
481 let node = Self::get_or_insert_node_inner(
481 let node = Self::get_or_insert_node_inner(
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 /// `each_ancestor` is a callback that is called for each ancestor node
548 /// `each_ancestor` is a callback that is called for each ancestor node
549 /// when descending the tree. It is used to keep the different counters
549 /// when descending the tree. It is used to keep the different counters
550 /// of the `DirstateMap` up-to-date.
550 /// of the `DirstateMap` up-to-date.
551 fn get_node_mut<'tree>(
551 fn get_node_mut<'tree>(
552 &'tree mut self,
552 &'tree mut self,
553 path: &HgPath,
553 path: &HgPath,
554 each_ancestor: impl FnMut(&mut Node),
554 each_ancestor: impl FnMut(&mut Node),
555 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
555 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
556 Self::get_node_mut_inner(
556 Self::get_node_mut_inner(
557 self.on_disk,
557 self.on_disk,
558 &mut self.unreachable_bytes,
558 &mut self.unreachable_bytes,
559 &mut self.root,
559 &mut self.root,
560 path,
560 path,
561 each_ancestor,
561 each_ancestor,
562 )
562 )
563 }
563 }
564
564
565 /// Lower-level version of `get_node_mut`.
565 /// Lower-level version of `get_node_mut`.
566 ///
566 ///
567 /// This takes `root` instead of `&mut self` so that callers can mutate
567 /// This takes `root` instead of `&mut self` so that callers can mutate
568 /// other fields while the returned borrow is still valid.
568 /// other fields while the returned borrow is still valid.
569 ///
569 ///
570 /// `each_ancestor` is a callback that is called for each ancestor node
570 /// `each_ancestor` is a callback that is called for each ancestor node
571 /// when descending the tree. It is used to keep the different counters
571 /// when descending the tree. It is used to keep the different counters
572 /// of the `DirstateMap` up-to-date.
572 /// of the `DirstateMap` up-to-date.
573 fn get_node_mut_inner<'tree>(
573 fn get_node_mut_inner<'tree>(
574 on_disk: &'on_disk [u8],
574 on_disk: &'on_disk [u8],
575 unreachable_bytes: &mut u32,
575 unreachable_bytes: &mut u32,
576 root: &'tree mut ChildNodes<'on_disk>,
576 root: &'tree mut ChildNodes<'on_disk>,
577 path: &HgPath,
577 path: &HgPath,
578 mut each_ancestor: impl FnMut(&mut Node),
578 mut each_ancestor: impl FnMut(&mut Node),
579 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
579 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
580 let mut children = root;
580 let mut children = root;
581 let mut components = path.components();
581 let mut components = path.components();
582 let mut component =
582 let mut component =
583 components.next().expect("expected at least one components");
583 components.next().expect("expected at least one components");
584 loop {
584 loop {
585 if let Some(child) = children
585 if let Some(child) = children
586 .make_mut(on_disk, unreachable_bytes)?
586 .make_mut(on_disk, unreachable_bytes)?
587 .get_mut(component)
587 .get_mut(component)
588 {
588 {
589 if let Some(next_component) = components.next() {
589 if let Some(next_component) = components.next() {
590 each_ancestor(child);
590 each_ancestor(child);
591 component = next_component;
591 component = next_component;
592 children = &mut child.children;
592 children = &mut child.children;
593 } else {
593 } else {
594 return Ok(Some(child));
594 return Ok(Some(child));
595 }
595 }
596 } else {
596 } else {
597 return Ok(None);
597 return Ok(None);
598 }
598 }
599 }
599 }
600 }
600 }
601
601
602 /// Get a mutable reference to the node at `path`, creating it if it does
602 /// Get a mutable reference to the node at `path`, creating it if it does
603 /// not exist.
603 /// not exist.
604 ///
604 ///
605 /// `each_ancestor` is a callback that is called for each ancestor node
605 /// `each_ancestor` is a callback that is called for each ancestor node
606 /// when descending the tree. It is used to keep the different counters
606 /// when descending the tree. It is used to keep the different counters
607 /// of the `DirstateMap` up-to-date.
607 /// of the `DirstateMap` up-to-date.
608 fn get_or_insert_node<'tree, 'path>(
608 fn get_or_insert_node<'tree, 'path>(
609 &'tree mut self,
609 &'tree mut self,
610 path: &'path HgPath,
610 path: &'path HgPath,
611 each_ancestor: impl FnMut(&mut Node),
611 each_ancestor: impl FnMut(&mut Node),
612 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
612 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
613 Self::get_or_insert_node_inner(
613 Self::get_or_insert_node_inner(
614 self.on_disk,
614 self.on_disk,
615 &mut self.unreachable_bytes,
615 &mut self.unreachable_bytes,
616 &mut self.root,
616 &mut self.root,
617 path,
617 path,
618 WithBasename::to_cow_owned,
618 WithBasename::to_cow_owned,
619 each_ancestor,
619 each_ancestor,
620 )
620 )
621 }
621 }
622
622
623 /// Lower-level version of `get_or_insert_node_inner`, which is used when
623 /// Lower-level version of `get_or_insert_node_inner`, which is used when
624 /// parsing disk data to remove allocations for new nodes.
624 /// parsing disk data to remove allocations for new nodes.
625 fn get_or_insert_node_inner<'tree, 'path>(
625 fn get_or_insert_node_inner<'tree, 'path>(
626 on_disk: &'on_disk [u8],
626 on_disk: &'on_disk [u8],
627 unreachable_bytes: &mut u32,
627 unreachable_bytes: &mut u32,
628 root: &'tree mut ChildNodes<'on_disk>,
628 root: &'tree mut ChildNodes<'on_disk>,
629 path: &'path HgPath,
629 path: &'path HgPath,
630 to_cow: impl Fn(
630 to_cow: impl Fn(
631 WithBasename<&'path HgPath>,
631 WithBasename<&'path HgPath>,
632 ) -> WithBasename<Cow<'on_disk, HgPath>>,
632 ) -> WithBasename<Cow<'on_disk, HgPath>>,
633 mut each_ancestor: impl FnMut(&mut Node),
633 mut each_ancestor: impl FnMut(&mut Node),
634 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
634 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
635 let mut child_nodes = root;
635 let mut child_nodes = root;
636 let mut inclusive_ancestor_paths =
636 let mut inclusive_ancestor_paths =
637 WithBasename::inclusive_ancestors_of(path);
637 WithBasename::inclusive_ancestors_of(path);
638 let mut ancestor_path = inclusive_ancestor_paths
638 let mut ancestor_path = inclusive_ancestor_paths
639 .next()
639 .next()
640 .expect("expected at least one inclusive ancestor");
640 .expect("expected at least one inclusive ancestor");
641 loop {
641 loop {
642 let (_, child_node) = child_nodes
642 let (_, child_node) = child_nodes
643 .make_mut(on_disk, unreachable_bytes)?
643 .make_mut(on_disk, unreachable_bytes)?
644 .raw_entry_mut()
644 .raw_entry_mut()
645 .from_key(ancestor_path.base_name())
645 .from_key(ancestor_path.base_name())
646 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
646 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
647 if let Some(next) = inclusive_ancestor_paths.next() {
647 if let Some(next) = inclusive_ancestor_paths.next() {
648 each_ancestor(child_node);
648 each_ancestor(child_node);
649 ancestor_path = next;
649 ancestor_path = next;
650 child_nodes = &mut child_node.children;
650 child_nodes = &mut child_node.children;
651 } else {
651 } else {
652 return Ok(child_node);
652 return Ok(child_node);
653 }
653 }
654 }
654 }
655 }
655 }
656
656
657 fn reset_state(
657 fn reset_state(
658 &mut self,
658 &mut self,
659 filename: &HgPath,
659 filename: &HgPath,
660 old_entry_opt: Option<DirstateEntry>,
660 old_entry_opt: Option<DirstateEntry>,
661 wc_tracked: bool,
661 wc_tracked: bool,
662 p1_tracked: bool,
662 p1_tracked: bool,
663 p2_info: bool,
663 p2_info: bool,
664 has_meaningful_mtime: bool,
664 has_meaningful_mtime: bool,
665 parent_file_data_opt: Option<ParentFileData>,
665 parent_file_data_opt: Option<ParentFileData>,
666 ) -> Result<(), DirstateError> {
666 ) -> Result<(), DirstateError> {
667 let (had_entry, was_tracked) = match old_entry_opt {
667 let (had_entry, was_tracked) = match old_entry_opt {
668 Some(old_entry) => (true, old_entry.tracked()),
668 Some(old_entry) => (true, old_entry.tracked()),
669 None => (false, false),
669 None => (false, false),
670 };
670 };
671 let node = self.get_or_insert_node(filename, |ancestor| {
671 let node = self.get_or_insert_node(filename, |ancestor| {
672 if !had_entry {
672 if !had_entry {
673 ancestor.descendants_with_entry_count += 1;
673 ancestor.descendants_with_entry_count += 1;
674 }
674 }
675 if was_tracked {
675 if was_tracked {
676 if !wc_tracked {
676 if !wc_tracked {
677 ancestor.tracked_descendants_count = ancestor
677 ancestor.tracked_descendants_count = ancestor
678 .tracked_descendants_count
678 .tracked_descendants_count
679 .checked_sub(1)
679 .checked_sub(1)
680 .expect("tracked count to be >= 0");
680 .expect("tracked count to be >= 0");
681 }
681 }
682 } else {
682 } else {
683 if wc_tracked {
683 if wc_tracked {
684 ancestor.tracked_descendants_count += 1;
684 ancestor.tracked_descendants_count += 1;
685 }
685 }
686 }
686 }
687 })?;
687 })?;
688
688
689 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
689 let v2_data = if let Some(parent_file_data) = parent_file_data_opt {
690 DirstateV2Data {
690 DirstateV2Data {
691 wc_tracked,
691 wc_tracked,
692 p1_tracked,
692 p1_tracked,
693 p2_info,
693 p2_info,
694 mode_size: parent_file_data.mode_size,
694 mode_size: parent_file_data.mode_size,
695 mtime: if has_meaningful_mtime {
695 mtime: if has_meaningful_mtime {
696 parent_file_data.mtime
696 parent_file_data.mtime
697 } else {
697 } else {
698 None
698 None
699 },
699 },
700 ..Default::default()
700 ..Default::default()
701 }
701 }
702 } else {
702 } else {
703 DirstateV2Data {
703 DirstateV2Data {
704 wc_tracked,
704 wc_tracked,
705 p1_tracked,
705 p1_tracked,
706 p2_info,
706 p2_info,
707 ..Default::default()
707 ..Default::default()
708 }
708 }
709 };
709 };
710 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
710 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data));
711 if !had_entry {
711 if !had_entry {
712 self.nodes_with_entry_count += 1;
712 self.nodes_with_entry_count += 1;
713 }
713 }
714 Ok(())
714 Ok(())
715 }
715 }
716
716
717 fn set_tracked(
717 fn set_tracked(
718 &mut self,
718 &mut self,
719 filename: &HgPath,
719 filename: &HgPath,
720 old_entry_opt: Option<DirstateEntry>,
720 old_entry_opt: Option<DirstateEntry>,
721 ) -> Result<bool, DirstateV2ParseError> {
721 ) -> Result<bool, DirstateV2ParseError> {
722 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
722 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked());
723 let had_entry = old_entry_opt.is_some();
723 let had_entry = old_entry_opt.is_some();
724 let tracked_count_increment = if was_tracked { 0 } else { 1 };
724 let tracked_count_increment = if was_tracked { 0 } else { 1 };
725 let mut new = false;
725 let mut new = false;
726
726
727 let node = self.get_or_insert_node(filename, |ancestor| {
727 let node = self.get_or_insert_node(filename, |ancestor| {
728 if !had_entry {
728 if !had_entry {
729 ancestor.descendants_with_entry_count += 1;
729 ancestor.descendants_with_entry_count += 1;
730 }
730 }
731
731
732 ancestor.tracked_descendants_count += tracked_count_increment;
732 ancestor.tracked_descendants_count += tracked_count_increment;
733 })?;
733 })?;
734 if let Some(old_entry) = old_entry_opt {
734 if let Some(old_entry) = old_entry_opt {
735 let mut e = old_entry.clone();
735 let mut e = old_entry.clone();
736 if e.tracked() {
736 if e.tracked() {
737 // XXX
737 // XXX
738 // This is probably overkill for more case, but we need this to
738 // This is probably overkill for more case, but we need this to
739 // fully replace the `normallookup` call with `set_tracked`
739 // fully replace the `normallookup` call with `set_tracked`
740 // one. Consider smoothing this in the future.
740 // one. Consider smoothing this in the future.
741 e.set_possibly_dirty();
741 e.set_possibly_dirty();
742 } else {
742 } else {
743 new = true;
743 new = true;
744 e.set_tracked();
744 e.set_tracked();
745 }
745 }
746 node.data = NodeData::Entry(e)
746 node.data = NodeData::Entry(e)
747 } else {
747 } else {
748 node.data = NodeData::Entry(DirstateEntry::new_tracked());
748 node.data = NodeData::Entry(DirstateEntry::new_tracked());
749 self.nodes_with_entry_count += 1;
749 self.nodes_with_entry_count += 1;
750 new = true;
750 new = true;
751 };
751 };
752 Ok(new)
752 Ok(new)
753 }
753 }
754
754
755 /// Set a node as untracked in the dirstate.
755 /// Set a node as untracked in the dirstate.
756 ///
756 ///
757 /// It is the responsibility of the caller to remove the copy source and/or
757 /// It is the responsibility of the caller to remove the copy source and/or
758 /// the entry itself if appropriate.
758 /// the entry itself if appropriate.
759 ///
759 ///
760 /// # Panics
760 /// # Panics
761 ///
761 ///
762 /// Panics if the node does not exist.
762 /// Panics if the node does not exist.
763 fn set_untracked(
763 fn set_untracked(
764 &mut self,
764 &mut self,
765 filename: &HgPath,
765 filename: &HgPath,
766 old_entry: DirstateEntry,
766 old_entry: DirstateEntry,
767 ) -> Result<(), DirstateV2ParseError> {
767 ) -> Result<(), DirstateV2ParseError> {
768 let node = self
768 let node = self
769 .get_node_mut(filename, |ancestor| {
769 .get_node_mut(filename, |ancestor| {
770 ancestor.tracked_descendants_count = ancestor
770 ancestor.tracked_descendants_count = ancestor
771 .tracked_descendants_count
771 .tracked_descendants_count
772 .checked_sub(1)
772 .checked_sub(1)
773 .expect("tracked_descendants_count should be >= 0");
773 .expect("tracked_descendants_count should be >= 0");
774 })?
774 })?
775 .expect("node should exist");
775 .expect("node should exist");
776 let mut new_entry = old_entry.clone();
776 let mut new_entry = old_entry.clone();
777 new_entry.set_untracked();
777 new_entry.set_untracked();
778 node.data = NodeData::Entry(new_entry);
778 node.data = NodeData::Entry(new_entry);
779 Ok(())
779 Ok(())
780 }
780 }
781
781
782 /// Set a node as clean in the dirstate.
782 /// Set a node as clean in the dirstate.
783 ///
783 ///
784 /// It is the responsibility of the caller to remove the copy source.
784 /// It is the responsibility of the caller to remove the copy source.
785 ///
785 ///
786 /// # Panics
786 /// # Panics
787 ///
787 ///
788 /// Panics if the node does not exist.
788 /// Panics if the node does not exist.
789 fn set_clean(
789 fn set_clean(
790 &mut self,
790 &mut self,
791 filename: &HgPath,
791 filename: &HgPath,
792 old_entry: DirstateEntry,
792 old_entry: DirstateEntry,
793 mode: u32,
793 mode: u32,
794 size: u32,
794 size: u32,
795 mtime: TruncatedTimestamp,
795 mtime: TruncatedTimestamp,
796 ) -> Result<(), DirstateError> {
796 ) -> Result<(), DirstateError> {
797 let node = self
797 let node = self
798 .get_node_mut(filename, |ancestor| {
798 .get_node_mut(filename, |ancestor| {
799 if !old_entry.tracked() {
799 if !old_entry.tracked() {
800 ancestor.tracked_descendants_count += 1;
800 ancestor.tracked_descendants_count += 1;
801 }
801 }
802 })?
802 })?
803 .expect("node should exist");
803 .expect("node should exist");
804 let mut new_entry = old_entry.clone();
804 let mut new_entry = old_entry.clone();
805 new_entry.set_clean(mode, size, mtime);
805 new_entry.set_clean(mode, size, mtime);
806 node.data = NodeData::Entry(new_entry);
806 node.data = NodeData::Entry(new_entry);
807 Ok(())
807 Ok(())
808 }
808 }
809
809
810 /// Set a node as possibly dirty in the dirstate.
810 /// Set a node as possibly dirty in the dirstate.
811 ///
811 ///
812 /// # Panics
812 /// # Panics
813 ///
813 ///
814 /// Panics if the node does not exist.
814 /// Panics if the node does not exist.
815 fn set_possibly_dirty(
815 fn set_possibly_dirty(
816 &mut self,
816 &mut self,
817 filename: &HgPath,
817 filename: &HgPath,
818 ) -> Result<(), DirstateError> {
818 ) -> Result<(), DirstateError> {
819 let node = self
819 let node = self
820 .get_node_mut(filename, |_ancestor| {})?
820 .get_node_mut(filename, |_ancestor| {})?
821 .expect("node should exist");
821 .expect("node should exist");
822 let entry = node.data.as_entry_mut().expect("entry should exist");
822 let entry = node.data.as_entry_mut().expect("entry should exist");
823 entry.set_possibly_dirty();
823 entry.set_possibly_dirty();
824 node.data = NodeData::Entry(*entry);
824 node.data = NodeData::Entry(*entry);
825 Ok(())
825 Ok(())
826 }
826 }
827
827
828 /// Clears the cached mtime for the (potential) folder at `path`.
828 /// Clears the cached mtime for the (potential) folder at `path`.
829 pub(super) fn clear_cached_mtime(
829 pub(super) fn clear_cached_mtime(
830 &mut self,
830 &mut self,
831 path: &HgPath,
831 path: &HgPath,
832 ) -> Result<(), DirstateV2ParseError> {
832 ) -> Result<(), DirstateV2ParseError> {
833 let node = match self.get_node_mut(path, |_ancestor| {})? {
833 let node = match self.get_node_mut(path, |_ancestor| {})? {
834 Some(node) => node,
834 Some(node) => node,
835 None => return Ok(()),
835 None => return Ok(()),
836 };
836 };
837 if let NodeData::CachedDirectory { .. } = &node.data {
837 if let NodeData::CachedDirectory { .. } = &node.data {
838 node.data = NodeData::None
838 node.data = NodeData::None
839 }
839 }
840 Ok(())
840 Ok(())
841 }
841 }
842
842
843 /// Sets the cached mtime for the (potential) folder at `path`.
843 /// Sets the cached mtime for the (potential) folder at `path`.
844 pub(super) fn set_cached_mtime(
844 pub(super) fn set_cached_mtime(
845 &mut self,
845 &mut self,
846 path: &HgPath,
846 path: &HgPath,
847 mtime: TruncatedTimestamp,
847 mtime: TruncatedTimestamp,
848 ) -> Result<(), DirstateV2ParseError> {
848 ) -> Result<(), DirstateV2ParseError> {
849 let node = match self.get_node_mut(path, |_ancestor| {})? {
849 let node = match self.get_node_mut(path, |_ancestor| {})? {
850 Some(node) => node,
850 Some(node) => node,
851 None => return Ok(()),
851 None => return Ok(()),
852 };
852 };
853 match &node.data {
853 match &node.data {
854 NodeData::Entry(_) => {} // Don’t overwrite an entry
854 NodeData::Entry(_) => {} // Don’t overwrite an entry
855 NodeData::CachedDirectory { .. } | NodeData::None => {
855 NodeData::CachedDirectory { .. } | NodeData::None => {
856 node.data = NodeData::CachedDirectory { mtime }
856 node.data = NodeData::CachedDirectory { mtime }
857 }
857 }
858 }
858 }
859 Ok(())
859 Ok(())
860 }
860 }
861
861
862 fn iter_nodes<'tree>(
862 fn iter_nodes<'tree>(
863 &'tree self,
863 &'tree self,
864 ) -> impl Iterator<
864 ) -> impl Iterator<
865 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
865 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
866 > + 'tree {
866 > + 'tree {
867 // Depth first tree traversal.
867 // Depth first tree traversal.
868 //
868 //
869 // If we could afford internal iteration and recursion,
869 // If we could afford internal iteration and recursion,
870 // this would look like:
870 // this would look like:
871 //
871 //
872 // ```
872 // ```
873 // fn traverse_children(
873 // fn traverse_children(
874 // children: &ChildNodes,
874 // children: &ChildNodes,
875 // each: &mut impl FnMut(&Node),
875 // each: &mut impl FnMut(&Node),
876 // ) {
876 // ) {
877 // for child in children.values() {
877 // for child in children.values() {
878 // traverse_children(&child.children, each);
878 // traverse_children(&child.children, each);
879 // each(child);
879 // each(child);
880 // }
880 // }
881 // }
881 // }
882 // ```
882 // ```
883 //
883 //
884 // However we want an external iterator and therefore can’t use the
884 // However we want an external iterator and therefore can’t use the
885 // call stack. Use an explicit stack instead:
885 // call stack. Use an explicit stack instead:
886 let mut stack = Vec::new();
886 let mut stack = Vec::new();
887 let mut iter = self.root.as_ref().iter();
887 let mut iter = self.root.as_ref().iter();
888 std::iter::from_fn(move || {
888 std::iter::from_fn(move || {
889 while let Some(child_node) = iter.next() {
889 while let Some(child_node) = iter.next() {
890 let children = match child_node.children(self.on_disk) {
890 let children = match child_node.children(self.on_disk) {
891 Ok(children) => children,
891 Ok(children) => children,
892 Err(error) => return Some(Err(error)),
892 Err(error) => return Some(Err(error)),
893 };
893 };
894 // Pseudo-recursion
894 // Pseudo-recursion
895 let new_iter = children.iter();
895 let new_iter = children.iter();
896 let old_iter = std::mem::replace(&mut iter, new_iter);
896 let old_iter = std::mem::replace(&mut iter, new_iter);
897 stack.push((child_node, old_iter));
897 stack.push((child_node, old_iter));
898 }
898 }
899 // Found the end of a `children.iter()` iterator.
899 // Found the end of a `children.iter()` iterator.
900 if let Some((child_node, next_iter)) = stack.pop() {
900 if let Some((child_node, next_iter)) = stack.pop() {
901 // "Return" from pseudo-recursion by restoring state from the
901 // "Return" from pseudo-recursion by restoring state from the
902 // explicit stack
902 // explicit stack
903 iter = next_iter;
903 iter = next_iter;
904
904
905 Some(Ok(child_node))
905 Some(Ok(child_node))
906 } else {
906 } else {
907 // Reached the bottom of the stack, we’re done
907 // Reached the bottom of the stack, we’re done
908 None
908 None
909 }
909 }
910 })
910 })
911 }
911 }
912
912
913 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
913 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
914 if let Cow::Borrowed(path) = path {
914 if let Cow::Borrowed(path) = path {
915 *unreachable_bytes += path.len() as u32
915 *unreachable_bytes += path.len() as u32
916 }
916 }
917 }
917 }
918 }
918 }
919
919
920 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
920 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
921 ///
921 ///
922 /// The callback is only called for incoming `Ok` values. Errors are passed
922 /// The callback is only called for incoming `Ok` values. Errors are passed
923 /// through as-is. In order to let it use the `?` operator the callback is
923 /// through as-is. In order to let it use the `?` operator the callback is
924 /// expected to return a `Result` of `Option`, instead of an `Option` of
924 /// expected to return a `Result` of `Option`, instead of an `Option` of
925 /// `Result`.
925 /// `Result`.
926 fn filter_map_results<'a, I, F, A, B, E>(
926 fn filter_map_results<'a, I, F, A, B, E>(
927 iter: I,
927 iter: I,
928 f: F,
928 f: F,
929 ) -> impl Iterator<Item = Result<B, E>> + 'a
929 ) -> impl Iterator<Item = Result<B, E>> + 'a
930 where
930 where
931 I: Iterator<Item = Result<A, E>> + 'a,
931 I: Iterator<Item = Result<A, E>> + 'a,
932 F: Fn(A) -> Result<Option<B>, E> + 'a,
932 F: Fn(A) -> Result<Option<B>, E> + 'a,
933 {
933 {
934 iter.filter_map(move |result| match result {
934 iter.filter_map(move |result| match result {
935 Ok(node) => f(node).transpose(),
935 Ok(node) => f(node).transpose(),
936 Err(e) => Some(Err(e)),
936 Err(e) => Some(Err(e)),
937 })
937 })
938 }
938 }
939
939
940 impl OwningDirstateMap {
940 impl OwningDirstateMap {
941 pub fn clear(&mut self) {
941 pub fn clear(&mut self) {
942 self.with_dmap_mut(|map| {
942 self.with_dmap_mut(|map| {
943 map.root = Default::default();
943 map.root = Default::default();
944 map.nodes_with_entry_count = 0;
944 map.nodes_with_entry_count = 0;
945 map.nodes_with_copy_source_count = 0;
945 map.nodes_with_copy_source_count = 0;
946 });
946 });
947 }
947 }
948
948
949 pub fn set_tracked(
949 pub fn set_tracked(
950 &mut self,
950 &mut self,
951 filename: &HgPath,
951 filename: &HgPath,
952 ) -> Result<bool, DirstateV2ParseError> {
952 ) -> Result<bool, DirstateV2ParseError> {
953 let old_entry_opt = self.get(filename)?;
953 let old_entry_opt = self.get(filename)?;
954 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
954 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt))
955 }
955 }
956
956
957 pub fn set_untracked(
957 pub fn set_untracked(
958 &mut self,
958 &mut self,
959 filename: &HgPath,
959 filename: &HgPath,
960 ) -> Result<bool, DirstateError> {
960 ) -> Result<bool, DirstateError> {
961 let old_entry_opt = self.get(filename)?;
961 let old_entry_opt = self.get(filename)?;
962 match old_entry_opt {
962 match old_entry_opt {
963 None => Ok(false),
963 None => Ok(false),
964 Some(old_entry) => {
964 Some(old_entry) => {
965 if !old_entry.tracked() {
965 if !old_entry.tracked() {
966 // `DirstateMap::set_untracked` is not a noop if
966 // `DirstateMap::set_untracked` is not a noop if
967 // already not tracked as it will decrement the
967 // already not tracked as it will decrement the
968 // tracked counters while going down.
968 // tracked counters while going down.
969 return Ok(true);
969 return Ok(true);
970 }
970 }
971 if old_entry.added() {
971 if old_entry.added() {
972 // Untracking an "added" entry will just result in a
972 // Untracking an "added" entry will just result in a
973 // worthless entry (and other parts of the code will
973 // worthless entry (and other parts of the code will
974 // complain about it), just drop it entirely.
974 // complain about it), just drop it entirely.
975 self.drop_entry_and_copy_source(filename)?;
975 self.drop_entry_and_copy_source(filename)?;
976 return Ok(true);
976 return Ok(true);
977 }
977 }
978 if !old_entry.p2_info() {
978 if !old_entry.p2_info() {
979 self.copy_map_remove(filename)?;
979 self.copy_map_remove(filename)?;
980 }
980 }
981
981
982 self.with_dmap_mut(|map| {
982 self.with_dmap_mut(|map| {
983 map.set_untracked(filename, old_entry)?;
983 map.set_untracked(filename, old_entry)?;
984 Ok(true)
984 Ok(true)
985 })
985 })
986 }
986 }
987 }
987 }
988 }
988 }
989
989
990 pub fn set_clean(
990 pub fn set_clean(
991 &mut self,
991 &mut self,
992 filename: &HgPath,
992 filename: &HgPath,
993 mode: u32,
993 mode: u32,
994 size: u32,
994 size: u32,
995 mtime: TruncatedTimestamp,
995 mtime: TruncatedTimestamp,
996 ) -> Result<(), DirstateError> {
996 ) -> Result<(), DirstateError> {
997 let old_entry = match self.get(filename)? {
997 let old_entry = match self.get(filename)? {
998 None => {
998 None => {
999 return Err(
999 return Err(
1000 DirstateMapError::PathNotFound(filename.into()).into()
1000 DirstateMapError::PathNotFound(filename.into()).into()
1001 )
1001 )
1002 }
1002 }
1003 Some(e) => e,
1003 Some(e) => e,
1004 };
1004 };
1005 self.copy_map_remove(filename)?;
1005 self.copy_map_remove(filename)?;
1006 self.with_dmap_mut(|map| {
1006 self.with_dmap_mut(|map| {
1007 map.set_clean(filename, old_entry, mode, size, mtime)
1007 map.set_clean(filename, old_entry, mode, size, mtime)
1008 })
1008 })
1009 }
1009 }
1010
1010
1011 pub fn set_possibly_dirty(
1011 pub fn set_possibly_dirty(
1012 &mut self,
1012 &mut self,
1013 filename: &HgPath,
1013 filename: &HgPath,
1014 ) -> Result<(), DirstateError> {
1014 ) -> Result<(), DirstateError> {
1015 if self.get(filename)?.is_none() {
1015 if self.get(filename)?.is_none() {
1016 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1016 return Err(DirstateMapError::PathNotFound(filename.into()).into());
1017 }
1017 }
1018 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1018 self.with_dmap_mut(|map| map.set_possibly_dirty(filename))
1019 }
1019 }
1020
1020
1021 pub fn reset_state(
1021 pub fn reset_state(
1022 &mut self,
1022 &mut self,
1023 filename: &HgPath,
1023 filename: &HgPath,
1024 wc_tracked: bool,
1024 wc_tracked: bool,
1025 p1_tracked: bool,
1025 p1_tracked: bool,
1026 p2_info: bool,
1026 p2_info: bool,
1027 has_meaningful_mtime: bool,
1027 has_meaningful_mtime: bool,
1028 parent_file_data_opt: Option<ParentFileData>,
1028 parent_file_data_opt: Option<ParentFileData>,
1029 ) -> Result<(), DirstateError> {
1029 ) -> Result<(), DirstateError> {
1030 if !(p1_tracked || p2_info || wc_tracked) {
1030 if !(p1_tracked || p2_info || wc_tracked) {
1031 self.drop_entry_and_copy_source(filename)?;
1031 self.drop_entry_and_copy_source(filename)?;
1032 return Ok(());
1032 return Ok(());
1033 }
1033 }
1034 self.copy_map_remove(filename)?;
1034 self.copy_map_remove(filename)?;
1035 let old_entry_opt = self.get(filename)?;
1035 let old_entry_opt = self.get(filename)?;
1036 self.with_dmap_mut(|map| {
1036 self.with_dmap_mut(|map| {
1037 map.reset_state(
1037 map.reset_state(
1038 filename,
1038 filename,
1039 old_entry_opt,
1039 old_entry_opt,
1040 wc_tracked,
1040 wc_tracked,
1041 p1_tracked,
1041 p1_tracked,
1042 p2_info,
1042 p2_info,
1043 has_meaningful_mtime,
1043 has_meaningful_mtime,
1044 parent_file_data_opt,
1044 parent_file_data_opt,
1045 )
1045 )
1046 })
1046 })
1047 }
1047 }
1048
1048
1049 pub fn drop_entry_and_copy_source(
1049 pub fn drop_entry_and_copy_source(
1050 &mut self,
1050 &mut self,
1051 filename: &HgPath,
1051 filename: &HgPath,
1052 ) -> Result<(), DirstateError> {
1052 ) -> Result<(), DirstateError> {
1053 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1053 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked());
1054 struct Dropped {
1054 struct Dropped {
1055 was_tracked: bool,
1055 was_tracked: bool,
1056 had_entry: bool,
1056 had_entry: bool,
1057 had_copy_source: bool,
1057 had_copy_source: bool,
1058 }
1058 }
1059
1059
1060 /// If this returns `Ok(Some((dropped, removed)))`, then
1060 /// If this returns `Ok(Some((dropped, removed)))`, then
1061 ///
1061 ///
1062 /// * `dropped` is about the leaf node that was at `filename`
1062 /// * `dropped` is about the leaf node that was at `filename`
1063 /// * `removed` is whether this particular level of recursion just
1063 /// * `removed` is whether this particular level of recursion just
1064 /// removed a node in `nodes`.
1064 /// removed a node in `nodes`.
1065 fn recur<'on_disk>(
1065 fn recur<'on_disk>(
1066 on_disk: &'on_disk [u8],
1066 on_disk: &'on_disk [u8],
1067 unreachable_bytes: &mut u32,
1067 unreachable_bytes: &mut u32,
1068 nodes: &mut ChildNodes<'on_disk>,
1068 nodes: &mut ChildNodes<'on_disk>,
1069 path: &HgPath,
1069 path: &HgPath,
1070 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1070 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
1071 let (first_path_component, rest_of_path) =
1071 let (first_path_component, rest_of_path) =
1072 path.split_first_component();
1072 path.split_first_component();
1073 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1073 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
1074 let node = if let Some(node) = nodes.get_mut(first_path_component)
1074 let node = if let Some(node) = nodes.get_mut(first_path_component)
1075 {
1075 {
1076 node
1076 node
1077 } else {
1077 } else {
1078 return Ok(None);
1078 return Ok(None);
1079 };
1079 };
1080 let dropped;
1080 let dropped;
1081 if let Some(rest) = rest_of_path {
1081 if let Some(rest) = rest_of_path {
1082 if let Some((d, removed)) = recur(
1082 if let Some((d, removed)) = recur(
1083 on_disk,
1083 on_disk,
1084 unreachable_bytes,
1084 unreachable_bytes,
1085 &mut node.children,
1085 &mut node.children,
1086 rest,
1086 rest,
1087 )? {
1087 )? {
1088 dropped = d;
1088 dropped = d;
1089 if dropped.had_entry {
1089 if dropped.had_entry {
1090 node.descendants_with_entry_count = node
1090 node.descendants_with_entry_count = node
1091 .descendants_with_entry_count
1091 .descendants_with_entry_count
1092 .checked_sub(1)
1092 .checked_sub(1)
1093 .expect(
1093 .expect(
1094 "descendants_with_entry_count should be >= 0",
1094 "descendants_with_entry_count should be >= 0",
1095 );
1095 );
1096 }
1096 }
1097 if dropped.was_tracked {
1097 if dropped.was_tracked {
1098 node.tracked_descendants_count = node
1098 node.tracked_descendants_count = node
1099 .tracked_descendants_count
1099 .tracked_descendants_count
1100 .checked_sub(1)
1100 .checked_sub(1)
1101 .expect(
1101 .expect(
1102 "tracked_descendants_count should be >= 0",
1102 "tracked_descendants_count should be >= 0",
1103 );
1103 );
1104 }
1104 }
1105
1105
1106 // Directory caches must be invalidated when removing a
1106 // Directory caches must be invalidated when removing a
1107 // child node
1107 // child node
1108 if removed {
1108 if removed {
1109 if let NodeData::CachedDirectory { .. } = &node.data {
1109 if let NodeData::CachedDirectory { .. } = &node.data {
1110 node.data = NodeData::None
1110 node.data = NodeData::None
1111 }
1111 }
1112 }
1112 }
1113 } else {
1113 } else {
1114 return Ok(None);
1114 return Ok(None);
1115 }
1115 }
1116 } else {
1116 } else {
1117 let entry = node.data.as_entry();
1117 let entry = node.data.as_entry();
1118 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1118 let was_tracked = entry.map_or(false, |entry| entry.tracked());
1119 let had_entry = entry.is_some();
1119 let had_entry = entry.is_some();
1120 if had_entry {
1120 if had_entry {
1121 node.data = NodeData::None
1121 node.data = NodeData::None
1122 }
1122 }
1123 let mut had_copy_source = false;
1123 let mut had_copy_source = false;
1124 if let Some(source) = &node.copy_source {
1124 if let Some(source) = &node.copy_source {
1125 DirstateMap::count_dropped_path(unreachable_bytes, source);
1125 DirstateMap::count_dropped_path(unreachable_bytes, source);
1126 had_copy_source = true;
1126 had_copy_source = true;
1127 node.copy_source = None
1127 node.copy_source = None
1128 }
1128 }
1129 dropped = Dropped {
1129 dropped = Dropped {
1130 was_tracked,
1130 was_tracked,
1131 had_entry,
1131 had_entry,
1132 had_copy_source,
1132 had_copy_source,
1133 };
1133 };
1134 }
1134 }
1135 // After recursion, for both leaf (rest_of_path is None) nodes and
1135 // After recursion, for both leaf (rest_of_path is None) nodes and
1136 // parent nodes, remove a node if it just became empty.
1136 // parent nodes, remove a node if it just became empty.
1137 let remove = !node.data.has_entry()
1137 let remove = !node.data.has_entry()
1138 && node.copy_source.is_none()
1138 && node.copy_source.is_none()
1139 && node.children.is_empty();
1139 && node.children.is_empty();
1140 if remove {
1140 if remove {
1141 let (key, _) =
1141 let (key, _) =
1142 nodes.remove_entry(first_path_component).unwrap();
1142 nodes.remove_entry(first_path_component).unwrap();
1143 DirstateMap::count_dropped_path(
1143 DirstateMap::count_dropped_path(
1144 unreachable_bytes,
1144 unreachable_bytes,
1145 key.full_path(),
1145 key.full_path(),
1146 )
1146 )
1147 }
1147 }
1148 Ok(Some((dropped, remove)))
1148 Ok(Some((dropped, remove)))
1149 }
1149 }
1150
1150
1151 self.with_dmap_mut(|map| {
1151 self.with_dmap_mut(|map| {
1152 if let Some((dropped, _removed)) = recur(
1152 if let Some((dropped, _removed)) = recur(
1153 map.on_disk,
1153 map.on_disk,
1154 &mut map.unreachable_bytes,
1154 &mut map.unreachable_bytes,
1155 &mut map.root,
1155 &mut map.root,
1156 filename,
1156 filename,
1157 )? {
1157 )? {
1158 if dropped.had_entry {
1158 if dropped.had_entry {
1159 map.nodes_with_entry_count = map
1159 map.nodes_with_entry_count = map
1160 .nodes_with_entry_count
1160 .nodes_with_entry_count
1161 .checked_sub(1)
1161 .checked_sub(1)
1162 .expect("nodes_with_entry_count should be >= 0");
1162 .expect("nodes_with_entry_count should be >= 0");
1163 }
1163 }
1164 if dropped.had_copy_source {
1164 if dropped.had_copy_source {
1165 map.nodes_with_copy_source_count = map
1165 map.nodes_with_copy_source_count = map
1166 .nodes_with_copy_source_count
1166 .nodes_with_copy_source_count
1167 .checked_sub(1)
1167 .checked_sub(1)
1168 .expect("nodes_with_copy_source_count should be >= 0");
1168 .expect("nodes_with_copy_source_count should be >= 0");
1169 }
1169 }
1170 } else {
1170 } else {
1171 debug_assert!(!was_tracked);
1171 debug_assert!(!was_tracked);
1172 }
1172 }
1173 Ok(())
1173 Ok(())
1174 })
1174 })
1175 }
1175 }
1176
1176
1177 pub fn has_tracked_dir(
1177 pub fn has_tracked_dir(
1178 &mut self,
1178 &mut self,
1179 directory: &HgPath,
1179 directory: &HgPath,
1180 ) -> Result<bool, DirstateError> {
1180 ) -> Result<bool, DirstateError> {
1181 self.with_dmap_mut(|map| {
1181 self.with_dmap_mut(|map| {
1182 if let Some(node) = map.get_node(directory)? {
1182 if let Some(node) = map.get_node(directory)? {
1183 // A node without a `DirstateEntry` was created to hold child
1183 // A node without a `DirstateEntry` was created to hold child
1184 // nodes, and is therefore a directory.
1184 // nodes, and is therefore a directory.
1185 let state = node.state()?;
1185 let state = node.state()?;
1186 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1186 Ok(state.is_none() && node.tracked_descendants_count() > 0)
1187 } else {
1187 } else {
1188 Ok(false)
1188 Ok(false)
1189 }
1189 }
1190 })
1190 })
1191 }
1191 }
1192
1192
1193 pub fn has_dir(
1193 pub fn has_dir(
1194 &mut self,
1194 &mut self,
1195 directory: &HgPath,
1195 directory: &HgPath,
1196 ) -> Result<bool, DirstateError> {
1196 ) -> Result<bool, DirstateError> {
1197 self.with_dmap_mut(|map| {
1197 self.with_dmap_mut(|map| {
1198 if let Some(node) = map.get_node(directory)? {
1198 if let Some(node) = map.get_node(directory)? {
1199 // A node without a `DirstateEntry` was created to hold child
1199 // A node without a `DirstateEntry` was created to hold child
1200 // nodes, and is therefore a directory.
1200 // nodes, and is therefore a directory.
1201 let state = node.state()?;
1201 let state = node.state()?;
1202 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1202 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
1203 } else {
1203 } else {
1204 Ok(false)
1204 Ok(false)
1205 }
1205 }
1206 })
1206 })
1207 }
1207 }
1208
1208
1209 #[timed]
1209 #[timed]
1210 pub fn pack_v1(
1210 pub fn pack_v1(
1211 &self,
1211 &self,
1212 parents: DirstateParents,
1212 parents: DirstateParents,
1213 ) -> Result<Vec<u8>, DirstateError> {
1213 ) -> Result<Vec<u8>, DirstateError> {
1214 let map = self.get_map();
1214 let map = self.get_map();
1215 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1215 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
1216 // reallocations
1216 // reallocations
1217 let mut size = parents.as_bytes().len();
1217 let mut size = parents.as_bytes().len();
1218 for node in map.iter_nodes() {
1218 for node in map.iter_nodes() {
1219 let node = node?;
1219 let node = node?;
1220 if node.entry()?.is_some() {
1220 if node.entry()?.is_some() {
1221 size += packed_entry_size(
1221 size += packed_entry_size(
1222 node.full_path(map.on_disk)?,
1222 node.full_path(map.on_disk)?,
1223 node.copy_source(map.on_disk)?,
1223 node.copy_source(map.on_disk)?,
1224 );
1224 );
1225 }
1225 }
1226 }
1226 }
1227
1227
1228 let mut packed = Vec::with_capacity(size);
1228 let mut packed = Vec::with_capacity(size);
1229 packed.extend(parents.as_bytes());
1229 packed.extend(parents.as_bytes());
1230
1230
1231 for node in map.iter_nodes() {
1231 for node in map.iter_nodes() {
1232 let node = node?;
1232 let node = node?;
1233 if let Some(entry) = node.entry()? {
1233 if let Some(entry) = node.entry()? {
1234 pack_entry(
1234 pack_entry(
1235 node.full_path(map.on_disk)?,
1235 node.full_path(map.on_disk)?,
1236 &entry,
1236 &entry,
1237 node.copy_source(map.on_disk)?,
1237 node.copy_source(map.on_disk)?,
1238 &mut packed,
1238 &mut packed,
1239 );
1239 );
1240 }
1240 }
1241 }
1241 }
1242 Ok(packed)
1242 Ok(packed)
1243 }
1243 }
1244
1244
1245 /// Returns new data and metadata together with whether that data should be
1245 /// Returns new data and metadata together with whether that data should be
1246 /// appended to the existing data file whose content is at
1246 /// appended to the existing data file whose content is at
1247 /// `map.on_disk` (true), instead of written to a new data file
1247 /// `map.on_disk` (true), instead of written to a new data file
1248 /// (false).
1248 /// (false).
1249 #[timed]
1249 #[timed]
1250 pub fn pack_v2(
1250 pub fn pack_v2(
1251 &self,
1251 &self,
1252 can_append: bool,
1252 can_append: bool,
1253 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1253 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
1254 let map = self.get_map();
1254 let map = self.get_map();
1255 on_disk::write(map, can_append)
1255 on_disk::write(map, can_append)
1256 }
1256 }
1257
1257
1258 /// `callback` allows the caller to process and do something with the
1258 /// `callback` allows the caller to process and do something with the
1259 /// results of the status. This is needed to do so efficiently (i.e.
1259 /// results of the status. This is needed to do so efficiently (i.e.
1260 /// without cloning the `DirstateStatus` object with its paths) because
1260 /// without cloning the `DirstateStatus` object with its paths) because
1261 /// we need to borrow from `Self`.
1261 /// we need to borrow from `Self`.
1262 pub fn with_status<R>(
1262 pub fn with_status<R>(
1263 &mut self,
1263 &mut self,
1264 matcher: &(dyn Matcher + Sync),
1264 matcher: &(dyn Matcher + Sync),
1265 root_dir: PathBuf,
1265 root_dir: PathBuf,
1266 ignore_files: Vec<PathBuf>,
1266 ignore_files: Vec<PathBuf>,
1267 options: StatusOptions,
1267 options: StatusOptions,
1268 callback: impl for<'r> FnOnce(
1268 callback: impl for<'r> FnOnce(
1269 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1269 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
1270 ) -> R,
1270 ) -> R,
1271 ) -> R {
1271 ) -> R {
1272 self.with_dmap_mut(|map| {
1272 self.with_dmap_mut(|map| {
1273 callback(super::status::status(
1273 callback(super::status::status(
1274 map,
1274 map,
1275 matcher,
1275 matcher,
1276 root_dir,
1276 root_dir,
1277 ignore_files,
1277 ignore_files,
1278 options,
1278 options,
1279 ))
1279 ))
1280 })
1280 })
1281 }
1281 }
1282
1282
1283 pub fn copy_map_len(&self) -> usize {
1283 pub fn copy_map_len(&self) -> usize {
1284 let map = self.get_map();
1284 let map = self.get_map();
1285 map.nodes_with_copy_source_count as usize
1285 map.nodes_with_copy_source_count as usize
1286 }
1286 }
1287
1287
1288 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1288 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1289 let map = self.get_map();
1289 let map = self.get_map();
1290 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1290 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1291 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1291 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1292 Some((node.full_path(map.on_disk)?, source))
1292 Some((node.full_path(map.on_disk)?, source))
1293 } else {
1293 } else {
1294 None
1294 None
1295 })
1295 })
1296 }))
1296 }))
1297 }
1297 }
1298
1298
1299 pub fn copy_map_contains_key(
1299 pub fn copy_map_contains_key(
1300 &self,
1300 &self,
1301 key: &HgPath,
1301 key: &HgPath,
1302 ) -> Result<bool, DirstateV2ParseError> {
1302 ) -> Result<bool, DirstateV2ParseError> {
1303 let map = self.get_map();
1303 let map = self.get_map();
1304 Ok(if let Some(node) = map.get_node(key)? {
1304 Ok(if let Some(node) = map.get_node(key)? {
1305 node.has_copy_source()
1305 node.has_copy_source()
1306 } else {
1306 } else {
1307 false
1307 false
1308 })
1308 })
1309 }
1309 }
1310
1310
1311 pub fn copy_map_get(
1311 pub fn copy_map_get(
1312 &self,
1312 &self,
1313 key: &HgPath,
1313 key: &HgPath,
1314 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1314 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1315 let map = self.get_map();
1315 let map = self.get_map();
1316 if let Some(node) = map.get_node(key)? {
1316 if let Some(node) = map.get_node(key)? {
1317 if let Some(source) = node.copy_source(map.on_disk)? {
1317 if let Some(source) = node.copy_source(map.on_disk)? {
1318 return Ok(Some(source));
1318 return Ok(Some(source));
1319 }
1319 }
1320 }
1320 }
1321 Ok(None)
1321 Ok(None)
1322 }
1322 }
1323
1323
1324 pub fn copy_map_remove(
1324 pub fn copy_map_remove(
1325 &mut self,
1325 &mut self,
1326 key: &HgPath,
1326 key: &HgPath,
1327 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1327 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1328 self.with_dmap_mut(|map| {
1328 self.with_dmap_mut(|map| {
1329 let count = &mut map.nodes_with_copy_source_count;
1329 let count = &mut map.nodes_with_copy_source_count;
1330 let unreachable_bytes = &mut map.unreachable_bytes;
1330 let unreachable_bytes = &mut map.unreachable_bytes;
1331 Ok(DirstateMap::get_node_mut_inner(
1331 Ok(DirstateMap::get_node_mut_inner(
1332 map.on_disk,
1332 map.on_disk,
1333 unreachable_bytes,
1333 unreachable_bytes,
1334 &mut map.root,
1334 &mut map.root,
1335 key,
1335 key,
1336 |_ancestor| {},
1336 |_ancestor| {},
1337 )?
1337 )?
1338 .and_then(|node| {
1338 .and_then(|node| {
1339 if let Some(source) = &node.copy_source {
1339 if let Some(source) = &node.copy_source {
1340 *count = count
1340 *count = count
1341 .checked_sub(1)
1341 .checked_sub(1)
1342 .expect("nodes_with_copy_source_count should be >= 0");
1342 .expect("nodes_with_copy_source_count should be >= 0");
1343 DirstateMap::count_dropped_path(unreachable_bytes, source);
1343 DirstateMap::count_dropped_path(unreachable_bytes, source);
1344 }
1344 }
1345 node.copy_source.take().map(Cow::into_owned)
1345 node.copy_source.take().map(Cow::into_owned)
1346 }))
1346 }))
1347 })
1347 })
1348 }
1348 }
1349
1349
1350 pub fn copy_map_insert(
1350 pub fn copy_map_insert(
1351 &mut self,
1351 &mut self,
1352 key: &HgPath,
1352 key: &HgPath,
1353 value: &HgPath,
1353 value: &HgPath,
1354 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1354 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1355 self.with_dmap_mut(|map| {
1355 self.with_dmap_mut(|map| {
1356 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1356 let node = map.get_or_insert_node(&key, |_ancestor| {})?;
1357 let had_copy_source = node.copy_source.is_none();
1357 let had_copy_source = node.copy_source.is_none();
1358 let old = node
1358 let old = node
1359 .copy_source
1359 .copy_source
1360 .replace(value.to_owned().into())
1360 .replace(value.to_owned().into())
1361 .map(Cow::into_owned);
1361 .map(Cow::into_owned);
1362 if had_copy_source {
1362 if had_copy_source {
1363 map.nodes_with_copy_source_count += 1
1363 map.nodes_with_copy_source_count += 1
1364 }
1364 }
1365 Ok(old)
1365 Ok(old)
1366 })
1366 })
1367 }
1367 }
1368
1368
1369 pub fn len(&self) -> usize {
1369 pub fn len(&self) -> usize {
1370 let map = self.get_map();
1370 let map = self.get_map();
1371 map.nodes_with_entry_count as usize
1371 map.nodes_with_entry_count as usize
1372 }
1372 }
1373
1373
1374 pub fn contains_key(
1374 pub fn contains_key(
1375 &self,
1375 &self,
1376 key: &HgPath,
1376 key: &HgPath,
1377 ) -> Result<bool, DirstateV2ParseError> {
1377 ) -> Result<bool, DirstateV2ParseError> {
1378 Ok(self.get(key)?.is_some())
1378 Ok(self.get(key)?.is_some())
1379 }
1379 }
1380
1380
1381 pub fn get(
1381 pub fn get(
1382 &self,
1382 &self,
1383 key: &HgPath,
1383 key: &HgPath,
1384 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1384 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1385 let map = self.get_map();
1385 let map = self.get_map();
1386 Ok(if let Some(node) = map.get_node(key)? {
1386 Ok(if let Some(node) = map.get_node(key)? {
1387 node.entry()?
1387 node.entry()?
1388 } else {
1388 } else {
1389 None
1389 None
1390 })
1390 })
1391 }
1391 }
1392
1392
1393 pub fn iter(&self) -> StateMapIter<'_> {
1393 pub fn iter(&self) -> StateMapIter<'_> {
1394 let map = self.get_map();
1394 let map = self.get_map();
1395 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1395 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1396 Ok(if let Some(entry) = node.entry()? {
1396 Ok(if let Some(entry) = node.entry()? {
1397 Some((node.full_path(map.on_disk)?, entry))
1397 Some((node.full_path(map.on_disk)?, entry))
1398 } else {
1398 } else {
1399 None
1399 None
1400 })
1400 })
1401 }))
1401 }))
1402 }
1402 }
1403
1403
1404 pub fn iter_tracked_dirs(
1404 pub fn iter_tracked_dirs(
1405 &mut self,
1405 &mut self,
1406 ) -> Result<
1406 ) -> Result<
1407 Box<
1407 Box<
1408 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1408 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1409 + Send
1409 + Send
1410 + '_,
1410 + '_,
1411 >,
1411 >,
1412 DirstateError,
1412 DirstateError,
1413 > {
1413 > {
1414 let map = self.get_map();
1414 let map = self.get_map();
1415 let on_disk = map.on_disk;
1415 let on_disk = map.on_disk;
1416 Ok(Box::new(filter_map_results(
1416 Ok(Box::new(filter_map_results(
1417 map.iter_nodes(),
1417 map.iter_nodes(),
1418 move |node| {
1418 move |node| {
1419 Ok(if node.tracked_descendants_count() > 0 {
1419 Ok(if node.tracked_descendants_count() > 0 {
1420 Some(node.full_path(on_disk)?)
1420 Some(node.full_path(on_disk)?)
1421 } else {
1421 } else {
1422 None
1422 None
1423 })
1423 })
1424 },
1424 },
1425 )))
1425 )))
1426 }
1426 }
1427
1427
1428 /// Only public because it needs to be exposed to the Python layer.
1428 /// Only public because it needs to be exposed to the Python layer.
1429 /// It is not the full `setparents` logic, only the parts that mutate the
1429 /// It is not the full `setparents` logic, only the parts that mutate the
1430 /// entries.
1430 /// entries.
1431 pub fn setparents_fixup(
1431 pub fn setparents_fixup(
1432 &mut self,
1432 &mut self,
1433 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1433 ) -> Result<Vec<(HgPathBuf, HgPathBuf)>, DirstateV2ParseError> {
1434 // XXX
1434 // XXX
1435 // All the copying and re-querying is quite inefficient, but this is
1435 // All the copying and re-querying is quite inefficient, but this is
1436 // still a lot better than doing it from Python.
1436 // still a lot better than doing it from Python.
1437 //
1437 //
1438 // The better solution is to develop a mechanism for `iter_mut`,
1438 // The better solution is to develop a mechanism for `iter_mut`,
1439 // which will be a lot more involved: we're dealing with a lazy,
1439 // which will be a lot more involved: we're dealing with a lazy,
1440 // append-mostly, tree-like data structure. This will do for now.
1440 // append-mostly, tree-like data structure. This will do for now.
1441 let mut copies = vec![];
1441 let mut copies = vec![];
1442 let mut files_with_p2_info = vec![];
1442 let mut files_with_p2_info = vec![];
1443 for res in self.iter() {
1443 for res in self.iter() {
1444 let (path, entry) = res?;
1444 let (path, entry) = res?;
1445 if entry.p2_info() {
1445 if entry.p2_info() {
1446 files_with_p2_info.push(path.to_owned())
1446 files_with_p2_info.push(path.to_owned())
1447 }
1447 }
1448 }
1448 }
1449 self.with_dmap_mut(|map| {
1449 self.with_dmap_mut(|map| {
1450 for path in files_with_p2_info.iter() {
1450 for path in files_with_p2_info.iter() {
1451 let node = map.get_or_insert_node(path, |_| {})?;
1451 let node = map.get_or_insert_node(path, |_| {})?;
1452 let entry =
1452 let entry =
1453 node.data.as_entry_mut().expect("entry should exist");
1453 node.data.as_entry_mut().expect("entry should exist");
1454 entry.drop_merge_data();
1454 entry.drop_merge_data();
1455 if let Some(source) = node.copy_source.take().as_deref() {
1455 if let Some(source) = node.copy_source.take().as_deref() {
1456 copies.push((path.to_owned(), source.to_owned()));
1456 copies.push((path.to_owned(), source.to_owned()));
1457 }
1457 }
1458 }
1458 }
1459 Ok(copies)
1459 Ok(copies)
1460 })
1460 })
1461 }
1461 }
1462
1462
1463 pub fn debug_iter(
1463 pub fn debug_iter(
1464 &self,
1464 &self,
1465 all: bool,
1465 all: bool,
1466 ) -> Box<
1466 ) -> Box<
1467 dyn Iterator<
1467 dyn Iterator<
1468 Item = Result<
1468 Item = Result<
1469 (&HgPath, (u8, i32, i32, i32)),
1469 (&HgPath, (u8, i32, i32, i32)),
1470 DirstateV2ParseError,
1470 DirstateV2ParseError,
1471 >,
1471 >,
1472 > + Send
1472 > + Send
1473 + '_,
1473 + '_,
1474 > {
1474 > {
1475 let map = self.get_map();
1475 let map = self.get_map();
1476 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1476 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1477 let debug_tuple = if let Some(entry) = node.entry()? {
1477 let debug_tuple = if let Some(entry) = node.entry()? {
1478 entry.debug_tuple()
1478 entry.debug_tuple()
1479 } else if !all {
1479 } else if !all {
1480 return Ok(None);
1480 return Ok(None);
1481 } else if let Some(mtime) = node.cached_directory_mtime()? {
1481 } else if let Some(mtime) = node.cached_directory_mtime()? {
1482 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1482 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1483 } else {
1483 } else {
1484 (b' ', 0, -1, -1)
1484 (b' ', 0, -1, -1)
1485 };
1485 };
1486 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1486 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1487 }))
1487 }))
1488 }
1488 }
1489 }
1489 }
1490 #[cfg(test)]
1490 #[cfg(test)]
1491 mod tests {
1491 mod tests {
1492 use super::*;
1492 use super::*;
1493
1493
1494 /// Shortcut to return tracked descendants of a path.
1494 /// Shortcut to return tracked descendants of a path.
1495 /// Panics if the path does not exist.
1495 /// Panics if the path does not exist.
1496 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1496 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1497 let path = dbg!(HgPath::new(path));
1497 let path = dbg!(HgPath::new(path));
1498 let node = map.get_map().get_node(path);
1498 let node = map.get_map().get_node(path);
1499 node.unwrap().unwrap().tracked_descendants_count()
1499 node.unwrap().unwrap().tracked_descendants_count()
1500 }
1500 }
1501
1501
1502 /// Shortcut to return descendants with an entry.
1502 /// Shortcut to return descendants with an entry.
1503 /// Panics if the path does not exist.
1503 /// Panics if the path does not exist.
1504 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1504 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 {
1505 let path = dbg!(HgPath::new(path));
1505 let path = dbg!(HgPath::new(path));
1506 let node = map.get_map().get_node(path);
1506 let node = map.get_map().get_node(path);
1507 node.unwrap().unwrap().descendants_with_entry_count()
1507 node.unwrap().unwrap().descendants_with_entry_count()
1508 }
1508 }
1509
1509
1510 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1510 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) {
1511 let path = dbg!(HgPath::new(path));
1511 let path = dbg!(HgPath::new(path));
1512 let node = map.get_map().get_node(path);
1512 let node = map.get_map().get_node(path);
1513 assert!(node.unwrap().is_none());
1513 assert!(node.unwrap().is_none());
1514 }
1514 }
1515
1515
1516 /// Shortcut for path creation in tests
1516 /// Shortcut for path creation in tests
1517 fn p(b: &[u8]) -> &HgPath {
1517 fn p(b: &[u8]) -> &HgPath {
1518 HgPath::new(b)
1518 HgPath::new(b)
1519 }
1519 }
1520
1520
1521 /// Test the very simple case a single tracked file
1521 /// Test the very simple case a single tracked file
1522 #[test]
1522 #[test]
1523 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1523 fn test_tracked_descendants_simple() -> Result<(), DirstateError> {
1524 let mut map = OwningDirstateMap::new_empty(vec![]);
1524 let mut map = OwningDirstateMap::new_empty(vec![]);
1525 assert_eq!(map.len(), 0);
1525 assert_eq!(map.len(), 0);
1526
1526
1527 map.set_tracked(p(b"some/nested/path"))?;
1527 map.set_tracked(p(b"some/nested/path"))?;
1528
1528
1529 assert_eq!(map.len(), 1);
1529 assert_eq!(map.len(), 1);
1530 assert_eq!(tracked_descendants(&map, b"some"), 1);
1530 assert_eq!(tracked_descendants(&map, b"some"), 1);
1531 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1531 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1532 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1532 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1533
1533
1534 map.set_untracked(p(b"some/nested/path"))?;
1534 map.set_untracked(p(b"some/nested/path"))?;
1535 assert_eq!(map.len(), 0);
1535 assert_eq!(map.len(), 0);
1536 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1536 assert!(map.get_map().get_node(p(b"some"))?.is_none());
1537
1537
1538 Ok(())
1538 Ok(())
1539 }
1539 }
1540
1540
1541 /// Test the simple case of all tracked, but multiple files
1541 /// Test the simple case of all tracked, but multiple files
1542 #[test]
1542 #[test]
1543 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1543 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> {
1544 let mut map = OwningDirstateMap::new_empty(vec![]);
1544 let mut map = OwningDirstateMap::new_empty(vec![]);
1545
1545
1546 map.set_tracked(p(b"some/nested/path"))?;
1546 map.set_tracked(p(b"some/nested/path"))?;
1547 map.set_tracked(p(b"some/nested/file"))?;
1547 map.set_tracked(p(b"some/nested/file"))?;
1548 // one layer without any files to test deletion cascade
1548 // one layer without any files to test deletion cascade
1549 map.set_tracked(p(b"some/other/nested/path"))?;
1549 map.set_tracked(p(b"some/other/nested/path"))?;
1550 map.set_tracked(p(b"root_file"))?;
1550 map.set_tracked(p(b"root_file"))?;
1551 map.set_tracked(p(b"some/file"))?;
1551 map.set_tracked(p(b"some/file"))?;
1552 map.set_tracked(p(b"some/file2"))?;
1552 map.set_tracked(p(b"some/file2"))?;
1553 map.set_tracked(p(b"some/file3"))?;
1553 map.set_tracked(p(b"some/file3"))?;
1554
1554
1555 assert_eq!(map.len(), 7);
1555 assert_eq!(map.len(), 7);
1556 assert_eq!(tracked_descendants(&map, b"some"), 6);
1556 assert_eq!(tracked_descendants(&map, b"some"), 6);
1557 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1557 assert_eq!(tracked_descendants(&map, b"some/nested"), 2);
1558 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1558 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1559 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1559 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1560 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1560 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0);
1561
1561
1562 map.set_untracked(p(b"some/nested/path"))?;
1562 map.set_untracked(p(b"some/nested/path"))?;
1563 assert_eq!(map.len(), 6);
1563 assert_eq!(map.len(), 6);
1564 assert_eq!(tracked_descendants(&map, b"some"), 5);
1564 assert_eq!(tracked_descendants(&map, b"some"), 5);
1565 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1565 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1566 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1566 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1567 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1567 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1568
1568
1569 map.set_untracked(p(b"some/nested/file"))?;
1569 map.set_untracked(p(b"some/nested/file"))?;
1570 assert_eq!(map.len(), 5);
1570 assert_eq!(map.len(), 5);
1571 assert_eq!(tracked_descendants(&map, b"some"), 4);
1571 assert_eq!(tracked_descendants(&map, b"some"), 4);
1572 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1572 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1573 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1573 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1574 assert_does_not_exist(&map, b"some_nested");
1574 assert_does_not_exist(&map, b"some_nested");
1575
1575
1576 map.set_untracked(p(b"some/other/nested/path"))?;
1576 map.set_untracked(p(b"some/other/nested/path"))?;
1577 assert_eq!(map.len(), 4);
1577 assert_eq!(map.len(), 4);
1578 assert_eq!(tracked_descendants(&map, b"some"), 3);
1578 assert_eq!(tracked_descendants(&map, b"some"), 3);
1579 assert_does_not_exist(&map, b"some/other");
1579 assert_does_not_exist(&map, b"some/other");
1580
1580
1581 map.set_untracked(p(b"root_file"))?;
1581 map.set_untracked(p(b"root_file"))?;
1582 assert_eq!(map.len(), 3);
1582 assert_eq!(map.len(), 3);
1583 assert_eq!(tracked_descendants(&map, b"some"), 3);
1583 assert_eq!(tracked_descendants(&map, b"some"), 3);
1584 assert_does_not_exist(&map, b"root_file");
1584 assert_does_not_exist(&map, b"root_file");
1585
1585
1586 map.set_untracked(p(b"some/file"))?;
1586 map.set_untracked(p(b"some/file"))?;
1587 assert_eq!(map.len(), 2);
1587 assert_eq!(map.len(), 2);
1588 assert_eq!(tracked_descendants(&map, b"some"), 2);
1588 assert_eq!(tracked_descendants(&map, b"some"), 2);
1589 assert_does_not_exist(&map, b"some/file");
1589 assert_does_not_exist(&map, b"some/file");
1590
1590
1591 map.set_untracked(p(b"some/file2"))?;
1591 map.set_untracked(p(b"some/file2"))?;
1592 assert_eq!(map.len(), 1);
1592 assert_eq!(map.len(), 1);
1593 assert_eq!(tracked_descendants(&map, b"some"), 1);
1593 assert_eq!(tracked_descendants(&map, b"some"), 1);
1594 assert_does_not_exist(&map, b"some/file2");
1594 assert_does_not_exist(&map, b"some/file2");
1595
1595
1596 map.set_untracked(p(b"some/file3"))?;
1596 map.set_untracked(p(b"some/file3"))?;
1597 assert_eq!(map.len(), 0);
1597 assert_eq!(map.len(), 0);
1598 assert_does_not_exist(&map, b"some/file3");
1598 assert_does_not_exist(&map, b"some/file3");
1599
1599
1600 Ok(())
1600 Ok(())
1601 }
1601 }
1602
1602
1603 /// Check with a mix of tracked and non-tracked items
1603 /// Check with a mix of tracked and non-tracked items
1604 #[test]
1604 #[test]
1605 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1605 fn test_tracked_descendants_different() -> Result<(), DirstateError> {
1606 let mut map = OwningDirstateMap::new_empty(vec![]);
1606 let mut map = OwningDirstateMap::new_empty(vec![]);
1607
1607
1608 // A file that was just added
1608 // A file that was just added
1609 map.set_tracked(p(b"some/nested/path"))?;
1609 map.set_tracked(p(b"some/nested/path"))?;
1610 // This has no information, the dirstate should ignore it
1610 // This has no information, the dirstate should ignore it
1611 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1611 map.reset_state(p(b"some/file"), false, false, false, false, None)?;
1612 assert_does_not_exist(&map, b"some/file");
1612 assert_does_not_exist(&map, b"some/file");
1613
1613
1614 // A file that was removed
1614 // A file that was removed
1615 map.reset_state(
1615 map.reset_state(
1616 p(b"some/nested/file"),
1616 p(b"some/nested/file"),
1617 false,
1617 false,
1618 true,
1618 true,
1619 false,
1619 false,
1620 false,
1620 false,
1621 None,
1621 None,
1622 )?;
1622 )?;
1623 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1623 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked());
1624 // Only present in p2
1624 // Only present in p2
1625 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1625 map.reset_state(p(b"some/file3"), false, false, true, false, None)?;
1626 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1626 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked());
1627 // A file that was merged
1627 // A file that was merged
1628 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1628 map.reset_state(p(b"root_file"), true, true, true, false, None)?;
1629 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1629 assert!(map.get(p(b"root_file"))?.unwrap().tracked());
1630 // A file that is added, with info from p2
1630 // A file that is added, with info from p2
1631 // XXX is that actually possible?
1631 // XXX is that actually possible?
1632 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1632 map.reset_state(p(b"some/file2"), true, false, true, false, None)?;
1633 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1633 assert!(map.get(p(b"some/file2"))?.unwrap().tracked());
1634 // A clean file
1634 // A clean file
1635 // One layer without any files to test deletion cascade
1635 // One layer without any files to test deletion cascade
1636 map.reset_state(
1636 map.reset_state(
1637 p(b"some/other/nested/path"),
1637 p(b"some/other/nested/path"),
1638 true,
1638 true,
1639 true,
1639 true,
1640 false,
1640 false,
1641 false,
1641 false,
1642 None,
1642 None,
1643 )?;
1643 )?;
1644 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1644 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked());
1645
1645
1646 assert_eq!(map.len(), 6);
1646 assert_eq!(map.len(), 6);
1647 assert_eq!(tracked_descendants(&map, b"some"), 3);
1647 assert_eq!(tracked_descendants(&map, b"some"), 3);
1648 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1648 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1649 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1649 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1650 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1650 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1651 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1651 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0);
1652 assert_eq!(
1652 assert_eq!(
1653 descendants_with_an_entry(&map, b"some/other/nested/path"),
1653 descendants_with_an_entry(&map, b"some/other/nested/path"),
1654 0
1654 0
1655 );
1655 );
1656 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1656 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1657 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1657 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1658
1658
1659 // might as well check this
1659 // might as well check this
1660 map.set_untracked(p(b"path/does/not/exist"))?;
1660 map.set_untracked(p(b"path/does/not/exist"))?;
1661 assert_eq!(map.len(), 6);
1661 assert_eq!(map.len(), 6);
1662
1662
1663 map.set_untracked(p(b"some/other/nested/path"))?;
1663 map.set_untracked(p(b"some/other/nested/path"))?;
1664 // It is set untracked but not deleted since it held other information
1664 // It is set untracked but not deleted since it held other information
1665 assert_eq!(map.len(), 6);
1665 assert_eq!(map.len(), 6);
1666 assert_eq!(tracked_descendants(&map, b"some"), 2);
1666 assert_eq!(tracked_descendants(&map, b"some"), 2);
1667 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1667 assert_eq!(descendants_with_an_entry(&map, b"some"), 5);
1668 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1668 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1669 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1669 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1670 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1670 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1671 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1671 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1672
1672
1673 map.set_untracked(p(b"some/nested/path"))?;
1673 map.set_untracked(p(b"some/nested/path"))?;
1674 // It is set untracked *and* deleted since it was only added
1674 // It is set untracked *and* deleted since it was only added
1675 assert_eq!(map.len(), 5);
1675 assert_eq!(map.len(), 5);
1676 assert_eq!(tracked_descendants(&map, b"some"), 1);
1676 assert_eq!(tracked_descendants(&map, b"some"), 1);
1677 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1677 assert_eq!(descendants_with_an_entry(&map, b"some"), 4);
1678 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1678 assert_eq!(tracked_descendants(&map, b"some/nested"), 0);
1679 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1679 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1);
1680 assert_does_not_exist(&map, b"some/nested/path");
1680 assert_does_not_exist(&map, b"some/nested/path");
1681
1681
1682 map.set_untracked(p(b"root_file"))?;
1682 map.set_untracked(p(b"root_file"))?;
1683 // Untracked but not deleted
1683 // Untracked but not deleted
1684 assert_eq!(map.len(), 5);
1684 assert_eq!(map.len(), 5);
1685 assert!(map.get(p(b"root_file"))?.is_some());
1685 assert!(map.get(p(b"root_file"))?.is_some());
1686
1686
1687 map.set_untracked(p(b"some/file2"))?;
1687 map.set_untracked(p(b"some/file2"))?;
1688 assert_eq!(map.len(), 5);
1688 assert_eq!(map.len(), 5);
1689 assert_eq!(tracked_descendants(&map, b"some"), 0);
1689 assert_eq!(tracked_descendants(&map, b"some"), 0);
1690 assert!(map.get(p(b"some/file2"))?.is_some());
1690 assert!(map.get(p(b"some/file2"))?.is_some());
1691
1691
1692 map.set_untracked(p(b"some/file3"))?;
1692 map.set_untracked(p(b"some/file3"))?;
1693 assert_eq!(map.len(), 5);
1693 assert_eq!(map.len(), 5);
1694 assert_eq!(tracked_descendants(&map, b"some"), 0);
1694 assert_eq!(tracked_descendants(&map, b"some"), 0);
1695 assert!(map.get(p(b"some/file3"))?.is_some());
1695 assert!(map.get(p(b"some/file3"))?.is_some());
1696
1696
1697 Ok(())
1697 Ok(())
1698 }
1698 }
1699
1699
1700 /// Check that copies counter is correctly updated
1700 /// Check that copies counter is correctly updated
1701 #[test]
1701 #[test]
1702 fn test_copy_source() -> Result<(), DirstateError> {
1702 fn test_copy_source() -> Result<(), DirstateError> {
1703 let mut map = OwningDirstateMap::new_empty(vec![]);
1703 let mut map = OwningDirstateMap::new_empty(vec![]);
1704
1704
1705 // Clean file
1705 // Clean file
1706 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1706 map.reset_state(p(b"files/clean"), true, true, false, false, None)?;
1707 // Merged file
1707 // Merged file
1708 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1708 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?;
1709 // Removed file
1709 // Removed file
1710 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1710 map.reset_state(p(b"removed"), false, true, false, false, None)?;
1711 // Added file
1711 // Added file
1712 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1712 map.reset_state(p(b"files/added"), true, false, false, false, None)?;
1713 // Add copy
1713 // Add copy
1714 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1714 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?;
1715 assert_eq!(map.copy_map_len(), 1);
1715 assert_eq!(map.copy_map_len(), 1);
1716
1716
1717 // Copy override
1717 // Copy override
1718 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1718 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?;
1719 assert_eq!(map.copy_map_len(), 1);
1719 assert_eq!(map.copy_map_len(), 1);
1720
1720
1721 // Multiple copies
1721 // Multiple copies
1722 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1722 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?;
1723 assert_eq!(map.copy_map_len(), 2);
1723 assert_eq!(map.copy_map_len(), 2);
1724
1724
1725 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1725 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?;
1726 assert_eq!(map.copy_map_len(), 3);
1726 assert_eq!(map.copy_map_len(), 3);
1727
1727
1728 // Added, so the entry is completely removed
1728 // Added, so the entry is completely removed
1729 map.set_untracked(p(b"files/added"))?;
1729 map.set_untracked(p(b"files/added"))?;
1730 assert_does_not_exist(&map, b"files/added");
1730 assert_does_not_exist(&map, b"files/added");
1731 assert_eq!(map.copy_map_len(), 2);
1731 assert_eq!(map.copy_map_len(), 2);
1732
1732
1733 // Removed, so the entry is kept around, so is its copy
1733 // Removed, so the entry is kept around, so is its copy
1734 map.set_untracked(p(b"removed"))?;
1734 map.set_untracked(p(b"removed"))?;
1735 assert!(map.get(p(b"removed"))?.is_some());
1735 assert!(map.get(p(b"removed"))?.is_some());
1736 assert_eq!(map.copy_map_len(), 2);
1736 assert_eq!(map.copy_map_len(), 2);
1737
1737
1738 // Clean, so the entry is kept around, but not its copy
1738 // Clean, so the entry is kept around, but not its copy
1739 map.set_untracked(p(b"files/clean"))?;
1739 map.set_untracked(p(b"files/clean"))?;
1740 assert!(map.get(p(b"files/clean"))?.is_some());
1740 assert!(map.get(p(b"files/clean"))?.is_some());
1741 assert_eq!(map.copy_map_len(), 1);
1741 assert_eq!(map.copy_map_len(), 1);
1742
1742
1743 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1743 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?;
1744 assert_eq!(map.copy_map_len(), 2);
1744 assert_eq!(map.copy_map_len(), 2);
1745
1745
1746 // Info from p2, so its copy source info is kept around
1746 // Info from p2, so its copy source info is kept around
1747 map.set_untracked(p(b"files/from_p2"))?;
1747 map.set_untracked(p(b"files/from_p2"))?;
1748 assert!(map.get(p(b"files/from_p2"))?.is_some());
1748 assert!(map.get(p(b"files/from_p2"))?.is_some());
1749 assert_eq!(map.copy_map_len(), 2);
1749 assert_eq!(map.copy_map_len(), 2);
1750
1750
1751 Ok(())
1751 Ok(())
1752 }
1752 }
1753
1753
1754 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1754 /// Test with "on disk" data. For the sake of this test, the "on disk" data
1755 /// does not actually come from the disk, but it's opaque to the code being
1755 /// does not actually come from the disk, but it's opaque to the code being
1756 /// tested.
1756 /// tested.
1757 #[test]
1757 #[test]
1758 fn test_on_disk() -> Result<(), DirstateError> {
1758 fn test_on_disk() -> Result<(), DirstateError> {
1759 // First let's create some data to put "on disk"
1759 // First let's create some data to put "on disk"
1760 let mut map = OwningDirstateMap::new_empty(vec![]);
1760 let mut map = OwningDirstateMap::new_empty(vec![]);
1761
1761
1762 // A file that was just added
1762 // A file that was just added
1763 map.set_tracked(p(b"some/nested/added"))?;
1763 map.set_tracked(p(b"some/nested/added"))?;
1764 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1764 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?;
1765
1765
1766 // A file that was removed
1766 // A file that was removed
1767 map.reset_state(
1767 map.reset_state(
1768 p(b"some/nested/removed"),
1768 p(b"some/nested/removed"),
1769 false,
1769 false,
1770 true,
1770 true,
1771 false,
1771 false,
1772 false,
1772 false,
1773 None,
1773 None,
1774 )?;
1774 )?;
1775 // Only present in p2
1775 // Only present in p2
1776 map.reset_state(
1776 map.reset_state(
1777 p(b"other/p2_info_only"),
1777 p(b"other/p2_info_only"),
1778 false,
1778 false,
1779 false,
1779 false,
1780 true,
1780 true,
1781 false,
1781 false,
1782 None,
1782 None,
1783 )?;
1783 )?;
1784 map.copy_map_insert(
1784 map.copy_map_insert(
1785 p(b"other/p2_info_only"),
1785 p(b"other/p2_info_only"),
1786 p(b"other/p2_info_copy_source"),
1786 p(b"other/p2_info_copy_source"),
1787 )?;
1787 )?;
1788 // A file that was merged
1788 // A file that was merged
1789 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1789 map.reset_state(p(b"merged"), true, true, true, false, None)?;
1790 // A file that is added, with info from p2
1790 // A file that is added, with info from p2
1791 // XXX is that actually possible?
1791 // XXX is that actually possible?
1792 map.reset_state(
1792 map.reset_state(
1793 p(b"other/added_with_p2"),
1793 p(b"other/added_with_p2"),
1794 true,
1794 true,
1795 false,
1795 false,
1796 true,
1796 true,
1797 false,
1797 false,
1798 None,
1798 None,
1799 )?;
1799 )?;
1800 // One layer without any files to test deletion cascade
1800 // One layer without any files to test deletion cascade
1801 // A clean file
1801 // A clean file
1802 map.reset_state(
1802 map.reset_state(
1803 p(b"some/other/nested/clean"),
1803 p(b"some/other/nested/clean"),
1804 true,
1804 true,
1805 true,
1805 true,
1806 false,
1806 false,
1807 false,
1807 false,
1808 None,
1808 None,
1809 )?;
1809 )?;
1810
1810
1811 let (packed, metadata, _should_append) = map.pack_v2(false)?;
1811 let (packed, metadata, _should_append) = map.pack_v2(false)?;
1812 let packed_len = packed.len();
1812 let packed_len = packed.len();
1813 assert!(packed_len > 0);
1813 assert!(packed_len > 0);
1814
1814
1815 // Recreate "from disk"
1815 // Recreate "from disk"
1816 let mut map = OwningDirstateMap::new_v2(
1816 let mut map = OwningDirstateMap::new_v2(
1817 packed,
1817 packed,
1818 packed_len,
1818 packed_len,
1819 metadata.as_bytes(),
1819 metadata.as_bytes(),
1820 )?;
1820 )?;
1821
1821
1822 // Check that everything is accounted for
1822 // Check that everything is accounted for
1823 assert!(map.contains_key(p(b"some/nested/added"))?);
1823 assert!(map.contains_key(p(b"some/nested/added"))?);
1824 assert!(map.contains_key(p(b"some/nested/removed"))?);
1824 assert!(map.contains_key(p(b"some/nested/removed"))?);
1825 assert!(map.contains_key(p(b"merged"))?);
1825 assert!(map.contains_key(p(b"merged"))?);
1826 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1826 assert!(map.contains_key(p(b"other/p2_info_only"))?);
1827 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1827 assert!(map.contains_key(p(b"other/added_with_p2"))?);
1828 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1828 assert!(map.contains_key(p(b"some/other/nested/clean"))?);
1829 assert_eq!(
1829 assert_eq!(
1830 map.copy_map_get(p(b"some/nested/added"))?,
1830 map.copy_map_get(p(b"some/nested/added"))?,
1831 Some(p(b"added_copy_source"))
1831 Some(p(b"added_copy_source"))
1832 );
1832 );
1833 assert_eq!(
1833 assert_eq!(
1834 map.copy_map_get(p(b"other/p2_info_only"))?,
1834 map.copy_map_get(p(b"other/p2_info_only"))?,
1835 Some(p(b"other/p2_info_copy_source"))
1835 Some(p(b"other/p2_info_copy_source"))
1836 );
1836 );
1837 assert_eq!(tracked_descendants(&map, b"some"), 2);
1837 assert_eq!(tracked_descendants(&map, b"some"), 2);
1838 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1838 assert_eq!(descendants_with_an_entry(&map, b"some"), 3);
1839 assert_eq!(tracked_descendants(&map, b"other"), 1);
1839 assert_eq!(tracked_descendants(&map, b"other"), 1);
1840 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1840 assert_eq!(descendants_with_an_entry(&map, b"other"), 2);
1841 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1841 assert_eq!(tracked_descendants(&map, b"some/other"), 1);
1842 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1842 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1);
1843 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1843 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1);
1844 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1844 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1);
1845 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1845 assert_eq!(tracked_descendants(&map, b"some/nested"), 1);
1846 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1846 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2);
1847 assert_eq!(map.len(), 6);
1847 assert_eq!(map.len(), 6);
1848 assert_eq!(map.get_map().unreachable_bytes, 0);
1848 assert_eq!(map.get_map().unreachable_bytes, 0);
1849 assert_eq!(map.copy_map_len(), 2);
1849 assert_eq!(map.copy_map_len(), 2);
1850
1850
1851 // Shouldn't change anything since it's already not tracked
1851 // Shouldn't change anything since it's already not tracked
1852 map.set_untracked(p(b"some/nested/removed"))?;
1852 map.set_untracked(p(b"some/nested/removed"))?;
1853 assert_eq!(map.get_map().unreachable_bytes, 0);
1853 assert_eq!(map.get_map().unreachable_bytes, 0);
1854
1854
1855 match map.get_map().root {
1855 match map.get_map().root {
1856 ChildNodes::InMemory(_) => {
1856 ChildNodes::InMemory(_) => {
1857 panic!("root should not have been mutated")
1857 panic!("root should not have been mutated")
1858 }
1858 }
1859 _ => (),
1859 _ => (),
1860 }
1860 }
1861 // We haven't mutated enough (nothing, actually), we should still be in
1861 // We haven't mutated enough (nothing, actually), we should still be in
1862 // the append strategy
1862 // the append strategy
1863 assert!(map.get_map().write_should_append());
1863 assert!(map.get_map().write_should_append());
1864
1864
1865 // But this mutates the structure, so there should be unreachable_bytes
1865 // But this mutates the structure, so there should be unreachable_bytes
1866 assert!(map.set_untracked(p(b"some/nested/added"))?);
1866 assert!(map.set_untracked(p(b"some/nested/added"))?);
1867 let unreachable_bytes = map.get_map().unreachable_bytes;
1867 let unreachable_bytes = map.get_map().unreachable_bytes;
1868 assert!(unreachable_bytes > 0);
1868 assert!(unreachable_bytes > 0);
1869
1869
1870 match map.get_map().root {
1870 match map.get_map().root {
1871 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1871 ChildNodes::OnDisk(_) => panic!("root should have been mutated"),
1872 _ => (),
1872 _ => (),
1873 }
1873 }
1874
1874
1875 // This should not mutate the structure either, since `root` has
1875 // This should not mutate the structure either, since `root` has
1876 // already been mutated along with its direct children.
1876 // already been mutated along with its direct children.
1877 map.set_untracked(p(b"merged"))?;
1877 map.set_untracked(p(b"merged"))?;
1878 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1878 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes);
1879
1879
1880 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1880 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1881 NodeRef::InMemory(_, _) => {
1881 NodeRef::InMemory(_, _) => {
1882 panic!("'other/added_with_p2' should not have been mutated")
1882 panic!("'other/added_with_p2' should not have been mutated")
1883 }
1883 }
1884 _ => (),
1884 _ => (),
1885 }
1885 }
1886 // But this should, since it's in a different path
1886 // But this should, since it's in a different path
1887 // than `<root>some/nested/add`
1887 // than `<root>some/nested/add`
1888 map.set_untracked(p(b"other/added_with_p2"))?;
1888 map.set_untracked(p(b"other/added_with_p2"))?;
1889 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1889 assert!(map.get_map().unreachable_bytes > unreachable_bytes);
1890
1890
1891 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1891 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() {
1892 NodeRef::OnDisk(_) => {
1892 NodeRef::OnDisk(_) => {
1893 panic!("'other/added_with_p2' should have been mutated")
1893 panic!("'other/added_with_p2' should have been mutated")
1894 }
1894 }
1895 _ => (),
1895 _ => (),
1896 }
1896 }
1897
1897
1898 // We have rewritten most of the tree, we should create a new file
1898 // We have rewritten most of the tree, we should create a new file
1899 assert!(!map.get_map().write_should_append());
1899 assert!(!map.get_map().write_should_append());
1900
1900
1901 Ok(())
1901 Ok(())
1902 }
1902 }
1903 }
1903 }
@@ -1,843 +1,843 b''
1 //! The "version 2" disk representation of the dirstate
1 //! The "version 2" disk representation of the dirstate
2 //!
2 //!
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
4
4
5 use crate::dirstate::{DirstateV2Data, TruncatedTimestamp};
5 use crate::dirstate::{DirstateV2Data, TruncatedTimestamp};
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
7 use crate::dirstate_tree::path_with_basename::WithBasename;
7 use crate::dirstate_tree::path_with_basename::WithBasename;
8 use crate::errors::HgError;
8 use crate::errors::HgError;
9 use crate::utils::hg_path::HgPath;
9 use crate::utils::hg_path::HgPath;
10 use crate::DirstateEntry;
10 use crate::DirstateEntry;
11 use crate::DirstateError;
11 use crate::DirstateError;
12 use crate::DirstateParents;
12 use crate::DirstateParents;
13 use bitflags::bitflags;
13 use bitflags::bitflags;
14 use bytes_cast::unaligned::{U16Be, U32Be};
14 use bytes_cast::unaligned::{U16Be, U32Be};
15 use bytes_cast::BytesCast;
15 use bytes_cast::BytesCast;
16 use format_bytes::format_bytes;
16 use format_bytes::format_bytes;
17 use rand::Rng;
17 use rand::Rng;
18 use std::borrow::Cow;
18 use std::borrow::Cow;
19 use std::convert::{TryFrom, TryInto};
19 use std::convert::{TryFrom, TryInto};
20 use std::fmt::Write;
20 use std::fmt::Write;
21
21
22 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
22 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
23 /// This a redundant sanity check more than an actual "magic number" since
23 /// This a redundant sanity check more than an actual "magic number" since
24 /// `.hg/requires` already governs which format should be used.
24 /// `.hg/requires` already governs which format should be used.
25 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
25 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
26
26
27 /// Keep space for 256-bit hashes
27 /// Keep space for 256-bit hashes
28 const STORED_NODE_ID_BYTES: usize = 32;
28 const STORED_NODE_ID_BYTES: usize = 32;
29
29
30 /// … even though only 160 bits are used for now, with SHA-1
30 /// … even though only 160 bits are used for now, with SHA-1
31 const USED_NODE_ID_BYTES: usize = 20;
31 const USED_NODE_ID_BYTES: usize = 20;
32
32
33 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
33 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
34 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
34 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
35
35
36 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
36 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
37 const TREE_METADATA_SIZE: usize = 44;
37 const TREE_METADATA_SIZE: usize = 44;
38 const NODE_SIZE: usize = 44;
38 const NODE_SIZE: usize = 44;
39
39
40 /// Make sure that size-affecting changes are made knowingly
40 /// Make sure that size-affecting changes are made knowingly
41 #[allow(unused)]
41 #[allow(unused)]
42 fn static_assert_size_of() {
42 fn static_assert_size_of() {
43 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
43 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
44 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
44 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
45 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
45 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
46 }
46 }
47
47
48 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
48 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
49 #[derive(BytesCast)]
49 #[derive(BytesCast)]
50 #[repr(C)]
50 #[repr(C)]
51 struct DocketHeader {
51 struct DocketHeader {
52 marker: [u8; V2_FORMAT_MARKER.len()],
52 marker: [u8; V2_FORMAT_MARKER.len()],
53 parent_1: [u8; STORED_NODE_ID_BYTES],
53 parent_1: [u8; STORED_NODE_ID_BYTES],
54 parent_2: [u8; STORED_NODE_ID_BYTES],
54 parent_2: [u8; STORED_NODE_ID_BYTES],
55
55
56 metadata: TreeMetadata,
56 metadata: TreeMetadata,
57
57
58 /// Counted in bytes
58 /// Counted in bytes
59 data_size: Size,
59 data_size: Size,
60
60
61 uuid_size: u8,
61 uuid_size: u8,
62 }
62 }
63
63
64 pub struct Docket<'on_disk> {
64 pub struct Docket<'on_disk> {
65 header: &'on_disk DocketHeader,
65 header: &'on_disk DocketHeader,
66 pub uuid: &'on_disk [u8],
66 pub uuid: &'on_disk [u8],
67 }
67 }
68
68
69 /// Fields are documented in the *Tree metadata in the docket file*
69 /// Fields are documented in the *Tree metadata in the docket file*
70 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
70 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
71 #[derive(BytesCast)]
71 #[derive(BytesCast)]
72 #[repr(C)]
72 #[repr(C)]
73 pub struct TreeMetadata {
73 pub struct TreeMetadata {
74 root_nodes: ChildNodes,
74 root_nodes: ChildNodes,
75 nodes_with_entry_count: Size,
75 nodes_with_entry_count: Size,
76 nodes_with_copy_source_count: Size,
76 nodes_with_copy_source_count: Size,
77 unreachable_bytes: Size,
77 unreachable_bytes: Size,
78 unused: [u8; 4],
78 unused: [u8; 4],
79
79
80 /// See *Optional hash of ignore patterns* section of
80 /// See *Optional hash of ignore patterns* section of
81 /// `mercurial/helptext/internals/dirstate-v2.txt`
81 /// `mercurial/helptext/internals/dirstate-v2.txt`
82 ignore_patterns_hash: IgnorePatternsHash,
82 ignore_patterns_hash: IgnorePatternsHash,
83 }
83 }
84
84
85 /// Fields are documented in the *The data file format*
85 /// Fields are documented in the *The data file format*
86 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
86 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
87 #[derive(BytesCast, Debug)]
87 #[derive(BytesCast, Debug)]
88 #[repr(C)]
88 #[repr(C)]
89 pub(super) struct Node {
89 pub(super) struct Node {
90 full_path: PathSlice,
90 full_path: PathSlice,
91
91
92 /// In bytes from `self.full_path.start`
92 /// In bytes from `self.full_path.start`
93 base_name_start: PathSize,
93 base_name_start: PathSize,
94
94
95 copy_source: OptPathSlice,
95 copy_source: OptPathSlice,
96 children: ChildNodes,
96 children: ChildNodes,
97 pub(super) descendants_with_entry_count: Size,
97 pub(super) descendants_with_entry_count: Size,
98 pub(super) tracked_descendants_count: Size,
98 pub(super) tracked_descendants_count: Size,
99 flags: U16Be,
99 flags: U16Be,
100 size: U32Be,
100 size: U32Be,
101 mtime: PackedTruncatedTimestamp,
101 mtime: PackedTruncatedTimestamp,
102 }
102 }
103
103
104 bitflags! {
104 bitflags! {
105 #[repr(C)]
105 #[repr(C)]
106 struct Flags: u16 {
106 struct Flags: u16 {
107 const WDIR_TRACKED = 1 << 0;
107 const WDIR_TRACKED = 1 << 0;
108 const P1_TRACKED = 1 << 1;
108 const P1_TRACKED = 1 << 1;
109 const P2_INFO = 1 << 2;
109 const P2_INFO = 1 << 2;
110 const MODE_EXEC_PERM = 1 << 3;
110 const MODE_EXEC_PERM = 1 << 3;
111 const MODE_IS_SYMLINK = 1 << 4;
111 const MODE_IS_SYMLINK = 1 << 4;
112 const HAS_FALLBACK_EXEC = 1 << 5;
112 const HAS_FALLBACK_EXEC = 1 << 5;
113 const FALLBACK_EXEC = 1 << 6;
113 const FALLBACK_EXEC = 1 << 6;
114 const HAS_FALLBACK_SYMLINK = 1 << 7;
114 const HAS_FALLBACK_SYMLINK = 1 << 7;
115 const FALLBACK_SYMLINK = 1 << 8;
115 const FALLBACK_SYMLINK = 1 << 8;
116 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
116 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
117 const HAS_MODE_AND_SIZE = 1 <<10;
117 const HAS_MODE_AND_SIZE = 1 <<10;
118 const HAS_MTIME = 1 <<11;
118 const HAS_MTIME = 1 <<11;
119 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
119 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
120 const DIRECTORY = 1 <<13;
120 const DIRECTORY = 1 <<13;
121 const ALL_UNKNOWN_RECORDED = 1 <<14;
121 const ALL_UNKNOWN_RECORDED = 1 <<14;
122 const ALL_IGNORED_RECORDED = 1 <<15;
122 const ALL_IGNORED_RECORDED = 1 <<15;
123 }
123 }
124 }
124 }
125
125
126 /// Duration since the Unix epoch
126 /// Duration since the Unix epoch
127 #[derive(BytesCast, Copy, Clone, Debug)]
127 #[derive(BytesCast, Copy, Clone, Debug)]
128 #[repr(C)]
128 #[repr(C)]
129 struct PackedTruncatedTimestamp {
129 struct PackedTruncatedTimestamp {
130 truncated_seconds: U32Be,
130 truncated_seconds: U32Be,
131 nanoseconds: U32Be,
131 nanoseconds: U32Be,
132 }
132 }
133
133
134 /// Counted in bytes from the start of the file
134 /// Counted in bytes from the start of the file
135 ///
135 ///
136 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
136 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
137 type Offset = U32Be;
137 type Offset = U32Be;
138
138
139 /// Counted in number of items
139 /// Counted in number of items
140 ///
140 ///
141 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
141 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
142 type Size = U32Be;
142 type Size = U32Be;
143
143
144 /// Counted in bytes
144 /// Counted in bytes
145 ///
145 ///
146 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
146 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
147 type PathSize = U16Be;
147 type PathSize = U16Be;
148
148
149 /// A contiguous sequence of `len` times `Node`, representing the child nodes
149 /// A contiguous sequence of `len` times `Node`, representing the child nodes
150 /// of either some other node or of the repository root.
150 /// of either some other node or of the repository root.
151 ///
151 ///
152 /// Always sorted by ascending `full_path`, to allow binary search.
152 /// Always sorted by ascending `full_path`, to allow binary search.
153 /// Since nodes with the same parent nodes also have the same parent path,
153 /// Since nodes with the same parent nodes also have the same parent path,
154 /// only the `base_name`s need to be compared during binary search.
154 /// only the `base_name`s need to be compared during binary search.
155 #[derive(BytesCast, Copy, Clone, Debug)]
155 #[derive(BytesCast, Copy, Clone, Debug)]
156 #[repr(C)]
156 #[repr(C)]
157 struct ChildNodes {
157 struct ChildNodes {
158 start: Offset,
158 start: Offset,
159 len: Size,
159 len: Size,
160 }
160 }
161
161
162 /// A `HgPath` of `len` bytes
162 /// A `HgPath` of `len` bytes
163 #[derive(BytesCast, Copy, Clone, Debug)]
163 #[derive(BytesCast, Copy, Clone, Debug)]
164 #[repr(C)]
164 #[repr(C)]
165 struct PathSlice {
165 struct PathSlice {
166 start: Offset,
166 start: Offset,
167 len: PathSize,
167 len: PathSize,
168 }
168 }
169
169
170 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
170 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
171 type OptPathSlice = PathSlice;
171 type OptPathSlice = PathSlice;
172
172
173 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
173 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
174 ///
174 ///
175 /// This should only happen if Mercurial is buggy or a repository is corrupted.
175 /// This should only happen if Mercurial is buggy or a repository is corrupted.
176 #[derive(Debug)]
176 #[derive(Debug)]
177 pub struct DirstateV2ParseError;
177 pub struct DirstateV2ParseError;
178
178
179 impl From<DirstateV2ParseError> for HgError {
179 impl From<DirstateV2ParseError> for HgError {
180 fn from(_: DirstateV2ParseError) -> Self {
180 fn from(_: DirstateV2ParseError) -> Self {
181 HgError::corrupted("dirstate-v2 parse error")
181 HgError::corrupted("dirstate-v2 parse error")
182 }
182 }
183 }
183 }
184
184
185 impl From<DirstateV2ParseError> for crate::DirstateError {
185 impl From<DirstateV2ParseError> for crate::DirstateError {
186 fn from(error: DirstateV2ParseError) -> Self {
186 fn from(error: DirstateV2ParseError) -> Self {
187 HgError::from(error).into()
187 HgError::from(error).into()
188 }
188 }
189 }
189 }
190
190
191 impl TreeMetadata {
191 impl TreeMetadata {
192 pub fn as_bytes(&self) -> &[u8] {
192 pub fn as_bytes(&self) -> &[u8] {
193 BytesCast::as_bytes(self)
193 BytesCast::as_bytes(self)
194 }
194 }
195 }
195 }
196
196
197 impl<'on_disk> Docket<'on_disk> {
197 impl<'on_disk> Docket<'on_disk> {
198 /// Generate the identifier for a new data file
198 /// Generate the identifier for a new data file
199 ///
199 ///
200 /// TODO: support the `HGTEST_UUIDFILE` environment variable.
200 /// TODO: support the `HGTEST_UUIDFILE` environment variable.
201 /// See `mercurial/revlogutils/docket.py`
201 /// See `mercurial/revlogutils/docket.py`
202 pub fn new_uid() -> String {
202 pub fn new_uid() -> String {
203 const ID_LENGTH: usize = 8;
203 const ID_LENGTH: usize = 8;
204 let mut id = String::with_capacity(ID_LENGTH);
204 let mut id = String::with_capacity(ID_LENGTH);
205 let mut rng = rand::thread_rng();
205 let mut rng = rand::thread_rng();
206 for _ in 0..ID_LENGTH {
206 for _ in 0..ID_LENGTH {
207 // One random hexadecimal digit.
207 // One random hexadecimal digit.
208 // `unwrap` never panics because `impl Write for String`
208 // `unwrap` never panics because `impl Write for String`
209 // never returns an error.
209 // never returns an error.
210 write!(&mut id, "{:x}", rng.gen_range(0..16)).unwrap();
210 write!(&mut id, "{:x}", rng.gen_range(0..16)).unwrap();
211 }
211 }
212 id
212 id
213 }
213 }
214
214
215 pub fn serialize(
215 pub fn serialize(
216 parents: DirstateParents,
216 parents: DirstateParents,
217 tree_metadata: TreeMetadata,
217 tree_metadata: TreeMetadata,
218 data_size: u64,
218 data_size: u64,
219 uuid: &[u8],
219 uuid: &[u8],
220 ) -> Result<Vec<u8>, std::num::TryFromIntError> {
220 ) -> Result<Vec<u8>, std::num::TryFromIntError> {
221 let header = DocketHeader {
221 let header = DocketHeader {
222 marker: *V2_FORMAT_MARKER,
222 marker: *V2_FORMAT_MARKER,
223 parent_1: parents.p1.pad_to_256_bits(),
223 parent_1: parents.p1.pad_to_256_bits(),
224 parent_2: parents.p2.pad_to_256_bits(),
224 parent_2: parents.p2.pad_to_256_bits(),
225 metadata: tree_metadata,
225 metadata: tree_metadata,
226 data_size: u32::try_from(data_size)?.into(),
226 data_size: u32::try_from(data_size)?.into(),
227 uuid_size: uuid.len().try_into()?,
227 uuid_size: uuid.len().try_into()?,
228 };
228 };
229 let header = header.as_bytes();
229 let header = header.as_bytes();
230 let mut docket = Vec::with_capacity(header.len() + uuid.len());
230 let mut docket = Vec::with_capacity(header.len() + uuid.len());
231 docket.extend_from_slice(header);
231 docket.extend_from_slice(header);
232 docket.extend_from_slice(uuid);
232 docket.extend_from_slice(uuid);
233 Ok(docket)
233 Ok(docket)
234 }
234 }
235
235
236 pub fn parents(&self) -> DirstateParents {
236 pub fn parents(&self) -> DirstateParents {
237 use crate::Node;
237 use crate::Node;
238 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
238 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
239 .unwrap()
239 .unwrap()
240 .clone();
240 .clone();
241 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
241 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
242 .unwrap()
242 .unwrap()
243 .clone();
243 .clone();
244 DirstateParents { p1, p2 }
244 DirstateParents { p1, p2 }
245 }
245 }
246
246
247 pub fn tree_metadata(&self) -> &[u8] {
247 pub fn tree_metadata(&self) -> &[u8] {
248 self.header.metadata.as_bytes()
248 self.header.metadata.as_bytes()
249 }
249 }
250
250
251 pub fn data_size(&self) -> usize {
251 pub fn data_size(&self) -> usize {
252 // This `unwrap` could only panic on a 16-bit CPU
252 // This `unwrap` could only panic on a 16-bit CPU
253 self.header.data_size.get().try_into().unwrap()
253 self.header.data_size.get().try_into().unwrap()
254 }
254 }
255
255
256 pub fn data_filename(&self) -> String {
256 pub fn data_filename(&self) -> String {
257 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
257 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
258 }
258 }
259 }
259 }
260
260
261 pub fn read_docket(
261 pub fn read_docket(
262 on_disk: &[u8],
262 on_disk: &[u8],
263 ) -> Result<Docket<'_>, DirstateV2ParseError> {
263 ) -> Result<Docket<'_>, DirstateV2ParseError> {
264 let (header, uuid) =
264 let (header, uuid) =
265 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
265 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
266 let uuid_size = header.uuid_size as usize;
266 let uuid_size = header.uuid_size as usize;
267 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
267 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
268 Ok(Docket { header, uuid })
268 Ok(Docket { header, uuid })
269 } else {
269 } else {
270 Err(DirstateV2ParseError)
270 Err(DirstateV2ParseError)
271 }
271 }
272 }
272 }
273
273
274 pub(super) fn read<'on_disk>(
274 pub(super) fn read<'on_disk>(
275 on_disk: &'on_disk [u8],
275 on_disk: &'on_disk [u8],
276 metadata: &[u8],
276 metadata: &[u8],
277 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
277 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
278 if on_disk.is_empty() {
278 if on_disk.is_empty() {
279 return Ok(DirstateMap::empty(on_disk));
279 return Ok(DirstateMap::empty(on_disk));
280 }
280 }
281 let (meta, _) = TreeMetadata::from_bytes(metadata)
281 let (meta, _) = TreeMetadata::from_bytes(metadata)
282 .map_err(|_| DirstateV2ParseError)?;
282 .map_err(|_| DirstateV2ParseError)?;
283 let dirstate_map = DirstateMap {
283 let dirstate_map = DirstateMap {
284 on_disk,
284 on_disk,
285 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
285 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
286 on_disk,
286 on_disk,
287 meta.root_nodes,
287 meta.root_nodes,
288 )?),
288 )?),
289 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
289 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
290 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
290 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
291 ignore_patterns_hash: meta.ignore_patterns_hash,
291 ignore_patterns_hash: meta.ignore_patterns_hash,
292 unreachable_bytes: meta.unreachable_bytes.get(),
292 unreachable_bytes: meta.unreachable_bytes.get(),
293 };
293 };
294 Ok(dirstate_map)
294 Ok(dirstate_map)
295 }
295 }
296
296
297 impl Node {
297 impl Node {
298 pub(super) fn full_path<'on_disk>(
298 pub(super) fn full_path<'on_disk>(
299 &self,
299 &self,
300 on_disk: &'on_disk [u8],
300 on_disk: &'on_disk [u8],
301 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
301 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
302 read_hg_path(on_disk, self.full_path)
302 read_hg_path(on_disk, self.full_path)
303 }
303 }
304
304
305 pub(super) fn base_name_start<'on_disk>(
305 pub(super) fn base_name_start<'on_disk>(
306 &self,
306 &self,
307 ) -> Result<usize, DirstateV2ParseError> {
307 ) -> Result<usize, DirstateV2ParseError> {
308 let start = self.base_name_start.get();
308 let start = self.base_name_start.get();
309 if start < self.full_path.len.get() {
309 if start < self.full_path.len.get() {
310 let start = usize::try_from(start)
310 let start = usize::try_from(start)
311 // u32 -> usize, could only panic on a 16-bit CPU
311 // u32 -> usize, could only panic on a 16-bit CPU
312 .expect("dirstate-v2 base_name_start out of bounds");
312 .expect("dirstate-v2 base_name_start out of bounds");
313 Ok(start)
313 Ok(start)
314 } else {
314 } else {
315 Err(DirstateV2ParseError)
315 Err(DirstateV2ParseError)
316 }
316 }
317 }
317 }
318
318
319 pub(super) fn base_name<'on_disk>(
319 pub(super) fn base_name<'on_disk>(
320 &self,
320 &self,
321 on_disk: &'on_disk [u8],
321 on_disk: &'on_disk [u8],
322 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
322 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
323 let full_path = self.full_path(on_disk)?;
323 let full_path = self.full_path(on_disk)?;
324 let base_name_start = self.base_name_start()?;
324 let base_name_start = self.base_name_start()?;
325 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
325 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
326 }
326 }
327
327
328 pub(super) fn path<'on_disk>(
328 pub(super) fn path<'on_disk>(
329 &self,
329 &self,
330 on_disk: &'on_disk [u8],
330 on_disk: &'on_disk [u8],
331 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
331 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
332 Ok(WithBasename::from_raw_parts(
332 Ok(WithBasename::from_raw_parts(
333 Cow::Borrowed(self.full_path(on_disk)?),
333 Cow::Borrowed(self.full_path(on_disk)?),
334 self.base_name_start()?,
334 self.base_name_start()?,
335 ))
335 ))
336 }
336 }
337
337
338 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
338 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
339 self.copy_source.start.get() != 0
339 self.copy_source.start.get() != 0
340 }
340 }
341
341
342 pub(super) fn copy_source<'on_disk>(
342 pub(super) fn copy_source<'on_disk>(
343 &self,
343 &self,
344 on_disk: &'on_disk [u8],
344 on_disk: &'on_disk [u8],
345 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
345 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
346 Ok(if self.has_copy_source() {
346 Ok(if self.has_copy_source() {
347 Some(read_hg_path(on_disk, self.copy_source)?)
347 Some(read_hg_path(on_disk, self.copy_source)?)
348 } else {
348 } else {
349 None
349 None
350 })
350 })
351 }
351 }
352
352
353 fn flags(&self) -> Flags {
353 fn flags(&self) -> Flags {
354 Flags::from_bits_truncate(self.flags.get())
354 Flags::from_bits_truncate(self.flags.get())
355 }
355 }
356
356
357 fn has_entry(&self) -> bool {
357 fn has_entry(&self) -> bool {
358 self.flags().intersects(
358 self.flags().intersects(
359 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
359 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
360 )
360 )
361 }
361 }
362
362
363 pub(super) fn node_data(
363 pub(super) fn node_data(
364 &self,
364 &self,
365 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
365 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
366 if self.has_entry() {
366 if self.has_entry() {
367 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
367 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
368 } else if let Some(mtime) = self.cached_directory_mtime()? {
368 } else if let Some(mtime) = self.cached_directory_mtime()? {
369 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
369 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
370 } else {
370 } else {
371 Ok(dirstate_map::NodeData::None)
371 Ok(dirstate_map::NodeData::None)
372 }
372 }
373 }
373 }
374
374
375 pub(super) fn cached_directory_mtime(
375 pub(super) fn cached_directory_mtime(
376 &self,
376 &self,
377 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
377 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
378 // For now we do not have code to handle the absence of
378 // For now we do not have code to handle the absence of
379 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
379 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
380 // unset.
380 // unset.
381 if self.flags().contains(Flags::DIRECTORY)
381 if self.flags().contains(Flags::DIRECTORY)
382 && self.flags().contains(Flags::HAS_MTIME)
382 && self.flags().contains(Flags::HAS_MTIME)
383 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
383 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
384 {
384 {
385 Ok(Some(self.mtime()?))
385 Ok(Some(self.mtime()?))
386 } else {
386 } else {
387 Ok(None)
387 Ok(None)
388 }
388 }
389 }
389 }
390
390
391 fn synthesize_unix_mode(&self) -> u32 {
391 fn synthesize_unix_mode(&self) -> u32 {
392 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
392 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
393 libc::S_IFLNK
393 libc::S_IFLNK
394 } else {
394 } else {
395 libc::S_IFREG
395 libc::S_IFREG
396 };
396 };
397 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
397 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
398 0o755
398 0o755
399 } else {
399 } else {
400 0o644
400 0o644
401 };
401 };
402 (file_type | permisions).into()
402 (file_type | permisions).into()
403 }
403 }
404
404
405 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
405 fn mtime(&self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
406 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
406 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
407 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
407 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
408 m.second_ambiguous = true;
408 m.second_ambiguous = true;
409 }
409 }
410 Ok(m)
410 Ok(m)
411 }
411 }
412
412
413 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
413 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
414 // TODO: convert through raw bits instead?
414 // TODO: convert through raw bits instead?
415 let wc_tracked = self.flags().contains(Flags::WDIR_TRACKED);
415 let wc_tracked = self.flags().contains(Flags::WDIR_TRACKED);
416 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
416 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
417 let p2_info = self.flags().contains(Flags::P2_INFO);
417 let p2_info = self.flags().contains(Flags::P2_INFO);
418 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
418 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
419 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
419 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
420 {
420 {
421 Some((self.synthesize_unix_mode(), self.size.into()))
421 Some((self.synthesize_unix_mode(), self.size.into()))
422 } else {
422 } else {
423 None
423 None
424 };
424 };
425 let mtime = if self.flags().contains(Flags::HAS_MTIME)
425 let mtime = if self.flags().contains(Flags::HAS_MTIME)
426 && !self.flags().contains(Flags::DIRECTORY)
426 && !self.flags().contains(Flags::DIRECTORY)
427 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
427 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
428 {
428 {
429 Some(self.mtime()?)
429 Some(self.mtime()?)
430 } else {
430 } else {
431 None
431 None
432 };
432 };
433 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
433 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
434 {
434 {
435 Some(self.flags().contains(Flags::FALLBACK_EXEC))
435 Some(self.flags().contains(Flags::FALLBACK_EXEC))
436 } else {
436 } else {
437 None
437 None
438 };
438 };
439 let fallback_symlink =
439 let fallback_symlink =
440 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
440 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
441 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
441 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
442 } else {
442 } else {
443 None
443 None
444 };
444 };
445 Ok(DirstateEntry::from_v2_data(DirstateV2Data {
445 Ok(DirstateEntry::from_v2_data(DirstateV2Data {
446 wc_tracked,
446 wc_tracked,
447 p1_tracked,
447 p1_tracked,
448 p2_info,
448 p2_info,
449 mode_size,
449 mode_size,
450 mtime,
450 mtime,
451 fallback_exec,
451 fallback_exec,
452 fallback_symlink,
452 fallback_symlink,
453 }))
453 }))
454 }
454 }
455
455
456 pub(super) fn entry(
456 pub(super) fn entry(
457 &self,
457 &self,
458 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
458 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
459 if self.has_entry() {
459 if self.has_entry() {
460 Ok(Some(self.assume_entry()?))
460 Ok(Some(self.assume_entry()?))
461 } else {
461 } else {
462 Ok(None)
462 Ok(None)
463 }
463 }
464 }
464 }
465
465
466 pub(super) fn children<'on_disk>(
466 pub(super) fn children<'on_disk>(
467 &self,
467 &self,
468 on_disk: &'on_disk [u8],
468 on_disk: &'on_disk [u8],
469 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
469 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
470 read_nodes(on_disk, self.children)
470 read_nodes(on_disk, self.children)
471 }
471 }
472
472
473 pub(super) fn to_in_memory_node<'on_disk>(
473 pub(super) fn to_in_memory_node<'on_disk>(
474 &self,
474 &self,
475 on_disk: &'on_disk [u8],
475 on_disk: &'on_disk [u8],
476 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
476 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
477 Ok(dirstate_map::Node {
477 Ok(dirstate_map::Node {
478 children: dirstate_map::ChildNodes::OnDisk(
478 children: dirstate_map::ChildNodes::OnDisk(
479 self.children(on_disk)?,
479 self.children(on_disk)?,
480 ),
480 ),
481 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
481 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
482 data: self.node_data()?,
482 data: self.node_data()?,
483 descendants_with_entry_count: self
483 descendants_with_entry_count: self
484 .descendants_with_entry_count
484 .descendants_with_entry_count
485 .get(),
485 .get(),
486 tracked_descendants_count: self.tracked_descendants_count.get(),
486 tracked_descendants_count: self.tracked_descendants_count.get(),
487 })
487 })
488 }
488 }
489
489
490 fn from_dirstate_entry(
490 fn from_dirstate_entry(
491 entry: &DirstateEntry,
491 entry: &DirstateEntry,
492 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
492 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
493 let DirstateV2Data {
493 let DirstateV2Data {
494 wc_tracked,
494 wc_tracked,
495 p1_tracked,
495 p1_tracked,
496 p2_info,
496 p2_info,
497 mode_size: mode_size_opt,
497 mode_size: mode_size_opt,
498 mtime: mtime_opt,
498 mtime: mtime_opt,
499 fallback_exec,
499 fallback_exec,
500 fallback_symlink,
500 fallback_symlink,
501 } = entry.v2_data();
501 } = entry.v2_data();
502 // TODO: convert through raw flag bits instead?
502 // TODO: convert through raw flag bits instead?
503 let mut flags = Flags::empty();
503 let mut flags = Flags::empty();
504 flags.set(Flags::WDIR_TRACKED, wc_tracked);
504 flags.set(Flags::WDIR_TRACKED, wc_tracked);
505 flags.set(Flags::P1_TRACKED, p1_tracked);
505 flags.set(Flags::P1_TRACKED, p1_tracked);
506 flags.set(Flags::P2_INFO, p2_info);
506 flags.set(Flags::P2_INFO, p2_info);
507 let size = if let Some((m, s)) = mode_size_opt {
507 let size = if let Some((m, s)) = mode_size_opt {
508 let exec_perm = m & (libc::S_IXUSR as u32) != 0;
508 let exec_perm = m & (libc::S_IXUSR as u32) != 0;
509 let is_symlink = m & (libc::S_IFMT as u32) == libc::S_IFLNK as u32;
509 let is_symlink = m & (libc::S_IFMT as u32) == libc::S_IFLNK as u32;
510 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
510 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
511 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
511 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
512 flags.insert(Flags::HAS_MODE_AND_SIZE);
512 flags.insert(Flags::HAS_MODE_AND_SIZE);
513 s.into()
513 s.into()
514 } else {
514 } else {
515 0.into()
515 0.into()
516 };
516 };
517 let mtime = if let Some(m) = mtime_opt {
517 let mtime = if let Some(m) = mtime_opt {
518 flags.insert(Flags::HAS_MTIME);
518 flags.insert(Flags::HAS_MTIME);
519 if m.second_ambiguous {
519 if m.second_ambiguous {
520 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
520 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
521 };
521 };
522 m.into()
522 m.into()
523 } else {
523 } else {
524 PackedTruncatedTimestamp::null()
524 PackedTruncatedTimestamp::null()
525 };
525 };
526 if let Some(f_exec) = fallback_exec {
526 if let Some(f_exec) = fallback_exec {
527 flags.insert(Flags::HAS_FALLBACK_EXEC);
527 flags.insert(Flags::HAS_FALLBACK_EXEC);
528 if f_exec {
528 if f_exec {
529 flags.insert(Flags::FALLBACK_EXEC);
529 flags.insert(Flags::FALLBACK_EXEC);
530 }
530 }
531 }
531 }
532 if let Some(f_symlink) = fallback_symlink {
532 if let Some(f_symlink) = fallback_symlink {
533 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
533 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
534 if f_symlink {
534 if f_symlink {
535 flags.insert(Flags::FALLBACK_SYMLINK);
535 flags.insert(Flags::FALLBACK_SYMLINK);
536 }
536 }
537 }
537 }
538 (flags, size, mtime)
538 (flags, size, mtime)
539 }
539 }
540 }
540 }
541
541
542 fn read_hg_path(
542 fn read_hg_path(
543 on_disk: &[u8],
543 on_disk: &[u8],
544 slice: PathSlice,
544 slice: PathSlice,
545 ) -> Result<&HgPath, DirstateV2ParseError> {
545 ) -> Result<&HgPath, DirstateV2ParseError> {
546 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
546 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
547 }
547 }
548
548
549 fn read_nodes(
549 fn read_nodes(
550 on_disk: &[u8],
550 on_disk: &[u8],
551 slice: ChildNodes,
551 slice: ChildNodes,
552 ) -> Result<&[Node], DirstateV2ParseError> {
552 ) -> Result<&[Node], DirstateV2ParseError> {
553 read_slice(on_disk, slice.start, slice.len.get())
553 read_slice(on_disk, slice.start, slice.len.get())
554 }
554 }
555
555
556 fn read_slice<T, Len>(
556 fn read_slice<T, Len>(
557 on_disk: &[u8],
557 on_disk: &[u8],
558 start: Offset,
558 start: Offset,
559 len: Len,
559 len: Len,
560 ) -> Result<&[T], DirstateV2ParseError>
560 ) -> Result<&[T], DirstateV2ParseError>
561 where
561 where
562 T: BytesCast,
562 T: BytesCast,
563 Len: TryInto<usize>,
563 Len: TryInto<usize>,
564 {
564 {
565 // Either `usize::MAX` would result in "out of bounds" error since a single
565 // Either `usize::MAX` would result in "out of bounds" error since a single
566 // `&[u8]` cannot occupy the entire addess space.
566 // `&[u8]` cannot occupy the entire addess space.
567 let start = start.get().try_into().unwrap_or(std::usize::MAX);
567 let start = start.get().try_into().unwrap_or(std::usize::MAX);
568 let len = len.try_into().unwrap_or(std::usize::MAX);
568 let len = len.try_into().unwrap_or(std::usize::MAX);
569 on_disk
569 on_disk
570 .get(start..)
570 .get(start..)
571 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
571 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
572 .map(|(slice, _rest)| slice)
572 .map(|(slice, _rest)| slice)
573 .ok_or_else(|| DirstateV2ParseError)
573 .ok_or_else(|| DirstateV2ParseError)
574 }
574 }
575
575
576 pub(crate) fn for_each_tracked_path<'on_disk>(
576 pub(crate) fn for_each_tracked_path<'on_disk>(
577 on_disk: &'on_disk [u8],
577 on_disk: &'on_disk [u8],
578 metadata: &[u8],
578 metadata: &[u8],
579 mut f: impl FnMut(&'on_disk HgPath),
579 mut f: impl FnMut(&'on_disk HgPath),
580 ) -> Result<(), DirstateV2ParseError> {
580 ) -> Result<(), DirstateV2ParseError> {
581 let (meta, _) = TreeMetadata::from_bytes(metadata)
581 let (meta, _) = TreeMetadata::from_bytes(metadata)
582 .map_err(|_| DirstateV2ParseError)?;
582 .map_err(|_| DirstateV2ParseError)?;
583 fn recur<'on_disk>(
583 fn recur<'on_disk>(
584 on_disk: &'on_disk [u8],
584 on_disk: &'on_disk [u8],
585 nodes: ChildNodes,
585 nodes: ChildNodes,
586 f: &mut impl FnMut(&'on_disk HgPath),
586 f: &mut impl FnMut(&'on_disk HgPath),
587 ) -> Result<(), DirstateV2ParseError> {
587 ) -> Result<(), DirstateV2ParseError> {
588 for node in read_nodes(on_disk, nodes)? {
588 for node in read_nodes(on_disk, nodes)? {
589 if let Some(entry) = node.entry()? {
589 if let Some(entry) = node.entry()? {
590 if entry.state().is_tracked() {
590 if entry.tracked() {
591 f(node.full_path(on_disk)?)
591 f(node.full_path(on_disk)?)
592 }
592 }
593 }
593 }
594 recur(on_disk, node.children, f)?
594 recur(on_disk, node.children, f)?
595 }
595 }
596 Ok(())
596 Ok(())
597 }
597 }
598 recur(on_disk, meta.root_nodes, &mut f)
598 recur(on_disk, meta.root_nodes, &mut f)
599 }
599 }
600
600
601 /// Returns new data and metadata, together with whether that data should be
601 /// Returns new data and metadata, together with whether that data should be
602 /// appended to the existing data file whose content is at
602 /// appended to the existing data file whose content is at
603 /// `dirstate_map.on_disk` (true), instead of written to a new data file
603 /// `dirstate_map.on_disk` (true), instead of written to a new data file
604 /// (false).
604 /// (false).
605 pub(super) fn write(
605 pub(super) fn write(
606 dirstate_map: &DirstateMap,
606 dirstate_map: &DirstateMap,
607 can_append: bool,
607 can_append: bool,
608 ) -> Result<(Vec<u8>, TreeMetadata, bool), DirstateError> {
608 ) -> Result<(Vec<u8>, TreeMetadata, bool), DirstateError> {
609 let append = can_append && dirstate_map.write_should_append();
609 let append = can_append && dirstate_map.write_should_append();
610
610
611 // This ignores the space for paths, and for nodes without an entry.
611 // This ignores the space for paths, and for nodes without an entry.
612 // TODO: better estimate? Skip the `Vec` and write to a file directly?
612 // TODO: better estimate? Skip the `Vec` and write to a file directly?
613 let size_guess = std::mem::size_of::<Node>()
613 let size_guess = std::mem::size_of::<Node>()
614 * dirstate_map.nodes_with_entry_count as usize;
614 * dirstate_map.nodes_with_entry_count as usize;
615
615
616 let mut writer = Writer {
616 let mut writer = Writer {
617 dirstate_map,
617 dirstate_map,
618 append,
618 append,
619 out: Vec::with_capacity(size_guess),
619 out: Vec::with_capacity(size_guess),
620 };
620 };
621
621
622 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
622 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
623
623
624 let meta = TreeMetadata {
624 let meta = TreeMetadata {
625 root_nodes,
625 root_nodes,
626 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
626 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
627 nodes_with_copy_source_count: dirstate_map
627 nodes_with_copy_source_count: dirstate_map
628 .nodes_with_copy_source_count
628 .nodes_with_copy_source_count
629 .into(),
629 .into(),
630 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
630 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
631 unused: [0; 4],
631 unused: [0; 4],
632 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
632 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
633 };
633 };
634 Ok((writer.out, meta, append))
634 Ok((writer.out, meta, append))
635 }
635 }
636
636
637 struct Writer<'dmap, 'on_disk> {
637 struct Writer<'dmap, 'on_disk> {
638 dirstate_map: &'dmap DirstateMap<'on_disk>,
638 dirstate_map: &'dmap DirstateMap<'on_disk>,
639 append: bool,
639 append: bool,
640 out: Vec<u8>,
640 out: Vec<u8>,
641 }
641 }
642
642
643 impl Writer<'_, '_> {
643 impl Writer<'_, '_> {
644 fn write_nodes(
644 fn write_nodes(
645 &mut self,
645 &mut self,
646 nodes: dirstate_map::ChildNodesRef,
646 nodes: dirstate_map::ChildNodesRef,
647 ) -> Result<ChildNodes, DirstateError> {
647 ) -> Result<ChildNodes, DirstateError> {
648 // Reuse already-written nodes if possible
648 // Reuse already-written nodes if possible
649 if self.append {
649 if self.append {
650 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
650 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
651 let start = self.on_disk_offset_of(nodes_slice).expect(
651 let start = self.on_disk_offset_of(nodes_slice).expect(
652 "dirstate-v2 OnDisk nodes not found within on_disk",
652 "dirstate-v2 OnDisk nodes not found within on_disk",
653 );
653 );
654 let len = child_nodes_len_from_usize(nodes_slice.len());
654 let len = child_nodes_len_from_usize(nodes_slice.len());
655 return Ok(ChildNodes { start, len });
655 return Ok(ChildNodes { start, len });
656 }
656 }
657 }
657 }
658
658
659 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
659 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
660 // undefined iteration order. Sort to enable binary search in the
660 // undefined iteration order. Sort to enable binary search in the
661 // written file.
661 // written file.
662 let nodes = nodes.sorted();
662 let nodes = nodes.sorted();
663 let nodes_len = nodes.len();
663 let nodes_len = nodes.len();
664
664
665 // First accumulate serialized nodes in a `Vec`
665 // First accumulate serialized nodes in a `Vec`
666 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
666 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
667 for node in nodes {
667 for node in nodes {
668 let children =
668 let children =
669 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
669 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
670 let full_path = node.full_path(self.dirstate_map.on_disk)?;
670 let full_path = node.full_path(self.dirstate_map.on_disk)?;
671 let full_path = self.write_path(full_path.as_bytes());
671 let full_path = self.write_path(full_path.as_bytes());
672 let copy_source = if let Some(source) =
672 let copy_source = if let Some(source) =
673 node.copy_source(self.dirstate_map.on_disk)?
673 node.copy_source(self.dirstate_map.on_disk)?
674 {
674 {
675 self.write_path(source.as_bytes())
675 self.write_path(source.as_bytes())
676 } else {
676 } else {
677 PathSlice {
677 PathSlice {
678 start: 0.into(),
678 start: 0.into(),
679 len: 0.into(),
679 len: 0.into(),
680 }
680 }
681 };
681 };
682 on_disk_nodes.push(match node {
682 on_disk_nodes.push(match node {
683 NodeRef::InMemory(path, node) => {
683 NodeRef::InMemory(path, node) => {
684 let (flags, size, mtime) = match &node.data {
684 let (flags, size, mtime) = match &node.data {
685 dirstate_map::NodeData::Entry(entry) => {
685 dirstate_map::NodeData::Entry(entry) => {
686 Node::from_dirstate_entry(entry)
686 Node::from_dirstate_entry(entry)
687 }
687 }
688 dirstate_map::NodeData::CachedDirectory { mtime } => {
688 dirstate_map::NodeData::CachedDirectory { mtime } => {
689 // we currently never set a mtime if unknown file
689 // we currently never set a mtime if unknown file
690 // are present.
690 // are present.
691 // So if we have a mtime for a directory, we know
691 // So if we have a mtime for a directory, we know
692 // they are no unknown
692 // they are no unknown
693 // files and we
693 // files and we
694 // blindly set ALL_UNKNOWN_RECORDED.
694 // blindly set ALL_UNKNOWN_RECORDED.
695 //
695 //
696 // We never set ALL_IGNORED_RECORDED since we
696 // We never set ALL_IGNORED_RECORDED since we
697 // don't track that case
697 // don't track that case
698 // currently.
698 // currently.
699 let mut flags = Flags::DIRECTORY
699 let mut flags = Flags::DIRECTORY
700 | Flags::HAS_MTIME
700 | Flags::HAS_MTIME
701 | Flags::ALL_UNKNOWN_RECORDED;
701 | Flags::ALL_UNKNOWN_RECORDED;
702 if mtime.second_ambiguous {
702 if mtime.second_ambiguous {
703 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
703 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS)
704 }
704 }
705 (flags, 0.into(), (*mtime).into())
705 (flags, 0.into(), (*mtime).into())
706 }
706 }
707 dirstate_map::NodeData::None => (
707 dirstate_map::NodeData::None => (
708 Flags::DIRECTORY,
708 Flags::DIRECTORY,
709 0.into(),
709 0.into(),
710 PackedTruncatedTimestamp::null(),
710 PackedTruncatedTimestamp::null(),
711 ),
711 ),
712 };
712 };
713 Node {
713 Node {
714 children,
714 children,
715 copy_source,
715 copy_source,
716 full_path,
716 full_path,
717 base_name_start: u16::try_from(path.base_name_start())
717 base_name_start: u16::try_from(path.base_name_start())
718 // Could only panic for paths over 64 KiB
718 // Could only panic for paths over 64 KiB
719 .expect("dirstate-v2 path length overflow")
719 .expect("dirstate-v2 path length overflow")
720 .into(),
720 .into(),
721 descendants_with_entry_count: node
721 descendants_with_entry_count: node
722 .descendants_with_entry_count
722 .descendants_with_entry_count
723 .into(),
723 .into(),
724 tracked_descendants_count: node
724 tracked_descendants_count: node
725 .tracked_descendants_count
725 .tracked_descendants_count
726 .into(),
726 .into(),
727 flags: flags.bits().into(),
727 flags: flags.bits().into(),
728 size,
728 size,
729 mtime,
729 mtime,
730 }
730 }
731 }
731 }
732 NodeRef::OnDisk(node) => Node {
732 NodeRef::OnDisk(node) => Node {
733 children,
733 children,
734 copy_source,
734 copy_source,
735 full_path,
735 full_path,
736 ..*node
736 ..*node
737 },
737 },
738 })
738 })
739 }
739 }
740 // … so we can write them contiguously, after writing everything else
740 // … so we can write them contiguously, after writing everything else
741 // they refer to.
741 // they refer to.
742 let start = self.current_offset();
742 let start = self.current_offset();
743 let len = child_nodes_len_from_usize(nodes_len);
743 let len = child_nodes_len_from_usize(nodes_len);
744 self.out.extend(on_disk_nodes.as_bytes());
744 self.out.extend(on_disk_nodes.as_bytes());
745 Ok(ChildNodes { start, len })
745 Ok(ChildNodes { start, len })
746 }
746 }
747
747
748 /// If the given slice of items is within `on_disk`, returns its offset
748 /// If the given slice of items is within `on_disk`, returns its offset
749 /// from the start of `on_disk`.
749 /// from the start of `on_disk`.
750 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
750 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
751 where
751 where
752 T: BytesCast,
752 T: BytesCast,
753 {
753 {
754 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
754 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
755 let start = slice.as_ptr() as usize;
755 let start = slice.as_ptr() as usize;
756 let end = start + slice.len();
756 let end = start + slice.len();
757 start..=end
757 start..=end
758 }
758 }
759 let slice_addresses = address_range(slice.as_bytes());
759 let slice_addresses = address_range(slice.as_bytes());
760 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
760 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
761 if on_disk_addresses.contains(slice_addresses.start())
761 if on_disk_addresses.contains(slice_addresses.start())
762 && on_disk_addresses.contains(slice_addresses.end())
762 && on_disk_addresses.contains(slice_addresses.end())
763 {
763 {
764 let offset = slice_addresses.start() - on_disk_addresses.start();
764 let offset = slice_addresses.start() - on_disk_addresses.start();
765 Some(offset_from_usize(offset))
765 Some(offset_from_usize(offset))
766 } else {
766 } else {
767 None
767 None
768 }
768 }
769 }
769 }
770
770
771 fn current_offset(&mut self) -> Offset {
771 fn current_offset(&mut self) -> Offset {
772 let mut offset = self.out.len();
772 let mut offset = self.out.len();
773 if self.append {
773 if self.append {
774 offset += self.dirstate_map.on_disk.len()
774 offset += self.dirstate_map.on_disk.len()
775 }
775 }
776 offset_from_usize(offset)
776 offset_from_usize(offset)
777 }
777 }
778
778
779 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
779 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
780 let len = path_len_from_usize(slice.len());
780 let len = path_len_from_usize(slice.len());
781 // Reuse an already-written path if possible
781 // Reuse an already-written path if possible
782 if self.append {
782 if self.append {
783 if let Some(start) = self.on_disk_offset_of(slice) {
783 if let Some(start) = self.on_disk_offset_of(slice) {
784 return PathSlice { start, len };
784 return PathSlice { start, len };
785 }
785 }
786 }
786 }
787 let start = self.current_offset();
787 let start = self.current_offset();
788 self.out.extend(slice.as_bytes());
788 self.out.extend(slice.as_bytes());
789 PathSlice { start, len }
789 PathSlice { start, len }
790 }
790 }
791 }
791 }
792
792
793 fn offset_from_usize(x: usize) -> Offset {
793 fn offset_from_usize(x: usize) -> Offset {
794 u32::try_from(x)
794 u32::try_from(x)
795 // Could only panic for a dirstate file larger than 4 GiB
795 // Could only panic for a dirstate file larger than 4 GiB
796 .expect("dirstate-v2 offset overflow")
796 .expect("dirstate-v2 offset overflow")
797 .into()
797 .into()
798 }
798 }
799
799
800 fn child_nodes_len_from_usize(x: usize) -> Size {
800 fn child_nodes_len_from_usize(x: usize) -> Size {
801 u32::try_from(x)
801 u32::try_from(x)
802 // Could only panic with over 4 billion nodes
802 // Could only panic with over 4 billion nodes
803 .expect("dirstate-v2 slice length overflow")
803 .expect("dirstate-v2 slice length overflow")
804 .into()
804 .into()
805 }
805 }
806
806
807 fn path_len_from_usize(x: usize) -> PathSize {
807 fn path_len_from_usize(x: usize) -> PathSize {
808 u16::try_from(x)
808 u16::try_from(x)
809 // Could only panic for paths over 64 KiB
809 // Could only panic for paths over 64 KiB
810 .expect("dirstate-v2 path length overflow")
810 .expect("dirstate-v2 path length overflow")
811 .into()
811 .into()
812 }
812 }
813
813
814 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
814 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
815 fn from(timestamp: TruncatedTimestamp) -> Self {
815 fn from(timestamp: TruncatedTimestamp) -> Self {
816 Self {
816 Self {
817 truncated_seconds: timestamp.truncated_seconds().into(),
817 truncated_seconds: timestamp.truncated_seconds().into(),
818 nanoseconds: timestamp.nanoseconds().into(),
818 nanoseconds: timestamp.nanoseconds().into(),
819 }
819 }
820 }
820 }
821 }
821 }
822
822
823 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
823 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
824 type Error = DirstateV2ParseError;
824 type Error = DirstateV2ParseError;
825
825
826 fn try_from(
826 fn try_from(
827 timestamp: PackedTruncatedTimestamp,
827 timestamp: PackedTruncatedTimestamp,
828 ) -> Result<Self, Self::Error> {
828 ) -> Result<Self, Self::Error> {
829 Self::from_already_truncated(
829 Self::from_already_truncated(
830 timestamp.truncated_seconds.get(),
830 timestamp.truncated_seconds.get(),
831 timestamp.nanoseconds.get(),
831 timestamp.nanoseconds.get(),
832 false,
832 false,
833 )
833 )
834 }
834 }
835 }
835 }
836 impl PackedTruncatedTimestamp {
836 impl PackedTruncatedTimestamp {
837 fn null() -> Self {
837 fn null() -> Self {
838 Self {
838 Self {
839 truncated_seconds: 0.into(),
839 truncated_seconds: 0.into(),
840 nanoseconds: 0.into(),
840 nanoseconds: 0.into(),
841 }
841 }
842 }
842 }
843 }
843 }
@@ -1,82 +1,82 b''
1 // list_tracked_files.rs
1 // list_tracked_files.rs
2 //
2 //
3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
3 // Copyright 2020 Antoine Cezar <antoine.cezar@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::dirstate::parsers::parse_dirstate_entries;
8 use crate::dirstate::parsers::parse_dirstate_entries;
9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
10 use crate::errors::HgError;
10 use crate::errors::HgError;
11 use crate::repo::Repo;
11 use crate::repo::Repo;
12 use crate::revlog::manifest::Manifest;
12 use crate::revlog::manifest::Manifest;
13 use crate::revlog::revlog::RevlogError;
13 use crate::revlog::revlog::RevlogError;
14 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
15 use crate::DirstateError;
15 use crate::DirstateError;
16 use rayon::prelude::*;
16 use rayon::prelude::*;
17
17
18 /// List files under Mercurial control in the working directory
18 /// List files under Mercurial control in the working directory
19 /// by reading the dirstate
19 /// by reading the dirstate
20 pub struct Dirstate {
20 pub struct Dirstate {
21 /// The `dirstate` content.
21 /// The `dirstate` content.
22 content: Vec<u8>,
22 content: Vec<u8>,
23 v2_metadata: Option<Vec<u8>>,
23 v2_metadata: Option<Vec<u8>>,
24 }
24 }
25
25
26 impl Dirstate {
26 impl Dirstate {
27 pub fn new(repo: &Repo) -> Result<Self, HgError> {
27 pub fn new(repo: &Repo) -> Result<Self, HgError> {
28 let mut content = repo.hg_vfs().read("dirstate")?;
28 let mut content = repo.hg_vfs().read("dirstate")?;
29 let v2_metadata = if repo.has_dirstate_v2() {
29 let v2_metadata = if repo.has_dirstate_v2() {
30 let docket = read_docket(&content)?;
30 let docket = read_docket(&content)?;
31 let meta = docket.tree_metadata().to_vec();
31 let meta = docket.tree_metadata().to_vec();
32 content = repo.hg_vfs().read(docket.data_filename())?;
32 content = repo.hg_vfs().read(docket.data_filename())?;
33 Some(meta)
33 Some(meta)
34 } else {
34 } else {
35 None
35 None
36 };
36 };
37 Ok(Self {
37 Ok(Self {
38 content,
38 content,
39 v2_metadata,
39 v2_metadata,
40 })
40 })
41 }
41 }
42
42
43 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
43 pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> {
44 let mut files = Vec::new();
44 let mut files = Vec::new();
45 if !self.content.is_empty() {
45 if !self.content.is_empty() {
46 if let Some(meta) = &self.v2_metadata {
46 if let Some(meta) = &self.v2_metadata {
47 for_each_tracked_path(&self.content, meta, |path| {
47 for_each_tracked_path(&self.content, meta, |path| {
48 files.push(path)
48 files.push(path)
49 })?
49 })?
50 } else {
50 } else {
51 let _parents = parse_dirstate_entries(
51 let _parents = parse_dirstate_entries(
52 &self.content,
52 &self.content,
53 |path, entry, _copy_source| {
53 |path, entry, _copy_source| {
54 if entry.state().is_tracked() {
54 if entry.tracked() {
55 files.push(path)
55 files.push(path)
56 }
56 }
57 Ok(())
57 Ok(())
58 },
58 },
59 )?;
59 )?;
60 }
60 }
61 }
61 }
62 files.par_sort_unstable();
62 files.par_sort_unstable();
63 Ok(files)
63 Ok(files)
64 }
64 }
65 }
65 }
66
66
67 /// List files under Mercurial control at a given revision.
67 /// List files under Mercurial control at a given revision.
68 pub fn list_rev_tracked_files(
68 pub fn list_rev_tracked_files(
69 repo: &Repo,
69 repo: &Repo,
70 revset: &str,
70 revset: &str,
71 ) -> Result<FilesForRev, RevlogError> {
71 ) -> Result<FilesForRev, RevlogError> {
72 let rev = crate::revset::resolve_single(revset, repo)?;
72 let rev = crate::revset::resolve_single(revset, repo)?;
73 Ok(FilesForRev(repo.manifest_for_rev(rev)?))
73 Ok(FilesForRev(repo.manifest_for_rev(rev)?))
74 }
74 }
75
75
76 pub struct FilesForRev(Manifest);
76 pub struct FilesForRev(Manifest);
77
77
78 impl FilesForRev {
78 impl FilesForRev {
79 pub fn iter(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> {
79 pub fn iter(&self) -> impl Iterator<Item = Result<&HgPath, HgError>> {
80 self.0.iter().map(|entry| Ok(entry?.path))
80 self.0.iter().map(|entry| Ok(entry?.path))
81 }
81 }
82 }
82 }
@@ -1,522 +1,522 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
4 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::errors::HgResultExt;
6 use crate::errors::HgResultExt;
7 use crate::errors::{HgError, IoResultExt};
7 use crate::errors::{HgError, IoResultExt};
8 use crate::lock::{try_with_lock_no_wait, LockError};
8 use crate::lock::{try_with_lock_no_wait, LockError};
9 use crate::manifest::{Manifest, Manifestlog};
9 use crate::manifest::{Manifest, Manifestlog};
10 use crate::revlog::filelog::Filelog;
10 use crate::revlog::filelog::Filelog;
11 use crate::revlog::revlog::RevlogError;
11 use crate::revlog::revlog::RevlogError;
12 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPath;
14 use crate::utils::SliceExt;
14 use crate::utils::SliceExt;
15 use crate::vfs::{is_dir, is_file, Vfs};
15 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::{requirements, NodePrefix};
16 use crate::{requirements, NodePrefix};
17 use crate::{DirstateError, Revision};
17 use crate::{DirstateError, Revision};
18 use std::cell::{Ref, RefCell, RefMut};
18 use std::cell::{Ref, RefCell, RefMut};
19 use std::collections::HashSet;
19 use std::collections::HashSet;
20 use std::io::Seek;
20 use std::io::Seek;
21 use std::io::SeekFrom;
21 use std::io::SeekFrom;
22 use std::io::Write as IoWrite;
22 use std::io::Write as IoWrite;
23 use std::path::{Path, PathBuf};
23 use std::path::{Path, PathBuf};
24
24
25 /// A repository on disk
25 /// A repository on disk
26 pub struct Repo {
26 pub struct Repo {
27 working_directory: PathBuf,
27 working_directory: PathBuf,
28 dot_hg: PathBuf,
28 dot_hg: PathBuf,
29 store: PathBuf,
29 store: PathBuf,
30 requirements: HashSet<String>,
30 requirements: HashSet<String>,
31 config: Config,
31 config: Config,
32 dirstate_parents: LazyCell<DirstateParents, HgError>,
32 dirstate_parents: LazyCell<DirstateParents, HgError>,
33 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
33 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
34 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
34 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
35 changelog: LazyCell<Changelog, HgError>,
35 changelog: LazyCell<Changelog, HgError>,
36 manifestlog: LazyCell<Manifestlog, HgError>,
36 manifestlog: LazyCell<Manifestlog, HgError>,
37 }
37 }
38
38
39 #[derive(Debug, derive_more::From)]
39 #[derive(Debug, derive_more::From)]
40 pub enum RepoError {
40 pub enum RepoError {
41 NotFound {
41 NotFound {
42 at: PathBuf,
42 at: PathBuf,
43 },
43 },
44 #[from]
44 #[from]
45 ConfigParseError(ConfigParseError),
45 ConfigParseError(ConfigParseError),
46 #[from]
46 #[from]
47 Other(HgError),
47 Other(HgError),
48 }
48 }
49
49
50 impl From<ConfigError> for RepoError {
50 impl From<ConfigError> for RepoError {
51 fn from(error: ConfigError) -> Self {
51 fn from(error: ConfigError) -> Self {
52 match error {
52 match error {
53 ConfigError::Parse(error) => error.into(),
53 ConfigError::Parse(error) => error.into(),
54 ConfigError::Other(error) => error.into(),
54 ConfigError::Other(error) => error.into(),
55 }
55 }
56 }
56 }
57 }
57 }
58
58
59 impl Repo {
59 impl Repo {
60 /// tries to find nearest repository root in current working directory or
60 /// tries to find nearest repository root in current working directory or
61 /// its ancestors
61 /// its ancestors
62 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
62 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
63 let current_directory = crate::utils::current_dir()?;
63 let current_directory = crate::utils::current_dir()?;
64 // ancestors() is inclusive: it first yields `current_directory`
64 // ancestors() is inclusive: it first yields `current_directory`
65 // as-is.
65 // as-is.
66 for ancestor in current_directory.ancestors() {
66 for ancestor in current_directory.ancestors() {
67 if is_dir(ancestor.join(".hg"))? {
67 if is_dir(ancestor.join(".hg"))? {
68 return Ok(ancestor.to_path_buf());
68 return Ok(ancestor.to_path_buf());
69 }
69 }
70 }
70 }
71 return Err(RepoError::NotFound {
71 return Err(RepoError::NotFound {
72 at: current_directory,
72 at: current_directory,
73 });
73 });
74 }
74 }
75
75
76 /// Find a repository, either at the given path (which must contain a `.hg`
76 /// Find a repository, either at the given path (which must contain a `.hg`
77 /// sub-directory) or by searching the current directory and its
77 /// sub-directory) or by searching the current directory and its
78 /// ancestors.
78 /// ancestors.
79 ///
79 ///
80 /// A method with two very different "modes" like this usually a code smell
80 /// A method with two very different "modes" like this usually a code smell
81 /// to make two methods instead, but in this case an `Option` is what rhg
81 /// to make two methods instead, but in this case an `Option` is what rhg
82 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
82 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
83 /// Having two methods would just move that `if` to almost all callers.
83 /// Having two methods would just move that `if` to almost all callers.
84 pub fn find(
84 pub fn find(
85 config: &Config,
85 config: &Config,
86 explicit_path: Option<PathBuf>,
86 explicit_path: Option<PathBuf>,
87 ) -> Result<Self, RepoError> {
87 ) -> Result<Self, RepoError> {
88 if let Some(root) = explicit_path {
88 if let Some(root) = explicit_path {
89 if is_dir(root.join(".hg"))? {
89 if is_dir(root.join(".hg"))? {
90 Self::new_at_path(root.to_owned(), config)
90 Self::new_at_path(root.to_owned(), config)
91 } else if is_file(&root)? {
91 } else if is_file(&root)? {
92 Err(HgError::unsupported("bundle repository").into())
92 Err(HgError::unsupported("bundle repository").into())
93 } else {
93 } else {
94 Err(RepoError::NotFound {
94 Err(RepoError::NotFound {
95 at: root.to_owned(),
95 at: root.to_owned(),
96 })
96 })
97 }
97 }
98 } else {
98 } else {
99 let root = Self::find_repo_root()?;
99 let root = Self::find_repo_root()?;
100 Self::new_at_path(root, config)
100 Self::new_at_path(root, config)
101 }
101 }
102 }
102 }
103
103
104 /// To be called after checking that `.hg` is a sub-directory
104 /// To be called after checking that `.hg` is a sub-directory
105 fn new_at_path(
105 fn new_at_path(
106 working_directory: PathBuf,
106 working_directory: PathBuf,
107 config: &Config,
107 config: &Config,
108 ) -> Result<Self, RepoError> {
108 ) -> Result<Self, RepoError> {
109 let dot_hg = working_directory.join(".hg");
109 let dot_hg = working_directory.join(".hg");
110
110
111 let mut repo_config_files = Vec::new();
111 let mut repo_config_files = Vec::new();
112 repo_config_files.push(dot_hg.join("hgrc"));
112 repo_config_files.push(dot_hg.join("hgrc"));
113 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
113 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
114
114
115 let hg_vfs = Vfs { base: &dot_hg };
115 let hg_vfs = Vfs { base: &dot_hg };
116 let mut reqs = requirements::load_if_exists(hg_vfs)?;
116 let mut reqs = requirements::load_if_exists(hg_vfs)?;
117 let relative =
117 let relative =
118 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
118 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
119 let shared =
119 let shared =
120 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
120 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
121
121
122 // From `mercurial/localrepo.py`:
122 // From `mercurial/localrepo.py`:
123 //
123 //
124 // if .hg/requires contains the sharesafe requirement, it means
124 // if .hg/requires contains the sharesafe requirement, it means
125 // there exists a `.hg/store/requires` too and we should read it
125 // there exists a `.hg/store/requires` too and we should read it
126 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
126 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
127 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
127 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
128 // is not present, refer checkrequirementscompat() for that
128 // is not present, refer checkrequirementscompat() for that
129 //
129 //
130 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
130 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
131 // repository was shared the old way. We check the share source
131 // repository was shared the old way. We check the share source
132 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
132 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
133 // current repository needs to be reshared
133 // current repository needs to be reshared
134 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
134 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
135
135
136 let store_path;
136 let store_path;
137 if !shared {
137 if !shared {
138 store_path = dot_hg.join("store");
138 store_path = dot_hg.join("store");
139 } else {
139 } else {
140 let bytes = hg_vfs.read("sharedpath")?;
140 let bytes = hg_vfs.read("sharedpath")?;
141 let mut shared_path =
141 let mut shared_path =
142 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
142 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
143 .to_owned();
143 .to_owned();
144 if relative {
144 if relative {
145 shared_path = dot_hg.join(shared_path)
145 shared_path = dot_hg.join(shared_path)
146 }
146 }
147 if !is_dir(&shared_path)? {
147 if !is_dir(&shared_path)? {
148 return Err(HgError::corrupted(format!(
148 return Err(HgError::corrupted(format!(
149 ".hg/sharedpath points to nonexistent directory {}",
149 ".hg/sharedpath points to nonexistent directory {}",
150 shared_path.display()
150 shared_path.display()
151 ))
151 ))
152 .into());
152 .into());
153 }
153 }
154
154
155 store_path = shared_path.join("store");
155 store_path = shared_path.join("store");
156
156
157 let source_is_share_safe =
157 let source_is_share_safe =
158 requirements::load(Vfs { base: &shared_path })?
158 requirements::load(Vfs { base: &shared_path })?
159 .contains(requirements::SHARESAFE_REQUIREMENT);
159 .contains(requirements::SHARESAFE_REQUIREMENT);
160
160
161 if share_safe != source_is_share_safe {
161 if share_safe != source_is_share_safe {
162 return Err(HgError::unsupported("share-safe mismatch").into());
162 return Err(HgError::unsupported("share-safe mismatch").into());
163 }
163 }
164
164
165 if share_safe {
165 if share_safe {
166 repo_config_files.insert(0, shared_path.join("hgrc"))
166 repo_config_files.insert(0, shared_path.join("hgrc"))
167 }
167 }
168 }
168 }
169 if share_safe {
169 if share_safe {
170 reqs.extend(requirements::load(Vfs { base: &store_path })?);
170 reqs.extend(requirements::load(Vfs { base: &store_path })?);
171 }
171 }
172
172
173 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
173 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
174 config.combine_with_repo(&repo_config_files)?
174 config.combine_with_repo(&repo_config_files)?
175 } else {
175 } else {
176 config.clone()
176 config.clone()
177 };
177 };
178
178
179 let repo = Self {
179 let repo = Self {
180 requirements: reqs,
180 requirements: reqs,
181 working_directory,
181 working_directory,
182 store: store_path,
182 store: store_path,
183 dot_hg,
183 dot_hg,
184 config: repo_config,
184 config: repo_config,
185 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
185 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
186 dirstate_data_file_uuid: LazyCell::new(
186 dirstate_data_file_uuid: LazyCell::new(
187 Self::read_dirstate_data_file_uuid,
187 Self::read_dirstate_data_file_uuid,
188 ),
188 ),
189 dirstate_map: LazyCell::new(Self::new_dirstate_map),
189 dirstate_map: LazyCell::new(Self::new_dirstate_map),
190 changelog: LazyCell::new(Self::new_changelog),
190 changelog: LazyCell::new(Self::new_changelog),
191 manifestlog: LazyCell::new(Self::new_manifestlog),
191 manifestlog: LazyCell::new(Self::new_manifestlog),
192 };
192 };
193
193
194 requirements::check(&repo)?;
194 requirements::check(&repo)?;
195
195
196 Ok(repo)
196 Ok(repo)
197 }
197 }
198
198
199 pub fn working_directory_path(&self) -> &Path {
199 pub fn working_directory_path(&self) -> &Path {
200 &self.working_directory
200 &self.working_directory
201 }
201 }
202
202
203 pub fn requirements(&self) -> &HashSet<String> {
203 pub fn requirements(&self) -> &HashSet<String> {
204 &self.requirements
204 &self.requirements
205 }
205 }
206
206
207 pub fn config(&self) -> &Config {
207 pub fn config(&self) -> &Config {
208 &self.config
208 &self.config
209 }
209 }
210
210
211 /// For accessing repository files (in `.hg`), except for the store
211 /// For accessing repository files (in `.hg`), except for the store
212 /// (`.hg/store`).
212 /// (`.hg/store`).
213 pub fn hg_vfs(&self) -> Vfs<'_> {
213 pub fn hg_vfs(&self) -> Vfs<'_> {
214 Vfs { base: &self.dot_hg }
214 Vfs { base: &self.dot_hg }
215 }
215 }
216
216
217 /// For accessing repository store files (in `.hg/store`)
217 /// For accessing repository store files (in `.hg/store`)
218 pub fn store_vfs(&self) -> Vfs<'_> {
218 pub fn store_vfs(&self) -> Vfs<'_> {
219 Vfs { base: &self.store }
219 Vfs { base: &self.store }
220 }
220 }
221
221
222 /// For accessing the working copy
222 /// For accessing the working copy
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
224 Vfs {
224 Vfs {
225 base: &self.working_directory,
225 base: &self.working_directory,
226 }
226 }
227 }
227 }
228
228
229 pub fn try_with_wlock_no_wait<R>(
229 pub fn try_with_wlock_no_wait<R>(
230 &self,
230 &self,
231 f: impl FnOnce() -> R,
231 f: impl FnOnce() -> R,
232 ) -> Result<R, LockError> {
232 ) -> Result<R, LockError> {
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
234 }
234 }
235
235
236 pub fn has_dirstate_v2(&self) -> bool {
236 pub fn has_dirstate_v2(&self) -> bool {
237 self.requirements
237 self.requirements
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
239 }
239 }
240
240
241 pub fn has_sparse(&self) -> bool {
241 pub fn has_sparse(&self) -> bool {
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
243 }
243 }
244
244
245 pub fn has_narrow(&self) -> bool {
245 pub fn has_narrow(&self) -> bool {
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
247 }
247 }
248
248
249 pub fn has_nodemap(&self) -> bool {
249 pub fn has_nodemap(&self) -> bool {
250 self.requirements
250 self.requirements
251 .contains(requirements::NODEMAP_REQUIREMENT)
251 .contains(requirements::NODEMAP_REQUIREMENT)
252 }
252 }
253
253
254 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
254 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
255 Ok(self
255 Ok(self
256 .hg_vfs()
256 .hg_vfs()
257 .read("dirstate")
257 .read("dirstate")
258 .io_not_found_as_none()?
258 .io_not_found_as_none()?
259 .unwrap_or(Vec::new()))
259 .unwrap_or(Vec::new()))
260 }
260 }
261
261
262 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
262 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
263 Ok(*self.dirstate_parents.get_or_init(self)?)
263 Ok(*self.dirstate_parents.get_or_init(self)?)
264 }
264 }
265
265
266 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
266 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
267 let dirstate = self.dirstate_file_contents()?;
267 let dirstate = self.dirstate_file_contents()?;
268 let parents = if dirstate.is_empty() {
268 let parents = if dirstate.is_empty() {
269 if self.has_dirstate_v2() {
269 if self.has_dirstate_v2() {
270 self.dirstate_data_file_uuid.set(None);
270 self.dirstate_data_file_uuid.set(None);
271 }
271 }
272 DirstateParents::NULL
272 DirstateParents::NULL
273 } else if self.has_dirstate_v2() {
273 } else if self.has_dirstate_v2() {
274 let docket =
274 let docket =
275 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
275 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
276 self.dirstate_data_file_uuid
276 self.dirstate_data_file_uuid
277 .set(Some(docket.uuid.to_owned()));
277 .set(Some(docket.uuid.to_owned()));
278 docket.parents()
278 docket.parents()
279 } else {
279 } else {
280 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
280 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
281 .clone()
281 .clone()
282 };
282 };
283 self.dirstate_parents.set(parents);
283 self.dirstate_parents.set(parents);
284 Ok(parents)
284 Ok(parents)
285 }
285 }
286
286
287 fn read_dirstate_data_file_uuid(
287 fn read_dirstate_data_file_uuid(
288 &self,
288 &self,
289 ) -> Result<Option<Vec<u8>>, HgError> {
289 ) -> Result<Option<Vec<u8>>, HgError> {
290 assert!(
290 assert!(
291 self.has_dirstate_v2(),
291 self.has_dirstate_v2(),
292 "accessing dirstate data file ID without dirstate-v2"
292 "accessing dirstate data file ID without dirstate-v2"
293 );
293 );
294 let dirstate = self.dirstate_file_contents()?;
294 let dirstate = self.dirstate_file_contents()?;
295 if dirstate.is_empty() {
295 if dirstate.is_empty() {
296 self.dirstate_parents.set(DirstateParents::NULL);
296 self.dirstate_parents.set(DirstateParents::NULL);
297 Ok(None)
297 Ok(None)
298 } else {
298 } else {
299 let docket =
299 let docket =
300 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
300 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
301 self.dirstate_parents.set(docket.parents());
301 self.dirstate_parents.set(docket.parents());
302 Ok(Some(docket.uuid.to_owned()))
302 Ok(Some(docket.uuid.to_owned()))
303 }
303 }
304 }
304 }
305
305
306 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
306 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
307 let dirstate_file_contents = self.dirstate_file_contents()?;
307 let dirstate_file_contents = self.dirstate_file_contents()?;
308 if dirstate_file_contents.is_empty() {
308 if dirstate_file_contents.is_empty() {
309 self.dirstate_parents.set(DirstateParents::NULL);
309 self.dirstate_parents.set(DirstateParents::NULL);
310 if self.has_dirstate_v2() {
310 if self.has_dirstate_v2() {
311 self.dirstate_data_file_uuid.set(None);
311 self.dirstate_data_file_uuid.set(None);
312 }
312 }
313 Ok(OwningDirstateMap::new_empty(Vec::new()))
313 Ok(OwningDirstateMap::new_empty(Vec::new()))
314 } else if self.has_dirstate_v2() {
314 } else if self.has_dirstate_v2() {
315 let docket = crate::dirstate_tree::on_disk::read_docket(
315 let docket = crate::dirstate_tree::on_disk::read_docket(
316 &dirstate_file_contents,
316 &dirstate_file_contents,
317 )?;
317 )?;
318 self.dirstate_parents.set(docket.parents());
318 self.dirstate_parents.set(docket.parents());
319 self.dirstate_data_file_uuid
319 self.dirstate_data_file_uuid
320 .set(Some(docket.uuid.to_owned()));
320 .set(Some(docket.uuid.to_owned()));
321 let data_size = docket.data_size();
321 let data_size = docket.data_size();
322 let metadata = docket.tree_metadata();
322 let metadata = docket.tree_metadata();
323 if let Some(data_mmap) = self
323 if let Some(data_mmap) = self
324 .hg_vfs()
324 .hg_vfs()
325 .mmap_open(docket.data_filename())
325 .mmap_open(docket.data_filename())
326 .io_not_found_as_none()?
326 .io_not_found_as_none()?
327 {
327 {
328 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
328 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
329 } else {
329 } else {
330 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
330 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
331 }
331 }
332 } else {
332 } else {
333 let (map, parents) =
333 let (map, parents) =
334 OwningDirstateMap::new_v1(dirstate_file_contents)?;
334 OwningDirstateMap::new_v1(dirstate_file_contents)?;
335 self.dirstate_parents.set(parents);
335 self.dirstate_parents.set(parents);
336 Ok(map)
336 Ok(map)
337 }
337 }
338 }
338 }
339
339
340 pub fn dirstate_map(
340 pub fn dirstate_map(
341 &self,
341 &self,
342 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
342 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
343 self.dirstate_map.get_or_init(self)
343 self.dirstate_map.get_or_init(self)
344 }
344 }
345
345
346 pub fn dirstate_map_mut(
346 pub fn dirstate_map_mut(
347 &self,
347 &self,
348 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
348 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
349 self.dirstate_map.get_mut_or_init(self)
349 self.dirstate_map.get_mut_or_init(self)
350 }
350 }
351
351
352 fn new_changelog(&self) -> Result<Changelog, HgError> {
352 fn new_changelog(&self) -> Result<Changelog, HgError> {
353 Changelog::open(&self.store_vfs(), self.has_nodemap())
353 Changelog::open(&self.store_vfs(), self.has_nodemap())
354 }
354 }
355
355
356 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
356 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
357 self.changelog.get_or_init(self)
357 self.changelog.get_or_init(self)
358 }
358 }
359
359
360 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
360 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
361 self.changelog.get_mut_or_init(self)
361 self.changelog.get_mut_or_init(self)
362 }
362 }
363
363
364 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
364 fn new_manifestlog(&self) -> Result<Manifestlog, HgError> {
365 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
365 Manifestlog::open(&self.store_vfs(), self.has_nodemap())
366 }
366 }
367
367
368 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
368 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
369 self.manifestlog.get_or_init(self)
369 self.manifestlog.get_or_init(self)
370 }
370 }
371
371
372 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
372 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
373 self.manifestlog.get_mut_or_init(self)
373 self.manifestlog.get_mut_or_init(self)
374 }
374 }
375
375
376 /// Returns the manifest of the *changeset* with the given node ID
376 /// Returns the manifest of the *changeset* with the given node ID
377 pub fn manifest_for_node(
377 pub fn manifest_for_node(
378 &self,
378 &self,
379 node: impl Into<NodePrefix>,
379 node: impl Into<NodePrefix>,
380 ) -> Result<Manifest, RevlogError> {
380 ) -> Result<Manifest, RevlogError> {
381 self.manifestlog()?.data_for_node(
381 self.manifestlog()?.data_for_node(
382 self.changelog()?
382 self.changelog()?
383 .data_for_node(node.into())?
383 .data_for_node(node.into())?
384 .manifest_node()?
384 .manifest_node()?
385 .into(),
385 .into(),
386 )
386 )
387 }
387 }
388
388
389 /// Returns the manifest of the *changeset* with the given revision number
389 /// Returns the manifest of the *changeset* with the given revision number
390 pub fn manifest_for_rev(
390 pub fn manifest_for_rev(
391 &self,
391 &self,
392 revision: Revision,
392 revision: Revision,
393 ) -> Result<Manifest, RevlogError> {
393 ) -> Result<Manifest, RevlogError> {
394 self.manifestlog()?.data_for_node(
394 self.manifestlog()?.data_for_node(
395 self.changelog()?
395 self.changelog()?
396 .data_for_rev(revision)?
396 .data_for_rev(revision)?
397 .manifest_node()?
397 .manifest_node()?
398 .into(),
398 .into(),
399 )
399 )
400 }
400 }
401
401
402 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
402 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
403 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
403 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
404 Ok(entry.state().is_tracked())
404 Ok(entry.tracked())
405 } else {
405 } else {
406 Ok(false)
406 Ok(false)
407 }
407 }
408 }
408 }
409
409
410 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
410 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
411 Filelog::open(self, path)
411 Filelog::open(self, path)
412 }
412 }
413
413
414 /// Write to disk any updates that were made through `dirstate_map_mut`.
414 /// Write to disk any updates that were made through `dirstate_map_mut`.
415 ///
415 ///
416 /// The "wlock" must be held while calling this.
416 /// The "wlock" must be held while calling this.
417 /// See for example `try_with_wlock_no_wait`.
417 /// See for example `try_with_wlock_no_wait`.
418 ///
418 ///
419 /// TODO: have a `WritableRepo` type only accessible while holding the
419 /// TODO: have a `WritableRepo` type only accessible while holding the
420 /// lock?
420 /// lock?
421 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
421 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
422 let map = self.dirstate_map()?;
422 let map = self.dirstate_map()?;
423 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
423 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
424 // it’s unset
424 // it’s unset
425 let parents = self.dirstate_parents()?;
425 let parents = self.dirstate_parents()?;
426 let packed_dirstate = if self.has_dirstate_v2() {
426 let packed_dirstate = if self.has_dirstate_v2() {
427 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
427 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
428 let mut uuid = uuid.as_ref();
428 let mut uuid = uuid.as_ref();
429 let can_append = uuid.is_some();
429 let can_append = uuid.is_some();
430 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
430 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
431 if !append {
431 if !append {
432 uuid = None
432 uuid = None
433 }
433 }
434 let uuid = if let Some(uuid) = uuid {
434 let uuid = if let Some(uuid) = uuid {
435 std::str::from_utf8(uuid)
435 std::str::from_utf8(uuid)
436 .map_err(|_| {
436 .map_err(|_| {
437 HgError::corrupted("non-UTF-8 dirstate data file ID")
437 HgError::corrupted("non-UTF-8 dirstate data file ID")
438 })?
438 })?
439 .to_owned()
439 .to_owned()
440 } else {
440 } else {
441 DirstateDocket::new_uid()
441 DirstateDocket::new_uid()
442 };
442 };
443 let data_filename = format!("dirstate.{}", uuid);
443 let data_filename = format!("dirstate.{}", uuid);
444 let data_filename = self.hg_vfs().join(data_filename);
444 let data_filename = self.hg_vfs().join(data_filename);
445 let mut options = std::fs::OpenOptions::new();
445 let mut options = std::fs::OpenOptions::new();
446 if append {
446 if append {
447 options.append(true);
447 options.append(true);
448 } else {
448 } else {
449 options.write(true).create_new(true);
449 options.write(true).create_new(true);
450 }
450 }
451 let data_size = (|| {
451 let data_size = (|| {
452 // TODO: loop and try another random ID if !append and this
452 // TODO: loop and try another random ID if !append and this
453 // returns `ErrorKind::AlreadyExists`? Collision chance of two
453 // returns `ErrorKind::AlreadyExists`? Collision chance of two
454 // random IDs is one in 2**32
454 // random IDs is one in 2**32
455 let mut file = options.open(&data_filename)?;
455 let mut file = options.open(&data_filename)?;
456 file.write_all(&data)?;
456 file.write_all(&data)?;
457 file.flush()?;
457 file.flush()?;
458 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
458 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
459 file.seek(SeekFrom::Current(0))
459 file.seek(SeekFrom::Current(0))
460 })()
460 })()
461 .when_writing_file(&data_filename)?;
461 .when_writing_file(&data_filename)?;
462 DirstateDocket::serialize(
462 DirstateDocket::serialize(
463 parents,
463 parents,
464 tree_metadata,
464 tree_metadata,
465 data_size,
465 data_size,
466 uuid.as_bytes(),
466 uuid.as_bytes(),
467 )
467 )
468 .map_err(|_: std::num::TryFromIntError| {
468 .map_err(|_: std::num::TryFromIntError| {
469 HgError::corrupted("overflow in dirstate docket serialization")
469 HgError::corrupted("overflow in dirstate docket serialization")
470 })?
470 })?
471 } else {
471 } else {
472 map.pack_v1(parents)?
472 map.pack_v1(parents)?
473 };
473 };
474 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
474 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
475 Ok(())
475 Ok(())
476 }
476 }
477 }
477 }
478
478
479 /// Lazily-initialized component of `Repo` with interior mutability
479 /// Lazily-initialized component of `Repo` with interior mutability
480 ///
480 ///
481 /// This differs from `OnceCell` in that the value can still be "deinitialized"
481 /// This differs from `OnceCell` in that the value can still be "deinitialized"
482 /// later by setting its inner `Option` to `None`.
482 /// later by setting its inner `Option` to `None`.
483 struct LazyCell<T, E> {
483 struct LazyCell<T, E> {
484 value: RefCell<Option<T>>,
484 value: RefCell<Option<T>>,
485 // `Fn`s that don’t capture environment are zero-size, so this box does
485 // `Fn`s that don’t capture environment are zero-size, so this box does
486 // not allocate:
486 // not allocate:
487 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
487 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
488 }
488 }
489
489
490 impl<T, E> LazyCell<T, E> {
490 impl<T, E> LazyCell<T, E> {
491 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
491 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
492 Self {
492 Self {
493 value: RefCell::new(None),
493 value: RefCell::new(None),
494 init: Box::new(init),
494 init: Box::new(init),
495 }
495 }
496 }
496 }
497
497
498 fn set(&self, value: T) {
498 fn set(&self, value: T) {
499 *self.value.borrow_mut() = Some(value)
499 *self.value.borrow_mut() = Some(value)
500 }
500 }
501
501
502 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
502 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
503 let mut borrowed = self.value.borrow();
503 let mut borrowed = self.value.borrow();
504 if borrowed.is_none() {
504 if borrowed.is_none() {
505 drop(borrowed);
505 drop(borrowed);
506 // Only use `borrow_mut` if it is really needed to avoid panic in
506 // Only use `borrow_mut` if it is really needed to avoid panic in
507 // case there is another outstanding borrow but mutation is not
507 // case there is another outstanding borrow but mutation is not
508 // needed.
508 // needed.
509 *self.value.borrow_mut() = Some((self.init)(repo)?);
509 *self.value.borrow_mut() = Some((self.init)(repo)?);
510 borrowed = self.value.borrow()
510 borrowed = self.value.borrow()
511 }
511 }
512 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
512 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
513 }
513 }
514
514
515 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
515 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
516 let mut borrowed = self.value.borrow_mut();
516 let mut borrowed = self.value.borrow_mut();
517 if borrowed.is_none() {
517 if borrowed.is_none() {
518 *borrowed = Some((self.init)(repo)?);
518 *borrowed = Some((self.init)(repo)?);
519 }
519 }
520 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
520 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
521 }
521 }
522 }
522 }
General Comments 0
You need to be logged in to leave comments. Login now