##// END OF EJS Templates
rhg: Add Repo::write_dirstate...
Simon Sapin -
r49249:2097f635 default
parent child Browse files
Show More
@@ -1,1139 +1,1139 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::StateMapIter;
14 use crate::dirstate::StateMapIter;
15 use crate::dirstate::TruncatedTimestamp;
15 use crate::dirstate::TruncatedTimestamp;
16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
17 use crate::dirstate::SIZE_NON_NORMAL;
17 use crate::dirstate::SIZE_NON_NORMAL;
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::DirstateParents;
22 use crate::DirstateParents;
23 use crate::DirstateStatus;
23 use crate::DirstateStatus;
24 use crate::EntryState;
24 use crate::EntryState;
25 use crate::FastHashMap;
25 use crate::FastHashMap;
26 use crate::PatternFileWarning;
26 use crate::PatternFileWarning;
27 use crate::StatusError;
27 use crate::StatusError;
28 use crate::StatusOptions;
28 use crate::StatusOptions;
29
29
30 /// Append to an existing data file if the amount of unreachable data (not used
30 /// Append to an existing data file if the amount of unreachable data (not used
31 /// anymore) is less than this fraction of the total amount of existing data.
31 /// anymore) is less than this fraction of the total amount of existing data.
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33
33
34 pub struct DirstateMap<'on_disk> {
34 pub struct DirstateMap<'on_disk> {
35 /// Contents of the `.hg/dirstate` file
35 /// Contents of the `.hg/dirstate` file
36 pub(super) on_disk: &'on_disk [u8],
36 pub(super) on_disk: &'on_disk [u8],
37
37
38 pub(super) root: ChildNodes<'on_disk>,
38 pub(super) root: ChildNodes<'on_disk>,
39
39
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 pub(super) nodes_with_entry_count: u32,
41 pub(super) nodes_with_entry_count: u32,
42
42
43 /// Number of nodes anywhere in the tree that have
43 /// Number of nodes anywhere in the tree that have
44 /// `.copy_source.is_some()`.
44 /// `.copy_source.is_some()`.
45 pub(super) nodes_with_copy_source_count: u32,
45 pub(super) nodes_with_copy_source_count: u32,
46
46
47 /// See on_disk::Header
47 /// See on_disk::Header
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49
49
50 /// How many bytes of `on_disk` are not used anymore
50 /// How many bytes of `on_disk` are not used anymore
51 pub(super) unreachable_bytes: u32,
51 pub(super) unreachable_bytes: u32,
52 }
52 }
53
53
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 /// map key would also work: all paths in a given map have the same parent
55 /// map key would also work: all paths in a given map have the same parent
56 /// path, so comparing full paths gives the same result as comparing base
56 /// path, so comparing full paths gives the same result as comparing base
57 /// names. However `HashMap` would waste time always re-hashing the same
57 /// names. However `HashMap` would waste time always re-hashing the same
58 /// string prefix.
58 /// string prefix.
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60
60
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 InMemory(&'tree HgPathBuf),
64 InMemory(&'tree HgPathBuf),
65 OnDisk(&'on_disk HgPath),
65 OnDisk(&'on_disk HgPath),
66 }
66 }
67
67
68 pub(super) enum ChildNodes<'on_disk> {
68 pub(super) enum ChildNodes<'on_disk> {
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 OnDisk(&'on_disk [on_disk::Node]),
70 OnDisk(&'on_disk [on_disk::Node]),
71 }
71 }
72
72
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 OnDisk(&'on_disk [on_disk::Node]),
75 OnDisk(&'on_disk [on_disk::Node]),
76 }
76 }
77
77
78 pub(super) enum NodeRef<'tree, 'on_disk> {
78 pub(super) enum NodeRef<'tree, 'on_disk> {
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 OnDisk(&'on_disk on_disk::Node),
80 OnDisk(&'on_disk on_disk::Node),
81 }
81 }
82
82
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 match *self {
85 match *self {
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 }
88 }
89 }
89 }
90 }
90 }
91
91
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 type Target = HgPath;
93 type Target = HgPath;
94
94
95 fn deref(&self) -> &HgPath {
95 fn deref(&self) -> &HgPath {
96 match *self {
96 match *self {
97 BorrowedPath::InMemory(in_memory) => in_memory,
97 BorrowedPath::InMemory(in_memory) => in_memory,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
99 }
99 }
100 }
100 }
101 }
101 }
102
102
103 impl Default for ChildNodes<'_> {
103 impl Default for ChildNodes<'_> {
104 fn default() -> Self {
104 fn default() -> Self {
105 ChildNodes::InMemory(Default::default())
105 ChildNodes::InMemory(Default::default())
106 }
106 }
107 }
107 }
108
108
109 impl<'on_disk> ChildNodes<'on_disk> {
109 impl<'on_disk> ChildNodes<'on_disk> {
110 pub(super) fn as_ref<'tree>(
110 pub(super) fn as_ref<'tree>(
111 &'tree self,
111 &'tree self,
112 ) -> ChildNodesRef<'tree, 'on_disk> {
112 ) -> ChildNodesRef<'tree, 'on_disk> {
113 match self {
113 match self {
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 }
116 }
117 }
117 }
118
118
119 pub(super) fn is_empty(&self) -> bool {
119 pub(super) fn is_empty(&self) -> bool {
120 match self {
120 match self {
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 }
123 }
124 }
124 }
125
125
126 fn make_mut(
126 fn make_mut(
127 &mut self,
127 &mut self,
128 on_disk: &'on_disk [u8],
128 on_disk: &'on_disk [u8],
129 unreachable_bytes: &mut u32,
129 unreachable_bytes: &mut u32,
130 ) -> Result<
130 ) -> Result<
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 DirstateV2ParseError,
132 DirstateV2ParseError,
133 > {
133 > {
134 match self {
134 match self {
135 ChildNodes::InMemory(nodes) => Ok(nodes),
135 ChildNodes::InMemory(nodes) => Ok(nodes),
136 ChildNodes::OnDisk(nodes) => {
136 ChildNodes::OnDisk(nodes) => {
137 *unreachable_bytes +=
137 *unreachable_bytes +=
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 let nodes = nodes
139 let nodes = nodes
140 .iter()
140 .iter()
141 .map(|node| {
141 .map(|node| {
142 Ok((
142 Ok((
143 node.path(on_disk)?,
143 node.path(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
145 ))
145 ))
146 })
146 })
147 .collect::<Result<_, _>>()?;
147 .collect::<Result<_, _>>()?;
148 *self = ChildNodes::InMemory(nodes);
148 *self = ChildNodes::InMemory(nodes);
149 match self {
149 match self {
150 ChildNodes::InMemory(nodes) => Ok(nodes),
150 ChildNodes::InMemory(nodes) => Ok(nodes),
151 ChildNodes::OnDisk(_) => unreachable!(),
151 ChildNodes::OnDisk(_) => unreachable!(),
152 }
152 }
153 }
153 }
154 }
154 }
155 }
155 }
156 }
156 }
157
157
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 pub(super) fn get(
159 pub(super) fn get(
160 &self,
160 &self,
161 base_name: &HgPath,
161 base_name: &HgPath,
162 on_disk: &'on_disk [u8],
162 on_disk: &'on_disk [u8],
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 match self {
164 match self {
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 .get_key_value(base_name)
166 .get_key_value(base_name)
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 ChildNodesRef::OnDisk(nodes) => {
168 ChildNodesRef::OnDisk(nodes) => {
169 let mut parse_result = Ok(());
169 let mut parse_result = Ok(());
170 let search_result = nodes.binary_search_by(|node| {
170 let search_result = nodes.binary_search_by(|node| {
171 match node.base_name(on_disk) {
171 match node.base_name(on_disk) {
172 Ok(node_base_name) => node_base_name.cmp(base_name),
172 Ok(node_base_name) => node_base_name.cmp(base_name),
173 Err(e) => {
173 Err(e) => {
174 parse_result = Err(e);
174 parse_result = Err(e);
175 // Dummy comparison result, `search_result` won’t
175 // Dummy comparison result, `search_result` won’t
176 // be used since `parse_result` is an error
176 // be used since `parse_result` is an error
177 std::cmp::Ordering::Equal
177 std::cmp::Ordering::Equal
178 }
178 }
179 }
179 }
180 });
180 });
181 parse_result.map(|()| {
181 parse_result.map(|()| {
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 })
183 })
184 }
184 }
185 }
185 }
186 }
186 }
187
187
188 /// Iterate in undefined order
188 /// Iterate in undefined order
189 pub(super) fn iter(
189 pub(super) fn iter(
190 &self,
190 &self,
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 match self {
192 match self {
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 ),
195 ),
196 ChildNodesRef::OnDisk(nodes) => {
196 ChildNodesRef::OnDisk(nodes) => {
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 }
198 }
199 }
199 }
200 }
200 }
201
201
202 /// Iterate in parallel in undefined order
202 /// Iterate in parallel in undefined order
203 pub(super) fn par_iter(
203 pub(super) fn par_iter(
204 &self,
204 &self,
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 {
206 {
207 use rayon::prelude::*;
207 use rayon::prelude::*;
208 match self {
208 match self {
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 ),
211 ),
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 nodes.par_iter().map(NodeRef::OnDisk),
213 nodes.par_iter().map(NodeRef::OnDisk),
214 ),
214 ),
215 }
215 }
216 }
216 }
217
217
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 match self {
219 match self {
220 ChildNodesRef::InMemory(nodes) => {
220 ChildNodesRef::InMemory(nodes) => {
221 let mut vec: Vec<_> = nodes
221 let mut vec: Vec<_> = nodes
222 .iter()
222 .iter()
223 .map(|(k, v)| NodeRef::InMemory(k, v))
223 .map(|(k, v)| NodeRef::InMemory(k, v))
224 .collect();
224 .collect();
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 match node {
226 match node {
227 NodeRef::InMemory(path, _node) => path.base_name(),
227 NodeRef::InMemory(path, _node) => path.base_name(),
228 NodeRef::OnDisk(_) => unreachable!(),
228 NodeRef::OnDisk(_) => unreachable!(),
229 }
229 }
230 }
230 }
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 // value: https://github.com/rust-lang/rust/issues/34162
232 // value: https://github.com/rust-lang/rust/issues/34162
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 vec
234 vec
235 }
235 }
236 ChildNodesRef::OnDisk(nodes) => {
236 ChildNodesRef::OnDisk(nodes) => {
237 // Nodes on disk are already sorted
237 // Nodes on disk are already sorted
238 nodes.iter().map(NodeRef::OnDisk).collect()
238 nodes.iter().map(NodeRef::OnDisk).collect()
239 }
239 }
240 }
240 }
241 }
241 }
242 }
242 }
243
243
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 pub(super) fn full_path(
245 pub(super) fn full_path(
246 &self,
246 &self,
247 on_disk: &'on_disk [u8],
247 on_disk: &'on_disk [u8],
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 match self {
249 match self {
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 }
252 }
253 }
253 }
254
254
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 /// HgPath>` detached from `'tree`
256 /// HgPath>` detached from `'tree`
257 pub(super) fn full_path_borrowed(
257 pub(super) fn full_path_borrowed(
258 &self,
258 &self,
259 on_disk: &'on_disk [u8],
259 on_disk: &'on_disk [u8],
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 match self {
261 match self {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 },
265 },
266 NodeRef::OnDisk(node) => {
266 NodeRef::OnDisk(node) => {
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 }
268 }
269 }
269 }
270 }
270 }
271
271
272 pub(super) fn base_name(
272 pub(super) fn base_name(
273 &self,
273 &self,
274 on_disk: &'on_disk [u8],
274 on_disk: &'on_disk [u8],
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 match self {
276 match self {
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 }
279 }
280 }
280 }
281
281
282 pub(super) fn children(
282 pub(super) fn children(
283 &self,
283 &self,
284 on_disk: &'on_disk [u8],
284 on_disk: &'on_disk [u8],
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 match self {
286 match self {
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 NodeRef::OnDisk(node) => {
288 NodeRef::OnDisk(node) => {
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 }
290 }
291 }
291 }
292 }
292 }
293
293
294 pub(super) fn has_copy_source(&self) -> bool {
294 pub(super) fn has_copy_source(&self) -> bool {
295 match self {
295 match self {
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
298 }
298 }
299 }
299 }
300
300
301 pub(super) fn copy_source(
301 pub(super) fn copy_source(
302 &self,
302 &self,
303 on_disk: &'on_disk [u8],
303 on_disk: &'on_disk [u8],
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 match self {
305 match self {
306 NodeRef::InMemory(_path, node) => {
306 NodeRef::InMemory(_path, node) => {
307 Ok(node.copy_source.as_ref().map(|s| &**s))
307 Ok(node.copy_source.as_ref().map(|s| &**s))
308 }
308 }
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 }
310 }
311 }
311 }
312
312
313 pub(super) fn entry(
313 pub(super) fn entry(
314 &self,
314 &self,
315 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
315 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
316 match self {
316 match self {
317 NodeRef::InMemory(_path, node) => {
317 NodeRef::InMemory(_path, node) => {
318 Ok(node.data.as_entry().copied())
318 Ok(node.data.as_entry().copied())
319 }
319 }
320 NodeRef::OnDisk(node) => node.entry(),
320 NodeRef::OnDisk(node) => node.entry(),
321 }
321 }
322 }
322 }
323
323
324 pub(super) fn state(
324 pub(super) fn state(
325 &self,
325 &self,
326 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
326 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
327 Ok(self.entry()?.map(|e| e.state()))
327 Ok(self.entry()?.map(|e| e.state()))
328 }
328 }
329
329
330 pub(super) fn cached_directory_mtime(
330 pub(super) fn cached_directory_mtime(
331 &self,
331 &self,
332 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
332 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
333 match self {
333 match self {
334 NodeRef::InMemory(_path, node) => Ok(match node.data {
334 NodeRef::InMemory(_path, node) => Ok(match node.data {
335 NodeData::CachedDirectory { mtime } => Some(mtime),
335 NodeData::CachedDirectory { mtime } => Some(mtime),
336 _ => None,
336 _ => None,
337 }),
337 }),
338 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
338 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
339 }
339 }
340 }
340 }
341
341
342 pub(super) fn descendants_with_entry_count(&self) -> u32 {
342 pub(super) fn descendants_with_entry_count(&self) -> u32 {
343 match self {
343 match self {
344 NodeRef::InMemory(_path, node) => {
344 NodeRef::InMemory(_path, node) => {
345 node.descendants_with_entry_count
345 node.descendants_with_entry_count
346 }
346 }
347 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
347 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
348 }
348 }
349 }
349 }
350
350
351 pub(super) fn tracked_descendants_count(&self) -> u32 {
351 pub(super) fn tracked_descendants_count(&self) -> u32 {
352 match self {
352 match self {
353 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
353 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
354 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
354 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
355 }
355 }
356 }
356 }
357 }
357 }
358
358
359 /// Represents a file or a directory
359 /// Represents a file or a directory
360 #[derive(Default)]
360 #[derive(Default)]
361 pub(super) struct Node<'on_disk> {
361 pub(super) struct Node<'on_disk> {
362 pub(super) data: NodeData,
362 pub(super) data: NodeData,
363
363
364 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
364 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
365
365
366 pub(super) children: ChildNodes<'on_disk>,
366 pub(super) children: ChildNodes<'on_disk>,
367
367
368 /// How many (non-inclusive) descendants of this node have an entry.
368 /// How many (non-inclusive) descendants of this node have an entry.
369 pub(super) descendants_with_entry_count: u32,
369 pub(super) descendants_with_entry_count: u32,
370
370
371 /// How many (non-inclusive) descendants of this node have an entry whose
371 /// How many (non-inclusive) descendants of this node have an entry whose
372 /// state is "tracked".
372 /// state is "tracked".
373 pub(super) tracked_descendants_count: u32,
373 pub(super) tracked_descendants_count: u32,
374 }
374 }
375
375
376 pub(super) enum NodeData {
376 pub(super) enum NodeData {
377 Entry(DirstateEntry),
377 Entry(DirstateEntry),
378 CachedDirectory { mtime: TruncatedTimestamp },
378 CachedDirectory { mtime: TruncatedTimestamp },
379 None,
379 None,
380 }
380 }
381
381
382 impl Default for NodeData {
382 impl Default for NodeData {
383 fn default() -> Self {
383 fn default() -> Self {
384 NodeData::None
384 NodeData::None
385 }
385 }
386 }
386 }
387
387
388 impl NodeData {
388 impl NodeData {
389 fn has_entry(&self) -> bool {
389 fn has_entry(&self) -> bool {
390 match self {
390 match self {
391 NodeData::Entry(_) => true,
391 NodeData::Entry(_) => true,
392 _ => false,
392 _ => false,
393 }
393 }
394 }
394 }
395
395
396 fn as_entry(&self) -> Option<&DirstateEntry> {
396 fn as_entry(&self) -> Option<&DirstateEntry> {
397 match self {
397 match self {
398 NodeData::Entry(entry) => Some(entry),
398 NodeData::Entry(entry) => Some(entry),
399 _ => None,
399 _ => None,
400 }
400 }
401 }
401 }
402 }
402 }
403
403
404 impl<'on_disk> DirstateMap<'on_disk> {
404 impl<'on_disk> DirstateMap<'on_disk> {
405 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
405 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
406 Self {
406 Self {
407 on_disk,
407 on_disk,
408 root: ChildNodes::default(),
408 root: ChildNodes::default(),
409 nodes_with_entry_count: 0,
409 nodes_with_entry_count: 0,
410 nodes_with_copy_source_count: 0,
410 nodes_with_copy_source_count: 0,
411 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
411 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
412 unreachable_bytes: 0,
412 unreachable_bytes: 0,
413 }
413 }
414 }
414 }
415
415
416 #[timed]
416 #[timed]
417 pub fn new_v2(
417 pub fn new_v2(
418 on_disk: &'on_disk [u8],
418 on_disk: &'on_disk [u8],
419 data_size: usize,
419 data_size: usize,
420 metadata: &[u8],
420 metadata: &[u8],
421 ) -> Result<Self, DirstateError> {
421 ) -> Result<Self, DirstateError> {
422 if let Some(data) = on_disk.get(..data_size) {
422 if let Some(data) = on_disk.get(..data_size) {
423 Ok(on_disk::read(data, metadata)?)
423 Ok(on_disk::read(data, metadata)?)
424 } else {
424 } else {
425 Err(DirstateV2ParseError.into())
425 Err(DirstateV2ParseError.into())
426 }
426 }
427 }
427 }
428
428
429 #[timed]
429 #[timed]
430 pub fn new_v1(
430 pub fn new_v1(
431 on_disk: &'on_disk [u8],
431 on_disk: &'on_disk [u8],
432 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
432 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
433 let mut map = Self::empty(on_disk);
433 let mut map = Self::empty(on_disk);
434 if map.on_disk.is_empty() {
434 if map.on_disk.is_empty() {
435 return Ok((map, None));
435 return Ok((map, None));
436 }
436 }
437
437
438 let parents = parse_dirstate_entries(
438 let parents = parse_dirstate_entries(
439 map.on_disk,
439 map.on_disk,
440 |path, entry, copy_source| {
440 |path, entry, copy_source| {
441 let tracked = entry.state().is_tracked();
441 let tracked = entry.state().is_tracked();
442 let node = Self::get_or_insert_node(
442 let node = Self::get_or_insert_node(
443 map.on_disk,
443 map.on_disk,
444 &mut map.unreachable_bytes,
444 &mut map.unreachable_bytes,
445 &mut map.root,
445 &mut map.root,
446 path,
446 path,
447 WithBasename::to_cow_borrowed,
447 WithBasename::to_cow_borrowed,
448 |ancestor| {
448 |ancestor| {
449 if tracked {
449 if tracked {
450 ancestor.tracked_descendants_count += 1
450 ancestor.tracked_descendants_count += 1
451 }
451 }
452 ancestor.descendants_with_entry_count += 1
452 ancestor.descendants_with_entry_count += 1
453 },
453 },
454 )?;
454 )?;
455 assert!(
455 assert!(
456 !node.data.has_entry(),
456 !node.data.has_entry(),
457 "duplicate dirstate entry in read"
457 "duplicate dirstate entry in read"
458 );
458 );
459 assert!(
459 assert!(
460 node.copy_source.is_none(),
460 node.copy_source.is_none(),
461 "duplicate dirstate entry in read"
461 "duplicate dirstate entry in read"
462 );
462 );
463 node.data = NodeData::Entry(*entry);
463 node.data = NodeData::Entry(*entry);
464 node.copy_source = copy_source.map(Cow::Borrowed);
464 node.copy_source = copy_source.map(Cow::Borrowed);
465 map.nodes_with_entry_count += 1;
465 map.nodes_with_entry_count += 1;
466 if copy_source.is_some() {
466 if copy_source.is_some() {
467 map.nodes_with_copy_source_count += 1
467 map.nodes_with_copy_source_count += 1
468 }
468 }
469 Ok(())
469 Ok(())
470 },
470 },
471 )?;
471 )?;
472 let parents = Some(parents.clone());
472 let parents = Some(parents.clone());
473
473
474 Ok((map, parents))
474 Ok((map, parents))
475 }
475 }
476
476
477 /// Assuming dirstate-v2 format, returns whether the next write should
477 /// Assuming dirstate-v2 format, returns whether the next write should
478 /// append to the existing data file that contains `self.on_disk` (true),
478 /// append to the existing data file that contains `self.on_disk` (true),
479 /// or create a new data file from scratch (false).
479 /// or create a new data file from scratch (false).
480 pub(super) fn write_should_append(&self) -> bool {
480 pub(super) fn write_should_append(&self) -> bool {
481 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
481 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
482 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
482 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
483 }
483 }
484
484
485 fn get_node<'tree>(
485 fn get_node<'tree>(
486 &'tree self,
486 &'tree self,
487 path: &HgPath,
487 path: &HgPath,
488 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
488 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
489 let mut children = self.root.as_ref();
489 let mut children = self.root.as_ref();
490 let mut components = path.components();
490 let mut components = path.components();
491 let mut component =
491 let mut component =
492 components.next().expect("expected at least one components");
492 components.next().expect("expected at least one components");
493 loop {
493 loop {
494 if let Some(child) = children.get(component, self.on_disk)? {
494 if let Some(child) = children.get(component, self.on_disk)? {
495 if let Some(next_component) = components.next() {
495 if let Some(next_component) = components.next() {
496 component = next_component;
496 component = next_component;
497 children = child.children(self.on_disk)?;
497 children = child.children(self.on_disk)?;
498 } else {
498 } else {
499 return Ok(Some(child));
499 return Ok(Some(child));
500 }
500 }
501 } else {
501 } else {
502 return Ok(None);
502 return Ok(None);
503 }
503 }
504 }
504 }
505 }
505 }
506
506
507 /// Returns a mutable reference to the node at `path` if it exists
507 /// Returns a mutable reference to the node at `path` if it exists
508 ///
508 ///
509 /// This takes `root` instead of `&mut self` so that callers can mutate
509 /// This takes `root` instead of `&mut self` so that callers can mutate
510 /// other fields while the returned borrow is still valid
510 /// other fields while the returned borrow is still valid
511 fn get_node_mut<'tree>(
511 fn get_node_mut<'tree>(
512 on_disk: &'on_disk [u8],
512 on_disk: &'on_disk [u8],
513 unreachable_bytes: &mut u32,
513 unreachable_bytes: &mut u32,
514 root: &'tree mut ChildNodes<'on_disk>,
514 root: &'tree mut ChildNodes<'on_disk>,
515 path: &HgPath,
515 path: &HgPath,
516 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
516 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
517 let mut children = root;
517 let mut children = root;
518 let mut components = path.components();
518 let mut components = path.components();
519 let mut component =
519 let mut component =
520 components.next().expect("expected at least one components");
520 components.next().expect("expected at least one components");
521 loop {
521 loop {
522 if let Some(child) = children
522 if let Some(child) = children
523 .make_mut(on_disk, unreachable_bytes)?
523 .make_mut(on_disk, unreachable_bytes)?
524 .get_mut(component)
524 .get_mut(component)
525 {
525 {
526 if let Some(next_component) = components.next() {
526 if let Some(next_component) = components.next() {
527 component = next_component;
527 component = next_component;
528 children = &mut child.children;
528 children = &mut child.children;
529 } else {
529 } else {
530 return Ok(Some(child));
530 return Ok(Some(child));
531 }
531 }
532 } else {
532 } else {
533 return Ok(None);
533 return Ok(None);
534 }
534 }
535 }
535 }
536 }
536 }
537
537
538 pub(super) fn get_or_insert<'tree, 'path>(
538 pub(super) fn get_or_insert<'tree, 'path>(
539 &'tree mut self,
539 &'tree mut self,
540 path: &HgPath,
540 path: &HgPath,
541 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
541 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
542 Self::get_or_insert_node(
542 Self::get_or_insert_node(
543 self.on_disk,
543 self.on_disk,
544 &mut self.unreachable_bytes,
544 &mut self.unreachable_bytes,
545 &mut self.root,
545 &mut self.root,
546 path,
546 path,
547 WithBasename::to_cow_owned,
547 WithBasename::to_cow_owned,
548 |_| {},
548 |_| {},
549 )
549 )
550 }
550 }
551
551
552 fn get_or_insert_node<'tree, 'path>(
552 fn get_or_insert_node<'tree, 'path>(
553 on_disk: &'on_disk [u8],
553 on_disk: &'on_disk [u8],
554 unreachable_bytes: &mut u32,
554 unreachable_bytes: &mut u32,
555 root: &'tree mut ChildNodes<'on_disk>,
555 root: &'tree mut ChildNodes<'on_disk>,
556 path: &'path HgPath,
556 path: &'path HgPath,
557 to_cow: impl Fn(
557 to_cow: impl Fn(
558 WithBasename<&'path HgPath>,
558 WithBasename<&'path HgPath>,
559 ) -> WithBasename<Cow<'on_disk, HgPath>>,
559 ) -> WithBasename<Cow<'on_disk, HgPath>>,
560 mut each_ancestor: impl FnMut(&mut Node),
560 mut each_ancestor: impl FnMut(&mut Node),
561 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
561 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
562 let mut child_nodes = root;
562 let mut child_nodes = root;
563 let mut inclusive_ancestor_paths =
563 let mut inclusive_ancestor_paths =
564 WithBasename::inclusive_ancestors_of(path);
564 WithBasename::inclusive_ancestors_of(path);
565 let mut ancestor_path = inclusive_ancestor_paths
565 let mut ancestor_path = inclusive_ancestor_paths
566 .next()
566 .next()
567 .expect("expected at least one inclusive ancestor");
567 .expect("expected at least one inclusive ancestor");
568 loop {
568 loop {
569 // TODO: can we avoid allocating an owned key in cases where the
569 // TODO: can we avoid allocating an owned key in cases where the
570 // map already contains that key, without introducing double
570 // map already contains that key, without introducing double
571 // lookup?
571 // lookup?
572 let child_node = child_nodes
572 let child_node = child_nodes
573 .make_mut(on_disk, unreachable_bytes)?
573 .make_mut(on_disk, unreachable_bytes)?
574 .entry(to_cow(ancestor_path))
574 .entry(to_cow(ancestor_path))
575 .or_default();
575 .or_default();
576 if let Some(next) = inclusive_ancestor_paths.next() {
576 if let Some(next) = inclusive_ancestor_paths.next() {
577 each_ancestor(child_node);
577 each_ancestor(child_node);
578 ancestor_path = next;
578 ancestor_path = next;
579 child_nodes = &mut child_node.children;
579 child_nodes = &mut child_node.children;
580 } else {
580 } else {
581 return Ok(child_node);
581 return Ok(child_node);
582 }
582 }
583 }
583 }
584 }
584 }
585
585
586 fn add_or_remove_file(
586 fn add_or_remove_file(
587 &mut self,
587 &mut self,
588 path: &HgPath,
588 path: &HgPath,
589 old_state: Option<EntryState>,
589 old_state: Option<EntryState>,
590 new_entry: DirstateEntry,
590 new_entry: DirstateEntry,
591 ) -> Result<(), DirstateV2ParseError> {
591 ) -> Result<(), DirstateV2ParseError> {
592 let had_entry = old_state.is_some();
592 let had_entry = old_state.is_some();
593 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
593 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
594 let tracked_count_increment =
594 let tracked_count_increment =
595 match (was_tracked, new_entry.state().is_tracked()) {
595 match (was_tracked, new_entry.state().is_tracked()) {
596 (false, true) => 1,
596 (false, true) => 1,
597 (true, false) => -1,
597 (true, false) => -1,
598 _ => 0,
598 _ => 0,
599 };
599 };
600
600
601 let node = Self::get_or_insert_node(
601 let node = Self::get_or_insert_node(
602 self.on_disk,
602 self.on_disk,
603 &mut self.unreachable_bytes,
603 &mut self.unreachable_bytes,
604 &mut self.root,
604 &mut self.root,
605 path,
605 path,
606 WithBasename::to_cow_owned,
606 WithBasename::to_cow_owned,
607 |ancestor| {
607 |ancestor| {
608 if !had_entry {
608 if !had_entry {
609 ancestor.descendants_with_entry_count += 1;
609 ancestor.descendants_with_entry_count += 1;
610 }
610 }
611
611
612 // We can’t use `+= increment` because the counter is unsigned,
612 // We can’t use `+= increment` because the counter is unsigned,
613 // and we want debug builds to detect accidental underflow
613 // and we want debug builds to detect accidental underflow
614 // through zero
614 // through zero
615 match tracked_count_increment {
615 match tracked_count_increment {
616 1 => ancestor.tracked_descendants_count += 1,
616 1 => ancestor.tracked_descendants_count += 1,
617 -1 => ancestor.tracked_descendants_count -= 1,
617 -1 => ancestor.tracked_descendants_count -= 1,
618 _ => {}
618 _ => {}
619 }
619 }
620 },
620 },
621 )?;
621 )?;
622 if !had_entry {
622 if !had_entry {
623 self.nodes_with_entry_count += 1
623 self.nodes_with_entry_count += 1
624 }
624 }
625 node.data = NodeData::Entry(new_entry);
625 node.data = NodeData::Entry(new_entry);
626 Ok(())
626 Ok(())
627 }
627 }
628
628
629 fn iter_nodes<'tree>(
629 fn iter_nodes<'tree>(
630 &'tree self,
630 &'tree self,
631 ) -> impl Iterator<
631 ) -> impl Iterator<
632 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
632 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
633 > + 'tree {
633 > + 'tree {
634 // Depth first tree traversal.
634 // Depth first tree traversal.
635 //
635 //
636 // If we could afford internal iteration and recursion,
636 // If we could afford internal iteration and recursion,
637 // this would look like:
637 // this would look like:
638 //
638 //
639 // ```
639 // ```
640 // fn traverse_children(
640 // fn traverse_children(
641 // children: &ChildNodes,
641 // children: &ChildNodes,
642 // each: &mut impl FnMut(&Node),
642 // each: &mut impl FnMut(&Node),
643 // ) {
643 // ) {
644 // for child in children.values() {
644 // for child in children.values() {
645 // traverse_children(&child.children, each);
645 // traverse_children(&child.children, each);
646 // each(child);
646 // each(child);
647 // }
647 // }
648 // }
648 // }
649 // ```
649 // ```
650 //
650 //
651 // However we want an external iterator and therefore can’t use the
651 // However we want an external iterator and therefore can’t use the
652 // call stack. Use an explicit stack instead:
652 // call stack. Use an explicit stack instead:
653 let mut stack = Vec::new();
653 let mut stack = Vec::new();
654 let mut iter = self.root.as_ref().iter();
654 let mut iter = self.root.as_ref().iter();
655 std::iter::from_fn(move || {
655 std::iter::from_fn(move || {
656 while let Some(child_node) = iter.next() {
656 while let Some(child_node) = iter.next() {
657 let children = match child_node.children(self.on_disk) {
657 let children = match child_node.children(self.on_disk) {
658 Ok(children) => children,
658 Ok(children) => children,
659 Err(error) => return Some(Err(error)),
659 Err(error) => return Some(Err(error)),
660 };
660 };
661 // Pseudo-recursion
661 // Pseudo-recursion
662 let new_iter = children.iter();
662 let new_iter = children.iter();
663 let old_iter = std::mem::replace(&mut iter, new_iter);
663 let old_iter = std::mem::replace(&mut iter, new_iter);
664 stack.push((child_node, old_iter));
664 stack.push((child_node, old_iter));
665 }
665 }
666 // Found the end of a `children.iter()` iterator.
666 // Found the end of a `children.iter()` iterator.
667 if let Some((child_node, next_iter)) = stack.pop() {
667 if let Some((child_node, next_iter)) = stack.pop() {
668 // "Return" from pseudo-recursion by restoring state from the
668 // "Return" from pseudo-recursion by restoring state from the
669 // explicit stack
669 // explicit stack
670 iter = next_iter;
670 iter = next_iter;
671
671
672 Some(Ok(child_node))
672 Some(Ok(child_node))
673 } else {
673 } else {
674 // Reached the bottom of the stack, we’re done
674 // Reached the bottom of the stack, we’re done
675 None
675 None
676 }
676 }
677 })
677 })
678 }
678 }
679
679
680 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
680 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
681 if let Cow::Borrowed(path) = path {
681 if let Cow::Borrowed(path) = path {
682 *unreachable_bytes += path.len() as u32
682 *unreachable_bytes += path.len() as u32
683 }
683 }
684 }
684 }
685 }
685 }
686
686
687 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
687 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
688 ///
688 ///
689 /// The callback is only called for incoming `Ok` values. Errors are passed
689 /// The callback is only called for incoming `Ok` values. Errors are passed
690 /// through as-is. In order to let it use the `?` operator the callback is
690 /// through as-is. In order to let it use the `?` operator the callback is
691 /// expected to return a `Result` of `Option`, instead of an `Option` of
691 /// expected to return a `Result` of `Option`, instead of an `Option` of
692 /// `Result`.
692 /// `Result`.
693 fn filter_map_results<'a, I, F, A, B, E>(
693 fn filter_map_results<'a, I, F, A, B, E>(
694 iter: I,
694 iter: I,
695 f: F,
695 f: F,
696 ) -> impl Iterator<Item = Result<B, E>> + 'a
696 ) -> impl Iterator<Item = Result<B, E>> + 'a
697 where
697 where
698 I: Iterator<Item = Result<A, E>> + 'a,
698 I: Iterator<Item = Result<A, E>> + 'a,
699 F: Fn(A) -> Result<Option<B>, E> + 'a,
699 F: Fn(A) -> Result<Option<B>, E> + 'a,
700 {
700 {
701 iter.filter_map(move |result| match result {
701 iter.filter_map(move |result| match result {
702 Ok(node) => f(node).transpose(),
702 Ok(node) => f(node).transpose(),
703 Err(e) => Some(Err(e)),
703 Err(e) => Some(Err(e)),
704 })
704 })
705 }
705 }
706
706
707 impl OwningDirstateMap {
707 impl OwningDirstateMap {
708 pub fn clear(&mut self) {
708 pub fn clear(&mut self) {
709 let map = self.get_map_mut();
709 let map = self.get_map_mut();
710 map.root = Default::default();
710 map.root = Default::default();
711 map.nodes_with_entry_count = 0;
711 map.nodes_with_entry_count = 0;
712 map.nodes_with_copy_source_count = 0;
712 map.nodes_with_copy_source_count = 0;
713 }
713 }
714
714
715 pub fn set_entry(
715 pub fn set_entry(
716 &mut self,
716 &mut self,
717 filename: &HgPath,
717 filename: &HgPath,
718 entry: DirstateEntry,
718 entry: DirstateEntry,
719 ) -> Result<(), DirstateV2ParseError> {
719 ) -> Result<(), DirstateV2ParseError> {
720 let map = self.get_map_mut();
720 let map = self.get_map_mut();
721 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
721 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
722 Ok(())
722 Ok(())
723 }
723 }
724
724
725 pub fn add_file(
725 pub fn add_file(
726 &mut self,
726 &mut self,
727 filename: &HgPath,
727 filename: &HgPath,
728 entry: DirstateEntry,
728 entry: DirstateEntry,
729 ) -> Result<(), DirstateError> {
729 ) -> Result<(), DirstateError> {
730 let old_state = self.get(filename)?.map(|e| e.state());
730 let old_state = self.get(filename)?.map(|e| e.state());
731 let map = self.get_map_mut();
731 let map = self.get_map_mut();
732 Ok(map.add_or_remove_file(filename, old_state, entry)?)
732 Ok(map.add_or_remove_file(filename, old_state, entry)?)
733 }
733 }
734
734
735 pub fn remove_file(
735 pub fn remove_file(
736 &mut self,
736 &mut self,
737 filename: &HgPath,
737 filename: &HgPath,
738 in_merge: bool,
738 in_merge: bool,
739 ) -> Result<(), DirstateError> {
739 ) -> Result<(), DirstateError> {
740 let old_entry_opt = self.get(filename)?;
740 let old_entry_opt = self.get(filename)?;
741 let old_state = old_entry_opt.map(|e| e.state());
741 let old_state = old_entry_opt.map(|e| e.state());
742 let mut size = 0;
742 let mut size = 0;
743 if in_merge {
743 if in_merge {
744 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
744 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
745 // during a merge. So I (marmoute) am not sure we need the
745 // during a merge. So I (marmoute) am not sure we need the
746 // conditionnal at all. Adding double checking this with assert
746 // conditionnal at all. Adding double checking this with assert
747 // would be nice.
747 // would be nice.
748 if let Some(old_entry) = old_entry_opt {
748 if let Some(old_entry) = old_entry_opt {
749 // backup the previous state
749 // backup the previous state
750 if old_entry.state() == EntryState::Merged {
750 if old_entry.state() == EntryState::Merged {
751 size = SIZE_NON_NORMAL;
751 size = SIZE_NON_NORMAL;
752 } else if old_entry.state() == EntryState::Normal
752 } else if old_entry.state() == EntryState::Normal
753 && old_entry.size() == SIZE_FROM_OTHER_PARENT
753 && old_entry.size() == SIZE_FROM_OTHER_PARENT
754 {
754 {
755 // other parent
755 // other parent
756 size = SIZE_FROM_OTHER_PARENT;
756 size = SIZE_FROM_OTHER_PARENT;
757 }
757 }
758 }
758 }
759 }
759 }
760 if size == 0 {
760 if size == 0 {
761 self.copy_map_remove(filename)?;
761 self.copy_map_remove(filename)?;
762 }
762 }
763 let map = self.get_map_mut();
763 let map = self.get_map_mut();
764 let entry = DirstateEntry::new_removed(size);
764 let entry = DirstateEntry::new_removed(size);
765 Ok(map.add_or_remove_file(filename, old_state, entry)?)
765 Ok(map.add_or_remove_file(filename, old_state, entry)?)
766 }
766 }
767
767
768 pub fn drop_entry_and_copy_source(
768 pub fn drop_entry_and_copy_source(
769 &mut self,
769 &mut self,
770 filename: &HgPath,
770 filename: &HgPath,
771 ) -> Result<(), DirstateError> {
771 ) -> Result<(), DirstateError> {
772 let was_tracked = self
772 let was_tracked = self
773 .get(filename)?
773 .get(filename)?
774 .map_or(false, |e| e.state().is_tracked());
774 .map_or(false, |e| e.state().is_tracked());
775 let map = self.get_map_mut();
775 let map = self.get_map_mut();
776 struct Dropped {
776 struct Dropped {
777 was_tracked: bool,
777 was_tracked: bool,
778 had_entry: bool,
778 had_entry: bool,
779 had_copy_source: bool,
779 had_copy_source: bool,
780 }
780 }
781
781
782 /// If this returns `Ok(Some((dropped, removed)))`, then
782 /// If this returns `Ok(Some((dropped, removed)))`, then
783 ///
783 ///
784 /// * `dropped` is about the leaf node that was at `filename`
784 /// * `dropped` is about the leaf node that was at `filename`
785 /// * `removed` is whether this particular level of recursion just
785 /// * `removed` is whether this particular level of recursion just
786 /// removed a node in `nodes`.
786 /// removed a node in `nodes`.
787 fn recur<'on_disk>(
787 fn recur<'on_disk>(
788 on_disk: &'on_disk [u8],
788 on_disk: &'on_disk [u8],
789 unreachable_bytes: &mut u32,
789 unreachable_bytes: &mut u32,
790 nodes: &mut ChildNodes<'on_disk>,
790 nodes: &mut ChildNodes<'on_disk>,
791 path: &HgPath,
791 path: &HgPath,
792 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
792 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
793 let (first_path_component, rest_of_path) =
793 let (first_path_component, rest_of_path) =
794 path.split_first_component();
794 path.split_first_component();
795 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
795 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
796 let node = if let Some(node) = nodes.get_mut(first_path_component)
796 let node = if let Some(node) = nodes.get_mut(first_path_component)
797 {
797 {
798 node
798 node
799 } else {
799 } else {
800 return Ok(None);
800 return Ok(None);
801 };
801 };
802 let dropped;
802 let dropped;
803 if let Some(rest) = rest_of_path {
803 if let Some(rest) = rest_of_path {
804 if let Some((d, removed)) = recur(
804 if let Some((d, removed)) = recur(
805 on_disk,
805 on_disk,
806 unreachable_bytes,
806 unreachable_bytes,
807 &mut node.children,
807 &mut node.children,
808 rest,
808 rest,
809 )? {
809 )? {
810 dropped = d;
810 dropped = d;
811 if dropped.had_entry {
811 if dropped.had_entry {
812 node.descendants_with_entry_count -= 1;
812 node.descendants_with_entry_count -= 1;
813 }
813 }
814 if dropped.was_tracked {
814 if dropped.was_tracked {
815 node.tracked_descendants_count -= 1;
815 node.tracked_descendants_count -= 1;
816 }
816 }
817
817
818 // Directory caches must be invalidated when removing a
818 // Directory caches must be invalidated when removing a
819 // child node
819 // child node
820 if removed {
820 if removed {
821 if let NodeData::CachedDirectory { .. } = &node.data {
821 if let NodeData::CachedDirectory { .. } = &node.data {
822 node.data = NodeData::None
822 node.data = NodeData::None
823 }
823 }
824 }
824 }
825 } else {
825 } else {
826 return Ok(None);
826 return Ok(None);
827 }
827 }
828 } else {
828 } else {
829 let had_entry = node.data.has_entry();
829 let had_entry = node.data.has_entry();
830 if had_entry {
830 if had_entry {
831 node.data = NodeData::None
831 node.data = NodeData::None
832 }
832 }
833 if let Some(source) = &node.copy_source {
833 if let Some(source) = &node.copy_source {
834 DirstateMap::count_dropped_path(unreachable_bytes, source);
834 DirstateMap::count_dropped_path(unreachable_bytes, source);
835 node.copy_source = None
835 node.copy_source = None
836 }
836 }
837 dropped = Dropped {
837 dropped = Dropped {
838 was_tracked: node
838 was_tracked: node
839 .data
839 .data
840 .as_entry()
840 .as_entry()
841 .map_or(false, |entry| entry.state().is_tracked()),
841 .map_or(false, |entry| entry.state().is_tracked()),
842 had_entry,
842 had_entry,
843 had_copy_source: node.copy_source.take().is_some(),
843 had_copy_source: node.copy_source.take().is_some(),
844 };
844 };
845 }
845 }
846 // After recursion, for both leaf (rest_of_path is None) nodes and
846 // After recursion, for both leaf (rest_of_path is None) nodes and
847 // parent nodes, remove a node if it just became empty.
847 // parent nodes, remove a node if it just became empty.
848 let remove = !node.data.has_entry()
848 let remove = !node.data.has_entry()
849 && node.copy_source.is_none()
849 && node.copy_source.is_none()
850 && node.children.is_empty();
850 && node.children.is_empty();
851 if remove {
851 if remove {
852 let (key, _) =
852 let (key, _) =
853 nodes.remove_entry(first_path_component).unwrap();
853 nodes.remove_entry(first_path_component).unwrap();
854 DirstateMap::count_dropped_path(
854 DirstateMap::count_dropped_path(
855 unreachable_bytes,
855 unreachable_bytes,
856 key.full_path(),
856 key.full_path(),
857 )
857 )
858 }
858 }
859 Ok(Some((dropped, remove)))
859 Ok(Some((dropped, remove)))
860 }
860 }
861
861
862 if let Some((dropped, _removed)) = recur(
862 if let Some((dropped, _removed)) = recur(
863 map.on_disk,
863 map.on_disk,
864 &mut map.unreachable_bytes,
864 &mut map.unreachable_bytes,
865 &mut map.root,
865 &mut map.root,
866 filename,
866 filename,
867 )? {
867 )? {
868 if dropped.had_entry {
868 if dropped.had_entry {
869 map.nodes_with_entry_count -= 1
869 map.nodes_with_entry_count -= 1
870 }
870 }
871 if dropped.had_copy_source {
871 if dropped.had_copy_source {
872 map.nodes_with_copy_source_count -= 1
872 map.nodes_with_copy_source_count -= 1
873 }
873 }
874 } else {
874 } else {
875 debug_assert!(!was_tracked);
875 debug_assert!(!was_tracked);
876 }
876 }
877 Ok(())
877 Ok(())
878 }
878 }
879
879
880 pub fn has_tracked_dir(
880 pub fn has_tracked_dir(
881 &mut self,
881 &mut self,
882 directory: &HgPath,
882 directory: &HgPath,
883 ) -> Result<bool, DirstateError> {
883 ) -> Result<bool, DirstateError> {
884 let map = self.get_map_mut();
884 let map = self.get_map_mut();
885 if let Some(node) = map.get_node(directory)? {
885 if let Some(node) = map.get_node(directory)? {
886 // A node without a `DirstateEntry` was created to hold child
886 // A node without a `DirstateEntry` was created to hold child
887 // nodes, and is therefore a directory.
887 // nodes, and is therefore a directory.
888 let state = node.state()?;
888 let state = node.state()?;
889 Ok(state.is_none() && node.tracked_descendants_count() > 0)
889 Ok(state.is_none() && node.tracked_descendants_count() > 0)
890 } else {
890 } else {
891 Ok(false)
891 Ok(false)
892 }
892 }
893 }
893 }
894
894
895 pub fn has_dir(
895 pub fn has_dir(
896 &mut self,
896 &mut self,
897 directory: &HgPath,
897 directory: &HgPath,
898 ) -> Result<bool, DirstateError> {
898 ) -> Result<bool, DirstateError> {
899 let map = self.get_map_mut();
899 let map = self.get_map_mut();
900 if let Some(node) = map.get_node(directory)? {
900 if let Some(node) = map.get_node(directory)? {
901 // A node without a `DirstateEntry` was created to hold child
901 // A node without a `DirstateEntry` was created to hold child
902 // nodes, and is therefore a directory.
902 // nodes, and is therefore a directory.
903 let state = node.state()?;
903 let state = node.state()?;
904 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
904 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
905 } else {
905 } else {
906 Ok(false)
906 Ok(false)
907 }
907 }
908 }
908 }
909
909
910 #[timed]
910 #[timed]
911 pub fn pack_v1(
911 pub fn pack_v1(
912 &self,
912 &self,
913 parents: DirstateParents,
913 parents: DirstateParents,
914 ) -> Result<Vec<u8>, DirstateError> {
914 ) -> Result<Vec<u8>, DirstateError> {
915 let map = self.get_map();
915 let map = self.get_map();
916 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
916 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
917 // reallocations
917 // reallocations
918 let mut size = parents.as_bytes().len();
918 let mut size = parents.as_bytes().len();
919 for node in map.iter_nodes() {
919 for node in map.iter_nodes() {
920 let node = node?;
920 let node = node?;
921 if node.entry()?.is_some() {
921 if node.entry()?.is_some() {
922 size += packed_entry_size(
922 size += packed_entry_size(
923 node.full_path(map.on_disk)?,
923 node.full_path(map.on_disk)?,
924 node.copy_source(map.on_disk)?,
924 node.copy_source(map.on_disk)?,
925 );
925 );
926 }
926 }
927 }
927 }
928
928
929 let mut packed = Vec::with_capacity(size);
929 let mut packed = Vec::with_capacity(size);
930 packed.extend(parents.as_bytes());
930 packed.extend(parents.as_bytes());
931
931
932 for node in map.iter_nodes() {
932 for node in map.iter_nodes() {
933 let node = node?;
933 let node = node?;
934 if let Some(entry) = node.entry()? {
934 if let Some(entry) = node.entry()? {
935 pack_entry(
935 pack_entry(
936 node.full_path(map.on_disk)?,
936 node.full_path(map.on_disk)?,
937 &entry,
937 &entry,
938 node.copy_source(map.on_disk)?,
938 node.copy_source(map.on_disk)?,
939 &mut packed,
939 &mut packed,
940 );
940 );
941 }
941 }
942 }
942 }
943 Ok(packed)
943 Ok(packed)
944 }
944 }
945
945
946 /// Returns new data and metadata together with whether that data should be
946 /// Returns new data and metadata together with whether that data should be
947 /// appended to the existing data file whose content is at
947 /// appended to the existing data file whose content is at
948 /// `map.on_disk` (true), instead of written to a new data file
948 /// `map.on_disk` (true), instead of written to a new data file
949 /// (false).
949 /// (false).
950 #[timed]
950 #[timed]
951 pub fn pack_v2(
951 pub fn pack_v2(
952 &self,
952 &self,
953 can_append: bool,
953 can_append: bool,
954 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
954 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
955 let map = self.get_map();
955 let map = self.get_map();
956 on_disk::write(map, can_append)
956 on_disk::write(map, can_append)
957 }
957 }
958
958
959 pub fn status<'a>(
959 pub fn status<'a>(
960 &'a mut self,
960 &'a mut self,
961 matcher: &'a (dyn Matcher + Sync),
961 matcher: &'a (dyn Matcher + Sync),
962 root_dir: PathBuf,
962 root_dir: PathBuf,
963 ignore_files: Vec<PathBuf>,
963 ignore_files: Vec<PathBuf>,
964 options: StatusOptions,
964 options: StatusOptions,
965 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
965 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
966 {
966 {
967 let map = self.get_map_mut();
967 let map = self.get_map_mut();
968 super::status::status(map, matcher, root_dir, ignore_files, options)
968 super::status::status(map, matcher, root_dir, ignore_files, options)
969 }
969 }
970
970
971 pub fn copy_map_len(&self) -> usize {
971 pub fn copy_map_len(&self) -> usize {
972 let map = self.get_map();
972 let map = self.get_map();
973 map.nodes_with_copy_source_count as usize
973 map.nodes_with_copy_source_count as usize
974 }
974 }
975
975
976 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
976 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
977 let map = self.get_map();
977 let map = self.get_map();
978 Box::new(filter_map_results(map.iter_nodes(), move |node| {
978 Box::new(filter_map_results(map.iter_nodes(), move |node| {
979 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
979 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
980 Some((node.full_path(map.on_disk)?, source))
980 Some((node.full_path(map.on_disk)?, source))
981 } else {
981 } else {
982 None
982 None
983 })
983 })
984 }))
984 }))
985 }
985 }
986
986
987 pub fn copy_map_contains_key(
987 pub fn copy_map_contains_key(
988 &self,
988 &self,
989 key: &HgPath,
989 key: &HgPath,
990 ) -> Result<bool, DirstateV2ParseError> {
990 ) -> Result<bool, DirstateV2ParseError> {
991 let map = self.get_map();
991 let map = self.get_map();
992 Ok(if let Some(node) = map.get_node(key)? {
992 Ok(if let Some(node) = map.get_node(key)? {
993 node.has_copy_source()
993 node.has_copy_source()
994 } else {
994 } else {
995 false
995 false
996 })
996 })
997 }
997 }
998
998
999 pub fn copy_map_get(
999 pub fn copy_map_get(
1000 &self,
1000 &self,
1001 key: &HgPath,
1001 key: &HgPath,
1002 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1002 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1003 let map = self.get_map();
1003 let map = self.get_map();
1004 if let Some(node) = map.get_node(key)? {
1004 if let Some(node) = map.get_node(key)? {
1005 if let Some(source) = node.copy_source(map.on_disk)? {
1005 if let Some(source) = node.copy_source(map.on_disk)? {
1006 return Ok(Some(source));
1006 return Ok(Some(source));
1007 }
1007 }
1008 }
1008 }
1009 Ok(None)
1009 Ok(None)
1010 }
1010 }
1011
1011
1012 pub fn copy_map_remove(
1012 pub fn copy_map_remove(
1013 &mut self,
1013 &mut self,
1014 key: &HgPath,
1014 key: &HgPath,
1015 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1015 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1016 let map = self.get_map_mut();
1016 let map = self.get_map_mut();
1017 let count = &mut map.nodes_with_copy_source_count;
1017 let count = &mut map.nodes_with_copy_source_count;
1018 let unreachable_bytes = &mut map.unreachable_bytes;
1018 let unreachable_bytes = &mut map.unreachable_bytes;
1019 Ok(DirstateMap::get_node_mut(
1019 Ok(DirstateMap::get_node_mut(
1020 map.on_disk,
1020 map.on_disk,
1021 unreachable_bytes,
1021 unreachable_bytes,
1022 &mut map.root,
1022 &mut map.root,
1023 key,
1023 key,
1024 )?
1024 )?
1025 .and_then(|node| {
1025 .and_then(|node| {
1026 if let Some(source) = &node.copy_source {
1026 if let Some(source) = &node.copy_source {
1027 *count -= 1;
1027 *count -= 1;
1028 DirstateMap::count_dropped_path(unreachable_bytes, source);
1028 DirstateMap::count_dropped_path(unreachable_bytes, source);
1029 }
1029 }
1030 node.copy_source.take().map(Cow::into_owned)
1030 node.copy_source.take().map(Cow::into_owned)
1031 }))
1031 }))
1032 }
1032 }
1033
1033
1034 pub fn copy_map_insert(
1034 pub fn copy_map_insert(
1035 &mut self,
1035 &mut self,
1036 key: HgPathBuf,
1036 key: HgPathBuf,
1037 value: HgPathBuf,
1037 value: HgPathBuf,
1038 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1038 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1039 let map = self.get_map_mut();
1039 let map = self.get_map_mut();
1040 let node = DirstateMap::get_or_insert_node(
1040 let node = DirstateMap::get_or_insert_node(
1041 map.on_disk,
1041 map.on_disk,
1042 &mut map.unreachable_bytes,
1042 &mut map.unreachable_bytes,
1043 &mut map.root,
1043 &mut map.root,
1044 &key,
1044 &key,
1045 WithBasename::to_cow_owned,
1045 WithBasename::to_cow_owned,
1046 |_ancestor| {},
1046 |_ancestor| {},
1047 )?;
1047 )?;
1048 if node.copy_source.is_none() {
1048 if node.copy_source.is_none() {
1049 map.nodes_with_copy_source_count += 1
1049 map.nodes_with_copy_source_count += 1
1050 }
1050 }
1051 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1051 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1052 }
1052 }
1053
1053
1054 pub fn len(&self) -> usize {
1054 pub fn len(&self) -> usize {
1055 let map = self.get_map();
1055 let map = self.get_map();
1056 map.nodes_with_entry_count as usize
1056 map.nodes_with_entry_count as usize
1057 }
1057 }
1058
1058
1059 pub fn contains_key(
1059 pub fn contains_key(
1060 &self,
1060 &self,
1061 key: &HgPath,
1061 key: &HgPath,
1062 ) -> Result<bool, DirstateV2ParseError> {
1062 ) -> Result<bool, DirstateV2ParseError> {
1063 Ok(self.get(key)?.is_some())
1063 Ok(self.get(key)?.is_some())
1064 }
1064 }
1065
1065
1066 pub fn get(
1066 pub fn get(
1067 &self,
1067 &self,
1068 key: &HgPath,
1068 key: &HgPath,
1069 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1069 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1070 let map = self.get_map();
1070 let map = self.get_map();
1071 Ok(if let Some(node) = map.get_node(key)? {
1071 Ok(if let Some(node) = map.get_node(key)? {
1072 node.entry()?
1072 node.entry()?
1073 } else {
1073 } else {
1074 None
1074 None
1075 })
1075 })
1076 }
1076 }
1077
1077
1078 pub fn iter(&self) -> StateMapIter<'_> {
1078 pub fn iter(&self) -> StateMapIter<'_> {
1079 let map = self.get_map();
1079 let map = self.get_map();
1080 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1080 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1081 Ok(if let Some(entry) = node.entry()? {
1081 Ok(if let Some(entry) = node.entry()? {
1082 Some((node.full_path(map.on_disk)?, entry))
1082 Some((node.full_path(map.on_disk)?, entry))
1083 } else {
1083 } else {
1084 None
1084 None
1085 })
1085 })
1086 }))
1086 }))
1087 }
1087 }
1088
1088
1089 pub fn iter_tracked_dirs(
1089 pub fn iter_tracked_dirs(
1090 &mut self,
1090 &mut self,
1091 ) -> Result<
1091 ) -> Result<
1092 Box<
1092 Box<
1093 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1093 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1094 + Send
1094 + Send
1095 + '_,
1095 + '_,
1096 >,
1096 >,
1097 DirstateError,
1097 DirstateError,
1098 > {
1098 > {
1099 let map = self.get_map_mut();
1099 let map = self.get_map_mut();
1100 let on_disk = map.on_disk;
1100 let on_disk = map.on_disk;
1101 Ok(Box::new(filter_map_results(
1101 Ok(Box::new(filter_map_results(
1102 map.iter_nodes(),
1102 map.iter_nodes(),
1103 move |node| {
1103 move |node| {
1104 Ok(if node.tracked_descendants_count() > 0 {
1104 Ok(if node.tracked_descendants_count() > 0 {
1105 Some(node.full_path(on_disk)?)
1105 Some(node.full_path(on_disk)?)
1106 } else {
1106 } else {
1107 None
1107 None
1108 })
1108 })
1109 },
1109 },
1110 )))
1110 )))
1111 }
1111 }
1112
1112
1113 pub fn debug_iter(
1113 pub fn debug_iter(
1114 &self,
1114 &self,
1115 all: bool,
1115 all: bool,
1116 ) -> Box<
1116 ) -> Box<
1117 dyn Iterator<
1117 dyn Iterator<
1118 Item = Result<
1118 Item = Result<
1119 (&HgPath, (u8, i32, i32, i32)),
1119 (&HgPath, (u8, i32, i32, i32)),
1120 DirstateV2ParseError,
1120 DirstateV2ParseError,
1121 >,
1121 >,
1122 > + Send
1122 > + Send
1123 + '_,
1123 + '_,
1124 > {
1124 > {
1125 let map = self.get_map();
1125 let map = self.get_map();
1126 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1126 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1127 let debug_tuple = if let Some(entry) = node.entry()? {
1127 let debug_tuple = if let Some(entry) = node.entry()? {
1128 entry.debug_tuple()
1128 entry.debug_tuple()
1129 } else if !all {
1129 } else if !all {
1130 return Ok(None);
1130 return Ok(None);
1131 } else if let Some(mtime) = node.cached_directory_mtime()? {
1131 } else if let Some(mtime) = node.cached_directory_mtime()? {
1132 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1132 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1133 } else {
1133 } else {
1134 (b' ', 0, -1, -1)
1134 (b' ', 0, -1, -1)
1135 };
1135 };
1136 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1136 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1137 }))
1137 }))
1138 }
1138 }
1139 }
1139 }
@@ -1,791 +1,837 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::TruncatedTimestamp;
5 use crate::dirstate::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 std::borrow::Cow;
18 use std::borrow::Cow;
18 use std::convert::{TryFrom, TryInto};
19 use std::convert::{TryFrom, TryInto};
20 use std::fmt::Write;
19
21
20 /// 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.
21 /// 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
22 /// `.hg/requires` already governs which format should be used.
24 /// `.hg/requires` already governs which format should be used.
23 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
25 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
24
26
25 /// Keep space for 256-bit hashes
27 /// Keep space for 256-bit hashes
26 const STORED_NODE_ID_BYTES: usize = 32;
28 const STORED_NODE_ID_BYTES: usize = 32;
27
29
28 /// … 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
29 const USED_NODE_ID_BYTES: usize = 20;
31 const USED_NODE_ID_BYTES: usize = 20;
30
32
31 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
33 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
32 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
34 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
33
35
34 /// 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`
35 const TREE_METADATA_SIZE: usize = 44;
37 const TREE_METADATA_SIZE: usize = 44;
36 const NODE_SIZE: usize = 44;
38 const NODE_SIZE: usize = 44;
37
39
38 /// Make sure that size-affecting changes are made knowingly
40 /// Make sure that size-affecting changes are made knowingly
39 #[allow(unused)]
41 #[allow(unused)]
40 fn static_assert_size_of() {
42 fn static_assert_size_of() {
41 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
43 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
42 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
44 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
43 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
45 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
44 }
46 }
45
47
46 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
48 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
47 #[derive(BytesCast)]
49 #[derive(BytesCast)]
48 #[repr(C)]
50 #[repr(C)]
49 struct DocketHeader {
51 struct DocketHeader {
50 marker: [u8; V2_FORMAT_MARKER.len()],
52 marker: [u8; V2_FORMAT_MARKER.len()],
51 parent_1: [u8; STORED_NODE_ID_BYTES],
53 parent_1: [u8; STORED_NODE_ID_BYTES],
52 parent_2: [u8; STORED_NODE_ID_BYTES],
54 parent_2: [u8; STORED_NODE_ID_BYTES],
53
55
54 metadata: TreeMetadata,
56 metadata: TreeMetadata,
55
57
56 /// Counted in bytes
58 /// Counted in bytes
57 data_size: Size,
59 data_size: Size,
58
60
59 uuid_size: u8,
61 uuid_size: u8,
60 }
62 }
61
63
62 pub struct Docket<'on_disk> {
64 pub struct Docket<'on_disk> {
63 header: &'on_disk DocketHeader,
65 header: &'on_disk DocketHeader,
64 pub uuid: &'on_disk [u8],
66 pub uuid: &'on_disk [u8],
65 }
67 }
66
68
67 /// Fields are documented in the *Tree metadata in the docket file*
69 /// Fields are documented in the *Tree metadata in the docket file*
68 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
70 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
69 #[derive(BytesCast)]
71 #[derive(BytesCast)]
70 #[repr(C)]
72 #[repr(C)]
71 struct TreeMetadata {
73 pub struct TreeMetadata {
72 root_nodes: ChildNodes,
74 root_nodes: ChildNodes,
73 nodes_with_entry_count: Size,
75 nodes_with_entry_count: Size,
74 nodes_with_copy_source_count: Size,
76 nodes_with_copy_source_count: Size,
75 unreachable_bytes: Size,
77 unreachable_bytes: Size,
76 unused: [u8; 4],
78 unused: [u8; 4],
77
79
78 /// See *Optional hash of ignore patterns* section of
80 /// See *Optional hash of ignore patterns* section of
79 /// `mercurial/helptext/internals/dirstate-v2.txt`
81 /// `mercurial/helptext/internals/dirstate-v2.txt`
80 ignore_patterns_hash: IgnorePatternsHash,
82 ignore_patterns_hash: IgnorePatternsHash,
81 }
83 }
82
84
83 /// Fields are documented in the *The data file format*
85 /// Fields are documented in the *The data file format*
84 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
86 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
85 #[derive(BytesCast)]
87 #[derive(BytesCast)]
86 #[repr(C)]
88 #[repr(C)]
87 pub(super) struct Node {
89 pub(super) struct Node {
88 full_path: PathSlice,
90 full_path: PathSlice,
89
91
90 /// In bytes from `self.full_path.start`
92 /// In bytes from `self.full_path.start`
91 base_name_start: PathSize,
93 base_name_start: PathSize,
92
94
93 copy_source: OptPathSlice,
95 copy_source: OptPathSlice,
94 children: ChildNodes,
96 children: ChildNodes,
95 pub(super) descendants_with_entry_count: Size,
97 pub(super) descendants_with_entry_count: Size,
96 pub(super) tracked_descendants_count: Size,
98 pub(super) tracked_descendants_count: Size,
97 flags: U16Be,
99 flags: U16Be,
98 size: U32Be,
100 size: U32Be,
99 mtime: PackedTruncatedTimestamp,
101 mtime: PackedTruncatedTimestamp,
100 }
102 }
101
103
102 bitflags! {
104 bitflags! {
103 #[repr(C)]
105 #[repr(C)]
104 struct Flags: u16 {
106 struct Flags: u16 {
105 const WDIR_TRACKED = 1 << 0;
107 const WDIR_TRACKED = 1 << 0;
106 const P1_TRACKED = 1 << 1;
108 const P1_TRACKED = 1 << 1;
107 const P2_INFO = 1 << 2;
109 const P2_INFO = 1 << 2;
108 const MODE_EXEC_PERM = 1 << 3;
110 const MODE_EXEC_PERM = 1 << 3;
109 const MODE_IS_SYMLINK = 1 << 4;
111 const MODE_IS_SYMLINK = 1 << 4;
110 const HAS_FALLBACK_EXEC = 1 << 5;
112 const HAS_FALLBACK_EXEC = 1 << 5;
111 const FALLBACK_EXEC = 1 << 6;
113 const FALLBACK_EXEC = 1 << 6;
112 const HAS_FALLBACK_SYMLINK = 1 << 7;
114 const HAS_FALLBACK_SYMLINK = 1 << 7;
113 const FALLBACK_SYMLINK = 1 << 8;
115 const FALLBACK_SYMLINK = 1 << 8;
114 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
116 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
115 const HAS_MODE_AND_SIZE = 1 <<10;
117 const HAS_MODE_AND_SIZE = 1 <<10;
116 const HAS_MTIME = 1 <<11;
118 const HAS_MTIME = 1 <<11;
117 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
119 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
118 const DIRECTORY = 1 <<13;
120 const DIRECTORY = 1 <<13;
119 const ALL_UNKNOWN_RECORDED = 1 <<14;
121 const ALL_UNKNOWN_RECORDED = 1 <<14;
120 const ALL_IGNORED_RECORDED = 1 <<15;
122 const ALL_IGNORED_RECORDED = 1 <<15;
121 }
123 }
122 }
124 }
123
125
124 /// Duration since the Unix epoch
126 /// Duration since the Unix epoch
125 #[derive(BytesCast, Copy, Clone)]
127 #[derive(BytesCast, Copy, Clone)]
126 #[repr(C)]
128 #[repr(C)]
127 struct PackedTruncatedTimestamp {
129 struct PackedTruncatedTimestamp {
128 truncated_seconds: U32Be,
130 truncated_seconds: U32Be,
129 nanoseconds: U32Be,
131 nanoseconds: U32Be,
130 }
132 }
131
133
132 /// Counted in bytes from the start of the file
134 /// Counted in bytes from the start of the file
133 ///
135 ///
134 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
136 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
135 type Offset = U32Be;
137 type Offset = U32Be;
136
138
137 /// Counted in number of items
139 /// Counted in number of items
138 ///
140 ///
139 /// 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.
140 type Size = U32Be;
142 type Size = U32Be;
141
143
142 /// Counted in bytes
144 /// Counted in bytes
143 ///
145 ///
144 /// 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.
145 type PathSize = U16Be;
147 type PathSize = U16Be;
146
148
147 /// A contiguous sequence of `len` times `Node`, representing the child nodes
149 /// A contiguous sequence of `len` times `Node`, representing the child nodes
148 /// of either some other node or of the repository root.
150 /// of either some other node or of the repository root.
149 ///
151 ///
150 /// Always sorted by ascending `full_path`, to allow binary search.
152 /// Always sorted by ascending `full_path`, to allow binary search.
151 /// 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,
152 /// 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.
153 #[derive(BytesCast, Copy, Clone)]
155 #[derive(BytesCast, Copy, Clone)]
154 #[repr(C)]
156 #[repr(C)]
155 struct ChildNodes {
157 struct ChildNodes {
156 start: Offset,
158 start: Offset,
157 len: Size,
159 len: Size,
158 }
160 }
159
161
160 /// A `HgPath` of `len` bytes
162 /// A `HgPath` of `len` bytes
161 #[derive(BytesCast, Copy, Clone)]
163 #[derive(BytesCast, Copy, Clone)]
162 #[repr(C)]
164 #[repr(C)]
163 struct PathSlice {
165 struct PathSlice {
164 start: Offset,
166 start: Offset,
165 len: PathSize,
167 len: PathSize,
166 }
168 }
167
169
168 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
170 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
169 type OptPathSlice = PathSlice;
171 type OptPathSlice = PathSlice;
170
172
171 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
173 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
172 ///
174 ///
173 /// 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.
174 #[derive(Debug)]
176 #[derive(Debug)]
175 pub struct DirstateV2ParseError;
177 pub struct DirstateV2ParseError;
176
178
177 impl From<DirstateV2ParseError> for HgError {
179 impl From<DirstateV2ParseError> for HgError {
178 fn from(_: DirstateV2ParseError) -> Self {
180 fn from(_: DirstateV2ParseError) -> Self {
179 HgError::corrupted("dirstate-v2 parse error")
181 HgError::corrupted("dirstate-v2 parse error")
180 }
182 }
181 }
183 }
182
184
183 impl From<DirstateV2ParseError> for crate::DirstateError {
185 impl From<DirstateV2ParseError> for crate::DirstateError {
184 fn from(error: DirstateV2ParseError) -> Self {
186 fn from(error: DirstateV2ParseError) -> Self {
185 HgError::from(error).into()
187 HgError::from(error).into()
186 }
188 }
187 }
189 }
188
190
191 impl TreeMetadata {
192 pub fn as_bytes(&self) -> &[u8] {
193 BytesCast::as_bytes(self)
194 }
195 }
196
189 impl<'on_disk> Docket<'on_disk> {
197 impl<'on_disk> Docket<'on_disk> {
198 /// Generate the identifier for a new data file
199 ///
200 /// TODO: support the `HGTEST_UUIDFILE` environment variable.
201 /// See `mercurial/revlogutils/docket.py`
202 pub fn new_uid() -> String {
203 const ID_LENGTH: usize = 8;
204 let mut id = String::with_capacity(ID_LENGTH);
205 let mut rng = rand::thread_rng();
206 for _ in 0..ID_LENGTH {
207 // One random hexadecimal digit.
208 // `unwrap` never panics because `impl Write for String`
209 // never returns an error.
210 write!(&mut id, "{:x}", rng.gen_range(0, 16)).unwrap();
211 }
212 id
213 }
214
215 pub fn serialize(
216 parents: DirstateParents,
217 tree_metadata: TreeMetadata,
218 data_size: u64,
219 uuid: &[u8],
220 ) -> Result<Vec<u8>, std::num::TryFromIntError> {
221 let header = DocketHeader {
222 marker: *V2_FORMAT_MARKER,
223 parent_1: parents.p1.pad_to_256_bits(),
224 parent_2: parents.p2.pad_to_256_bits(),
225 metadata: tree_metadata,
226 data_size: u32::try_from(data_size)?.into(),
227 uuid_size: uuid.len().try_into()?,
228 };
229 let header = header.as_bytes();
230 let mut docket = Vec::with_capacity(header.len() + uuid.len());
231 docket.extend_from_slice(header);
232 docket.extend_from_slice(uuid);
233 Ok(docket)
234 }
235
190 pub fn parents(&self) -> DirstateParents {
236 pub fn parents(&self) -> DirstateParents {
191 use crate::Node;
237 use crate::Node;
192 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])
193 .unwrap()
239 .unwrap()
194 .clone();
240 .clone();
195 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])
196 .unwrap()
242 .unwrap()
197 .clone();
243 .clone();
198 DirstateParents { p1, p2 }
244 DirstateParents { p1, p2 }
199 }
245 }
200
246
201 pub fn tree_metadata(&self) -> &[u8] {
247 pub fn tree_metadata(&self) -> &[u8] {
202 self.header.metadata.as_bytes()
248 self.header.metadata.as_bytes()
203 }
249 }
204
250
205 pub fn data_size(&self) -> usize {
251 pub fn data_size(&self) -> usize {
206 // This `unwrap` could only panic on a 16-bit CPU
252 // This `unwrap` could only panic on a 16-bit CPU
207 self.header.data_size.get().try_into().unwrap()
253 self.header.data_size.get().try_into().unwrap()
208 }
254 }
209
255
210 pub fn data_filename(&self) -> String {
256 pub fn data_filename(&self) -> String {
211 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
257 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
212 }
258 }
213 }
259 }
214
260
215 pub fn read_docket(
261 pub fn read_docket(
216 on_disk: &[u8],
262 on_disk: &[u8],
217 ) -> Result<Docket<'_>, DirstateV2ParseError> {
263 ) -> Result<Docket<'_>, DirstateV2ParseError> {
218 let (header, uuid) =
264 let (header, uuid) =
219 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
265 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
220 let uuid_size = header.uuid_size as usize;
266 let uuid_size = header.uuid_size as usize;
221 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
267 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
222 Ok(Docket { header, uuid })
268 Ok(Docket { header, uuid })
223 } else {
269 } else {
224 Err(DirstateV2ParseError)
270 Err(DirstateV2ParseError)
225 }
271 }
226 }
272 }
227
273
228 pub(super) fn read<'on_disk>(
274 pub(super) fn read<'on_disk>(
229 on_disk: &'on_disk [u8],
275 on_disk: &'on_disk [u8],
230 metadata: &[u8],
276 metadata: &[u8],
231 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
277 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
232 if on_disk.is_empty() {
278 if on_disk.is_empty() {
233 return Ok(DirstateMap::empty(on_disk));
279 return Ok(DirstateMap::empty(on_disk));
234 }
280 }
235 let (meta, _) = TreeMetadata::from_bytes(metadata)
281 let (meta, _) = TreeMetadata::from_bytes(metadata)
236 .map_err(|_| DirstateV2ParseError)?;
282 .map_err(|_| DirstateV2ParseError)?;
237 let dirstate_map = DirstateMap {
283 let dirstate_map = DirstateMap {
238 on_disk,
284 on_disk,
239 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
285 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
240 on_disk,
286 on_disk,
241 meta.root_nodes,
287 meta.root_nodes,
242 )?),
288 )?),
243 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
289 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
244 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(),
245 ignore_patterns_hash: meta.ignore_patterns_hash,
291 ignore_patterns_hash: meta.ignore_patterns_hash,
246 unreachable_bytes: meta.unreachable_bytes.get(),
292 unreachable_bytes: meta.unreachable_bytes.get(),
247 };
293 };
248 Ok(dirstate_map)
294 Ok(dirstate_map)
249 }
295 }
250
296
251 impl Node {
297 impl Node {
252 pub(super) fn full_path<'on_disk>(
298 pub(super) fn full_path<'on_disk>(
253 &self,
299 &self,
254 on_disk: &'on_disk [u8],
300 on_disk: &'on_disk [u8],
255 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
301 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
256 read_hg_path(on_disk, self.full_path)
302 read_hg_path(on_disk, self.full_path)
257 }
303 }
258
304
259 pub(super) fn base_name_start<'on_disk>(
305 pub(super) fn base_name_start<'on_disk>(
260 &self,
306 &self,
261 ) -> Result<usize, DirstateV2ParseError> {
307 ) -> Result<usize, DirstateV2ParseError> {
262 let start = self.base_name_start.get();
308 let start = self.base_name_start.get();
263 if start < self.full_path.len.get() {
309 if start < self.full_path.len.get() {
264 let start = usize::try_from(start)
310 let start = usize::try_from(start)
265 // u32 -> usize, could only panic on a 16-bit CPU
311 // u32 -> usize, could only panic on a 16-bit CPU
266 .expect("dirstate-v2 base_name_start out of bounds");
312 .expect("dirstate-v2 base_name_start out of bounds");
267 Ok(start)
313 Ok(start)
268 } else {
314 } else {
269 Err(DirstateV2ParseError)
315 Err(DirstateV2ParseError)
270 }
316 }
271 }
317 }
272
318
273 pub(super) fn base_name<'on_disk>(
319 pub(super) fn base_name<'on_disk>(
274 &self,
320 &self,
275 on_disk: &'on_disk [u8],
321 on_disk: &'on_disk [u8],
276 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
322 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
277 let full_path = self.full_path(on_disk)?;
323 let full_path = self.full_path(on_disk)?;
278 let base_name_start = self.base_name_start()?;
324 let base_name_start = self.base_name_start()?;
279 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
325 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
280 }
326 }
281
327
282 pub(super) fn path<'on_disk>(
328 pub(super) fn path<'on_disk>(
283 &self,
329 &self,
284 on_disk: &'on_disk [u8],
330 on_disk: &'on_disk [u8],
285 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
331 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
286 Ok(WithBasename::from_raw_parts(
332 Ok(WithBasename::from_raw_parts(
287 Cow::Borrowed(self.full_path(on_disk)?),
333 Cow::Borrowed(self.full_path(on_disk)?),
288 self.base_name_start()?,
334 self.base_name_start()?,
289 ))
335 ))
290 }
336 }
291
337
292 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
338 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
293 self.copy_source.start.get() != 0
339 self.copy_source.start.get() != 0
294 }
340 }
295
341
296 pub(super) fn copy_source<'on_disk>(
342 pub(super) fn copy_source<'on_disk>(
297 &self,
343 &self,
298 on_disk: &'on_disk [u8],
344 on_disk: &'on_disk [u8],
299 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
345 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
300 Ok(if self.has_copy_source() {
346 Ok(if self.has_copy_source() {
301 Some(read_hg_path(on_disk, self.copy_source)?)
347 Some(read_hg_path(on_disk, self.copy_source)?)
302 } else {
348 } else {
303 None
349 None
304 })
350 })
305 }
351 }
306
352
307 fn flags(&self) -> Flags {
353 fn flags(&self) -> Flags {
308 Flags::from_bits_truncate(self.flags.get())
354 Flags::from_bits_truncate(self.flags.get())
309 }
355 }
310
356
311 fn has_entry(&self) -> bool {
357 fn has_entry(&self) -> bool {
312 self.flags().intersects(
358 self.flags().intersects(
313 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
359 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
314 )
360 )
315 }
361 }
316
362
317 pub(super) fn node_data(
363 pub(super) fn node_data(
318 &self,
364 &self,
319 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
365 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
320 if self.has_entry() {
366 if self.has_entry() {
321 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
367 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
322 } else if let Some(mtime) = self.cached_directory_mtime()? {
368 } else if let Some(mtime) = self.cached_directory_mtime()? {
323 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
369 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
324 } else {
370 } else {
325 Ok(dirstate_map::NodeData::None)
371 Ok(dirstate_map::NodeData::None)
326 }
372 }
327 }
373 }
328
374
329 pub(super) fn cached_directory_mtime(
375 pub(super) fn cached_directory_mtime(
330 &self,
376 &self,
331 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
377 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
332 // 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
333 // 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
334 // unset.
380 // unset.
335 if self.flags().contains(Flags::DIRECTORY)
381 if self.flags().contains(Flags::DIRECTORY)
336 && self.flags().contains(Flags::HAS_MTIME)
382 && self.flags().contains(Flags::HAS_MTIME)
337 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
383 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
338 {
384 {
339 Ok(Some(self.mtime.try_into()?))
385 Ok(Some(self.mtime.try_into()?))
340 } else {
386 } else {
341 Ok(None)
387 Ok(None)
342 }
388 }
343 }
389 }
344
390
345 fn synthesize_unix_mode(&self) -> u32 {
391 fn synthesize_unix_mode(&self) -> u32 {
346 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
392 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
347 libc::S_IFLNK
393 libc::S_IFLNK
348 } else {
394 } else {
349 libc::S_IFREG
395 libc::S_IFREG
350 };
396 };
351 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
397 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
352 0o755
398 0o755
353 } else {
399 } else {
354 0o644
400 0o644
355 };
401 };
356 file_type | permisions
402 file_type | permisions
357 }
403 }
358
404
359 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
405 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
360 // TODO: convert through raw bits instead?
406 // TODO: convert through raw bits instead?
361 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
407 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
362 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
408 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
363 let p2_info = self.flags().contains(Flags::P2_INFO);
409 let p2_info = self.flags().contains(Flags::P2_INFO);
364 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
410 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
365 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
411 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
366 {
412 {
367 Some((self.synthesize_unix_mode(), self.size.into()))
413 Some((self.synthesize_unix_mode(), self.size.into()))
368 } else {
414 } else {
369 None
415 None
370 };
416 };
371 let mtime = if self.flags().contains(Flags::HAS_MTIME)
417 let mtime = if self.flags().contains(Flags::HAS_MTIME)
372 && !self.flags().contains(Flags::DIRECTORY)
418 && !self.flags().contains(Flags::DIRECTORY)
373 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
419 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
374 {
420 {
375 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
421 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
376 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
422 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
377 m.second_ambiguous = true;
423 m.second_ambiguous = true;
378 }
424 }
379 Some(m)
425 Some(m)
380 } else {
426 } else {
381 None
427 None
382 };
428 };
383 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
429 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
384 {
430 {
385 Some(self.flags().contains(Flags::FALLBACK_EXEC))
431 Some(self.flags().contains(Flags::FALLBACK_EXEC))
386 } else {
432 } else {
387 None
433 None
388 };
434 };
389 let fallback_symlink =
435 let fallback_symlink =
390 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
436 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
391 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
437 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
392 } else {
438 } else {
393 None
439 None
394 };
440 };
395 Ok(DirstateEntry::from_v2_data(
441 Ok(DirstateEntry::from_v2_data(
396 wdir_tracked,
442 wdir_tracked,
397 p1_tracked,
443 p1_tracked,
398 p2_info,
444 p2_info,
399 mode_size,
445 mode_size,
400 mtime,
446 mtime,
401 fallback_exec,
447 fallback_exec,
402 fallback_symlink,
448 fallback_symlink,
403 ))
449 ))
404 }
450 }
405
451
406 pub(super) fn entry(
452 pub(super) fn entry(
407 &self,
453 &self,
408 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
454 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
409 if self.has_entry() {
455 if self.has_entry() {
410 Ok(Some(self.assume_entry()?))
456 Ok(Some(self.assume_entry()?))
411 } else {
457 } else {
412 Ok(None)
458 Ok(None)
413 }
459 }
414 }
460 }
415
461
416 pub(super) fn children<'on_disk>(
462 pub(super) fn children<'on_disk>(
417 &self,
463 &self,
418 on_disk: &'on_disk [u8],
464 on_disk: &'on_disk [u8],
419 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
465 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
420 read_nodes(on_disk, self.children)
466 read_nodes(on_disk, self.children)
421 }
467 }
422
468
423 pub(super) fn to_in_memory_node<'on_disk>(
469 pub(super) fn to_in_memory_node<'on_disk>(
424 &self,
470 &self,
425 on_disk: &'on_disk [u8],
471 on_disk: &'on_disk [u8],
426 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
472 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
427 Ok(dirstate_map::Node {
473 Ok(dirstate_map::Node {
428 children: dirstate_map::ChildNodes::OnDisk(
474 children: dirstate_map::ChildNodes::OnDisk(
429 self.children(on_disk)?,
475 self.children(on_disk)?,
430 ),
476 ),
431 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
477 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
432 data: self.node_data()?,
478 data: self.node_data()?,
433 descendants_with_entry_count: self
479 descendants_with_entry_count: self
434 .descendants_with_entry_count
480 .descendants_with_entry_count
435 .get(),
481 .get(),
436 tracked_descendants_count: self.tracked_descendants_count.get(),
482 tracked_descendants_count: self.tracked_descendants_count.get(),
437 })
483 })
438 }
484 }
439
485
440 fn from_dirstate_entry(
486 fn from_dirstate_entry(
441 entry: &DirstateEntry,
487 entry: &DirstateEntry,
442 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
488 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
443 let (
489 let (
444 wdir_tracked,
490 wdir_tracked,
445 p1_tracked,
491 p1_tracked,
446 p2_info,
492 p2_info,
447 mode_size_opt,
493 mode_size_opt,
448 mtime_opt,
494 mtime_opt,
449 fallback_exec,
495 fallback_exec,
450 fallback_symlink,
496 fallback_symlink,
451 ) = entry.v2_data();
497 ) = entry.v2_data();
452 // TODO: convert throug raw flag bits instead?
498 // TODO: convert throug raw flag bits instead?
453 let mut flags = Flags::empty();
499 let mut flags = Flags::empty();
454 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
500 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
455 flags.set(Flags::P1_TRACKED, p1_tracked);
501 flags.set(Flags::P1_TRACKED, p1_tracked);
456 flags.set(Flags::P2_INFO, p2_info);
502 flags.set(Flags::P2_INFO, p2_info);
457 let size = if let Some((m, s)) = mode_size_opt {
503 let size = if let Some((m, s)) = mode_size_opt {
458 let exec_perm = m & libc::S_IXUSR != 0;
504 let exec_perm = m & libc::S_IXUSR != 0;
459 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
505 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
460 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
506 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
461 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
507 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
462 flags.insert(Flags::HAS_MODE_AND_SIZE);
508 flags.insert(Flags::HAS_MODE_AND_SIZE);
463 s.into()
509 s.into()
464 } else {
510 } else {
465 0.into()
511 0.into()
466 };
512 };
467 let mtime = if let Some(m) = mtime_opt {
513 let mtime = if let Some(m) = mtime_opt {
468 flags.insert(Flags::HAS_MTIME);
514 flags.insert(Flags::HAS_MTIME);
469 if m.second_ambiguous {
515 if m.second_ambiguous {
470 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
516 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
471 };
517 };
472 m.into()
518 m.into()
473 } else {
519 } else {
474 PackedTruncatedTimestamp::null()
520 PackedTruncatedTimestamp::null()
475 };
521 };
476 if let Some(f_exec) = fallback_exec {
522 if let Some(f_exec) = fallback_exec {
477 flags.insert(Flags::HAS_FALLBACK_EXEC);
523 flags.insert(Flags::HAS_FALLBACK_EXEC);
478 if f_exec {
524 if f_exec {
479 flags.insert(Flags::FALLBACK_EXEC);
525 flags.insert(Flags::FALLBACK_EXEC);
480 }
526 }
481 }
527 }
482 if let Some(f_symlink) = fallback_symlink {
528 if let Some(f_symlink) = fallback_symlink {
483 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
529 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
484 if f_symlink {
530 if f_symlink {
485 flags.insert(Flags::FALLBACK_SYMLINK);
531 flags.insert(Flags::FALLBACK_SYMLINK);
486 }
532 }
487 }
533 }
488 (flags, size, mtime)
534 (flags, size, mtime)
489 }
535 }
490 }
536 }
491
537
492 fn read_hg_path(
538 fn read_hg_path(
493 on_disk: &[u8],
539 on_disk: &[u8],
494 slice: PathSlice,
540 slice: PathSlice,
495 ) -> Result<&HgPath, DirstateV2ParseError> {
541 ) -> Result<&HgPath, DirstateV2ParseError> {
496 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
542 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
497 }
543 }
498
544
499 fn read_nodes(
545 fn read_nodes(
500 on_disk: &[u8],
546 on_disk: &[u8],
501 slice: ChildNodes,
547 slice: ChildNodes,
502 ) -> Result<&[Node], DirstateV2ParseError> {
548 ) -> Result<&[Node], DirstateV2ParseError> {
503 read_slice(on_disk, slice.start, slice.len.get())
549 read_slice(on_disk, slice.start, slice.len.get())
504 }
550 }
505
551
506 fn read_slice<T, Len>(
552 fn read_slice<T, Len>(
507 on_disk: &[u8],
553 on_disk: &[u8],
508 start: Offset,
554 start: Offset,
509 len: Len,
555 len: Len,
510 ) -> Result<&[T], DirstateV2ParseError>
556 ) -> Result<&[T], DirstateV2ParseError>
511 where
557 where
512 T: BytesCast,
558 T: BytesCast,
513 Len: TryInto<usize>,
559 Len: TryInto<usize>,
514 {
560 {
515 // Either `usize::MAX` would result in "out of bounds" error since a single
561 // Either `usize::MAX` would result in "out of bounds" error since a single
516 // `&[u8]` cannot occupy the entire addess space.
562 // `&[u8]` cannot occupy the entire addess space.
517 let start = start.get().try_into().unwrap_or(std::usize::MAX);
563 let start = start.get().try_into().unwrap_or(std::usize::MAX);
518 let len = len.try_into().unwrap_or(std::usize::MAX);
564 let len = len.try_into().unwrap_or(std::usize::MAX);
519 on_disk
565 on_disk
520 .get(start..)
566 .get(start..)
521 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
567 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
522 .map(|(slice, _rest)| slice)
568 .map(|(slice, _rest)| slice)
523 .ok_or_else(|| DirstateV2ParseError)
569 .ok_or_else(|| DirstateV2ParseError)
524 }
570 }
525
571
526 pub(crate) fn for_each_tracked_path<'on_disk>(
572 pub(crate) fn for_each_tracked_path<'on_disk>(
527 on_disk: &'on_disk [u8],
573 on_disk: &'on_disk [u8],
528 metadata: &[u8],
574 metadata: &[u8],
529 mut f: impl FnMut(&'on_disk HgPath),
575 mut f: impl FnMut(&'on_disk HgPath),
530 ) -> Result<(), DirstateV2ParseError> {
576 ) -> Result<(), DirstateV2ParseError> {
531 let (meta, _) = TreeMetadata::from_bytes(metadata)
577 let (meta, _) = TreeMetadata::from_bytes(metadata)
532 .map_err(|_| DirstateV2ParseError)?;
578 .map_err(|_| DirstateV2ParseError)?;
533 fn recur<'on_disk>(
579 fn recur<'on_disk>(
534 on_disk: &'on_disk [u8],
580 on_disk: &'on_disk [u8],
535 nodes: ChildNodes,
581 nodes: ChildNodes,
536 f: &mut impl FnMut(&'on_disk HgPath),
582 f: &mut impl FnMut(&'on_disk HgPath),
537 ) -> Result<(), DirstateV2ParseError> {
583 ) -> Result<(), DirstateV2ParseError> {
538 for node in read_nodes(on_disk, nodes)? {
584 for node in read_nodes(on_disk, nodes)? {
539 if let Some(entry) = node.entry()? {
585 if let Some(entry) = node.entry()? {
540 if entry.state().is_tracked() {
586 if entry.state().is_tracked() {
541 f(node.full_path(on_disk)?)
587 f(node.full_path(on_disk)?)
542 }
588 }
543 }
589 }
544 recur(on_disk, node.children, f)?
590 recur(on_disk, node.children, f)?
545 }
591 }
546 Ok(())
592 Ok(())
547 }
593 }
548 recur(on_disk, meta.root_nodes, &mut f)
594 recur(on_disk, meta.root_nodes, &mut f)
549 }
595 }
550
596
551 /// Returns new data and metadata, together with whether that data should be
597 /// Returns new data and metadata, together with whether that data should be
552 /// appended to the existing data file whose content is at
598 /// appended to the existing data file whose content is at
553 /// `dirstate_map.on_disk` (true), instead of written to a new data file
599 /// `dirstate_map.on_disk` (true), instead of written to a new data file
554 /// (false).
600 /// (false).
555 pub(super) fn write(
601 pub(super) fn write(
556 dirstate_map: &DirstateMap,
602 dirstate_map: &DirstateMap,
557 can_append: bool,
603 can_append: bool,
558 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
604 ) -> Result<(Vec<u8>, TreeMetadata, bool), DirstateError> {
559 let append = can_append && dirstate_map.write_should_append();
605 let append = can_append && dirstate_map.write_should_append();
560
606
561 // This ignores the space for paths, and for nodes without an entry.
607 // This ignores the space for paths, and for nodes without an entry.
562 // TODO: better estimate? Skip the `Vec` and write to a file directly?
608 // TODO: better estimate? Skip the `Vec` and write to a file directly?
563 let size_guess = std::mem::size_of::<Node>()
609 let size_guess = std::mem::size_of::<Node>()
564 * dirstate_map.nodes_with_entry_count as usize;
610 * dirstate_map.nodes_with_entry_count as usize;
565
611
566 let mut writer = Writer {
612 let mut writer = Writer {
567 dirstate_map,
613 dirstate_map,
568 append,
614 append,
569 out: Vec::with_capacity(size_guess),
615 out: Vec::with_capacity(size_guess),
570 };
616 };
571
617
572 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
618 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
573
619
574 let meta = TreeMetadata {
620 let meta = TreeMetadata {
575 root_nodes,
621 root_nodes,
576 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
622 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
577 nodes_with_copy_source_count: dirstate_map
623 nodes_with_copy_source_count: dirstate_map
578 .nodes_with_copy_source_count
624 .nodes_with_copy_source_count
579 .into(),
625 .into(),
580 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
626 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
581 unused: [0; 4],
627 unused: [0; 4],
582 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
628 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
583 };
629 };
584 Ok((writer.out, meta.as_bytes().to_vec(), append))
630 Ok((writer.out, meta, append))
585 }
631 }
586
632
587 struct Writer<'dmap, 'on_disk> {
633 struct Writer<'dmap, 'on_disk> {
588 dirstate_map: &'dmap DirstateMap<'on_disk>,
634 dirstate_map: &'dmap DirstateMap<'on_disk>,
589 append: bool,
635 append: bool,
590 out: Vec<u8>,
636 out: Vec<u8>,
591 }
637 }
592
638
593 impl Writer<'_, '_> {
639 impl Writer<'_, '_> {
594 fn write_nodes(
640 fn write_nodes(
595 &mut self,
641 &mut self,
596 nodes: dirstate_map::ChildNodesRef,
642 nodes: dirstate_map::ChildNodesRef,
597 ) -> Result<ChildNodes, DirstateError> {
643 ) -> Result<ChildNodes, DirstateError> {
598 // Reuse already-written nodes if possible
644 // Reuse already-written nodes if possible
599 if self.append {
645 if self.append {
600 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
646 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
601 let start = self.on_disk_offset_of(nodes_slice).expect(
647 let start = self.on_disk_offset_of(nodes_slice).expect(
602 "dirstate-v2 OnDisk nodes not found within on_disk",
648 "dirstate-v2 OnDisk nodes not found within on_disk",
603 );
649 );
604 let len = child_nodes_len_from_usize(nodes_slice.len());
650 let len = child_nodes_len_from_usize(nodes_slice.len());
605 return Ok(ChildNodes { start, len });
651 return Ok(ChildNodes { start, len });
606 }
652 }
607 }
653 }
608
654
609 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
655 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
610 // undefined iteration order. Sort to enable binary search in the
656 // undefined iteration order. Sort to enable binary search in the
611 // written file.
657 // written file.
612 let nodes = nodes.sorted();
658 let nodes = nodes.sorted();
613 let nodes_len = nodes.len();
659 let nodes_len = nodes.len();
614
660
615 // First accumulate serialized nodes in a `Vec`
661 // First accumulate serialized nodes in a `Vec`
616 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
662 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
617 for node in nodes {
663 for node in nodes {
618 let children =
664 let children =
619 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
665 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
620 let full_path = node.full_path(self.dirstate_map.on_disk)?;
666 let full_path = node.full_path(self.dirstate_map.on_disk)?;
621 let full_path = self.write_path(full_path.as_bytes());
667 let full_path = self.write_path(full_path.as_bytes());
622 let copy_source = if let Some(source) =
668 let copy_source = if let Some(source) =
623 node.copy_source(self.dirstate_map.on_disk)?
669 node.copy_source(self.dirstate_map.on_disk)?
624 {
670 {
625 self.write_path(source.as_bytes())
671 self.write_path(source.as_bytes())
626 } else {
672 } else {
627 PathSlice {
673 PathSlice {
628 start: 0.into(),
674 start: 0.into(),
629 len: 0.into(),
675 len: 0.into(),
630 }
676 }
631 };
677 };
632 on_disk_nodes.push(match node {
678 on_disk_nodes.push(match node {
633 NodeRef::InMemory(path, node) => {
679 NodeRef::InMemory(path, node) => {
634 let (flags, size, mtime) = match &node.data {
680 let (flags, size, mtime) = match &node.data {
635 dirstate_map::NodeData::Entry(entry) => {
681 dirstate_map::NodeData::Entry(entry) => {
636 Node::from_dirstate_entry(entry)
682 Node::from_dirstate_entry(entry)
637 }
683 }
638 dirstate_map::NodeData::CachedDirectory { mtime } => (
684 dirstate_map::NodeData::CachedDirectory { mtime } => (
639 // we currently never set a mtime if unknown file
685 // we currently never set a mtime if unknown file
640 // are present.
686 // are present.
641 // So if we have a mtime for a directory, we know
687 // So if we have a mtime for a directory, we know
642 // they are no unknown
688 // they are no unknown
643 // files and we
689 // files and we
644 // blindly set ALL_UNKNOWN_RECORDED.
690 // blindly set ALL_UNKNOWN_RECORDED.
645 //
691 //
646 // We never set ALL_IGNORED_RECORDED since we
692 // We never set ALL_IGNORED_RECORDED since we
647 // don't track that case
693 // don't track that case
648 // currently.
694 // currently.
649 Flags::DIRECTORY
695 Flags::DIRECTORY
650 | Flags::HAS_MTIME
696 | Flags::HAS_MTIME
651 | Flags::ALL_UNKNOWN_RECORDED,
697 | Flags::ALL_UNKNOWN_RECORDED,
652 0.into(),
698 0.into(),
653 (*mtime).into(),
699 (*mtime).into(),
654 ),
700 ),
655 dirstate_map::NodeData::None => (
701 dirstate_map::NodeData::None => (
656 Flags::DIRECTORY,
702 Flags::DIRECTORY,
657 0.into(),
703 0.into(),
658 PackedTruncatedTimestamp::null(),
704 PackedTruncatedTimestamp::null(),
659 ),
705 ),
660 };
706 };
661 Node {
707 Node {
662 children,
708 children,
663 copy_source,
709 copy_source,
664 full_path,
710 full_path,
665 base_name_start: u16::try_from(path.base_name_start())
711 base_name_start: u16::try_from(path.base_name_start())
666 // Could only panic for paths over 64 KiB
712 // Could only panic for paths over 64 KiB
667 .expect("dirstate-v2 path length overflow")
713 .expect("dirstate-v2 path length overflow")
668 .into(),
714 .into(),
669 descendants_with_entry_count: node
715 descendants_with_entry_count: node
670 .descendants_with_entry_count
716 .descendants_with_entry_count
671 .into(),
717 .into(),
672 tracked_descendants_count: node
718 tracked_descendants_count: node
673 .tracked_descendants_count
719 .tracked_descendants_count
674 .into(),
720 .into(),
675 flags: flags.bits().into(),
721 flags: flags.bits().into(),
676 size,
722 size,
677 mtime,
723 mtime,
678 }
724 }
679 }
725 }
680 NodeRef::OnDisk(node) => Node {
726 NodeRef::OnDisk(node) => Node {
681 children,
727 children,
682 copy_source,
728 copy_source,
683 full_path,
729 full_path,
684 ..*node
730 ..*node
685 },
731 },
686 })
732 })
687 }
733 }
688 // … so we can write them contiguously, after writing everything else
734 // … so we can write them contiguously, after writing everything else
689 // they refer to.
735 // they refer to.
690 let start = self.current_offset();
736 let start = self.current_offset();
691 let len = child_nodes_len_from_usize(nodes_len);
737 let len = child_nodes_len_from_usize(nodes_len);
692 self.out.extend(on_disk_nodes.as_bytes());
738 self.out.extend(on_disk_nodes.as_bytes());
693 Ok(ChildNodes { start, len })
739 Ok(ChildNodes { start, len })
694 }
740 }
695
741
696 /// If the given slice of items is within `on_disk`, returns its offset
742 /// If the given slice of items is within `on_disk`, returns its offset
697 /// from the start of `on_disk`.
743 /// from the start of `on_disk`.
698 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
744 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
699 where
745 where
700 T: BytesCast,
746 T: BytesCast,
701 {
747 {
702 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
748 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
703 let start = slice.as_ptr() as usize;
749 let start = slice.as_ptr() as usize;
704 let end = start + slice.len();
750 let end = start + slice.len();
705 start..=end
751 start..=end
706 }
752 }
707 let slice_addresses = address_range(slice.as_bytes());
753 let slice_addresses = address_range(slice.as_bytes());
708 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
754 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
709 if on_disk_addresses.contains(slice_addresses.start())
755 if on_disk_addresses.contains(slice_addresses.start())
710 && on_disk_addresses.contains(slice_addresses.end())
756 && on_disk_addresses.contains(slice_addresses.end())
711 {
757 {
712 let offset = slice_addresses.start() - on_disk_addresses.start();
758 let offset = slice_addresses.start() - on_disk_addresses.start();
713 Some(offset_from_usize(offset))
759 Some(offset_from_usize(offset))
714 } else {
760 } else {
715 None
761 None
716 }
762 }
717 }
763 }
718
764
719 fn current_offset(&mut self) -> Offset {
765 fn current_offset(&mut self) -> Offset {
720 let mut offset = self.out.len();
766 let mut offset = self.out.len();
721 if self.append {
767 if self.append {
722 offset += self.dirstate_map.on_disk.len()
768 offset += self.dirstate_map.on_disk.len()
723 }
769 }
724 offset_from_usize(offset)
770 offset_from_usize(offset)
725 }
771 }
726
772
727 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
773 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
728 let len = path_len_from_usize(slice.len());
774 let len = path_len_from_usize(slice.len());
729 // Reuse an already-written path if possible
775 // Reuse an already-written path if possible
730 if self.append {
776 if self.append {
731 if let Some(start) = self.on_disk_offset_of(slice) {
777 if let Some(start) = self.on_disk_offset_of(slice) {
732 return PathSlice { start, len };
778 return PathSlice { start, len };
733 }
779 }
734 }
780 }
735 let start = self.current_offset();
781 let start = self.current_offset();
736 self.out.extend(slice.as_bytes());
782 self.out.extend(slice.as_bytes());
737 PathSlice { start, len }
783 PathSlice { start, len }
738 }
784 }
739 }
785 }
740
786
741 fn offset_from_usize(x: usize) -> Offset {
787 fn offset_from_usize(x: usize) -> Offset {
742 u32::try_from(x)
788 u32::try_from(x)
743 // Could only panic for a dirstate file larger than 4 GiB
789 // Could only panic for a dirstate file larger than 4 GiB
744 .expect("dirstate-v2 offset overflow")
790 .expect("dirstate-v2 offset overflow")
745 .into()
791 .into()
746 }
792 }
747
793
748 fn child_nodes_len_from_usize(x: usize) -> Size {
794 fn child_nodes_len_from_usize(x: usize) -> Size {
749 u32::try_from(x)
795 u32::try_from(x)
750 // Could only panic with over 4 billion nodes
796 // Could only panic with over 4 billion nodes
751 .expect("dirstate-v2 slice length overflow")
797 .expect("dirstate-v2 slice length overflow")
752 .into()
798 .into()
753 }
799 }
754
800
755 fn path_len_from_usize(x: usize) -> PathSize {
801 fn path_len_from_usize(x: usize) -> PathSize {
756 u16::try_from(x)
802 u16::try_from(x)
757 // Could only panic for paths over 64 KiB
803 // Could only panic for paths over 64 KiB
758 .expect("dirstate-v2 path length overflow")
804 .expect("dirstate-v2 path length overflow")
759 .into()
805 .into()
760 }
806 }
761
807
762 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
808 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
763 fn from(timestamp: TruncatedTimestamp) -> Self {
809 fn from(timestamp: TruncatedTimestamp) -> Self {
764 Self {
810 Self {
765 truncated_seconds: timestamp.truncated_seconds().into(),
811 truncated_seconds: timestamp.truncated_seconds().into(),
766 nanoseconds: timestamp.nanoseconds().into(),
812 nanoseconds: timestamp.nanoseconds().into(),
767 }
813 }
768 }
814 }
769 }
815 }
770
816
771 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
817 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
772 type Error = DirstateV2ParseError;
818 type Error = DirstateV2ParseError;
773
819
774 fn try_from(
820 fn try_from(
775 timestamp: PackedTruncatedTimestamp,
821 timestamp: PackedTruncatedTimestamp,
776 ) -> Result<Self, Self::Error> {
822 ) -> Result<Self, Self::Error> {
777 Self::from_already_truncated(
823 Self::from_already_truncated(
778 timestamp.truncated_seconds.get(),
824 timestamp.truncated_seconds.get(),
779 timestamp.nanoseconds.get(),
825 timestamp.nanoseconds.get(),
780 false,
826 false,
781 )
827 )
782 }
828 }
783 }
829 }
784 impl PackedTruncatedTimestamp {
830 impl PackedTruncatedTimestamp {
785 fn null() -> Self {
831 fn null() -> Self {
786 Self {
832 Self {
787 truncated_seconds: 0.into(),
833 truncated_seconds: 0.into(),
788 nanoseconds: 0.into(),
834 nanoseconds: 0.into(),
789 }
835 }
790 }
836 }
791 }
837 }
@@ -1,464 +1,532 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::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::errors::HgError;
7 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
8 use crate::errors::{HgError, IoResultExt};
8 use crate::exit_codes;
9 use crate::exit_codes;
9 use crate::lock::{try_with_lock_no_wait, LockError};
10 use crate::lock::{try_with_lock_no_wait, LockError};
10 use crate::manifest::{Manifest, Manifestlog};
11 use crate::manifest::{Manifest, Manifestlog};
11 use crate::revlog::filelog::Filelog;
12 use crate::revlog::filelog::Filelog;
12 use crate::revlog::revlog::RevlogError;
13 use crate::revlog::revlog::RevlogError;
13 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::hg_path::HgPath;
15 use crate::utils::hg_path::HgPath;
15 use crate::utils::SliceExt;
16 use crate::utils::SliceExt;
16 use crate::vfs::{is_dir, is_file, Vfs};
17 use crate::vfs::{is_dir, is_file, Vfs};
17 use crate::{requirements, NodePrefix};
18 use crate::{requirements, NodePrefix};
18 use crate::{DirstateError, Revision};
19 use crate::{DirstateError, Revision};
19 use std::cell::{Ref, RefCell, RefMut};
20 use std::cell::{Ref, RefCell, RefMut};
20 use std::collections::HashSet;
21 use std::collections::HashSet;
22 use std::io::Seek;
23 use std::io::SeekFrom;
24 use std::io::Write as IoWrite;
21 use std::path::{Path, PathBuf};
25 use std::path::{Path, PathBuf};
22
26
23 /// A repository on disk
27 /// A repository on disk
24 pub struct Repo {
28 pub struct Repo {
25 working_directory: PathBuf,
29 working_directory: PathBuf,
26 dot_hg: PathBuf,
30 dot_hg: PathBuf,
27 store: PathBuf,
31 store: PathBuf,
28 requirements: HashSet<String>,
32 requirements: HashSet<String>,
29 config: Config,
33 config: Config,
30 dirstate_parents: LazyCell<DirstateParents, HgError>,
34 dirstate_parents: LazyCell<DirstateParents, HgError>,
31 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
35 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
32 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
36 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
33 changelog: LazyCell<Changelog, HgError>,
37 changelog: LazyCell<Changelog, HgError>,
34 manifestlog: LazyCell<Manifestlog, HgError>,
38 manifestlog: LazyCell<Manifestlog, HgError>,
35 }
39 }
36
40
37 #[derive(Debug, derive_more::From)]
41 #[derive(Debug, derive_more::From)]
38 pub enum RepoError {
42 pub enum RepoError {
39 NotFound {
43 NotFound {
40 at: PathBuf,
44 at: PathBuf,
41 },
45 },
42 #[from]
46 #[from]
43 ConfigParseError(ConfigParseError),
47 ConfigParseError(ConfigParseError),
44 #[from]
48 #[from]
45 Other(HgError),
49 Other(HgError),
46 }
50 }
47
51
48 impl From<ConfigError> for RepoError {
52 impl From<ConfigError> for RepoError {
49 fn from(error: ConfigError) -> Self {
53 fn from(error: ConfigError) -> Self {
50 match error {
54 match error {
51 ConfigError::Parse(error) => error.into(),
55 ConfigError::Parse(error) => error.into(),
52 ConfigError::Other(error) => error.into(),
56 ConfigError::Other(error) => error.into(),
53 }
57 }
54 }
58 }
55 }
59 }
56
60
57 impl Repo {
61 impl Repo {
58 /// tries to find nearest repository root in current working directory or
62 /// tries to find nearest repository root in current working directory or
59 /// its ancestors
63 /// its ancestors
60 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
64 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
61 let current_directory = crate::utils::current_dir()?;
65 let current_directory = crate::utils::current_dir()?;
62 // ancestors() is inclusive: it first yields `current_directory`
66 // ancestors() is inclusive: it first yields `current_directory`
63 // as-is.
67 // as-is.
64 for ancestor in current_directory.ancestors() {
68 for ancestor in current_directory.ancestors() {
65 if is_dir(ancestor.join(".hg"))? {
69 if is_dir(ancestor.join(".hg"))? {
66 return Ok(ancestor.to_path_buf());
70 return Ok(ancestor.to_path_buf());
67 }
71 }
68 }
72 }
69 return Err(RepoError::NotFound {
73 return Err(RepoError::NotFound {
70 at: current_directory,
74 at: current_directory,
71 });
75 });
72 }
76 }
73
77
74 /// Find a repository, either at the given path (which must contain a `.hg`
78 /// Find a repository, either at the given path (which must contain a `.hg`
75 /// sub-directory) or by searching the current directory and its
79 /// sub-directory) or by searching the current directory and its
76 /// ancestors.
80 /// ancestors.
77 ///
81 ///
78 /// A method with two very different "modes" like this usually a code smell
82 /// A method with two very different "modes" like this usually a code smell
79 /// to make two methods instead, but in this case an `Option` is what rhg
83 /// to make two methods instead, but in this case an `Option` is what rhg
80 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
84 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
81 /// Having two methods would just move that `if` to almost all callers.
85 /// Having two methods would just move that `if` to almost all callers.
82 pub fn find(
86 pub fn find(
83 config: &Config,
87 config: &Config,
84 explicit_path: Option<PathBuf>,
88 explicit_path: Option<PathBuf>,
85 ) -> Result<Self, RepoError> {
89 ) -> Result<Self, RepoError> {
86 if let Some(root) = explicit_path {
90 if let Some(root) = explicit_path {
87 if is_dir(root.join(".hg"))? {
91 if is_dir(root.join(".hg"))? {
88 Self::new_at_path(root.to_owned(), config)
92 Self::new_at_path(root.to_owned(), config)
89 } else if is_file(&root)? {
93 } else if is_file(&root)? {
90 Err(HgError::unsupported("bundle repository").into())
94 Err(HgError::unsupported("bundle repository").into())
91 } else {
95 } else {
92 Err(RepoError::NotFound {
96 Err(RepoError::NotFound {
93 at: root.to_owned(),
97 at: root.to_owned(),
94 })
98 })
95 }
99 }
96 } else {
100 } else {
97 let root = Self::find_repo_root()?;
101 let root = Self::find_repo_root()?;
98 Self::new_at_path(root, config)
102 Self::new_at_path(root, config)
99 }
103 }
100 }
104 }
101
105
102 /// To be called after checking that `.hg` is a sub-directory
106 /// To be called after checking that `.hg` is a sub-directory
103 fn new_at_path(
107 fn new_at_path(
104 working_directory: PathBuf,
108 working_directory: PathBuf,
105 config: &Config,
109 config: &Config,
106 ) -> Result<Self, RepoError> {
110 ) -> Result<Self, RepoError> {
107 let dot_hg = working_directory.join(".hg");
111 let dot_hg = working_directory.join(".hg");
108
112
109 let mut repo_config_files = Vec::new();
113 let mut repo_config_files = Vec::new();
110 repo_config_files.push(dot_hg.join("hgrc"));
114 repo_config_files.push(dot_hg.join("hgrc"));
111 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
115 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
112
116
113 let hg_vfs = Vfs { base: &dot_hg };
117 let hg_vfs = Vfs { base: &dot_hg };
114 let mut reqs = requirements::load_if_exists(hg_vfs)?;
118 let mut reqs = requirements::load_if_exists(hg_vfs)?;
115 let relative =
119 let relative =
116 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
120 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
117 let shared =
121 let shared =
118 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
122 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
119
123
120 // From `mercurial/localrepo.py`:
124 // From `mercurial/localrepo.py`:
121 //
125 //
122 // if .hg/requires contains the sharesafe requirement, it means
126 // if .hg/requires contains the sharesafe requirement, it means
123 // there exists a `.hg/store/requires` too and we should read it
127 // there exists a `.hg/store/requires` too and we should read it
124 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
128 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
125 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
129 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
126 // is not present, refer checkrequirementscompat() for that
130 // is not present, refer checkrequirementscompat() for that
127 //
131 //
128 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
132 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
129 // repository was shared the old way. We check the share source
133 // repository was shared the old way. We check the share source
130 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
134 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
131 // current repository needs to be reshared
135 // current repository needs to be reshared
132 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
136 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
133
137
134 let store_path;
138 let store_path;
135 if !shared {
139 if !shared {
136 store_path = dot_hg.join("store");
140 store_path = dot_hg.join("store");
137 } else {
141 } else {
138 let bytes = hg_vfs.read("sharedpath")?;
142 let bytes = hg_vfs.read("sharedpath")?;
139 let mut shared_path =
143 let mut shared_path =
140 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
144 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
141 .to_owned();
145 .to_owned();
142 if relative {
146 if relative {
143 shared_path = dot_hg.join(shared_path)
147 shared_path = dot_hg.join(shared_path)
144 }
148 }
145 if !is_dir(&shared_path)? {
149 if !is_dir(&shared_path)? {
146 return Err(HgError::corrupted(format!(
150 return Err(HgError::corrupted(format!(
147 ".hg/sharedpath points to nonexistent directory {}",
151 ".hg/sharedpath points to nonexistent directory {}",
148 shared_path.display()
152 shared_path.display()
149 ))
153 ))
150 .into());
154 .into());
151 }
155 }
152
156
153 store_path = shared_path.join("store");
157 store_path = shared_path.join("store");
154
158
155 let source_is_share_safe =
159 let source_is_share_safe =
156 requirements::load(Vfs { base: &shared_path })?
160 requirements::load(Vfs { base: &shared_path })?
157 .contains(requirements::SHARESAFE_REQUIREMENT);
161 .contains(requirements::SHARESAFE_REQUIREMENT);
158
162
159 if share_safe && !source_is_share_safe {
163 if share_safe && !source_is_share_safe {
160 return Err(match config
164 return Err(match config
161 .get(b"share", b"safe-mismatch.source-not-safe")
165 .get(b"share", b"safe-mismatch.source-not-safe")
162 {
166 {
163 Some(b"abort") | None => HgError::abort(
167 Some(b"abort") | None => HgError::abort(
164 "abort: share source does not support share-safe requirement\n\
168 "abort: share source does not support share-safe requirement\n\
165 (see `hg help config.format.use-share-safe` for more information)",
169 (see `hg help config.format.use-share-safe` for more information)",
166 exit_codes::ABORT,
170 exit_codes::ABORT,
167 ),
171 ),
168 _ => HgError::unsupported("share-safe downgrade"),
172 _ => HgError::unsupported("share-safe downgrade"),
169 }
173 }
170 .into());
174 .into());
171 } else if source_is_share_safe && !share_safe {
175 } else if source_is_share_safe && !share_safe {
172 return Err(
176 return Err(
173 match config.get(b"share", b"safe-mismatch.source-safe") {
177 match config.get(b"share", b"safe-mismatch.source-safe") {
174 Some(b"abort") | None => HgError::abort(
178 Some(b"abort") | None => HgError::abort(
175 "abort: version mismatch: source uses share-safe \
179 "abort: version mismatch: source uses share-safe \
176 functionality while the current share does not\n\
180 functionality while the current share does not\n\
177 (see `hg help config.format.use-share-safe` for more information)",
181 (see `hg help config.format.use-share-safe` for more information)",
178 exit_codes::ABORT,
182 exit_codes::ABORT,
179 ),
183 ),
180 _ => HgError::unsupported("share-safe upgrade"),
184 _ => HgError::unsupported("share-safe upgrade"),
181 }
185 }
182 .into(),
186 .into(),
183 );
187 );
184 }
188 }
185
189
186 if share_safe {
190 if share_safe {
187 repo_config_files.insert(0, shared_path.join("hgrc"))
191 repo_config_files.insert(0, shared_path.join("hgrc"))
188 }
192 }
189 }
193 }
190 if share_safe {
194 if share_safe {
191 reqs.extend(requirements::load(Vfs { base: &store_path })?);
195 reqs.extend(requirements::load(Vfs { base: &store_path })?);
192 }
196 }
193
197
194 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
198 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
195 config.combine_with_repo(&repo_config_files)?
199 config.combine_with_repo(&repo_config_files)?
196 } else {
200 } else {
197 config.clone()
201 config.clone()
198 };
202 };
199
203
200 let repo = Self {
204 let repo = Self {
201 requirements: reqs,
205 requirements: reqs,
202 working_directory,
206 working_directory,
203 store: store_path,
207 store: store_path,
204 dot_hg,
208 dot_hg,
205 config: repo_config,
209 config: repo_config,
206 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
210 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
207 dirstate_data_file_uuid: LazyCell::new(
211 dirstate_data_file_uuid: LazyCell::new(
208 Self::read_dirstate_data_file_uuid,
212 Self::read_dirstate_data_file_uuid,
209 ),
213 ),
210 dirstate_map: LazyCell::new(Self::new_dirstate_map),
214 dirstate_map: LazyCell::new(Self::new_dirstate_map),
211 changelog: LazyCell::new(Changelog::open),
215 changelog: LazyCell::new(Changelog::open),
212 manifestlog: LazyCell::new(Manifestlog::open),
216 manifestlog: LazyCell::new(Manifestlog::open),
213 };
217 };
214
218
215 requirements::check(&repo)?;
219 requirements::check(&repo)?;
216
220
217 Ok(repo)
221 Ok(repo)
218 }
222 }
219
223
220 pub fn working_directory_path(&self) -> &Path {
224 pub fn working_directory_path(&self) -> &Path {
221 &self.working_directory
225 &self.working_directory
222 }
226 }
223
227
224 pub fn requirements(&self) -> &HashSet<String> {
228 pub fn requirements(&self) -> &HashSet<String> {
225 &self.requirements
229 &self.requirements
226 }
230 }
227
231
228 pub fn config(&self) -> &Config {
232 pub fn config(&self) -> &Config {
229 &self.config
233 &self.config
230 }
234 }
231
235
232 /// For accessing repository files (in `.hg`), except for the store
236 /// For accessing repository files (in `.hg`), except for the store
233 /// (`.hg/store`).
237 /// (`.hg/store`).
234 pub fn hg_vfs(&self) -> Vfs<'_> {
238 pub fn hg_vfs(&self) -> Vfs<'_> {
235 Vfs { base: &self.dot_hg }
239 Vfs { base: &self.dot_hg }
236 }
240 }
237
241
238 /// For accessing repository store files (in `.hg/store`)
242 /// For accessing repository store files (in `.hg/store`)
239 pub fn store_vfs(&self) -> Vfs<'_> {
243 pub fn store_vfs(&self) -> Vfs<'_> {
240 Vfs { base: &self.store }
244 Vfs { base: &self.store }
241 }
245 }
242
246
243 /// For accessing the working copy
247 /// For accessing the working copy
244 pub fn working_directory_vfs(&self) -> Vfs<'_> {
248 pub fn working_directory_vfs(&self) -> Vfs<'_> {
245 Vfs {
249 Vfs {
246 base: &self.working_directory,
250 base: &self.working_directory,
247 }
251 }
248 }
252 }
249
253
250 pub fn try_with_wlock_no_wait<R>(
254 pub fn try_with_wlock_no_wait<R>(
251 &self,
255 &self,
252 f: impl FnOnce() -> R,
256 f: impl FnOnce() -> R,
253 ) -> Result<R, LockError> {
257 ) -> Result<R, LockError> {
254 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
258 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
255 }
259 }
256
260
257 pub fn has_dirstate_v2(&self) -> bool {
261 pub fn has_dirstate_v2(&self) -> bool {
258 self.requirements
262 self.requirements
259 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
263 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
260 }
264 }
261
265
262 pub fn has_sparse(&self) -> bool {
266 pub fn has_sparse(&self) -> bool {
263 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
267 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
264 }
268 }
265
269
266 pub fn has_narrow(&self) -> bool {
270 pub fn has_narrow(&self) -> bool {
267 self.requirements.contains(requirements::NARROW_REQUIREMENT)
271 self.requirements.contains(requirements::NARROW_REQUIREMENT)
268 }
272 }
269
273
270 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
274 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
271 Ok(self
275 Ok(self
272 .hg_vfs()
276 .hg_vfs()
273 .read("dirstate")
277 .read("dirstate")
274 .io_not_found_as_none()?
278 .io_not_found_as_none()?
275 .unwrap_or(Vec::new()))
279 .unwrap_or(Vec::new()))
276 }
280 }
277
281
278 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
282 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
279 Ok(*self.dirstate_parents.get_or_init(self)?)
283 Ok(*self.dirstate_parents.get_or_init(self)?)
280 }
284 }
281
285
282 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
286 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
283 let dirstate = self.dirstate_file_contents()?;
287 let dirstate = self.dirstate_file_contents()?;
284 let parents = if dirstate.is_empty() {
288 let parents = if dirstate.is_empty() {
285 if self.has_dirstate_v2() {
289 if self.has_dirstate_v2() {
286 self.dirstate_data_file_uuid.set(None);
290 self.dirstate_data_file_uuid.set(None);
287 }
291 }
288 DirstateParents::NULL
292 DirstateParents::NULL
289 } else if self.has_dirstate_v2() {
293 } else if self.has_dirstate_v2() {
290 let docket =
294 let docket =
291 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
295 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
292 self.dirstate_data_file_uuid
296 self.dirstate_data_file_uuid
293 .set(Some(docket.uuid.to_owned()));
297 .set(Some(docket.uuid.to_owned()));
294 docket.parents()
298 docket.parents()
295 } else {
299 } else {
296 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
300 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
297 .clone()
301 .clone()
298 };
302 };
299 self.dirstate_parents.set(parents);
303 self.dirstate_parents.set(parents);
300 Ok(parents)
304 Ok(parents)
301 }
305 }
302
306
303 fn read_dirstate_data_file_uuid(
307 fn read_dirstate_data_file_uuid(
304 &self,
308 &self,
305 ) -> Result<Option<Vec<u8>>, HgError> {
309 ) -> Result<Option<Vec<u8>>, HgError> {
306 assert!(
310 assert!(
307 self.has_dirstate_v2(),
311 self.has_dirstate_v2(),
308 "accessing dirstate data file ID without dirstate-v2"
312 "accessing dirstate data file ID without dirstate-v2"
309 );
313 );
310 let dirstate = self.dirstate_file_contents()?;
314 let dirstate = self.dirstate_file_contents()?;
311 if dirstate.is_empty() {
315 if dirstate.is_empty() {
312 self.dirstate_parents.set(DirstateParents::NULL);
316 self.dirstate_parents.set(DirstateParents::NULL);
313 Ok(None)
317 Ok(None)
314 } else {
318 } else {
315 let docket =
319 let docket =
316 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
320 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
317 self.dirstate_parents.set(docket.parents());
321 self.dirstate_parents.set(docket.parents());
318 Ok(Some(docket.uuid.to_owned()))
322 Ok(Some(docket.uuid.to_owned()))
319 }
323 }
320 }
324 }
321
325
322 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
326 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
323 let dirstate_file_contents = self.dirstate_file_contents()?;
327 let dirstate_file_contents = self.dirstate_file_contents()?;
324 if dirstate_file_contents.is_empty() {
328 if dirstate_file_contents.is_empty() {
325 self.dirstate_parents.set(DirstateParents::NULL);
329 self.dirstate_parents.set(DirstateParents::NULL);
326 if self.has_dirstate_v2() {
330 if self.has_dirstate_v2() {
327 self.dirstate_data_file_uuid.set(None);
331 self.dirstate_data_file_uuid.set(None);
328 }
332 }
329 Ok(OwningDirstateMap::new_empty(Vec::new()))
333 Ok(OwningDirstateMap::new_empty(Vec::new()))
330 } else if self.has_dirstate_v2() {
334 } else if self.has_dirstate_v2() {
331 let docket = crate::dirstate_tree::on_disk::read_docket(
335 let docket = crate::dirstate_tree::on_disk::read_docket(
332 &dirstate_file_contents,
336 &dirstate_file_contents,
333 )?;
337 )?;
334 self.dirstate_parents.set(docket.parents());
338 self.dirstate_parents.set(docket.parents());
335 self.dirstate_data_file_uuid
339 self.dirstate_data_file_uuid
336 .set(Some(docket.uuid.to_owned()));
340 .set(Some(docket.uuid.to_owned()));
337 let data_size = docket.data_size();
341 let data_size = docket.data_size();
338 let metadata = docket.tree_metadata();
342 let metadata = docket.tree_metadata();
339 let mut map = if let Some(data_mmap) = self
343 let mut map = if let Some(data_mmap) = self
340 .hg_vfs()
344 .hg_vfs()
341 .mmap_open(docket.data_filename())
345 .mmap_open(docket.data_filename())
342 .io_not_found_as_none()?
346 .io_not_found_as_none()?
343 {
347 {
344 OwningDirstateMap::new_empty(data_mmap)
348 OwningDirstateMap::new_empty(data_mmap)
345 } else {
349 } else {
346 OwningDirstateMap::new_empty(Vec::new())
350 OwningDirstateMap::new_empty(Vec::new())
347 };
351 };
348 let (on_disk, placeholder) = map.get_pair_mut();
352 let (on_disk, placeholder) = map.get_pair_mut();
349 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
353 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
350 Ok(map)
354 Ok(map)
351 } else {
355 } else {
352 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
356 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
353 let (on_disk, placeholder) = map.get_pair_mut();
357 let (on_disk, placeholder) = map.get_pair_mut();
354 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
358 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
355 self.dirstate_parents
359 self.dirstate_parents
356 .set(parents.unwrap_or(DirstateParents::NULL));
360 .set(parents.unwrap_or(DirstateParents::NULL));
357 *placeholder = inner;
361 *placeholder = inner;
358 Ok(map)
362 Ok(map)
359 }
363 }
360 }
364 }
361
365
362 pub fn dirstate_map(
366 pub fn dirstate_map(
363 &self,
367 &self,
364 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
368 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
365 self.dirstate_map.get_or_init(self)
369 self.dirstate_map.get_or_init(self)
366 }
370 }
367
371
368 pub fn dirstate_map_mut(
372 pub fn dirstate_map_mut(
369 &self,
373 &self,
370 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
374 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
371 self.dirstate_map.get_mut_or_init(self)
375 self.dirstate_map.get_mut_or_init(self)
372 }
376 }
373
377
374 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
378 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
375 self.changelog.get_or_init(self)
379 self.changelog.get_or_init(self)
376 }
380 }
377
381
378 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
382 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
379 self.changelog.get_mut_or_init(self)
383 self.changelog.get_mut_or_init(self)
380 }
384 }
381
385
382 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
386 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
383 self.manifestlog.get_or_init(self)
387 self.manifestlog.get_or_init(self)
384 }
388 }
385
389
386 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
390 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
387 self.manifestlog.get_mut_or_init(self)
391 self.manifestlog.get_mut_or_init(self)
388 }
392 }
389
393
390 /// Returns the manifest of the *changeset* with the given node ID
394 /// Returns the manifest of the *changeset* with the given node ID
391 pub fn manifest_for_node(
395 pub fn manifest_for_node(
392 &self,
396 &self,
393 node: impl Into<NodePrefix>,
397 node: impl Into<NodePrefix>,
394 ) -> Result<Manifest, RevlogError> {
398 ) -> Result<Manifest, RevlogError> {
395 self.manifestlog()?.data_for_node(
399 self.manifestlog()?.data_for_node(
396 self.changelog()?
400 self.changelog()?
397 .data_for_node(node.into())?
401 .data_for_node(node.into())?
398 .manifest_node()?
402 .manifest_node()?
399 .into(),
403 .into(),
400 )
404 )
401 }
405 }
402
406
403 /// Returns the manifest of the *changeset* with the given revision number
407 /// Returns the manifest of the *changeset* with the given revision number
404 pub fn manifest_for_rev(
408 pub fn manifest_for_rev(
405 &self,
409 &self,
406 revision: Revision,
410 revision: Revision,
407 ) -> Result<Manifest, RevlogError> {
411 ) -> Result<Manifest, RevlogError> {
408 self.manifestlog()?.data_for_node(
412 self.manifestlog()?.data_for_node(
409 self.changelog()?
413 self.changelog()?
410 .data_for_rev(revision)?
414 .data_for_rev(revision)?
411 .manifest_node()?
415 .manifest_node()?
412 .into(),
416 .into(),
413 )
417 )
414 }
418 }
415
419
416 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
420 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
417 Filelog::open(self, path)
421 Filelog::open(self, path)
418 }
422 }
423
424 /// Write to disk any updates that were made through `dirstate_map_mut`.
425 ///
426 /// The "wlock" must be held while calling this.
427 /// See for example `try_with_wlock_no_wait`.
428 ///
429 /// TODO: have a `WritableRepo` type only accessible while holding the
430 /// lock?
431 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
432 let map = self.dirstate_map()?;
433 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
434 // it’s unset
435 let parents = self.dirstate_parents()?;
436 let packed_dirstate = if self.has_dirstate_v2() {
437 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
438 let mut uuid = uuid.as_ref();
439 let can_append = uuid.is_some();
440 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
441 if !append {
442 uuid = None
443 }
444 let uuid = if let Some(uuid) = uuid {
445 std::str::from_utf8(uuid)
446 .map_err(|_| {
447 HgError::corrupted("non-UTF-8 dirstate data file ID")
448 })?
449 .to_owned()
450 } else {
451 DirstateDocket::new_uid()
452 };
453 let data_filename = format!("dirstate.{}", uuid);
454 let data_filename = self.hg_vfs().join(data_filename);
455 let mut options = std::fs::OpenOptions::new();
456 if append {
457 options.append(true);
458 } else {
459 options.write(true).create_new(true);
460 }
461 let data_size = (|| {
462 // TODO: loop and try another random ID if !append and this
463 // returns `ErrorKind::AlreadyExists`? Collision chance of two
464 // random IDs is one in 2**32
465 let mut file = options.open(&data_filename)?;
466 file.write_all(&data)?;
467 file.flush()?;
468 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
469 file.seek(SeekFrom::Current(0))
470 })()
471 .when_writing_file(&data_filename)?;
472 DirstateDocket::serialize(
473 parents,
474 tree_metadata,
475 data_size,
476 uuid.as_bytes(),
477 )
478 .map_err(|_: std::num::TryFromIntError| {
479 HgError::corrupted("overflow in dirstate docket serialization")
480 })?
481 } else {
482 map.pack_v1(parents)?
483 };
484 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
485 Ok(())
486 }
419 }
487 }
420
488
421 /// Lazily-initialized component of `Repo` with interior mutability
489 /// Lazily-initialized component of `Repo` with interior mutability
422 ///
490 ///
423 /// This differs from `OnceCell` in that the value can still be "deinitialized"
491 /// This differs from `OnceCell` in that the value can still be "deinitialized"
424 /// later by setting its inner `Option` to `None`.
492 /// later by setting its inner `Option` to `None`.
425 struct LazyCell<T, E> {
493 struct LazyCell<T, E> {
426 value: RefCell<Option<T>>,
494 value: RefCell<Option<T>>,
427 // `Fn`s that don’t capture environment are zero-size, so this box does
495 // `Fn`s that don’t capture environment are zero-size, so this box does
428 // not allocate:
496 // not allocate:
429 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
497 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
430 }
498 }
431
499
432 impl<T, E> LazyCell<T, E> {
500 impl<T, E> LazyCell<T, E> {
433 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
501 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
434 Self {
502 Self {
435 value: RefCell::new(None),
503 value: RefCell::new(None),
436 init: Box::new(init),
504 init: Box::new(init),
437 }
505 }
438 }
506 }
439
507
440 fn set(&self, value: T) {
508 fn set(&self, value: T) {
441 *self.value.borrow_mut() = Some(value)
509 *self.value.borrow_mut() = Some(value)
442 }
510 }
443
511
444 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
512 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
445 let mut borrowed = self.value.borrow();
513 let mut borrowed = self.value.borrow();
446 if borrowed.is_none() {
514 if borrowed.is_none() {
447 drop(borrowed);
515 drop(borrowed);
448 // Only use `borrow_mut` if it is really needed to avoid panic in
516 // Only use `borrow_mut` if it is really needed to avoid panic in
449 // case there is another outstanding borrow but mutation is not
517 // case there is another outstanding borrow but mutation is not
450 // needed.
518 // needed.
451 *self.value.borrow_mut() = Some((self.init)(repo)?);
519 *self.value.borrow_mut() = Some((self.init)(repo)?);
452 borrowed = self.value.borrow()
520 borrowed = self.value.borrow()
453 }
521 }
454 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
522 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
455 }
523 }
456
524
457 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
525 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
458 let mut borrowed = self.value.borrow_mut();
526 let mut borrowed = self.value.borrow_mut();
459 if borrowed.is_none() {
527 if borrowed.is_none() {
460 *borrowed = Some((self.init)(repo)?);
528 *borrowed = Some((self.init)(repo)?);
461 }
529 }
462 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
530 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
463 }
531 }
464 }
532 }
@@ -1,415 +1,421 b''
1 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
2 //
2 //
3 // This software may be used and distributed according to the terms of the
3 // This software may be used and distributed according to the terms of the
4 // GNU General Public License version 2 or any later version.
4 // GNU General Public License version 2 or any later version.
5
5
6 //! Definitions and utilities for Revision nodes
6 //! Definitions and utilities for Revision nodes
7 //!
7 //!
8 //! In Mercurial code base, it is customary to call "a node" the binary SHA
8 //! In Mercurial code base, it is customary to call "a node" the binary SHA
9 //! of a revision.
9 //! of a revision.
10
10
11 use crate::errors::HgError;
11 use crate::errors::HgError;
12 use bytes_cast::BytesCast;
12 use bytes_cast::BytesCast;
13 use std::convert::{TryFrom, TryInto};
13 use std::convert::{TryFrom, TryInto};
14 use std::fmt;
14 use std::fmt;
15
15
16 /// The length in bytes of a `Node`
16 /// The length in bytes of a `Node`
17 ///
17 ///
18 /// This constant is meant to ease refactors of this module, and
18 /// This constant is meant to ease refactors of this module, and
19 /// are private so that calling code does not expect all nodes have
19 /// are private so that calling code does not expect all nodes have
20 /// the same size, should we support several formats concurrently in
20 /// the same size, should we support several formats concurrently in
21 /// the future.
21 /// the future.
22 pub const NODE_BYTES_LENGTH: usize = 20;
22 pub const NODE_BYTES_LENGTH: usize = 20;
23
23
24 /// Id of the null node.
24 /// Id of the null node.
25 ///
25 ///
26 /// Used to indicate the absence of node.
26 /// Used to indicate the absence of node.
27 pub const NULL_NODE_ID: [u8; NODE_BYTES_LENGTH] = [0u8; NODE_BYTES_LENGTH];
27 pub const NULL_NODE_ID: [u8; NODE_BYTES_LENGTH] = [0u8; NODE_BYTES_LENGTH];
28
28
29 /// The length in bytes of a `Node`
29 /// The length in bytes of a `Node`
30 ///
30 ///
31 /// see also `NODES_BYTES_LENGTH` about it being private.
31 /// see also `NODES_BYTES_LENGTH` about it being private.
32 const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH;
32 const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH;
33
33
34 /// Default for UI presentation
34 /// Default for UI presentation
35 const SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH: u8 = 12;
35 const SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH: u8 = 12;
36
36
37 /// Private alias for readability and to ease future change
37 /// Private alias for readability and to ease future change
38 type NodeData = [u8; NODE_BYTES_LENGTH];
38 type NodeData = [u8; NODE_BYTES_LENGTH];
39
39
40 /// Binary revision SHA
40 /// Binary revision SHA
41 ///
41 ///
42 /// ## Future changes of hash size
42 /// ## Future changes of hash size
43 ///
43 ///
44 /// To accomodate future changes of hash size, Rust callers
44 /// To accomodate future changes of hash size, Rust callers
45 /// should use the conversion methods at the boundaries (FFI, actual
45 /// should use the conversion methods at the boundaries (FFI, actual
46 /// computation of hashes and I/O) only, and only if required.
46 /// computation of hashes and I/O) only, and only if required.
47 ///
47 ///
48 /// All other callers outside of unit tests should just handle `Node` values
48 /// All other callers outside of unit tests should just handle `Node` values
49 /// and never make any assumption on the actual length, using [`nybbles_len`]
49 /// and never make any assumption on the actual length, using [`nybbles_len`]
50 /// if they need a loop boundary.
50 /// if they need a loop boundary.
51 ///
51 ///
52 /// All methods that create a `Node` either take a type that enforces
52 /// All methods that create a `Node` either take a type that enforces
53 /// the size or return an error at runtime.
53 /// the size or return an error at runtime.
54 ///
54 ///
55 /// [`nybbles_len`]: #method.nybbles_len
55 /// [`nybbles_len`]: #method.nybbles_len
56 #[derive(Copy, Clone, Debug, PartialEq, BytesCast, derive_more::From)]
56 #[derive(Copy, Clone, Debug, PartialEq, BytesCast, derive_more::From)]
57 #[repr(transparent)]
57 #[repr(transparent)]
58 pub struct Node {
58 pub struct Node {
59 data: NodeData,
59 data: NodeData,
60 }
60 }
61
61
62 /// The node value for NULL_REVISION
62 /// The node value for NULL_REVISION
63 pub const NULL_NODE: Node = Node {
63 pub const NULL_NODE: Node = Node {
64 data: [0; NODE_BYTES_LENGTH],
64 data: [0; NODE_BYTES_LENGTH],
65 };
65 };
66
66
67 /// Return an error if the slice has an unexpected length
67 /// Return an error if the slice has an unexpected length
68 impl<'a> TryFrom<&'a [u8]> for &'a Node {
68 impl<'a> TryFrom<&'a [u8]> for &'a Node {
69 type Error = ();
69 type Error = ();
70
70
71 #[inline]
71 #[inline]
72 fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
72 fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
73 match Node::from_bytes(bytes) {
73 match Node::from_bytes(bytes) {
74 Ok((node, rest)) if rest.is_empty() => Ok(node),
74 Ok((node, rest)) if rest.is_empty() => Ok(node),
75 _ => Err(()),
75 _ => Err(()),
76 }
76 }
77 }
77 }
78 }
78 }
79
79
80 /// Return an error if the slice has an unexpected length
80 /// Return an error if the slice has an unexpected length
81 impl TryFrom<&'_ [u8]> for Node {
81 impl TryFrom<&'_ [u8]> for Node {
82 type Error = std::array::TryFromSliceError;
82 type Error = std::array::TryFromSliceError;
83
83
84 #[inline]
84 #[inline]
85 fn try_from(bytes: &'_ [u8]) -> Result<Self, Self::Error> {
85 fn try_from(bytes: &'_ [u8]) -> Result<Self, Self::Error> {
86 let data = bytes.try_into()?;
86 let data = bytes.try_into()?;
87 Ok(Self { data })
87 Ok(Self { data })
88 }
88 }
89 }
89 }
90
90
91 impl From<&'_ NodeData> for Node {
91 impl From<&'_ NodeData> for Node {
92 #[inline]
92 #[inline]
93 fn from(data: &'_ NodeData) -> Self {
93 fn from(data: &'_ NodeData) -> Self {
94 Self { data: *data }
94 Self { data: *data }
95 }
95 }
96 }
96 }
97
97
98 impl fmt::LowerHex for Node {
98 impl fmt::LowerHex for Node {
99 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100 for &byte in &self.data {
100 for &byte in &self.data {
101 write!(f, "{:02x}", byte)?
101 write!(f, "{:02x}", byte)?
102 }
102 }
103 Ok(())
103 Ok(())
104 }
104 }
105 }
105 }
106
106
107 #[derive(Debug)]
107 #[derive(Debug)]
108 pub struct FromHexError;
108 pub struct FromHexError;
109
109
110 /// Low level utility function, also for prefixes
110 /// Low level utility function, also for prefixes
111 fn get_nybble(s: &[u8], i: usize) -> u8 {
111 fn get_nybble(s: &[u8], i: usize) -> u8 {
112 if i % 2 == 0 {
112 if i % 2 == 0 {
113 s[i / 2] >> 4
113 s[i / 2] >> 4
114 } else {
114 } else {
115 s[i / 2] & 0x0f
115 s[i / 2] & 0x0f
116 }
116 }
117 }
117 }
118
118
119 impl Node {
119 impl Node {
120 /// Retrieve the `i`th half-byte of the binary data.
120 /// Retrieve the `i`th half-byte of the binary data.
121 ///
121 ///
122 /// This is also the `i`th hexadecimal digit in numeric form,
122 /// This is also the `i`th hexadecimal digit in numeric form,
123 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
123 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
124 pub fn get_nybble(&self, i: usize) -> u8 {
124 pub fn get_nybble(&self, i: usize) -> u8 {
125 get_nybble(&self.data, i)
125 get_nybble(&self.data, i)
126 }
126 }
127
127
128 /// Length of the data, in nybbles
128 /// Length of the data, in nybbles
129 pub fn nybbles_len(&self) -> usize {
129 pub fn nybbles_len(&self) -> usize {
130 // public exposure as an instance method only, so that we can
130 // public exposure as an instance method only, so that we can
131 // easily support several sizes of hashes if needed in the future.
131 // easily support several sizes of hashes if needed in the future.
132 NODE_NYBBLES_LENGTH
132 NODE_NYBBLES_LENGTH
133 }
133 }
134
134
135 /// Convert from hexadecimal string representation
135 /// Convert from hexadecimal string representation
136 ///
136 ///
137 /// Exact length is required.
137 /// Exact length is required.
138 ///
138 ///
139 /// To be used in FFI and I/O only, in order to facilitate future
139 /// To be used in FFI and I/O only, in order to facilitate future
140 /// changes of hash format.
140 /// changes of hash format.
141 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, FromHexError> {
141 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, FromHexError> {
142 let prefix = NodePrefix::from_hex(hex)?;
142 let prefix = NodePrefix::from_hex(hex)?;
143 if prefix.nybbles_len() == NODE_NYBBLES_LENGTH {
143 if prefix.nybbles_len() == NODE_NYBBLES_LENGTH {
144 Ok(Self { data: prefix.data })
144 Ok(Self { data: prefix.data })
145 } else {
145 } else {
146 Err(FromHexError)
146 Err(FromHexError)
147 }
147 }
148 }
148 }
149
149
150 /// `from_hex`, but for input from an internal file of the repository such
150 /// `from_hex`, but for input from an internal file of the repository such
151 /// as a changelog or manifest entry.
151 /// as a changelog or manifest entry.
152 ///
152 ///
153 /// An error is treated as repository corruption.
153 /// An error is treated as repository corruption.
154 pub fn from_hex_for_repo(hex: impl AsRef<[u8]>) -> Result<Node, HgError> {
154 pub fn from_hex_for_repo(hex: impl AsRef<[u8]>) -> Result<Node, HgError> {
155 Self::from_hex(hex.as_ref()).map_err(|FromHexError| {
155 Self::from_hex(hex.as_ref()).map_err(|FromHexError| {
156 HgError::CorruptedRepository(format!(
156 HgError::CorruptedRepository(format!(
157 "Expected a full hexadecimal node ID, found {}",
157 "Expected a full hexadecimal node ID, found {}",
158 String::from_utf8_lossy(hex.as_ref())
158 String::from_utf8_lossy(hex.as_ref())
159 ))
159 ))
160 })
160 })
161 }
161 }
162
162
163 /// Provide access to binary data
163 /// Provide access to binary data
164 ///
164 ///
165 /// This is needed by FFI layers, for instance to return expected
165 /// This is needed by FFI layers, for instance to return expected
166 /// binary values to Python.
166 /// binary values to Python.
167 pub fn as_bytes(&self) -> &[u8] {
167 pub fn as_bytes(&self) -> &[u8] {
168 &self.data
168 &self.data
169 }
169 }
170
170
171 pub fn short(&self) -> NodePrefix {
171 pub fn short(&self) -> NodePrefix {
172 NodePrefix {
172 NodePrefix {
173 nybbles_len: SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH,
173 nybbles_len: SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH,
174 data: self.data,
174 data: self.data,
175 }
175 }
176 }
176 }
177
178 pub fn pad_to_256_bits(&self) -> [u8; 32] {
179 let mut bits = [0; 32];
180 bits[..NODE_BYTES_LENGTH].copy_from_slice(&self.data);
181 bits
182 }
177 }
183 }
178
184
179 /// The beginning of a binary revision SHA.
185 /// The beginning of a binary revision SHA.
180 ///
186 ///
181 /// Since it can potentially come from an hexadecimal representation with
187 /// Since it can potentially come from an hexadecimal representation with
182 /// odd length, it needs to carry around whether the last 4 bits are relevant
188 /// odd length, it needs to carry around whether the last 4 bits are relevant
183 /// or not.
189 /// or not.
184 #[derive(Debug, PartialEq, Copy, Clone)]
190 #[derive(Debug, PartialEq, Copy, Clone)]
185 pub struct NodePrefix {
191 pub struct NodePrefix {
186 /// In `1..=NODE_NYBBLES_LENGTH`
192 /// In `1..=NODE_NYBBLES_LENGTH`
187 nybbles_len: u8,
193 nybbles_len: u8,
188 /// The first `4 * length_in_nybbles` bits are used (considering bits
194 /// The first `4 * length_in_nybbles` bits are used (considering bits
189 /// within a bytes in big-endian: most significant first), the rest
195 /// within a bytes in big-endian: most significant first), the rest
190 /// are zero.
196 /// are zero.
191 data: NodeData,
197 data: NodeData,
192 }
198 }
193
199
194 impl NodePrefix {
200 impl NodePrefix {
195 /// Convert from hexadecimal string representation
201 /// Convert from hexadecimal string representation
196 ///
202 ///
197 /// Similarly to `hex::decode`, can be used with Unicode string types
203 /// Similarly to `hex::decode`, can be used with Unicode string types
198 /// (`String`, `&str`) as well as bytes.
204 /// (`String`, `&str`) as well as bytes.
199 ///
205 ///
200 /// To be used in FFI and I/O only, in order to facilitate future
206 /// To be used in FFI and I/O only, in order to facilitate future
201 /// changes of hash format.
207 /// changes of hash format.
202 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, FromHexError> {
208 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, FromHexError> {
203 let hex = hex.as_ref();
209 let hex = hex.as_ref();
204 let len = hex.len();
210 let len = hex.len();
205 if len > NODE_NYBBLES_LENGTH || len == 0 {
211 if len > NODE_NYBBLES_LENGTH || len == 0 {
206 return Err(FromHexError);
212 return Err(FromHexError);
207 }
213 }
208
214
209 let mut data = [0; NODE_BYTES_LENGTH];
215 let mut data = [0; NODE_BYTES_LENGTH];
210 let mut nybbles_len = 0;
216 let mut nybbles_len = 0;
211 for &ascii_byte in hex {
217 for &ascii_byte in hex {
212 let nybble = match char::from(ascii_byte).to_digit(16) {
218 let nybble = match char::from(ascii_byte).to_digit(16) {
213 Some(digit) => digit as u8,
219 Some(digit) => digit as u8,
214 None => return Err(FromHexError),
220 None => return Err(FromHexError),
215 };
221 };
216 // Fill in the upper half of a byte first, then the lower half.
222 // Fill in the upper half of a byte first, then the lower half.
217 let shift = if nybbles_len % 2 == 0 { 4 } else { 0 };
223 let shift = if nybbles_len % 2 == 0 { 4 } else { 0 };
218 data[nybbles_len as usize / 2] |= nybble << shift;
224 data[nybbles_len as usize / 2] |= nybble << shift;
219 nybbles_len += 1;
225 nybbles_len += 1;
220 }
226 }
221 Ok(Self { data, nybbles_len })
227 Ok(Self { data, nybbles_len })
222 }
228 }
223
229
224 pub fn nybbles_len(&self) -> usize {
230 pub fn nybbles_len(&self) -> usize {
225 self.nybbles_len as _
231 self.nybbles_len as _
226 }
232 }
227
233
228 pub fn is_prefix_of(&self, node: &Node) -> bool {
234 pub fn is_prefix_of(&self, node: &Node) -> bool {
229 let full_bytes = self.nybbles_len() / 2;
235 let full_bytes = self.nybbles_len() / 2;
230 if self.data[..full_bytes] != node.data[..full_bytes] {
236 if self.data[..full_bytes] != node.data[..full_bytes] {
231 return false;
237 return false;
232 }
238 }
233 if self.nybbles_len() % 2 == 0 {
239 if self.nybbles_len() % 2 == 0 {
234 return true;
240 return true;
235 }
241 }
236 let last = self.nybbles_len() - 1;
242 let last = self.nybbles_len() - 1;
237 self.get_nybble(last) == node.get_nybble(last)
243 self.get_nybble(last) == node.get_nybble(last)
238 }
244 }
239
245
240 /// Retrieve the `i`th half-byte from the prefix.
246 /// Retrieve the `i`th half-byte from the prefix.
241 ///
247 ///
242 /// This is also the `i`th hexadecimal digit in numeric form,
248 /// This is also the `i`th hexadecimal digit in numeric form,
243 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
249 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
244 pub fn get_nybble(&self, i: usize) -> u8 {
250 pub fn get_nybble(&self, i: usize) -> u8 {
245 assert!(i < self.nybbles_len());
251 assert!(i < self.nybbles_len());
246 get_nybble(&self.data, i)
252 get_nybble(&self.data, i)
247 }
253 }
248
254
249 fn iter_nybbles(&self) -> impl Iterator<Item = u8> + '_ {
255 fn iter_nybbles(&self) -> impl Iterator<Item = u8> + '_ {
250 (0..self.nybbles_len()).map(move |i| get_nybble(&self.data, i))
256 (0..self.nybbles_len()).map(move |i| get_nybble(&self.data, i))
251 }
257 }
252
258
253 /// Return the index first nybble that's different from `node`
259 /// Return the index first nybble that's different from `node`
254 ///
260 ///
255 /// If the return value is `None` that means that `self` is
261 /// If the return value is `None` that means that `self` is
256 /// a prefix of `node`, but the current method is a bit slower
262 /// a prefix of `node`, but the current method is a bit slower
257 /// than `is_prefix_of`.
263 /// than `is_prefix_of`.
258 ///
264 ///
259 /// Returned index is as in `get_nybble`, i.e., starting at 0.
265 /// Returned index is as in `get_nybble`, i.e., starting at 0.
260 pub fn first_different_nybble(&self, node: &Node) -> Option<usize> {
266 pub fn first_different_nybble(&self, node: &Node) -> Option<usize> {
261 self.iter_nybbles()
267 self.iter_nybbles()
262 .zip(NodePrefix::from(*node).iter_nybbles())
268 .zip(NodePrefix::from(*node).iter_nybbles())
263 .position(|(a, b)| a != b)
269 .position(|(a, b)| a != b)
264 }
270 }
265 }
271 }
266
272
267 impl fmt::LowerHex for NodePrefix {
273 impl fmt::LowerHex for NodePrefix {
268 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
274 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
269 let full_bytes = self.nybbles_len() / 2;
275 let full_bytes = self.nybbles_len() / 2;
270 for &byte in &self.data[..full_bytes] {
276 for &byte in &self.data[..full_bytes] {
271 write!(f, "{:02x}", byte)?
277 write!(f, "{:02x}", byte)?
272 }
278 }
273 if self.nybbles_len() % 2 == 1 {
279 if self.nybbles_len() % 2 == 1 {
274 let last = self.nybbles_len() - 1;
280 let last = self.nybbles_len() - 1;
275 write!(f, "{:x}", self.get_nybble(last))?
281 write!(f, "{:x}", self.get_nybble(last))?
276 }
282 }
277 Ok(())
283 Ok(())
278 }
284 }
279 }
285 }
280
286
281 /// A shortcut for full `Node` references
287 /// A shortcut for full `Node` references
282 impl From<&'_ Node> for NodePrefix {
288 impl From<&'_ Node> for NodePrefix {
283 fn from(node: &'_ Node) -> Self {
289 fn from(node: &'_ Node) -> Self {
284 NodePrefix {
290 NodePrefix {
285 nybbles_len: node.nybbles_len() as _,
291 nybbles_len: node.nybbles_len() as _,
286 data: node.data,
292 data: node.data,
287 }
293 }
288 }
294 }
289 }
295 }
290
296
291 /// A shortcut for full `Node` references
297 /// A shortcut for full `Node` references
292 impl From<Node> for NodePrefix {
298 impl From<Node> for NodePrefix {
293 fn from(node: Node) -> Self {
299 fn from(node: Node) -> Self {
294 NodePrefix {
300 NodePrefix {
295 nybbles_len: node.nybbles_len() as _,
301 nybbles_len: node.nybbles_len() as _,
296 data: node.data,
302 data: node.data,
297 }
303 }
298 }
304 }
299 }
305 }
300
306
301 impl PartialEq<Node> for NodePrefix {
307 impl PartialEq<Node> for NodePrefix {
302 fn eq(&self, other: &Node) -> bool {
308 fn eq(&self, other: &Node) -> bool {
303 Self::from(*other) == *self
309 Self::from(*other) == *self
304 }
310 }
305 }
311 }
306
312
307 #[cfg(test)]
313 #[cfg(test)]
308 mod tests {
314 mod tests {
309 use super::*;
315 use super::*;
310
316
311 const SAMPLE_NODE_HEX: &str = "0123456789abcdeffedcba9876543210deadbeef";
317 const SAMPLE_NODE_HEX: &str = "0123456789abcdeffedcba9876543210deadbeef";
312 const SAMPLE_NODE: Node = Node {
318 const SAMPLE_NODE: Node = Node {
313 data: [
319 data: [
314 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
320 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
315 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef,
321 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef,
316 ],
322 ],
317 };
323 };
318
324
319 /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH`
325 /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH`
320 /// The padding is made with zeros.
326 /// The padding is made with zeros.
321 pub fn hex_pad_right(hex: &str) -> String {
327 pub fn hex_pad_right(hex: &str) -> String {
322 let mut res = hex.to_string();
328 let mut res = hex.to_string();
323 while res.len() < NODE_NYBBLES_LENGTH {
329 while res.len() < NODE_NYBBLES_LENGTH {
324 res.push('0');
330 res.push('0');
325 }
331 }
326 res
332 res
327 }
333 }
328
334
329 #[test]
335 #[test]
330 fn test_node_from_hex() {
336 fn test_node_from_hex() {
331 let not_hex = "012... oops";
337 let not_hex = "012... oops";
332 let too_short = "0123";
338 let too_short = "0123";
333 let too_long = format!("{}0", SAMPLE_NODE_HEX);
339 let too_long = format!("{}0", SAMPLE_NODE_HEX);
334 assert_eq!(Node::from_hex(SAMPLE_NODE_HEX).unwrap(), SAMPLE_NODE);
340 assert_eq!(Node::from_hex(SAMPLE_NODE_HEX).unwrap(), SAMPLE_NODE);
335 assert!(Node::from_hex(not_hex).is_err());
341 assert!(Node::from_hex(not_hex).is_err());
336 assert!(Node::from_hex(too_short).is_err());
342 assert!(Node::from_hex(too_short).is_err());
337 assert!(Node::from_hex(&too_long).is_err());
343 assert!(Node::from_hex(&too_long).is_err());
338 }
344 }
339
345
340 #[test]
346 #[test]
341 fn test_node_encode_hex() {
347 fn test_node_encode_hex() {
342 assert_eq!(format!("{:x}", SAMPLE_NODE), SAMPLE_NODE_HEX);
348 assert_eq!(format!("{:x}", SAMPLE_NODE), SAMPLE_NODE_HEX);
343 }
349 }
344
350
345 #[test]
351 #[test]
346 fn test_prefix_from_to_hex() -> Result<(), FromHexError> {
352 fn test_prefix_from_to_hex() -> Result<(), FromHexError> {
347 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1")?), "0e1");
353 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1")?), "0e1");
348 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1a")?), "0e1a");
354 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1a")?), "0e1a");
349 assert_eq!(
355 assert_eq!(
350 format!("{:x}", NodePrefix::from_hex(SAMPLE_NODE_HEX)?),
356 format!("{:x}", NodePrefix::from_hex(SAMPLE_NODE_HEX)?),
351 SAMPLE_NODE_HEX
357 SAMPLE_NODE_HEX
352 );
358 );
353 Ok(())
359 Ok(())
354 }
360 }
355
361
356 #[test]
362 #[test]
357 fn test_prefix_from_hex_errors() {
363 fn test_prefix_from_hex_errors() {
358 assert!(NodePrefix::from_hex("testgr").is_err());
364 assert!(NodePrefix::from_hex("testgr").is_err());
359 let mut long = format!("{:x}", NULL_NODE);
365 let mut long = format!("{:x}", NULL_NODE);
360 long.push('c');
366 long.push('c');
361 assert!(NodePrefix::from_hex(&long).is_err())
367 assert!(NodePrefix::from_hex(&long).is_err())
362 }
368 }
363
369
364 #[test]
370 #[test]
365 fn test_is_prefix_of() -> Result<(), FromHexError> {
371 fn test_is_prefix_of() -> Result<(), FromHexError> {
366 let mut node_data = [0; NODE_BYTES_LENGTH];
372 let mut node_data = [0; NODE_BYTES_LENGTH];
367 node_data[0] = 0x12;
373 node_data[0] = 0x12;
368 node_data[1] = 0xca;
374 node_data[1] = 0xca;
369 let node = Node::from(node_data);
375 let node = Node::from(node_data);
370 assert!(NodePrefix::from_hex("12")?.is_prefix_of(&node));
376 assert!(NodePrefix::from_hex("12")?.is_prefix_of(&node));
371 assert!(!NodePrefix::from_hex("1a")?.is_prefix_of(&node));
377 assert!(!NodePrefix::from_hex("1a")?.is_prefix_of(&node));
372 assert!(NodePrefix::from_hex("12c")?.is_prefix_of(&node));
378 assert!(NodePrefix::from_hex("12c")?.is_prefix_of(&node));
373 assert!(!NodePrefix::from_hex("12d")?.is_prefix_of(&node));
379 assert!(!NodePrefix::from_hex("12d")?.is_prefix_of(&node));
374 Ok(())
380 Ok(())
375 }
381 }
376
382
377 #[test]
383 #[test]
378 fn test_get_nybble() -> Result<(), FromHexError> {
384 fn test_get_nybble() -> Result<(), FromHexError> {
379 let prefix = NodePrefix::from_hex("dead6789cafe")?;
385 let prefix = NodePrefix::from_hex("dead6789cafe")?;
380 assert_eq!(prefix.get_nybble(0), 13);
386 assert_eq!(prefix.get_nybble(0), 13);
381 assert_eq!(prefix.get_nybble(7), 9);
387 assert_eq!(prefix.get_nybble(7), 9);
382 Ok(())
388 Ok(())
383 }
389 }
384
390
385 #[test]
391 #[test]
386 fn test_first_different_nybble_even_prefix() {
392 fn test_first_different_nybble_even_prefix() {
387 let prefix = NodePrefix::from_hex("12ca").unwrap();
393 let prefix = NodePrefix::from_hex("12ca").unwrap();
388 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
394 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
389 assert_eq!(prefix.first_different_nybble(&node), Some(0));
395 assert_eq!(prefix.first_different_nybble(&node), Some(0));
390 node.data[0] = 0x13;
396 node.data[0] = 0x13;
391 assert_eq!(prefix.first_different_nybble(&node), Some(1));
397 assert_eq!(prefix.first_different_nybble(&node), Some(1));
392 node.data[0] = 0x12;
398 node.data[0] = 0x12;
393 assert_eq!(prefix.first_different_nybble(&node), Some(2));
399 assert_eq!(prefix.first_different_nybble(&node), Some(2));
394 node.data[1] = 0xca;
400 node.data[1] = 0xca;
395 // now it is a prefix
401 // now it is a prefix
396 assert_eq!(prefix.first_different_nybble(&node), None);
402 assert_eq!(prefix.first_different_nybble(&node), None);
397 }
403 }
398
404
399 #[test]
405 #[test]
400 fn test_first_different_nybble_odd_prefix() {
406 fn test_first_different_nybble_odd_prefix() {
401 let prefix = NodePrefix::from_hex("12c").unwrap();
407 let prefix = NodePrefix::from_hex("12c").unwrap();
402 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
408 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
403 assert_eq!(prefix.first_different_nybble(&node), Some(0));
409 assert_eq!(prefix.first_different_nybble(&node), Some(0));
404 node.data[0] = 0x13;
410 node.data[0] = 0x13;
405 assert_eq!(prefix.first_different_nybble(&node), Some(1));
411 assert_eq!(prefix.first_different_nybble(&node), Some(1));
406 node.data[0] = 0x12;
412 node.data[0] = 0x12;
407 assert_eq!(prefix.first_different_nybble(&node), Some(2));
413 assert_eq!(prefix.first_different_nybble(&node), Some(2));
408 node.data[1] = 0xca;
414 node.data[1] = 0xca;
409 // now it is a prefix
415 // now it is a prefix
410 assert_eq!(prefix.first_different_nybble(&node), None);
416 assert_eq!(prefix.first_different_nybble(&node), None);
411 }
417 }
412 }
418 }
413
419
414 #[cfg(test)]
420 #[cfg(test)]
415 pub use tests::hex_pad_right;
421 pub use tests::hex_pad_right;
@@ -1,499 +1,499 b''
1 // dirstate_map.rs
1 // dirstate_map.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@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 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::{RefCell, RefMut};
11 use std::cell::{RefCell, RefMut};
12 use std::convert::TryInto;
12 use std::convert::TryInto;
13
13
14 use cpython::{
14 use cpython::{
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
17 };
17 };
18
18
19 use crate::{
19 use crate::{
20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
21 dirstate::item::DirstateItem,
21 dirstate::item::DirstateItem,
22 pybytes_deref::PyBytesDeref,
22 pybytes_deref::PyBytesDeref,
23 };
23 };
24 use hg::{
24 use hg::{
25 dirstate::StateMapIter,
25 dirstate::StateMapIter,
26 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
26 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
27 dirstate_tree::on_disk::DirstateV2ParseError,
27 dirstate_tree::on_disk::DirstateV2ParseError,
28 dirstate_tree::owning::OwningDirstateMap,
28 dirstate_tree::owning::OwningDirstateMap,
29 revlog::Node,
29 revlog::Node,
30 utils::files::normalize_case,
30 utils::files::normalize_case,
31 utils::hg_path::{HgPath, HgPathBuf},
31 utils::hg_path::{HgPath, HgPathBuf},
32 DirstateEntry, DirstateError, DirstateParents, EntryState,
32 DirstateEntry, DirstateError, DirstateParents, EntryState,
33 };
33 };
34
34
35 // TODO
35 // TODO
36 // This object needs to share references to multiple members of its Rust
36 // This object needs to share references to multiple members of its Rust
37 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
37 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
38 // Right now `CopyMap` is done, but it needs to have an explicit reference
38 // Right now `CopyMap` is done, but it needs to have an explicit reference
39 // to `RustDirstateMap` which itself needs to have an encapsulation for
39 // to `RustDirstateMap` which itself needs to have an encapsulation for
40 // every method in `CopyMap` (copymapcopy, etc.).
40 // every method in `CopyMap` (copymapcopy, etc.).
41 // This is ugly and hard to maintain.
41 // This is ugly and hard to maintain.
42 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
42 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
43 // `py_class!` is already implemented and does not mention
43 // `py_class!` is already implemented and does not mention
44 // `RustDirstateMap`, rightfully so.
44 // `RustDirstateMap`, rightfully so.
45 // All attributes also have to have a separate refcount data attribute for
45 // All attributes also have to have a separate refcount data attribute for
46 // leaks, with all methods that go along for reference sharing.
46 // leaks, with all methods that go along for reference sharing.
47 py_class!(pub class DirstateMap |py| {
47 py_class!(pub class DirstateMap |py| {
48 @shared data inner: OwningDirstateMap;
48 @shared data inner: OwningDirstateMap;
49
49
50 /// Returns a `(dirstate_map, parents)` tuple
50 /// Returns a `(dirstate_map, parents)` tuple
51 @staticmethod
51 @staticmethod
52 def new_v1(
52 def new_v1(
53 on_disk: PyBytes,
53 on_disk: PyBytes,
54 ) -> PyResult<PyObject> {
54 ) -> PyResult<PyObject> {
55 let on_disk = PyBytesDeref::new(py, on_disk);
55 let on_disk = PyBytesDeref::new(py, on_disk);
56 let mut map = OwningDirstateMap::new_empty(on_disk);
56 let mut map = OwningDirstateMap::new_empty(on_disk);
57 let (on_disk, map_placeholder) = map.get_pair_mut();
57 let (on_disk, map_placeholder) = map.get_pair_mut();
58
58
59 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
59 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
60 .map_err(|e| dirstate_error(py, e))?;
60 .map_err(|e| dirstate_error(py, e))?;
61 *map_placeholder = actual_map;
61 *map_placeholder = actual_map;
62 let map = Self::create_instance(py, map)?;
62 let map = Self::create_instance(py, map)?;
63 let parents = parents.map(|p| {
63 let parents = parents.map(|p| {
64 let p1 = PyBytes::new(py, p.p1.as_bytes());
64 let p1 = PyBytes::new(py, p.p1.as_bytes());
65 let p2 = PyBytes::new(py, p.p2.as_bytes());
65 let p2 = PyBytes::new(py, p.p2.as_bytes());
66 (p1, p2)
66 (p1, p2)
67 });
67 });
68 Ok((map, parents).to_py_object(py).into_object())
68 Ok((map, parents).to_py_object(py).into_object())
69 }
69 }
70
70
71 /// Returns a DirstateMap
71 /// Returns a DirstateMap
72 @staticmethod
72 @staticmethod
73 def new_v2(
73 def new_v2(
74 on_disk: PyBytes,
74 on_disk: PyBytes,
75 data_size: usize,
75 data_size: usize,
76 tree_metadata: PyBytes,
76 tree_metadata: PyBytes,
77 ) -> PyResult<PyObject> {
77 ) -> PyResult<PyObject> {
78 let dirstate_error = |e: DirstateError| {
78 let dirstate_error = |e: DirstateError| {
79 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
79 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
80 };
80 };
81 let on_disk = PyBytesDeref::new(py, on_disk);
81 let on_disk = PyBytesDeref::new(py, on_disk);
82 let mut map = OwningDirstateMap::new_empty(on_disk);
82 let mut map = OwningDirstateMap::new_empty(on_disk);
83 let (on_disk, map_placeholder) = map.get_pair_mut();
83 let (on_disk, map_placeholder) = map.get_pair_mut();
84 *map_placeholder = TreeDirstateMap::new_v2(
84 *map_placeholder = TreeDirstateMap::new_v2(
85 on_disk, data_size, tree_metadata.data(py),
85 on_disk, data_size, tree_metadata.data(py),
86 ).map_err(dirstate_error)?;
86 ).map_err(dirstate_error)?;
87 let map = Self::create_instance(py, map)?;
87 let map = Self::create_instance(py, map)?;
88 Ok(map.into_object())
88 Ok(map.into_object())
89 }
89 }
90
90
91 def clear(&self) -> PyResult<PyObject> {
91 def clear(&self) -> PyResult<PyObject> {
92 self.inner(py).borrow_mut().clear();
92 self.inner(py).borrow_mut().clear();
93 Ok(py.None())
93 Ok(py.None())
94 }
94 }
95
95
96 def get(
96 def get(
97 &self,
97 &self,
98 key: PyObject,
98 key: PyObject,
99 default: Option<PyObject> = None
99 default: Option<PyObject> = None
100 ) -> PyResult<Option<PyObject>> {
100 ) -> PyResult<Option<PyObject>> {
101 let key = key.extract::<PyBytes>(py)?;
101 let key = key.extract::<PyBytes>(py)?;
102 match self
102 match self
103 .inner(py)
103 .inner(py)
104 .borrow()
104 .borrow()
105 .get(HgPath::new(key.data(py)))
105 .get(HgPath::new(key.data(py)))
106 .map_err(|e| v2_error(py, e))?
106 .map_err(|e| v2_error(py, e))?
107 {
107 {
108 Some(entry) => {
108 Some(entry) => {
109 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
109 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
110 },
110 },
111 None => Ok(default)
111 None => Ok(default)
112 }
112 }
113 }
113 }
114
114
115 def set_dirstate_item(
115 def set_dirstate_item(
116 &self,
116 &self,
117 path: PyObject,
117 path: PyObject,
118 item: DirstateItem
118 item: DirstateItem
119 ) -> PyResult<PyObject> {
119 ) -> PyResult<PyObject> {
120 let f = path.extract::<PyBytes>(py)?;
120 let f = path.extract::<PyBytes>(py)?;
121 let filename = HgPath::new(f.data(py));
121 let filename = HgPath::new(f.data(py));
122 self.inner(py)
122 self.inner(py)
123 .borrow_mut()
123 .borrow_mut()
124 .set_entry(filename, item.get_entry(py))
124 .set_entry(filename, item.get_entry(py))
125 .map_err(|e| v2_error(py, e))?;
125 .map_err(|e| v2_error(py, e))?;
126 Ok(py.None())
126 Ok(py.None())
127 }
127 }
128
128
129 def addfile(
129 def addfile(
130 &self,
130 &self,
131 f: PyBytes,
131 f: PyBytes,
132 item: DirstateItem,
132 item: DirstateItem,
133 ) -> PyResult<PyNone> {
133 ) -> PyResult<PyNone> {
134 let filename = HgPath::new(f.data(py));
134 let filename = HgPath::new(f.data(py));
135 let entry = item.get_entry(py);
135 let entry = item.get_entry(py);
136 self.inner(py)
136 self.inner(py)
137 .borrow_mut()
137 .borrow_mut()
138 .add_file(filename, entry)
138 .add_file(filename, entry)
139 .map_err(|e |dirstate_error(py, e))?;
139 .map_err(|e |dirstate_error(py, e))?;
140 Ok(PyNone)
140 Ok(PyNone)
141 }
141 }
142
142
143 def removefile(
143 def removefile(
144 &self,
144 &self,
145 f: PyObject,
145 f: PyObject,
146 in_merge: PyObject
146 in_merge: PyObject
147 ) -> PyResult<PyObject> {
147 ) -> PyResult<PyObject> {
148 self.inner(py).borrow_mut()
148 self.inner(py).borrow_mut()
149 .remove_file(
149 .remove_file(
150 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
150 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
151 in_merge.extract::<PyBool>(py)?.is_true(),
151 in_merge.extract::<PyBool>(py)?.is_true(),
152 )
152 )
153 .or_else(|_| {
153 .or_else(|_| {
154 Err(PyErr::new::<exc::OSError, _>(
154 Err(PyErr::new::<exc::OSError, _>(
155 py,
155 py,
156 "Dirstate error".to_string(),
156 "Dirstate error".to_string(),
157 ))
157 ))
158 })?;
158 })?;
159 Ok(py.None())
159 Ok(py.None())
160 }
160 }
161
161
162 def drop_item_and_copy_source(
162 def drop_item_and_copy_source(
163 &self,
163 &self,
164 f: PyBytes,
164 f: PyBytes,
165 ) -> PyResult<PyNone> {
165 ) -> PyResult<PyNone> {
166 self.inner(py)
166 self.inner(py)
167 .borrow_mut()
167 .borrow_mut()
168 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
168 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
169 .map_err(|e |dirstate_error(py, e))?;
169 .map_err(|e |dirstate_error(py, e))?;
170 Ok(PyNone)
170 Ok(PyNone)
171 }
171 }
172
172
173 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
173 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
174 let d = d.extract::<PyBytes>(py)?;
174 let d = d.extract::<PyBytes>(py)?;
175 Ok(self.inner(py).borrow_mut()
175 Ok(self.inner(py).borrow_mut()
176 .has_tracked_dir(HgPath::new(d.data(py)))
176 .has_tracked_dir(HgPath::new(d.data(py)))
177 .map_err(|e| {
177 .map_err(|e| {
178 PyErr::new::<exc::ValueError, _>(py, e.to_string())
178 PyErr::new::<exc::ValueError, _>(py, e.to_string())
179 })?
179 })?
180 .to_py_object(py))
180 .to_py_object(py))
181 }
181 }
182
182
183 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
183 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
184 let d = d.extract::<PyBytes>(py)?;
184 let d = d.extract::<PyBytes>(py)?;
185 Ok(self.inner(py).borrow_mut()
185 Ok(self.inner(py).borrow_mut()
186 .has_dir(HgPath::new(d.data(py)))
186 .has_dir(HgPath::new(d.data(py)))
187 .map_err(|e| {
187 .map_err(|e| {
188 PyErr::new::<exc::ValueError, _>(py, e.to_string())
188 PyErr::new::<exc::ValueError, _>(py, e.to_string())
189 })?
189 })?
190 .to_py_object(py))
190 .to_py_object(py))
191 }
191 }
192
192
193 def write_v1(
193 def write_v1(
194 &self,
194 &self,
195 p1: PyObject,
195 p1: PyObject,
196 p2: PyObject,
196 p2: PyObject,
197 ) -> PyResult<PyBytes> {
197 ) -> PyResult<PyBytes> {
198 let inner = self.inner(py).borrow();
198 let inner = self.inner(py).borrow();
199 let parents = DirstateParents {
199 let parents = DirstateParents {
200 p1: extract_node_id(py, &p1)?,
200 p1: extract_node_id(py, &p1)?,
201 p2: extract_node_id(py, &p2)?,
201 p2: extract_node_id(py, &p2)?,
202 };
202 };
203 let result = inner.pack_v1(parents);
203 let result = inner.pack_v1(parents);
204 match result {
204 match result {
205 Ok(packed) => Ok(PyBytes::new(py, &packed)),
205 Ok(packed) => Ok(PyBytes::new(py, &packed)),
206 Err(_) => Err(PyErr::new::<exc::OSError, _>(
206 Err(_) => Err(PyErr::new::<exc::OSError, _>(
207 py,
207 py,
208 "Dirstate error".to_string(),
208 "Dirstate error".to_string(),
209 )),
209 )),
210 }
210 }
211 }
211 }
212
212
213 /// Returns new data together with whether that data should be appended to
213 /// Returns new data together with whether that data should be appended to
214 /// the existing data file whose content is at `self.on_disk` (True),
214 /// the existing data file whose content is at `self.on_disk` (True),
215 /// instead of written to a new data file (False).
215 /// instead of written to a new data file (False).
216 def write_v2(
216 def write_v2(
217 &self,
217 &self,
218 can_append: bool,
218 can_append: bool,
219 ) -> PyResult<PyObject> {
219 ) -> PyResult<PyObject> {
220 let inner = self.inner(py).borrow();
220 let inner = self.inner(py).borrow();
221 let result = inner.pack_v2(can_append);
221 let result = inner.pack_v2(can_append);
222 match result {
222 match result {
223 Ok((packed, tree_metadata, append)) => {
223 Ok((packed, tree_metadata, append)) => {
224 let packed = PyBytes::new(py, &packed);
224 let packed = PyBytes::new(py, &packed);
225 let tree_metadata = PyBytes::new(py, &tree_metadata);
225 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
226 let tuple = (packed, tree_metadata, append);
226 let tuple = (packed, tree_metadata, append);
227 Ok(tuple.to_py_object(py).into_object())
227 Ok(tuple.to_py_object(py).into_object())
228 },
228 },
229 Err(_) => Err(PyErr::new::<exc::OSError, _>(
229 Err(_) => Err(PyErr::new::<exc::OSError, _>(
230 py,
230 py,
231 "Dirstate error".to_string(),
231 "Dirstate error".to_string(),
232 )),
232 )),
233 }
233 }
234 }
234 }
235
235
236 def filefoldmapasdict(&self) -> PyResult<PyDict> {
236 def filefoldmapasdict(&self) -> PyResult<PyDict> {
237 let dict = PyDict::new(py);
237 let dict = PyDict::new(py);
238 for item in self.inner(py).borrow_mut().iter() {
238 for item in self.inner(py).borrow_mut().iter() {
239 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
239 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
240 if entry.state() != EntryState::Removed {
240 if entry.state() != EntryState::Removed {
241 let key = normalize_case(path);
241 let key = normalize_case(path);
242 let value = path;
242 let value = path;
243 dict.set_item(
243 dict.set_item(
244 py,
244 py,
245 PyBytes::new(py, key.as_bytes()).into_object(),
245 PyBytes::new(py, key.as_bytes()).into_object(),
246 PyBytes::new(py, value.as_bytes()).into_object(),
246 PyBytes::new(py, value.as_bytes()).into_object(),
247 )?;
247 )?;
248 }
248 }
249 }
249 }
250 Ok(dict)
250 Ok(dict)
251 }
251 }
252
252
253 def __len__(&self) -> PyResult<usize> {
253 def __len__(&self) -> PyResult<usize> {
254 Ok(self.inner(py).borrow().len())
254 Ok(self.inner(py).borrow().len())
255 }
255 }
256
256
257 def __contains__(&self, key: PyObject) -> PyResult<bool> {
257 def __contains__(&self, key: PyObject) -> PyResult<bool> {
258 let key = key.extract::<PyBytes>(py)?;
258 let key = key.extract::<PyBytes>(py)?;
259 self.inner(py)
259 self.inner(py)
260 .borrow()
260 .borrow()
261 .contains_key(HgPath::new(key.data(py)))
261 .contains_key(HgPath::new(key.data(py)))
262 .map_err(|e| v2_error(py, e))
262 .map_err(|e| v2_error(py, e))
263 }
263 }
264
264
265 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
265 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
266 let key = key.extract::<PyBytes>(py)?;
266 let key = key.extract::<PyBytes>(py)?;
267 let key = HgPath::new(key.data(py));
267 let key = HgPath::new(key.data(py));
268 match self
268 match self
269 .inner(py)
269 .inner(py)
270 .borrow()
270 .borrow()
271 .get(key)
271 .get(key)
272 .map_err(|e| v2_error(py, e))?
272 .map_err(|e| v2_error(py, e))?
273 {
273 {
274 Some(entry) => {
274 Some(entry) => {
275 Ok(DirstateItem::new_as_pyobject(py, entry)?)
275 Ok(DirstateItem::new_as_pyobject(py, entry)?)
276 },
276 },
277 None => Err(PyErr::new::<exc::KeyError, _>(
277 None => Err(PyErr::new::<exc::KeyError, _>(
278 py,
278 py,
279 String::from_utf8_lossy(key.as_bytes()),
279 String::from_utf8_lossy(key.as_bytes()),
280 )),
280 )),
281 }
281 }
282 }
282 }
283
283
284 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
284 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
285 let leaked_ref = self.inner(py).leak_immutable();
285 let leaked_ref = self.inner(py).leak_immutable();
286 DirstateMapKeysIterator::from_inner(
286 DirstateMapKeysIterator::from_inner(
287 py,
287 py,
288 unsafe { leaked_ref.map(py, |o| o.iter()) },
288 unsafe { leaked_ref.map(py, |o| o.iter()) },
289 )
289 )
290 }
290 }
291
291
292 def items(&self) -> PyResult<DirstateMapItemsIterator> {
292 def items(&self) -> PyResult<DirstateMapItemsIterator> {
293 let leaked_ref = self.inner(py).leak_immutable();
293 let leaked_ref = self.inner(py).leak_immutable();
294 DirstateMapItemsIterator::from_inner(
294 DirstateMapItemsIterator::from_inner(
295 py,
295 py,
296 unsafe { leaked_ref.map(py, |o| o.iter()) },
296 unsafe { leaked_ref.map(py, |o| o.iter()) },
297 )
297 )
298 }
298 }
299
299
300 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
300 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
301 let leaked_ref = self.inner(py).leak_immutable();
301 let leaked_ref = self.inner(py).leak_immutable();
302 DirstateMapKeysIterator::from_inner(
302 DirstateMapKeysIterator::from_inner(
303 py,
303 py,
304 unsafe { leaked_ref.map(py, |o| o.iter()) },
304 unsafe { leaked_ref.map(py, |o| o.iter()) },
305 )
305 )
306 }
306 }
307
307
308 // TODO all copymap* methods, see docstring above
308 // TODO all copymap* methods, see docstring above
309 def copymapcopy(&self) -> PyResult<PyDict> {
309 def copymapcopy(&self) -> PyResult<PyDict> {
310 let dict = PyDict::new(py);
310 let dict = PyDict::new(py);
311 for item in self.inner(py).borrow().copy_map_iter() {
311 for item in self.inner(py).borrow().copy_map_iter() {
312 let (key, value) = item.map_err(|e| v2_error(py, e))?;
312 let (key, value) = item.map_err(|e| v2_error(py, e))?;
313 dict.set_item(
313 dict.set_item(
314 py,
314 py,
315 PyBytes::new(py, key.as_bytes()),
315 PyBytes::new(py, key.as_bytes()),
316 PyBytes::new(py, value.as_bytes()),
316 PyBytes::new(py, value.as_bytes()),
317 )?;
317 )?;
318 }
318 }
319 Ok(dict)
319 Ok(dict)
320 }
320 }
321
321
322 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
322 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
323 let key = key.extract::<PyBytes>(py)?;
323 let key = key.extract::<PyBytes>(py)?;
324 match self
324 match self
325 .inner(py)
325 .inner(py)
326 .borrow()
326 .borrow()
327 .copy_map_get(HgPath::new(key.data(py)))
327 .copy_map_get(HgPath::new(key.data(py)))
328 .map_err(|e| v2_error(py, e))?
328 .map_err(|e| v2_error(py, e))?
329 {
329 {
330 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
330 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
331 None => Err(PyErr::new::<exc::KeyError, _>(
331 None => Err(PyErr::new::<exc::KeyError, _>(
332 py,
332 py,
333 String::from_utf8_lossy(key.data(py)),
333 String::from_utf8_lossy(key.data(py)),
334 )),
334 )),
335 }
335 }
336 }
336 }
337 def copymap(&self) -> PyResult<CopyMap> {
337 def copymap(&self) -> PyResult<CopyMap> {
338 CopyMap::from_inner(py, self.clone_ref(py))
338 CopyMap::from_inner(py, self.clone_ref(py))
339 }
339 }
340
340
341 def copymaplen(&self) -> PyResult<usize> {
341 def copymaplen(&self) -> PyResult<usize> {
342 Ok(self.inner(py).borrow().copy_map_len())
342 Ok(self.inner(py).borrow().copy_map_len())
343 }
343 }
344 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
344 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
345 let key = key.extract::<PyBytes>(py)?;
345 let key = key.extract::<PyBytes>(py)?;
346 self.inner(py)
346 self.inner(py)
347 .borrow()
347 .borrow()
348 .copy_map_contains_key(HgPath::new(key.data(py)))
348 .copy_map_contains_key(HgPath::new(key.data(py)))
349 .map_err(|e| v2_error(py, e))
349 .map_err(|e| v2_error(py, e))
350 }
350 }
351 def copymapget(
351 def copymapget(
352 &self,
352 &self,
353 key: PyObject,
353 key: PyObject,
354 default: Option<PyObject>
354 default: Option<PyObject>
355 ) -> PyResult<Option<PyObject>> {
355 ) -> PyResult<Option<PyObject>> {
356 let key = key.extract::<PyBytes>(py)?;
356 let key = key.extract::<PyBytes>(py)?;
357 match self
357 match self
358 .inner(py)
358 .inner(py)
359 .borrow()
359 .borrow()
360 .copy_map_get(HgPath::new(key.data(py)))
360 .copy_map_get(HgPath::new(key.data(py)))
361 .map_err(|e| v2_error(py, e))?
361 .map_err(|e| v2_error(py, e))?
362 {
362 {
363 Some(copy) => Ok(Some(
363 Some(copy) => Ok(Some(
364 PyBytes::new(py, copy.as_bytes()).into_object(),
364 PyBytes::new(py, copy.as_bytes()).into_object(),
365 )),
365 )),
366 None => Ok(default),
366 None => Ok(default),
367 }
367 }
368 }
368 }
369 def copymapsetitem(
369 def copymapsetitem(
370 &self,
370 &self,
371 key: PyObject,
371 key: PyObject,
372 value: PyObject
372 value: PyObject
373 ) -> PyResult<PyObject> {
373 ) -> PyResult<PyObject> {
374 let key = key.extract::<PyBytes>(py)?;
374 let key = key.extract::<PyBytes>(py)?;
375 let value = value.extract::<PyBytes>(py)?;
375 let value = value.extract::<PyBytes>(py)?;
376 self.inner(py)
376 self.inner(py)
377 .borrow_mut()
377 .borrow_mut()
378 .copy_map_insert(
378 .copy_map_insert(
379 HgPathBuf::from_bytes(key.data(py)),
379 HgPathBuf::from_bytes(key.data(py)),
380 HgPathBuf::from_bytes(value.data(py)),
380 HgPathBuf::from_bytes(value.data(py)),
381 )
381 )
382 .map_err(|e| v2_error(py, e))?;
382 .map_err(|e| v2_error(py, e))?;
383 Ok(py.None())
383 Ok(py.None())
384 }
384 }
385 def copymappop(
385 def copymappop(
386 &self,
386 &self,
387 key: PyObject,
387 key: PyObject,
388 default: Option<PyObject>
388 default: Option<PyObject>
389 ) -> PyResult<Option<PyObject>> {
389 ) -> PyResult<Option<PyObject>> {
390 let key = key.extract::<PyBytes>(py)?;
390 let key = key.extract::<PyBytes>(py)?;
391 match self
391 match self
392 .inner(py)
392 .inner(py)
393 .borrow_mut()
393 .borrow_mut()
394 .copy_map_remove(HgPath::new(key.data(py)))
394 .copy_map_remove(HgPath::new(key.data(py)))
395 .map_err(|e| v2_error(py, e))?
395 .map_err(|e| v2_error(py, e))?
396 {
396 {
397 Some(copy) => Ok(Some(
397 Some(copy) => Ok(Some(
398 PyBytes::new(py, copy.as_bytes()).into_object(),
398 PyBytes::new(py, copy.as_bytes()).into_object(),
399 )),
399 )),
400 None => Ok(default),
400 None => Ok(default),
401 }
401 }
402 }
402 }
403
403
404 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
404 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
405 let leaked_ref = self.inner(py).leak_immutable();
405 let leaked_ref = self.inner(py).leak_immutable();
406 CopyMapKeysIterator::from_inner(
406 CopyMapKeysIterator::from_inner(
407 py,
407 py,
408 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
408 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
409 )
409 )
410 }
410 }
411
411
412 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
412 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
413 let leaked_ref = self.inner(py).leak_immutable();
413 let leaked_ref = self.inner(py).leak_immutable();
414 CopyMapItemsIterator::from_inner(
414 CopyMapItemsIterator::from_inner(
415 py,
415 py,
416 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
416 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
417 )
417 )
418 }
418 }
419
419
420 def tracked_dirs(&self) -> PyResult<PyList> {
420 def tracked_dirs(&self) -> PyResult<PyList> {
421 let dirs = PyList::new(py, &[]);
421 let dirs = PyList::new(py, &[]);
422 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
422 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
423 .map_err(|e |dirstate_error(py, e))?
423 .map_err(|e |dirstate_error(py, e))?
424 {
424 {
425 let path = path.map_err(|e| v2_error(py, e))?;
425 let path = path.map_err(|e| v2_error(py, e))?;
426 let path = PyBytes::new(py, path.as_bytes());
426 let path = PyBytes::new(py, path.as_bytes());
427 dirs.append(py, path.into_object())
427 dirs.append(py, path.into_object())
428 }
428 }
429 Ok(dirs)
429 Ok(dirs)
430 }
430 }
431
431
432 def debug_iter(&self, all: bool) -> PyResult<PyList> {
432 def debug_iter(&self, all: bool) -> PyResult<PyList> {
433 let dirs = PyList::new(py, &[]);
433 let dirs = PyList::new(py, &[]);
434 for item in self.inner(py).borrow().debug_iter(all) {
434 for item in self.inner(py).borrow().debug_iter(all) {
435 let (path, (state, mode, size, mtime)) =
435 let (path, (state, mode, size, mtime)) =
436 item.map_err(|e| v2_error(py, e))?;
436 item.map_err(|e| v2_error(py, e))?;
437 let path = PyBytes::new(py, path.as_bytes());
437 let path = PyBytes::new(py, path.as_bytes());
438 let item = (path, state, mode, size, mtime);
438 let item = (path, state, mode, size, mtime);
439 dirs.append(py, item.to_py_object(py).into_object())
439 dirs.append(py, item.to_py_object(py).into_object())
440 }
440 }
441 Ok(dirs)
441 Ok(dirs)
442 }
442 }
443 });
443 });
444
444
445 impl DirstateMap {
445 impl DirstateMap {
446 pub fn get_inner_mut<'a>(
446 pub fn get_inner_mut<'a>(
447 &'a self,
447 &'a self,
448 py: Python<'a>,
448 py: Python<'a>,
449 ) -> RefMut<'a, OwningDirstateMap> {
449 ) -> RefMut<'a, OwningDirstateMap> {
450 self.inner(py).borrow_mut()
450 self.inner(py).borrow_mut()
451 }
451 }
452 fn translate_key(
452 fn translate_key(
453 py: Python,
453 py: Python,
454 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
454 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
455 ) -> PyResult<Option<PyBytes>> {
455 ) -> PyResult<Option<PyBytes>> {
456 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
456 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
457 Ok(Some(PyBytes::new(py, f.as_bytes())))
457 Ok(Some(PyBytes::new(py, f.as_bytes())))
458 }
458 }
459 fn translate_key_value(
459 fn translate_key_value(
460 py: Python,
460 py: Python,
461 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
461 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
462 ) -> PyResult<Option<(PyBytes, PyObject)>> {
462 ) -> PyResult<Option<(PyBytes, PyObject)>> {
463 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
463 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
464 Ok(Some((
464 Ok(Some((
465 PyBytes::new(py, f.as_bytes()),
465 PyBytes::new(py, f.as_bytes()),
466 DirstateItem::new_as_pyobject(py, entry)?,
466 DirstateItem::new_as_pyobject(py, entry)?,
467 )))
467 )))
468 }
468 }
469 }
469 }
470
470
471 py_shared_iterator!(
471 py_shared_iterator!(
472 DirstateMapKeysIterator,
472 DirstateMapKeysIterator,
473 UnsafePyLeaked<StateMapIter<'static>>,
473 UnsafePyLeaked<StateMapIter<'static>>,
474 DirstateMap::translate_key,
474 DirstateMap::translate_key,
475 Option<PyBytes>
475 Option<PyBytes>
476 );
476 );
477
477
478 py_shared_iterator!(
478 py_shared_iterator!(
479 DirstateMapItemsIterator,
479 DirstateMapItemsIterator,
480 UnsafePyLeaked<StateMapIter<'static>>,
480 UnsafePyLeaked<StateMapIter<'static>>,
481 DirstateMap::translate_key_value,
481 DirstateMap::translate_key_value,
482 Option<(PyBytes, PyObject)>
482 Option<(PyBytes, PyObject)>
483 );
483 );
484
484
485 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
485 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
486 let bytes = obj.extract::<PyBytes>(py)?;
486 let bytes = obj.extract::<PyBytes>(py)?;
487 match bytes.data(py).try_into() {
487 match bytes.data(py).try_into() {
488 Ok(s) => Ok(s),
488 Ok(s) => Ok(s),
489 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
489 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
490 }
490 }
491 }
491 }
492
492
493 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
493 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
494 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
494 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
495 }
495 }
496
496
497 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
497 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
498 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
498 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
499 }
499 }
General Comments 0
You need to be logged in to leave comments. Login now