##// END OF EJS Templates
dirstate: Remove the Rust abstraction DirstateMapMethods...
Simon Sapin -
r48883:3d0a9c6e default
parent child Browse files
Show More
@@ -1,7 +1,5 b''
1 pub mod dirstate_map;
1 pub mod dirstate_map;
2 pub mod dispatch;
3 pub mod on_disk;
2 pub mod on_disk;
4 pub mod owning;
3 pub mod owning;
5 mod owning_dispatch;
6 pub mod path_with_basename;
4 pub mod path_with_basename;
7 pub mod status;
5 pub mod status;
@@ -1,1168 +1,1193 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::convert::TryInto;
4 use std::convert::TryInto;
5 use std::path::PathBuf;
5 use std::path::PathBuf;
6
6
7 use super::on_disk;
7 use super::on_disk;
8 use super::on_disk::DirstateV2ParseError;
8 use super::on_disk::DirstateV2ParseError;
9 use super::owning::OwningDirstateMap;
9 use super::path_with_basename::WithBasename;
10 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::parsers::Timestamp;
14 use crate::dirstate::parsers::Timestamp;
14 use crate::dirstate::CopyMapIter;
15 use crate::dirstate::CopyMapIter;
15 use crate::dirstate::StateMapIter;
16 use crate::dirstate::StateMapIter;
16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
17 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
17 use crate::dirstate::SIZE_NON_NORMAL;
18 use crate::dirstate::SIZE_NON_NORMAL;
18 use crate::matchers::Matcher;
19 use crate::matchers::Matcher;
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::DirstateEntry;
21 use crate::DirstateEntry;
21 use crate::DirstateError;
22 use crate::DirstateError;
22 use crate::DirstateParents;
23 use crate::DirstateParents;
23 use crate::DirstateStatus;
24 use crate::DirstateStatus;
24 use crate::EntryState;
25 use crate::EntryState;
25 use crate::FastHashMap;
26 use crate::FastHashMap;
26 use crate::PatternFileWarning;
27 use crate::PatternFileWarning;
27 use crate::StatusError;
28 use crate::StatusError;
28 use crate::StatusOptions;
29 use crate::StatusOptions;
29
30
30 /// Append to an existing data file if the amount of unreachable data (not used
31 /// Append to an existing data file if the amount of unreachable data (not used
31 /// anymore) is less than this fraction of the total amount of existing data.
32 /// anymore) is less than this fraction of the total amount of existing data.
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33
34
34 pub struct DirstateMap<'on_disk> {
35 pub struct DirstateMap<'on_disk> {
35 /// Contents of the `.hg/dirstate` file
36 /// Contents of the `.hg/dirstate` file
36 pub(super) on_disk: &'on_disk [u8],
37 pub(super) on_disk: &'on_disk [u8],
37
38
38 pub(super) root: ChildNodes<'on_disk>,
39 pub(super) root: ChildNodes<'on_disk>,
39
40
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 pub(super) nodes_with_entry_count: u32,
42 pub(super) nodes_with_entry_count: u32,
42
43
43 /// Number of nodes anywhere in the tree that have
44 /// Number of nodes anywhere in the tree that have
44 /// `.copy_source.is_some()`.
45 /// `.copy_source.is_some()`.
45 pub(super) nodes_with_copy_source_count: u32,
46 pub(super) nodes_with_copy_source_count: u32,
46
47
47 /// See on_disk::Header
48 /// See on_disk::Header
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49
50
50 /// How many bytes of `on_disk` are not used anymore
51 /// How many bytes of `on_disk` are not used anymore
51 pub(super) unreachable_bytes: u32,
52 pub(super) unreachable_bytes: u32,
52 }
53 }
53
54
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 /// 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
56 /// 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
57 /// path, so comparing full paths gives the same result as comparing base
57 /// names. However `HashMap` would waste time always re-hashing the same
58 /// names. However `HashMap` would waste time always re-hashing the same
58 /// string prefix.
59 /// string prefix.
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60
61
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 /// 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.
63 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 InMemory(&'tree HgPathBuf),
65 InMemory(&'tree HgPathBuf),
65 OnDisk(&'on_disk HgPath),
66 OnDisk(&'on_disk HgPath),
66 }
67 }
67
68
68 pub(super) enum ChildNodes<'on_disk> {
69 pub(super) enum ChildNodes<'on_disk> {
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 OnDisk(&'on_disk [on_disk::Node]),
71 OnDisk(&'on_disk [on_disk::Node]),
71 }
72 }
72
73
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 OnDisk(&'on_disk [on_disk::Node]),
76 OnDisk(&'on_disk [on_disk::Node]),
76 }
77 }
77
78
78 pub(super) enum NodeRef<'tree, 'on_disk> {
79 pub(super) enum NodeRef<'tree, 'on_disk> {
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 OnDisk(&'on_disk on_disk::Node),
81 OnDisk(&'on_disk on_disk::Node),
81 }
82 }
82
83
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 match *self {
86 match *self {
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 }
89 }
89 }
90 }
90 }
91 }
91
92
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 type Target = HgPath;
94 type Target = HgPath;
94
95
95 fn deref(&self) -> &HgPath {
96 fn deref(&self) -> &HgPath {
96 match *self {
97 match *self {
97 BorrowedPath::InMemory(in_memory) => in_memory,
98 BorrowedPath::InMemory(in_memory) => in_memory,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
99 BorrowedPath::OnDisk(on_disk) => on_disk,
99 }
100 }
100 }
101 }
101 }
102 }
102
103
103 impl Default for ChildNodes<'_> {
104 impl Default for ChildNodes<'_> {
104 fn default() -> Self {
105 fn default() -> Self {
105 ChildNodes::InMemory(Default::default())
106 ChildNodes::InMemory(Default::default())
106 }
107 }
107 }
108 }
108
109
109 impl<'on_disk> ChildNodes<'on_disk> {
110 impl<'on_disk> ChildNodes<'on_disk> {
110 pub(super) fn as_ref<'tree>(
111 pub(super) fn as_ref<'tree>(
111 &'tree self,
112 &'tree self,
112 ) -> ChildNodesRef<'tree, 'on_disk> {
113 ) -> ChildNodesRef<'tree, 'on_disk> {
113 match self {
114 match self {
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 }
117 }
117 }
118 }
118
119
119 pub(super) fn is_empty(&self) -> bool {
120 pub(super) fn is_empty(&self) -> bool {
120 match self {
121 match self {
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 }
124 }
124 }
125 }
125
126
126 fn make_mut(
127 fn make_mut(
127 &mut self,
128 &mut self,
128 on_disk: &'on_disk [u8],
129 on_disk: &'on_disk [u8],
129 unreachable_bytes: &mut u32,
130 unreachable_bytes: &mut u32,
130 ) -> Result<
131 ) -> Result<
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 DirstateV2ParseError,
133 DirstateV2ParseError,
133 > {
134 > {
134 match self {
135 match self {
135 ChildNodes::InMemory(nodes) => Ok(nodes),
136 ChildNodes::InMemory(nodes) => Ok(nodes),
136 ChildNodes::OnDisk(nodes) => {
137 ChildNodes::OnDisk(nodes) => {
137 *unreachable_bytes +=
138 *unreachable_bytes +=
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 let nodes = nodes
140 let nodes = nodes
140 .iter()
141 .iter()
141 .map(|node| {
142 .map(|node| {
142 Ok((
143 Ok((
143 node.path(on_disk)?,
144 node.path(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
145 node.to_in_memory_node(on_disk)?,
145 ))
146 ))
146 })
147 })
147 .collect::<Result<_, _>>()?;
148 .collect::<Result<_, _>>()?;
148 *self = ChildNodes::InMemory(nodes);
149 *self = ChildNodes::InMemory(nodes);
149 match self {
150 match self {
150 ChildNodes::InMemory(nodes) => Ok(nodes),
151 ChildNodes::InMemory(nodes) => Ok(nodes),
151 ChildNodes::OnDisk(_) => unreachable!(),
152 ChildNodes::OnDisk(_) => unreachable!(),
152 }
153 }
153 }
154 }
154 }
155 }
155 }
156 }
156 }
157 }
157
158
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 pub(super) fn get(
160 pub(super) fn get(
160 &self,
161 &self,
161 base_name: &HgPath,
162 base_name: &HgPath,
162 on_disk: &'on_disk [u8],
163 on_disk: &'on_disk [u8],
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 match self {
165 match self {
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 .get_key_value(base_name)
167 .get_key_value(base_name)
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 ChildNodesRef::OnDisk(nodes) => {
169 ChildNodesRef::OnDisk(nodes) => {
169 let mut parse_result = Ok(());
170 let mut parse_result = Ok(());
170 let search_result = nodes.binary_search_by(|node| {
171 let search_result = nodes.binary_search_by(|node| {
171 match node.base_name(on_disk) {
172 match node.base_name(on_disk) {
172 Ok(node_base_name) => node_base_name.cmp(base_name),
173 Ok(node_base_name) => node_base_name.cmp(base_name),
173 Err(e) => {
174 Err(e) => {
174 parse_result = Err(e);
175 parse_result = Err(e);
175 // Dummy comparison result, `search_result` won’t
176 // Dummy comparison result, `search_result` won’t
176 // be used since `parse_result` is an error
177 // be used since `parse_result` is an error
177 std::cmp::Ordering::Equal
178 std::cmp::Ordering::Equal
178 }
179 }
179 }
180 }
180 });
181 });
181 parse_result.map(|()| {
182 parse_result.map(|()| {
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 })
184 })
184 }
185 }
185 }
186 }
186 }
187 }
187
188
188 /// Iterate in undefined order
189 /// Iterate in undefined order
189 pub(super) fn iter(
190 pub(super) fn iter(
190 &self,
191 &self,
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 match self {
193 match self {
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 ),
196 ),
196 ChildNodesRef::OnDisk(nodes) => {
197 ChildNodesRef::OnDisk(nodes) => {
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 }
199 }
199 }
200 }
200 }
201 }
201
202
202 /// Iterate in parallel in undefined order
203 /// Iterate in parallel in undefined order
203 pub(super) fn par_iter(
204 pub(super) fn par_iter(
204 &self,
205 &self,
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 {
207 {
207 use rayon::prelude::*;
208 use rayon::prelude::*;
208 match self {
209 match self {
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 ),
212 ),
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 nodes.par_iter().map(NodeRef::OnDisk),
214 nodes.par_iter().map(NodeRef::OnDisk),
214 ),
215 ),
215 }
216 }
216 }
217 }
217
218
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 match self {
220 match self {
220 ChildNodesRef::InMemory(nodes) => {
221 ChildNodesRef::InMemory(nodes) => {
221 let mut vec: Vec<_> = nodes
222 let mut vec: Vec<_> = nodes
222 .iter()
223 .iter()
223 .map(|(k, v)| NodeRef::InMemory(k, v))
224 .map(|(k, v)| NodeRef::InMemory(k, v))
224 .collect();
225 .collect();
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 match node {
227 match node {
227 NodeRef::InMemory(path, _node) => path.base_name(),
228 NodeRef::InMemory(path, _node) => path.base_name(),
228 NodeRef::OnDisk(_) => unreachable!(),
229 NodeRef::OnDisk(_) => unreachable!(),
229 }
230 }
230 }
231 }
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 // value: https://github.com/rust-lang/rust/issues/34162
233 // value: https://github.com/rust-lang/rust/issues/34162
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 vec
235 vec
235 }
236 }
236 ChildNodesRef::OnDisk(nodes) => {
237 ChildNodesRef::OnDisk(nodes) => {
237 // Nodes on disk are already sorted
238 // Nodes on disk are already sorted
238 nodes.iter().map(NodeRef::OnDisk).collect()
239 nodes.iter().map(NodeRef::OnDisk).collect()
239 }
240 }
240 }
241 }
241 }
242 }
242 }
243 }
243
244
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 pub(super) fn full_path(
246 pub(super) fn full_path(
246 &self,
247 &self,
247 on_disk: &'on_disk [u8],
248 on_disk: &'on_disk [u8],
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 match self {
250 match self {
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 }
253 }
253 }
254 }
254
255
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 /// HgPath>` detached from `'tree`
257 /// HgPath>` detached from `'tree`
257 pub(super) fn full_path_borrowed(
258 pub(super) fn full_path_borrowed(
258 &self,
259 &self,
259 on_disk: &'on_disk [u8],
260 on_disk: &'on_disk [u8],
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 match self {
262 match self {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
263 NodeRef::InMemory(path, _node) => match path.full_path() {
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 },
266 },
266 NodeRef::OnDisk(node) => {
267 NodeRef::OnDisk(node) => {
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 }
269 }
269 }
270 }
270 }
271 }
271
272
272 pub(super) fn base_name(
273 pub(super) fn base_name(
273 &self,
274 &self,
274 on_disk: &'on_disk [u8],
275 on_disk: &'on_disk [u8],
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 match self {
277 match self {
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 }
280 }
280 }
281 }
281
282
282 pub(super) fn children(
283 pub(super) fn children(
283 &self,
284 &self,
284 on_disk: &'on_disk [u8],
285 on_disk: &'on_disk [u8],
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 match self {
287 match self {
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 NodeRef::OnDisk(node) => {
289 NodeRef::OnDisk(node) => {
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 }
291 }
291 }
292 }
292 }
293 }
293
294
294 pub(super) fn has_copy_source(&self) -> bool {
295 pub(super) fn has_copy_source(&self) -> bool {
295 match self {
296 match self {
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
298 NodeRef::OnDisk(node) => node.has_copy_source(),
298 }
299 }
299 }
300 }
300
301
301 pub(super) fn copy_source(
302 pub(super) fn copy_source(
302 &self,
303 &self,
303 on_disk: &'on_disk [u8],
304 on_disk: &'on_disk [u8],
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 match self {
306 match self {
306 NodeRef::InMemory(_path, node) => {
307 NodeRef::InMemory(_path, node) => {
307 Ok(node.copy_source.as_ref().map(|s| &**s))
308 Ok(node.copy_source.as_ref().map(|s| &**s))
308 }
309 }
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 }
311 }
311 }
312 }
312
313
313 pub(super) fn entry(
314 pub(super) fn entry(
314 &self,
315 &self,
315 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
316 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
316 match self {
317 match self {
317 NodeRef::InMemory(_path, node) => {
318 NodeRef::InMemory(_path, node) => {
318 Ok(node.data.as_entry().copied())
319 Ok(node.data.as_entry().copied())
319 }
320 }
320 NodeRef::OnDisk(node) => node.entry(),
321 NodeRef::OnDisk(node) => node.entry(),
321 }
322 }
322 }
323 }
323
324
324 pub(super) fn state(
325 pub(super) fn state(
325 &self,
326 &self,
326 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
327 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
327 match self {
328 match self {
328 NodeRef::InMemory(_path, node) => {
329 NodeRef::InMemory(_path, node) => {
329 Ok(node.data.as_entry().map(|entry| entry.state()))
330 Ok(node.data.as_entry().map(|entry| entry.state()))
330 }
331 }
331 NodeRef::OnDisk(node) => node.state(),
332 NodeRef::OnDisk(node) => node.state(),
332 }
333 }
333 }
334 }
334
335
335 pub(super) fn cached_directory_mtime(
336 pub(super) fn cached_directory_mtime(
336 &self,
337 &self,
337 ) -> Option<&'tree on_disk::Timestamp> {
338 ) -> Option<&'tree on_disk::Timestamp> {
338 match self {
339 match self {
339 NodeRef::InMemory(_path, node) => match &node.data {
340 NodeRef::InMemory(_path, node) => match &node.data {
340 NodeData::CachedDirectory { mtime } => Some(mtime),
341 NodeData::CachedDirectory { mtime } => Some(mtime),
341 _ => None,
342 _ => None,
342 },
343 },
343 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
344 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
344 }
345 }
345 }
346 }
346
347
347 pub(super) fn descendants_with_entry_count(&self) -> u32 {
348 pub(super) fn descendants_with_entry_count(&self) -> u32 {
348 match self {
349 match self {
349 NodeRef::InMemory(_path, node) => {
350 NodeRef::InMemory(_path, node) => {
350 node.descendants_with_entry_count
351 node.descendants_with_entry_count
351 }
352 }
352 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
353 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
353 }
354 }
354 }
355 }
355
356
356 pub(super) fn tracked_descendants_count(&self) -> u32 {
357 pub(super) fn tracked_descendants_count(&self) -> u32 {
357 match self {
358 match self {
358 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
359 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
359 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
360 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
360 }
361 }
361 }
362 }
362 }
363 }
363
364
364 /// Represents a file or a directory
365 /// Represents a file or a directory
365 #[derive(Default)]
366 #[derive(Default)]
366 pub(super) struct Node<'on_disk> {
367 pub(super) struct Node<'on_disk> {
367 pub(super) data: NodeData,
368 pub(super) data: NodeData,
368
369
369 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
370 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
370
371
371 pub(super) children: ChildNodes<'on_disk>,
372 pub(super) children: ChildNodes<'on_disk>,
372
373
373 /// How many (non-inclusive) descendants of this node have an entry.
374 /// How many (non-inclusive) descendants of this node have an entry.
374 pub(super) descendants_with_entry_count: u32,
375 pub(super) descendants_with_entry_count: u32,
375
376
376 /// How many (non-inclusive) descendants of this node have an entry whose
377 /// How many (non-inclusive) descendants of this node have an entry whose
377 /// state is "tracked".
378 /// state is "tracked".
378 pub(super) tracked_descendants_count: u32,
379 pub(super) tracked_descendants_count: u32,
379 }
380 }
380
381
381 pub(super) enum NodeData {
382 pub(super) enum NodeData {
382 Entry(DirstateEntry),
383 Entry(DirstateEntry),
383 CachedDirectory { mtime: on_disk::Timestamp },
384 CachedDirectory { mtime: on_disk::Timestamp },
384 None,
385 None,
385 }
386 }
386
387
387 impl Default for NodeData {
388 impl Default for NodeData {
388 fn default() -> Self {
389 fn default() -> Self {
389 NodeData::None
390 NodeData::None
390 }
391 }
391 }
392 }
392
393
393 impl NodeData {
394 impl NodeData {
394 fn has_entry(&self) -> bool {
395 fn has_entry(&self) -> bool {
395 match self {
396 match self {
396 NodeData::Entry(_) => true,
397 NodeData::Entry(_) => true,
397 _ => false,
398 _ => false,
398 }
399 }
399 }
400 }
400
401
401 fn as_entry(&self) -> Option<&DirstateEntry> {
402 fn as_entry(&self) -> Option<&DirstateEntry> {
402 match self {
403 match self {
403 NodeData::Entry(entry) => Some(entry),
404 NodeData::Entry(entry) => Some(entry),
404 _ => None,
405 _ => None,
405 }
406 }
406 }
407 }
407 }
408 }
408
409
409 impl<'on_disk> DirstateMap<'on_disk> {
410 impl<'on_disk> DirstateMap<'on_disk> {
410 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
411 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
411 Self {
412 Self {
412 on_disk,
413 on_disk,
413 root: ChildNodes::default(),
414 root: ChildNodes::default(),
414 nodes_with_entry_count: 0,
415 nodes_with_entry_count: 0,
415 nodes_with_copy_source_count: 0,
416 nodes_with_copy_source_count: 0,
416 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
417 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
417 unreachable_bytes: 0,
418 unreachable_bytes: 0,
418 }
419 }
419 }
420 }
420
421
421 #[timed]
422 #[timed]
422 pub fn new_v2(
423 pub fn new_v2(
423 on_disk: &'on_disk [u8],
424 on_disk: &'on_disk [u8],
424 data_size: usize,
425 data_size: usize,
425 metadata: &[u8],
426 metadata: &[u8],
426 ) -> Result<Self, DirstateError> {
427 ) -> Result<Self, DirstateError> {
427 if let Some(data) = on_disk.get(..data_size) {
428 if let Some(data) = on_disk.get(..data_size) {
428 Ok(on_disk::read(data, metadata)?)
429 Ok(on_disk::read(data, metadata)?)
429 } else {
430 } else {
430 Err(DirstateV2ParseError.into())
431 Err(DirstateV2ParseError.into())
431 }
432 }
432 }
433 }
433
434
434 #[timed]
435 #[timed]
435 pub fn new_v1(
436 pub fn new_v1(
436 on_disk: &'on_disk [u8],
437 on_disk: &'on_disk [u8],
437 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
438 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
438 let mut map = Self::empty(on_disk);
439 let mut map = Self::empty(on_disk);
439 if map.on_disk.is_empty() {
440 if map.on_disk.is_empty() {
440 return Ok((map, None));
441 return Ok((map, None));
441 }
442 }
442
443
443 let parents = parse_dirstate_entries(
444 let parents = parse_dirstate_entries(
444 map.on_disk,
445 map.on_disk,
445 |path, entry, copy_source| {
446 |path, entry, copy_source| {
446 let tracked = entry.state().is_tracked();
447 let tracked = entry.state().is_tracked();
447 let node = Self::get_or_insert_node(
448 let node = Self::get_or_insert_node(
448 map.on_disk,
449 map.on_disk,
449 &mut map.unreachable_bytes,
450 &mut map.unreachable_bytes,
450 &mut map.root,
451 &mut map.root,
451 path,
452 path,
452 WithBasename::to_cow_borrowed,
453 WithBasename::to_cow_borrowed,
453 |ancestor| {
454 |ancestor| {
454 if tracked {
455 if tracked {
455 ancestor.tracked_descendants_count += 1
456 ancestor.tracked_descendants_count += 1
456 }
457 }
457 ancestor.descendants_with_entry_count += 1
458 ancestor.descendants_with_entry_count += 1
458 },
459 },
459 )?;
460 )?;
460 assert!(
461 assert!(
461 !node.data.has_entry(),
462 !node.data.has_entry(),
462 "duplicate dirstate entry in read"
463 "duplicate dirstate entry in read"
463 );
464 );
464 assert!(
465 assert!(
465 node.copy_source.is_none(),
466 node.copy_source.is_none(),
466 "duplicate dirstate entry in read"
467 "duplicate dirstate entry in read"
467 );
468 );
468 node.data = NodeData::Entry(*entry);
469 node.data = NodeData::Entry(*entry);
469 node.copy_source = copy_source.map(Cow::Borrowed);
470 node.copy_source = copy_source.map(Cow::Borrowed);
470 map.nodes_with_entry_count += 1;
471 map.nodes_with_entry_count += 1;
471 if copy_source.is_some() {
472 if copy_source.is_some() {
472 map.nodes_with_copy_source_count += 1
473 map.nodes_with_copy_source_count += 1
473 }
474 }
474 Ok(())
475 Ok(())
475 },
476 },
476 )?;
477 )?;
477 let parents = Some(parents.clone());
478 let parents = Some(parents.clone());
478
479
479 Ok((map, parents))
480 Ok((map, parents))
480 }
481 }
481
482
482 /// Assuming dirstate-v2 format, returns whether the next write should
483 /// Assuming dirstate-v2 format, returns whether the next write should
483 /// append to the existing data file that contains `self.on_disk` (true),
484 /// append to the existing data file that contains `self.on_disk` (true),
484 /// or create a new data file from scratch (false).
485 /// or create a new data file from scratch (false).
485 pub(super) fn write_should_append(&self) -> bool {
486 pub(super) fn write_should_append(&self) -> bool {
486 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
487 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
487 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
488 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
488 }
489 }
489
490
490 fn get_node<'tree>(
491 fn get_node<'tree>(
491 &'tree self,
492 &'tree self,
492 path: &HgPath,
493 path: &HgPath,
493 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
494 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
494 let mut children = self.root.as_ref();
495 let mut children = self.root.as_ref();
495 let mut components = path.components();
496 let mut components = path.components();
496 let mut component =
497 let mut component =
497 components.next().expect("expected at least one components");
498 components.next().expect("expected at least one components");
498 loop {
499 loop {
499 if let Some(child) = children.get(component, self.on_disk)? {
500 if let Some(child) = children.get(component, self.on_disk)? {
500 if let Some(next_component) = components.next() {
501 if let Some(next_component) = components.next() {
501 component = next_component;
502 component = next_component;
502 children = child.children(self.on_disk)?;
503 children = child.children(self.on_disk)?;
503 } else {
504 } else {
504 return Ok(Some(child));
505 return Ok(Some(child));
505 }
506 }
506 } else {
507 } else {
507 return Ok(None);
508 return Ok(None);
508 }
509 }
509 }
510 }
510 }
511 }
511
512
512 /// Returns a mutable reference to the node at `path` if it exists
513 /// Returns a mutable reference to the node at `path` if it exists
513 ///
514 ///
514 /// This takes `root` instead of `&mut self` so that callers can mutate
515 /// This takes `root` instead of `&mut self` so that callers can mutate
515 /// other fields while the returned borrow is still valid
516 /// other fields while the returned borrow is still valid
516 fn get_node_mut<'tree>(
517 fn get_node_mut<'tree>(
517 on_disk: &'on_disk [u8],
518 on_disk: &'on_disk [u8],
518 unreachable_bytes: &mut u32,
519 unreachable_bytes: &mut u32,
519 root: &'tree mut ChildNodes<'on_disk>,
520 root: &'tree mut ChildNodes<'on_disk>,
520 path: &HgPath,
521 path: &HgPath,
521 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
522 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
522 let mut children = root;
523 let mut children = root;
523 let mut components = path.components();
524 let mut components = path.components();
524 let mut component =
525 let mut component =
525 components.next().expect("expected at least one components");
526 components.next().expect("expected at least one components");
526 loop {
527 loop {
527 if let Some(child) = children
528 if let Some(child) = children
528 .make_mut(on_disk, unreachable_bytes)?
529 .make_mut(on_disk, unreachable_bytes)?
529 .get_mut(component)
530 .get_mut(component)
530 {
531 {
531 if let Some(next_component) = components.next() {
532 if let Some(next_component) = components.next() {
532 component = next_component;
533 component = next_component;
533 children = &mut child.children;
534 children = &mut child.children;
534 } else {
535 } else {
535 return Ok(Some(child));
536 return Ok(Some(child));
536 }
537 }
537 } else {
538 } else {
538 return Ok(None);
539 return Ok(None);
539 }
540 }
540 }
541 }
541 }
542 }
542
543
543 pub(super) fn get_or_insert<'tree, 'path>(
544 pub(super) fn get_or_insert<'tree, 'path>(
544 &'tree mut self,
545 &'tree mut self,
545 path: &HgPath,
546 path: &HgPath,
546 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
547 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
547 Self::get_or_insert_node(
548 Self::get_or_insert_node(
548 self.on_disk,
549 self.on_disk,
549 &mut self.unreachable_bytes,
550 &mut self.unreachable_bytes,
550 &mut self.root,
551 &mut self.root,
551 path,
552 path,
552 WithBasename::to_cow_owned,
553 WithBasename::to_cow_owned,
553 |_| {},
554 |_| {},
554 )
555 )
555 }
556 }
556
557
557 fn get_or_insert_node<'tree, 'path>(
558 fn get_or_insert_node<'tree, 'path>(
558 on_disk: &'on_disk [u8],
559 on_disk: &'on_disk [u8],
559 unreachable_bytes: &mut u32,
560 unreachable_bytes: &mut u32,
560 root: &'tree mut ChildNodes<'on_disk>,
561 root: &'tree mut ChildNodes<'on_disk>,
561 path: &'path HgPath,
562 path: &'path HgPath,
562 to_cow: impl Fn(
563 to_cow: impl Fn(
563 WithBasename<&'path HgPath>,
564 WithBasename<&'path HgPath>,
564 ) -> WithBasename<Cow<'on_disk, HgPath>>,
565 ) -> WithBasename<Cow<'on_disk, HgPath>>,
565 mut each_ancestor: impl FnMut(&mut Node),
566 mut each_ancestor: impl FnMut(&mut Node),
566 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
567 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
567 let mut child_nodes = root;
568 let mut child_nodes = root;
568 let mut inclusive_ancestor_paths =
569 let mut inclusive_ancestor_paths =
569 WithBasename::inclusive_ancestors_of(path);
570 WithBasename::inclusive_ancestors_of(path);
570 let mut ancestor_path = inclusive_ancestor_paths
571 let mut ancestor_path = inclusive_ancestor_paths
571 .next()
572 .next()
572 .expect("expected at least one inclusive ancestor");
573 .expect("expected at least one inclusive ancestor");
573 loop {
574 loop {
574 // TODO: can we avoid allocating an owned key in cases where the
575 // TODO: can we avoid allocating an owned key in cases where the
575 // map already contains that key, without introducing double
576 // map already contains that key, without introducing double
576 // lookup?
577 // lookup?
577 let child_node = child_nodes
578 let child_node = child_nodes
578 .make_mut(on_disk, unreachable_bytes)?
579 .make_mut(on_disk, unreachable_bytes)?
579 .entry(to_cow(ancestor_path))
580 .entry(to_cow(ancestor_path))
580 .or_default();
581 .or_default();
581 if let Some(next) = inclusive_ancestor_paths.next() {
582 if let Some(next) = inclusive_ancestor_paths.next() {
582 each_ancestor(child_node);
583 each_ancestor(child_node);
583 ancestor_path = next;
584 ancestor_path = next;
584 child_nodes = &mut child_node.children;
585 child_nodes = &mut child_node.children;
585 } else {
586 } else {
586 return Ok(child_node);
587 return Ok(child_node);
587 }
588 }
588 }
589 }
589 }
590 }
590
591
591 fn add_or_remove_file(
592 fn add_or_remove_file(
592 &mut self,
593 &mut self,
593 path: &HgPath,
594 path: &HgPath,
594 old_state: Option<EntryState>,
595 old_state: Option<EntryState>,
595 new_entry: DirstateEntry,
596 new_entry: DirstateEntry,
596 ) -> Result<(), DirstateV2ParseError> {
597 ) -> Result<(), DirstateV2ParseError> {
597 let had_entry = old_state.is_some();
598 let had_entry = old_state.is_some();
598 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
599 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
599 let tracked_count_increment =
600 let tracked_count_increment =
600 match (was_tracked, new_entry.state().is_tracked()) {
601 match (was_tracked, new_entry.state().is_tracked()) {
601 (false, true) => 1,
602 (false, true) => 1,
602 (true, false) => -1,
603 (true, false) => -1,
603 _ => 0,
604 _ => 0,
604 };
605 };
605
606
606 let node = Self::get_or_insert_node(
607 let node = Self::get_or_insert_node(
607 self.on_disk,
608 self.on_disk,
608 &mut self.unreachable_bytes,
609 &mut self.unreachable_bytes,
609 &mut self.root,
610 &mut self.root,
610 path,
611 path,
611 WithBasename::to_cow_owned,
612 WithBasename::to_cow_owned,
612 |ancestor| {
613 |ancestor| {
613 if !had_entry {
614 if !had_entry {
614 ancestor.descendants_with_entry_count += 1;
615 ancestor.descendants_with_entry_count += 1;
615 }
616 }
616
617
617 // We can’t use `+= increment` because the counter is unsigned,
618 // We can’t use `+= increment` because the counter is unsigned,
618 // and we want debug builds to detect accidental underflow
619 // and we want debug builds to detect accidental underflow
619 // through zero
620 // through zero
620 match tracked_count_increment {
621 match tracked_count_increment {
621 1 => ancestor.tracked_descendants_count += 1,
622 1 => ancestor.tracked_descendants_count += 1,
622 -1 => ancestor.tracked_descendants_count -= 1,
623 -1 => ancestor.tracked_descendants_count -= 1,
623 _ => {}
624 _ => {}
624 }
625 }
625 },
626 },
626 )?;
627 )?;
627 if !had_entry {
628 if !had_entry {
628 self.nodes_with_entry_count += 1
629 self.nodes_with_entry_count += 1
629 }
630 }
630 node.data = NodeData::Entry(new_entry);
631 node.data = NodeData::Entry(new_entry);
631 Ok(())
632 Ok(())
632 }
633 }
633
634
634 fn iter_nodes<'tree>(
635 fn iter_nodes<'tree>(
635 &'tree self,
636 &'tree self,
636 ) -> impl Iterator<
637 ) -> impl Iterator<
637 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
638 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
638 > + 'tree {
639 > + 'tree {
639 // Depth first tree traversal.
640 // Depth first tree traversal.
640 //
641 //
641 // If we could afford internal iteration and recursion,
642 // If we could afford internal iteration and recursion,
642 // this would look like:
643 // this would look like:
643 //
644 //
644 // ```
645 // ```
645 // fn traverse_children(
646 // fn traverse_children(
646 // children: &ChildNodes,
647 // children: &ChildNodes,
647 // each: &mut impl FnMut(&Node),
648 // each: &mut impl FnMut(&Node),
648 // ) {
649 // ) {
649 // for child in children.values() {
650 // for child in children.values() {
650 // traverse_children(&child.children, each);
651 // traverse_children(&child.children, each);
651 // each(child);
652 // each(child);
652 // }
653 // }
653 // }
654 // }
654 // ```
655 // ```
655 //
656 //
656 // However we want an external iterator and therefore can’t use the
657 // However we want an external iterator and therefore can’t use the
657 // call stack. Use an explicit stack instead:
658 // call stack. Use an explicit stack instead:
658 let mut stack = Vec::new();
659 let mut stack = Vec::new();
659 let mut iter = self.root.as_ref().iter();
660 let mut iter = self.root.as_ref().iter();
660 std::iter::from_fn(move || {
661 std::iter::from_fn(move || {
661 while let Some(child_node) = iter.next() {
662 while let Some(child_node) = iter.next() {
662 let children = match child_node.children(self.on_disk) {
663 let children = match child_node.children(self.on_disk) {
663 Ok(children) => children,
664 Ok(children) => children,
664 Err(error) => return Some(Err(error)),
665 Err(error) => return Some(Err(error)),
665 };
666 };
666 // Pseudo-recursion
667 // Pseudo-recursion
667 let new_iter = children.iter();
668 let new_iter = children.iter();
668 let old_iter = std::mem::replace(&mut iter, new_iter);
669 let old_iter = std::mem::replace(&mut iter, new_iter);
669 stack.push((child_node, old_iter));
670 stack.push((child_node, old_iter));
670 }
671 }
671 // Found the end of a `children.iter()` iterator.
672 // Found the end of a `children.iter()` iterator.
672 if let Some((child_node, next_iter)) = stack.pop() {
673 if let Some((child_node, next_iter)) = stack.pop() {
673 // "Return" from pseudo-recursion by restoring state from the
674 // "Return" from pseudo-recursion by restoring state from the
674 // explicit stack
675 // explicit stack
675 iter = next_iter;
676 iter = next_iter;
676
677
677 Some(Ok(child_node))
678 Some(Ok(child_node))
678 } else {
679 } else {
679 // Reached the bottom of the stack, we’re done
680 // Reached the bottom of the stack, we’re done
680 None
681 None
681 }
682 }
682 })
683 })
683 }
684 }
684
685
685 fn clear_known_ambiguous_mtimes(
686 fn clear_known_ambiguous_mtimes(
686 &mut self,
687 &mut self,
687 paths: &[impl AsRef<HgPath>],
688 paths: &[impl AsRef<HgPath>],
688 ) -> Result<(), DirstateV2ParseError> {
689 ) -> Result<(), DirstateV2ParseError> {
689 for path in paths {
690 for path in paths {
690 if let Some(node) = Self::get_node_mut(
691 if let Some(node) = Self::get_node_mut(
691 self.on_disk,
692 self.on_disk,
692 &mut self.unreachable_bytes,
693 &mut self.unreachable_bytes,
693 &mut self.root,
694 &mut self.root,
694 path.as_ref(),
695 path.as_ref(),
695 )? {
696 )? {
696 if let NodeData::Entry(entry) = &mut node.data {
697 if let NodeData::Entry(entry) = &mut node.data {
697 entry.clear_mtime();
698 entry.clear_mtime();
698 }
699 }
699 }
700 }
700 }
701 }
701 Ok(())
702 Ok(())
702 }
703 }
703
704
704 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
705 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
705 if let Cow::Borrowed(path) = path {
706 if let Cow::Borrowed(path) = path {
706 *unreachable_bytes += path.len() as u32
707 *unreachable_bytes += path.len() as u32
707 }
708 }
708 }
709 }
709 }
710 }
710
711
711 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
712 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
712 ///
713 ///
713 /// The callback is only called for incoming `Ok` values. Errors are passed
714 /// The callback is only called for incoming `Ok` values. Errors are passed
714 /// through as-is. In order to let it use the `?` operator the callback is
715 /// through as-is. In order to let it use the `?` operator the callback is
715 /// expected to return a `Result` of `Option`, instead of an `Option` of
716 /// expected to return a `Result` of `Option`, instead of an `Option` of
716 /// `Result`.
717 /// `Result`.
717 fn filter_map_results<'a, I, F, A, B, E>(
718 fn filter_map_results<'a, I, F, A, B, E>(
718 iter: I,
719 iter: I,
719 f: F,
720 f: F,
720 ) -> impl Iterator<Item = Result<B, E>> + 'a
721 ) -> impl Iterator<Item = Result<B, E>> + 'a
721 where
722 where
722 I: Iterator<Item = Result<A, E>> + 'a,
723 I: Iterator<Item = Result<A, E>> + 'a,
723 F: Fn(A) -> Result<Option<B>, E> + 'a,
724 F: Fn(A) -> Result<Option<B>, E> + 'a,
724 {
725 {
725 iter.filter_map(move |result| match result {
726 iter.filter_map(move |result| match result {
726 Ok(node) => f(node).transpose(),
727 Ok(node) => f(node).transpose(),
727 Err(e) => Some(Err(e)),
728 Err(e) => Some(Err(e)),
728 })
729 })
729 }
730 }
730
731
731 impl<'on_disk> super::dispatch::DirstateMapMethods for DirstateMap<'on_disk> {
732 impl OwningDirstateMap {
732 fn clear(&mut self) {
733 pub fn clear(&mut self) {
733 self.root = Default::default();
734 let map = self.get_map_mut();
734 self.nodes_with_entry_count = 0;
735 map.root = Default::default();
735 self.nodes_with_copy_source_count = 0;
736 map.nodes_with_entry_count = 0;
737 map.nodes_with_copy_source_count = 0;
736 }
738 }
737
739
738 fn set_entry(
740 pub fn set_entry(
739 &mut self,
741 &mut self,
740 filename: &HgPath,
742 filename: &HgPath,
741 entry: DirstateEntry,
743 entry: DirstateEntry,
742 ) -> Result<(), DirstateV2ParseError> {
744 ) -> Result<(), DirstateV2ParseError> {
743 self.get_or_insert(&filename)?.data = NodeData::Entry(entry);
745 let map = self.get_map_mut();
746 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
744 Ok(())
747 Ok(())
745 }
748 }
746
749
747 fn add_file(
750 pub fn add_file(
748 &mut self,
751 &mut self,
749 filename: &HgPath,
752 filename: &HgPath,
750 entry: DirstateEntry,
753 entry: DirstateEntry,
751 ) -> Result<(), DirstateError> {
754 ) -> Result<(), DirstateError> {
752 let old_state = self.get(filename)?.map(|e| e.state());
755 let old_state = self.get(filename)?.map(|e| e.state());
753 Ok(self.add_or_remove_file(filename, old_state, entry)?)
756 let map = self.get_map_mut();
757 Ok(map.add_or_remove_file(filename, old_state, entry)?)
754 }
758 }
755
759
756 fn remove_file(
760 pub fn remove_file(
757 &mut self,
761 &mut self,
758 filename: &HgPath,
762 filename: &HgPath,
759 in_merge: bool,
763 in_merge: bool,
760 ) -> Result<(), DirstateError> {
764 ) -> Result<(), DirstateError> {
761 let old_entry_opt = self.get(filename)?;
765 let old_entry_opt = self.get(filename)?;
762 let old_state = old_entry_opt.map(|e| e.state());
766 let old_state = old_entry_opt.map(|e| e.state());
763 let mut size = 0;
767 let mut size = 0;
764 if in_merge {
768 if in_merge {
765 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
769 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
766 // during a merge. So I (marmoute) am not sure we need the
770 // during a merge. So I (marmoute) am not sure we need the
767 // conditionnal at all. Adding double checking this with assert
771 // conditionnal at all. Adding double checking this with assert
768 // would be nice.
772 // would be nice.
769 if let Some(old_entry) = old_entry_opt {
773 if let Some(old_entry) = old_entry_opt {
770 // backup the previous state
774 // backup the previous state
771 if old_entry.state() == EntryState::Merged {
775 if old_entry.state() == EntryState::Merged {
772 size = SIZE_NON_NORMAL;
776 size = SIZE_NON_NORMAL;
773 } else if old_entry.state() == EntryState::Normal
777 } else if old_entry.state() == EntryState::Normal
774 && old_entry.size() == SIZE_FROM_OTHER_PARENT
778 && old_entry.size() == SIZE_FROM_OTHER_PARENT
775 {
779 {
776 // other parent
780 // other parent
777 size = SIZE_FROM_OTHER_PARENT;
781 size = SIZE_FROM_OTHER_PARENT;
778 }
782 }
779 }
783 }
780 }
784 }
781 if size == 0 {
785 if size == 0 {
782 self.copy_map_remove(filename)?;
786 self.copy_map_remove(filename)?;
783 }
787 }
788 let map = self.get_map_mut();
784 let entry = DirstateEntry::new_removed(size);
789 let entry = DirstateEntry::new_removed(size);
785 Ok(self.add_or_remove_file(filename, old_state, entry)?)
790 Ok(map.add_or_remove_file(filename, old_state, entry)?)
786 }
791 }
787
792
788 fn drop_entry_and_copy_source(
793 pub fn drop_entry_and_copy_source(
789 &mut self,
794 &mut self,
790 filename: &HgPath,
795 filename: &HgPath,
791 ) -> Result<(), DirstateError> {
796 ) -> Result<(), DirstateError> {
792 let was_tracked = self
797 let was_tracked = self
793 .get(filename)?
798 .get(filename)?
794 .map_or(false, |e| e.state().is_tracked());
799 .map_or(false, |e| e.state().is_tracked());
800 let map = self.get_map_mut();
795 struct Dropped {
801 struct Dropped {
796 was_tracked: bool,
802 was_tracked: bool,
797 had_entry: bool,
803 had_entry: bool,
798 had_copy_source: bool,
804 had_copy_source: bool,
799 }
805 }
800
806
801 /// If this returns `Ok(Some((dropped, removed)))`, then
807 /// If this returns `Ok(Some((dropped, removed)))`, then
802 ///
808 ///
803 /// * `dropped` is about the leaf node that was at `filename`
809 /// * `dropped` is about the leaf node that was at `filename`
804 /// * `removed` is whether this particular level of recursion just
810 /// * `removed` is whether this particular level of recursion just
805 /// removed a node in `nodes`.
811 /// removed a node in `nodes`.
806 fn recur<'on_disk>(
812 fn recur<'on_disk>(
807 on_disk: &'on_disk [u8],
813 on_disk: &'on_disk [u8],
808 unreachable_bytes: &mut u32,
814 unreachable_bytes: &mut u32,
809 nodes: &mut ChildNodes<'on_disk>,
815 nodes: &mut ChildNodes<'on_disk>,
810 path: &HgPath,
816 path: &HgPath,
811 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
817 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
812 let (first_path_component, rest_of_path) =
818 let (first_path_component, rest_of_path) =
813 path.split_first_component();
819 path.split_first_component();
814 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
820 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
815 let node = if let Some(node) = nodes.get_mut(first_path_component)
821 let node = if let Some(node) = nodes.get_mut(first_path_component)
816 {
822 {
817 node
823 node
818 } else {
824 } else {
819 return Ok(None);
825 return Ok(None);
820 };
826 };
821 let dropped;
827 let dropped;
822 if let Some(rest) = rest_of_path {
828 if let Some(rest) = rest_of_path {
823 if let Some((d, removed)) = recur(
829 if let Some((d, removed)) = recur(
824 on_disk,
830 on_disk,
825 unreachable_bytes,
831 unreachable_bytes,
826 &mut node.children,
832 &mut node.children,
827 rest,
833 rest,
828 )? {
834 )? {
829 dropped = d;
835 dropped = d;
830 if dropped.had_entry {
836 if dropped.had_entry {
831 node.descendants_with_entry_count -= 1;
837 node.descendants_with_entry_count -= 1;
832 }
838 }
833 if dropped.was_tracked {
839 if dropped.was_tracked {
834 node.tracked_descendants_count -= 1;
840 node.tracked_descendants_count -= 1;
835 }
841 }
836
842
837 // Directory caches must be invalidated when removing a
843 // Directory caches must be invalidated when removing a
838 // child node
844 // child node
839 if removed {
845 if removed {
840 if let NodeData::CachedDirectory { .. } = &node.data {
846 if let NodeData::CachedDirectory { .. } = &node.data {
841 node.data = NodeData::None
847 node.data = NodeData::None
842 }
848 }
843 }
849 }
844 } else {
850 } else {
845 return Ok(None);
851 return Ok(None);
846 }
852 }
847 } else {
853 } else {
848 let had_entry = node.data.has_entry();
854 let had_entry = node.data.has_entry();
849 if had_entry {
855 if had_entry {
850 node.data = NodeData::None
856 node.data = NodeData::None
851 }
857 }
852 if let Some(source) = &node.copy_source {
858 if let Some(source) = &node.copy_source {
853 DirstateMap::count_dropped_path(unreachable_bytes, source);
859 DirstateMap::count_dropped_path(unreachable_bytes, source);
854 node.copy_source = None
860 node.copy_source = None
855 }
861 }
856 dropped = Dropped {
862 dropped = Dropped {
857 was_tracked: node
863 was_tracked: node
858 .data
864 .data
859 .as_entry()
865 .as_entry()
860 .map_or(false, |entry| entry.state().is_tracked()),
866 .map_or(false, |entry| entry.state().is_tracked()),
861 had_entry,
867 had_entry,
862 had_copy_source: node.copy_source.take().is_some(),
868 had_copy_source: node.copy_source.take().is_some(),
863 };
869 };
864 }
870 }
865 // After recursion, for both leaf (rest_of_path is None) nodes and
871 // After recursion, for both leaf (rest_of_path is None) nodes and
866 // parent nodes, remove a node if it just became empty.
872 // parent nodes, remove a node if it just became empty.
867 let remove = !node.data.has_entry()
873 let remove = !node.data.has_entry()
868 && node.copy_source.is_none()
874 && node.copy_source.is_none()
869 && node.children.is_empty();
875 && node.children.is_empty();
870 if remove {
876 if remove {
871 let (key, _) =
877 let (key, _) =
872 nodes.remove_entry(first_path_component).unwrap();
878 nodes.remove_entry(first_path_component).unwrap();
873 DirstateMap::count_dropped_path(
879 DirstateMap::count_dropped_path(
874 unreachable_bytes,
880 unreachable_bytes,
875 key.full_path(),
881 key.full_path(),
876 )
882 )
877 }
883 }
878 Ok(Some((dropped, remove)))
884 Ok(Some((dropped, remove)))
879 }
885 }
880
886
881 if let Some((dropped, _removed)) = recur(
887 if let Some((dropped, _removed)) = recur(
882 self.on_disk,
888 map.on_disk,
883 &mut self.unreachable_bytes,
889 &mut map.unreachable_bytes,
884 &mut self.root,
890 &mut map.root,
885 filename,
891 filename,
886 )? {
892 )? {
887 if dropped.had_entry {
893 if dropped.had_entry {
888 self.nodes_with_entry_count -= 1
894 map.nodes_with_entry_count -= 1
889 }
895 }
890 if dropped.had_copy_source {
896 if dropped.had_copy_source {
891 self.nodes_with_copy_source_count -= 1
897 map.nodes_with_copy_source_count -= 1
892 }
898 }
893 } else {
899 } else {
894 debug_assert!(!was_tracked);
900 debug_assert!(!was_tracked);
895 }
901 }
896 Ok(())
902 Ok(())
897 }
903 }
898
904
899 fn has_tracked_dir(
905 pub fn has_tracked_dir(
900 &mut self,
906 &mut self,
901 directory: &HgPath,
907 directory: &HgPath,
902 ) -> Result<bool, DirstateError> {
908 ) -> Result<bool, DirstateError> {
903 if let Some(node) = self.get_node(directory)? {
909 let map = self.get_map_mut();
910 if let Some(node) = map.get_node(directory)? {
904 // A node without a `DirstateEntry` was created to hold child
911 // A node without a `DirstateEntry` was created to hold child
905 // nodes, and is therefore a directory.
912 // nodes, and is therefore a directory.
906 let state = node.state()?;
913 let state = node.state()?;
907 Ok(state.is_none() && node.tracked_descendants_count() > 0)
914 Ok(state.is_none() && node.tracked_descendants_count() > 0)
908 } else {
915 } else {
909 Ok(false)
916 Ok(false)
910 }
917 }
911 }
918 }
912
919
913 fn has_dir(&mut self, directory: &HgPath) -> Result<bool, DirstateError> {
920 pub fn has_dir(
914 if let Some(node) = self.get_node(directory)? {
921 &mut self,
922 directory: &HgPath,
923 ) -> Result<bool, DirstateError> {
924 let map = self.get_map_mut();
925 if let Some(node) = map.get_node(directory)? {
915 // A node without a `DirstateEntry` was created to hold child
926 // A node without a `DirstateEntry` was created to hold child
916 // nodes, and is therefore a directory.
927 // nodes, and is therefore a directory.
917 let state = node.state()?;
928 let state = node.state()?;
918 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
929 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
919 } else {
930 } else {
920 Ok(false)
931 Ok(false)
921 }
932 }
922 }
933 }
923
934
924 #[timed]
935 #[timed]
925 fn pack_v1(
936 pub fn pack_v1(
926 &mut self,
937 &mut self,
927 parents: DirstateParents,
938 parents: DirstateParents,
928 now: Timestamp,
939 now: Timestamp,
929 ) -> Result<Vec<u8>, DirstateError> {
940 ) -> Result<Vec<u8>, DirstateError> {
941 let map = self.get_map_mut();
930 let now: i32 = now.0.try_into().expect("time overflow");
942 let now: i32 = now.0.try_into().expect("time overflow");
931 let mut ambiguous_mtimes = Vec::new();
943 let mut ambiguous_mtimes = Vec::new();
932 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
944 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
933 // reallocations
945 // reallocations
934 let mut size = parents.as_bytes().len();
946 let mut size = parents.as_bytes().len();
935 for node in self.iter_nodes() {
947 for node in map.iter_nodes() {
936 let node = node?;
948 let node = node?;
937 if let Some(entry) = node.entry()? {
949 if let Some(entry) = node.entry()? {
938 size += packed_entry_size(
950 size += packed_entry_size(
939 node.full_path(self.on_disk)?,
951 node.full_path(map.on_disk)?,
940 node.copy_source(self.on_disk)?,
952 node.copy_source(map.on_disk)?,
941 );
953 );
942 if entry.mtime_is_ambiguous(now) {
954 if entry.mtime_is_ambiguous(now) {
943 ambiguous_mtimes.push(
955 ambiguous_mtimes.push(
944 node.full_path_borrowed(self.on_disk)?
956 node.full_path_borrowed(map.on_disk)?
945 .detach_from_tree(),
957 .detach_from_tree(),
946 )
958 )
947 }
959 }
948 }
960 }
949 }
961 }
950 self.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
962 map.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
951
963
952 let mut packed = Vec::with_capacity(size);
964 let mut packed = Vec::with_capacity(size);
953 packed.extend(parents.as_bytes());
965 packed.extend(parents.as_bytes());
954
966
955 for node in self.iter_nodes() {
967 for node in map.iter_nodes() {
956 let node = node?;
968 let node = node?;
957 if let Some(entry) = node.entry()? {
969 if let Some(entry) = node.entry()? {
958 pack_entry(
970 pack_entry(
959 node.full_path(self.on_disk)?,
971 node.full_path(map.on_disk)?,
960 &entry,
972 &entry,
961 node.copy_source(self.on_disk)?,
973 node.copy_source(map.on_disk)?,
962 &mut packed,
974 &mut packed,
963 );
975 );
964 }
976 }
965 }
977 }
966 Ok(packed)
978 Ok(packed)
967 }
979 }
968
980
969 /// Returns new data and metadata together with whether that data should be
981 /// Returns new data and metadata together with whether that data should be
970 /// appended to the existing data file whose content is at
982 /// appended to the existing data file whose content is at
971 /// `self.on_disk` (true), instead of written to a new data file
983 /// `map.on_disk` (true), instead of written to a new data file
972 /// (false).
984 /// (false).
973 #[timed]
985 #[timed]
974 fn pack_v2(
986 pub fn pack_v2(
975 &mut self,
987 &mut self,
976 now: Timestamp,
988 now: Timestamp,
977 can_append: bool,
989 can_append: bool,
978 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
990 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
991 let map = self.get_map_mut();
979 // TODO: how do we want to handle this in 2038?
992 // TODO: how do we want to handle this in 2038?
980 let now: i32 = now.0.try_into().expect("time overflow");
993 let now: i32 = now.0.try_into().expect("time overflow");
981 let mut paths = Vec::new();
994 let mut paths = Vec::new();
982 for node in self.iter_nodes() {
995 for node in map.iter_nodes() {
983 let node = node?;
996 let node = node?;
984 if let Some(entry) = node.entry()? {
997 if let Some(entry) = node.entry()? {
985 if entry.mtime_is_ambiguous(now) {
998 if entry.mtime_is_ambiguous(now) {
986 paths.push(
999 paths.push(
987 node.full_path_borrowed(self.on_disk)?
1000 node.full_path_borrowed(map.on_disk)?
988 .detach_from_tree(),
1001 .detach_from_tree(),
989 )
1002 )
990 }
1003 }
991 }
1004 }
992 }
1005 }
993 // Borrow of `self` ends here since we collect cloned paths
1006 // Borrow of `self` ends here since we collect cloned paths
994
1007
995 self.clear_known_ambiguous_mtimes(&paths)?;
1008 map.clear_known_ambiguous_mtimes(&paths)?;
996
1009
997 on_disk::write(self, can_append)
1010 on_disk::write(map, can_append)
998 }
1011 }
999
1012
1000 fn status<'a>(
1013 pub fn status<'a>(
1001 &'a mut self,
1014 &'a mut self,
1002 matcher: &'a (dyn Matcher + Sync),
1015 matcher: &'a (dyn Matcher + Sync),
1003 root_dir: PathBuf,
1016 root_dir: PathBuf,
1004 ignore_files: Vec<PathBuf>,
1017 ignore_files: Vec<PathBuf>,
1005 options: StatusOptions,
1018 options: StatusOptions,
1006 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1019 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1007 {
1020 {
1008 super::status::status(self, matcher, root_dir, ignore_files, options)
1021 let map = self.get_map_mut();
1022 super::status::status(map, matcher, root_dir, ignore_files, options)
1009 }
1023 }
1010
1024
1011 fn copy_map_len(&self) -> usize {
1025 pub fn copy_map_len(&self) -> usize {
1012 self.nodes_with_copy_source_count as usize
1026 let map = self.get_map();
1027 map.nodes_with_copy_source_count as usize
1013 }
1028 }
1014
1029
1015 fn copy_map_iter(&self) -> CopyMapIter<'_> {
1030 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1016 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1031 let map = self.get_map();
1017 Ok(if let Some(source) = node.copy_source(self.on_disk)? {
1032 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1018 Some((node.full_path(self.on_disk)?, source))
1033 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1034 Some((node.full_path(map.on_disk)?, source))
1019 } else {
1035 } else {
1020 None
1036 None
1021 })
1037 })
1022 }))
1038 }))
1023 }
1039 }
1024
1040
1025 fn copy_map_contains_key(
1041 pub fn copy_map_contains_key(
1026 &self,
1042 &self,
1027 key: &HgPath,
1043 key: &HgPath,
1028 ) -> Result<bool, DirstateV2ParseError> {
1044 ) -> Result<bool, DirstateV2ParseError> {
1029 Ok(if let Some(node) = self.get_node(key)? {
1045 let map = self.get_map();
1046 Ok(if let Some(node) = map.get_node(key)? {
1030 node.has_copy_source()
1047 node.has_copy_source()
1031 } else {
1048 } else {
1032 false
1049 false
1033 })
1050 })
1034 }
1051 }
1035
1052
1036 fn copy_map_get(
1053 pub fn copy_map_get(
1037 &self,
1054 &self,
1038 key: &HgPath,
1055 key: &HgPath,
1039 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1056 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1040 if let Some(node) = self.get_node(key)? {
1057 let map = self.get_map();
1041 if let Some(source) = node.copy_source(self.on_disk)? {
1058 if let Some(node) = map.get_node(key)? {
1059 if let Some(source) = node.copy_source(map.on_disk)? {
1042 return Ok(Some(source));
1060 return Ok(Some(source));
1043 }
1061 }
1044 }
1062 }
1045 Ok(None)
1063 Ok(None)
1046 }
1064 }
1047
1065
1048 fn copy_map_remove(
1066 pub fn copy_map_remove(
1049 &mut self,
1067 &mut self,
1050 key: &HgPath,
1068 key: &HgPath,
1051 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1069 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1052 let count = &mut self.nodes_with_copy_source_count;
1070 let map = self.get_map_mut();
1053 let unreachable_bytes = &mut self.unreachable_bytes;
1071 let count = &mut map.nodes_with_copy_source_count;
1054 Ok(Self::get_node_mut(
1072 let unreachable_bytes = &mut map.unreachable_bytes;
1055 self.on_disk,
1073 Ok(DirstateMap::get_node_mut(
1074 map.on_disk,
1056 unreachable_bytes,
1075 unreachable_bytes,
1057 &mut self.root,
1076 &mut map.root,
1058 key,
1077 key,
1059 )?
1078 )?
1060 .and_then(|node| {
1079 .and_then(|node| {
1061 if let Some(source) = &node.copy_source {
1080 if let Some(source) = &node.copy_source {
1062 *count -= 1;
1081 *count -= 1;
1063 Self::count_dropped_path(unreachable_bytes, source);
1082 DirstateMap::count_dropped_path(unreachable_bytes, source);
1064 }
1083 }
1065 node.copy_source.take().map(Cow::into_owned)
1084 node.copy_source.take().map(Cow::into_owned)
1066 }))
1085 }))
1067 }
1086 }
1068
1087
1069 fn copy_map_insert(
1088 pub fn copy_map_insert(
1070 &mut self,
1089 &mut self,
1071 key: HgPathBuf,
1090 key: HgPathBuf,
1072 value: HgPathBuf,
1091 value: HgPathBuf,
1073 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1092 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1074 let node = Self::get_or_insert_node(
1093 let map = self.get_map_mut();
1075 self.on_disk,
1094 let node = DirstateMap::get_or_insert_node(
1076 &mut self.unreachable_bytes,
1095 map.on_disk,
1077 &mut self.root,
1096 &mut map.unreachable_bytes,
1097 &mut map.root,
1078 &key,
1098 &key,
1079 WithBasename::to_cow_owned,
1099 WithBasename::to_cow_owned,
1080 |_ancestor| {},
1100 |_ancestor| {},
1081 )?;
1101 )?;
1082 if node.copy_source.is_none() {
1102 if node.copy_source.is_none() {
1083 self.nodes_with_copy_source_count += 1
1103 map.nodes_with_copy_source_count += 1
1084 }
1104 }
1085 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1105 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1086 }
1106 }
1087
1107
1088 fn len(&self) -> usize {
1108 pub fn len(&self) -> usize {
1089 self.nodes_with_entry_count as usize
1109 let map = self.get_map();
1110 map.nodes_with_entry_count as usize
1090 }
1111 }
1091
1112
1092 fn contains_key(
1113 pub fn contains_key(
1093 &self,
1114 &self,
1094 key: &HgPath,
1115 key: &HgPath,
1095 ) -> Result<bool, DirstateV2ParseError> {
1116 ) -> Result<bool, DirstateV2ParseError> {
1096 Ok(self.get(key)?.is_some())
1117 Ok(self.get(key)?.is_some())
1097 }
1118 }
1098
1119
1099 fn get(
1120 pub fn get(
1100 &self,
1121 &self,
1101 key: &HgPath,
1122 key: &HgPath,
1102 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1123 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1103 Ok(if let Some(node) = self.get_node(key)? {
1124 let map = self.get_map();
1125 Ok(if let Some(node) = map.get_node(key)? {
1104 node.entry()?
1126 node.entry()?
1105 } else {
1127 } else {
1106 None
1128 None
1107 })
1129 })
1108 }
1130 }
1109
1131
1110 fn iter(&self) -> StateMapIter<'_> {
1132 pub fn iter(&self) -> StateMapIter<'_> {
1111 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1133 let map = self.get_map();
1134 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1112 Ok(if let Some(entry) = node.entry()? {
1135 Ok(if let Some(entry) = node.entry()? {
1113 Some((node.full_path(self.on_disk)?, entry))
1136 Some((node.full_path(map.on_disk)?, entry))
1114 } else {
1137 } else {
1115 None
1138 None
1116 })
1139 })
1117 }))
1140 }))
1118 }
1141 }
1119
1142
1120 fn iter_tracked_dirs(
1143 pub fn iter_tracked_dirs(
1121 &mut self,
1144 &mut self,
1122 ) -> Result<
1145 ) -> Result<
1123 Box<
1146 Box<
1124 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1147 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1125 + Send
1148 + Send
1126 + '_,
1149 + '_,
1127 >,
1150 >,
1128 DirstateError,
1151 DirstateError,
1129 > {
1152 > {
1130 let on_disk = self.on_disk;
1153 let map = self.get_map_mut();
1154 let on_disk = map.on_disk;
1131 Ok(Box::new(filter_map_results(
1155 Ok(Box::new(filter_map_results(
1132 self.iter_nodes(),
1156 map.iter_nodes(),
1133 move |node| {
1157 move |node| {
1134 Ok(if node.tracked_descendants_count() > 0 {
1158 Ok(if node.tracked_descendants_count() > 0 {
1135 Some(node.full_path(on_disk)?)
1159 Some(node.full_path(on_disk)?)
1136 } else {
1160 } else {
1137 None
1161 None
1138 })
1162 })
1139 },
1163 },
1140 )))
1164 )))
1141 }
1165 }
1142
1166
1143 fn debug_iter(
1167 pub fn debug_iter(
1144 &self,
1168 &self,
1145 all: bool,
1169 all: bool,
1146 ) -> Box<
1170 ) -> Box<
1147 dyn Iterator<
1171 dyn Iterator<
1148 Item = Result<
1172 Item = Result<
1149 (&HgPath, (u8, i32, i32, i32)),
1173 (&HgPath, (u8, i32, i32, i32)),
1150 DirstateV2ParseError,
1174 DirstateV2ParseError,
1151 >,
1175 >,
1152 > + Send
1176 > + Send
1153 + '_,
1177 + '_,
1154 > {
1178 > {
1155 Box::new(filter_map_results(self.iter_nodes(), move |node| {
1179 let map = self.get_map();
1180 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1156 let debug_tuple = if let Some(entry) = node.entry()? {
1181 let debug_tuple = if let Some(entry) = node.entry()? {
1157 entry.debug_tuple()
1182 entry.debug_tuple()
1158 } else if !all {
1183 } else if !all {
1159 return Ok(None);
1184 return Ok(None);
1160 } else if let Some(mtime) = node.cached_directory_mtime() {
1185 } else if let Some(mtime) = node.cached_directory_mtime() {
1161 (b' ', 0, -1, mtime.seconds() as i32)
1186 (b' ', 0, -1, mtime.seconds() as i32)
1162 } else {
1187 } else {
1163 (b' ', 0, -1, -1)
1188 (b' ', 0, -1, -1)
1164 };
1189 };
1165 Ok(Some((node.full_path(self.on_disk)?, debug_tuple)))
1190 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1166 }))
1191 }))
1167 }
1192 }
1168 }
1193 }
@@ -1,105 +1,105 b''
1 use super::dirstate_map::DirstateMap;
1 use super::dirstate_map::DirstateMap;
2 use stable_deref_trait::StableDeref;
2 use stable_deref_trait::StableDeref;
3 use std::ops::Deref;
3 use std::ops::Deref;
4
4
5 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
5 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
6 /// borrows.
6 /// borrows.
7 ///
7 ///
8 /// This is similar to [`OwningRef`] which is more limited because it
8 /// This is similar to [`OwningRef`] which is more limited because it
9 /// represents exactly one `&T` reference next to the value it borrows, as
9 /// represents exactly one `&T` reference next to the value it borrows, as
10 /// opposed to a struct that may contain an arbitrary number of references in
10 /// opposed to a struct that may contain an arbitrary number of references in
11 /// arbitrarily-nested data structures.
11 /// arbitrarily-nested data structures.
12 ///
12 ///
13 /// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html
13 /// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html
14 pub struct OwningDirstateMap {
14 pub struct OwningDirstateMap {
15 /// Owned handle to a bytes buffer with a stable address.
15 /// Owned handle to a bytes buffer with a stable address.
16 ///
16 ///
17 /// See <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>.
17 /// See <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>.
18 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
18 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
19
19
20 /// Pointer for `Box<DirstateMap<'on_disk>>`, typed-erased because the
20 /// Pointer for `Box<DirstateMap<'on_disk>>`, typed-erased because the
21 /// language cannot represent a lifetime referencing a sibling field.
21 /// language cannot represent a lifetime referencing a sibling field.
22 /// This is not quite a self-referencial struct (moving this struct is not
22 /// This is not quite a self-referencial struct (moving this struct is not
23 /// a problem as it doesn’t change the address of the bytes buffer owned
23 /// a problem as it doesn’t change the address of the bytes buffer owned
24 /// by `PyBytes`) but touches similar borrow-checker limitations.
24 /// by `PyBytes`) but touches similar borrow-checker limitations.
25 ptr: *mut (),
25 ptr: *mut (),
26 }
26 }
27
27
28 impl OwningDirstateMap {
28 impl OwningDirstateMap {
29 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
29 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
30 where
30 where
31 OnDisk: Deref<Target = [u8]> + StableDeref + Send + 'static,
31 OnDisk: Deref<Target = [u8]> + StableDeref + Send + 'static,
32 {
32 {
33 let on_disk = Box::new(on_disk);
33 let on_disk = Box::new(on_disk);
34 let bytes: &'_ [u8] = &on_disk;
34 let bytes: &'_ [u8] = &on_disk;
35 let map = DirstateMap::empty(bytes);
35 let map = DirstateMap::empty(bytes);
36
36
37 // Like in `bytes` above, this `'_` lifetime parameter borrows from
37 // Like in `bytes` above, this `'_` lifetime parameter borrows from
38 // the bytes buffer owned by `on_disk`.
38 // the bytes buffer owned by `on_disk`.
39 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
39 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
40
40
41 // Erase the pointed type entirely in order to erase the lifetime.
41 // Erase the pointed type entirely in order to erase the lifetime.
42 let ptr: *mut () = ptr.cast();
42 let ptr: *mut () = ptr.cast();
43
43
44 Self { on_disk, ptr }
44 Self { on_disk, ptr }
45 }
45 }
46
46
47 pub fn get_mut_pair<'a>(
47 pub fn get_pair_mut<'a>(
48 &'a mut self,
48 &'a mut self,
49 ) -> (&'a [u8], &'a mut DirstateMap<'a>) {
49 ) -> (&'a [u8], &'a mut DirstateMap<'a>) {
50 // SAFETY: We cast the type-erased pointer back to the same type it had
50 // SAFETY: We cast the type-erased pointer back to the same type it had
51 // in `new`, except with a different lifetime parameter. This time we
51 // in `new`, except with a different lifetime parameter. This time we
52 // connect the lifetime to that of `self`. This cast is valid because
52 // connect the lifetime to that of `self`. This cast is valid because
53 // `self` owns the same `PyBytes` whose buffer `DirstateMap`
53 // `self` owns the same `PyBytes` whose buffer `DirstateMap`
54 // references. That buffer has a stable memory address because the byte
54 // references. That buffer has a stable memory address because the byte
55 // string value of a `PyBytes` is immutable.
55 // string value of a `PyBytes` is immutable.
56 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
56 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
57 // SAFETY: we dereference that pointer, connecting the lifetime of the
57 // SAFETY: we dereference that pointer, connecting the lifetime of the
58 // new `&mut` to that of `self`. This is valid because the
58 // new `&mut` to that of `self`. This is valid because the
59 // raw pointer is to a boxed value, and `self` owns that box.
59 // raw pointer is to a boxed value, and `self` owns that box.
60 (&self.on_disk, unsafe { &mut *ptr })
60 (&self.on_disk, unsafe { &mut *ptr })
61 }
61 }
62
62
63 pub fn get_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
63 pub fn get_map_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
64 self.get_mut_pair().1
64 self.get_pair_mut().1
65 }
65 }
66
66
67 pub fn get<'a>(&'a self) -> &'a DirstateMap<'a> {
67 pub fn get_map<'a>(&'a self) -> &'a DirstateMap<'a> {
68 // SAFETY: same reasoning as in `get_mut` above.
68 // SAFETY: same reasoning as in `get_mut` above.
69 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
69 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
70 unsafe { &*ptr }
70 unsafe { &*ptr }
71 }
71 }
72
72
73 pub fn on_disk<'a>(&'a self) -> &'a [u8] {
73 pub fn on_disk<'a>(&'a self) -> &'a [u8] {
74 &self.on_disk
74 &self.on_disk
75 }
75 }
76 }
76 }
77
77
78 impl Drop for OwningDirstateMap {
78 impl Drop for OwningDirstateMap {
79 fn drop(&mut self) {
79 fn drop(&mut self) {
80 // Silence a "field is never read" warning, and demonstrate that this
80 // Silence a "field is never read" warning, and demonstrate that this
81 // value is still alive.
81 // value is still alive.
82 let _ = &self.on_disk;
82 let _ = &self.on_disk;
83 // SAFETY: this cast is the same as in `get_mut`, and is valid for the
83 // SAFETY: this cast is the same as in `get_mut`, and is valid for the
84 // same reason. `self.on_disk` still exists at this point, drop glue
84 // same reason. `self.on_disk` still exists at this point, drop glue
85 // will drop it implicitly after this `drop` method returns.
85 // will drop it implicitly after this `drop` method returns.
86 let ptr: *mut DirstateMap<'_> = self.ptr.cast();
86 let ptr: *mut DirstateMap<'_> = self.ptr.cast();
87 // SAFETY: `Box::from_raw` takes ownership of the box away from `self`.
87 // SAFETY: `Box::from_raw` takes ownership of the box away from `self`.
88 // This is fine because drop glue does nothig for `*mut ()` and we’re
88 // This is fine because drop glue does nothig for `*mut ()` and we’re
89 // in `drop`, so `get` and `get_mut` cannot be called again.
89 // in `drop`, so `get` and `get_mut` cannot be called again.
90 unsafe { drop(Box::from_raw(ptr)) }
90 unsafe { drop(Box::from_raw(ptr)) }
91 }
91 }
92 }
92 }
93
93
94 fn _static_assert_is_send<T: Send>() {}
94 fn _static_assert_is_send<T: Send>() {}
95
95
96 fn _static_assert_fields_are_send() {
96 fn _static_assert_fields_are_send() {
97 _static_assert_is_send::<Box<DirstateMap<'_>>>();
97 _static_assert_is_send::<Box<DirstateMap<'_>>>();
98 }
98 }
99
99
100 // SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because
100 // SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because
101 // thread-safety of raw pointers is unknown in the general case. However this
101 // thread-safety of raw pointers is unknown in the general case. However this
102 // particular raw pointer represents a `Box<DirstateMap<'on_disk>>` that we
102 // particular raw pointer represents a `Box<DirstateMap<'on_disk>>` that we
103 // own. Since that `Box` is `Send` as shown in above, it is sound to mark
103 // own. Since that `Box` is `Send` as shown in above, it is sound to mark
104 // this struct as `Send` too.
104 // this struct as `Send` too.
105 unsafe impl Send for OwningDirstateMap {}
105 unsafe impl Send for OwningDirstateMap {}
@@ -1,409 +1,409 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::owning::OwningDirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::errors::HgError;
6 use crate::errors::HgError;
7 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
8 use crate::exit_codes;
8 use crate::exit_codes;
9 use crate::manifest::{Manifest, Manifestlog};
9 use crate::manifest::{Manifest, Manifestlog};
10 use crate::revlog::filelog::Filelog;
10 use crate::revlog::filelog::Filelog;
11 use crate::revlog::revlog::RevlogError;
11 use crate::revlog::revlog::RevlogError;
12 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPath;
14 use crate::utils::SliceExt;
14 use crate::utils::SliceExt;
15 use crate::vfs::{is_dir, is_file, Vfs};
15 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::{requirements, NodePrefix};
16 use crate::{requirements, NodePrefix};
17 use crate::{DirstateError, Revision};
17 use crate::{DirstateError, Revision};
18 use std::cell::{Cell, Ref, RefCell, RefMut};
18 use std::cell::{Cell, Ref, RefCell, RefMut};
19 use std::collections::HashSet;
19 use std::collections::HashSet;
20 use std::path::{Path, PathBuf};
20 use std::path::{Path, PathBuf};
21
21
22 /// A repository on disk
22 /// A repository on disk
23 pub struct Repo {
23 pub struct Repo {
24 working_directory: PathBuf,
24 working_directory: PathBuf,
25 dot_hg: PathBuf,
25 dot_hg: PathBuf,
26 store: PathBuf,
26 store: PathBuf,
27 requirements: HashSet<String>,
27 requirements: HashSet<String>,
28 config: Config,
28 config: Config,
29 // None means not known/initialized yet
29 // None means not known/initialized yet
30 dirstate_parents: Cell<Option<DirstateParents>>,
30 dirstate_parents: Cell<Option<DirstateParents>>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
32 changelog: LazyCell<Changelog, HgError>,
32 changelog: LazyCell<Changelog, HgError>,
33 manifestlog: LazyCell<Manifestlog, HgError>,
33 manifestlog: LazyCell<Manifestlog, HgError>,
34 }
34 }
35
35
36 #[derive(Debug, derive_more::From)]
36 #[derive(Debug, derive_more::From)]
37 pub enum RepoError {
37 pub enum RepoError {
38 NotFound {
38 NotFound {
39 at: PathBuf,
39 at: PathBuf,
40 },
40 },
41 #[from]
41 #[from]
42 ConfigParseError(ConfigParseError),
42 ConfigParseError(ConfigParseError),
43 #[from]
43 #[from]
44 Other(HgError),
44 Other(HgError),
45 }
45 }
46
46
47 impl From<ConfigError> for RepoError {
47 impl From<ConfigError> for RepoError {
48 fn from(error: ConfigError) -> Self {
48 fn from(error: ConfigError) -> Self {
49 match error {
49 match error {
50 ConfigError::Parse(error) => error.into(),
50 ConfigError::Parse(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
52 }
52 }
53 }
53 }
54 }
54 }
55
55
56 impl Repo {
56 impl Repo {
57 /// tries to find nearest repository root in current working directory or
57 /// tries to find nearest repository root in current working directory or
58 /// its ancestors
58 /// its ancestors
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
60 let current_directory = crate::utils::current_dir()?;
60 let current_directory = crate::utils::current_dir()?;
61 // ancestors() is inclusive: it first yields `current_directory`
61 // ancestors() is inclusive: it first yields `current_directory`
62 // as-is.
62 // as-is.
63 for ancestor in current_directory.ancestors() {
63 for ancestor in current_directory.ancestors() {
64 if is_dir(ancestor.join(".hg"))? {
64 if is_dir(ancestor.join(".hg"))? {
65 return Ok(ancestor.to_path_buf());
65 return Ok(ancestor.to_path_buf());
66 }
66 }
67 }
67 }
68 return Err(RepoError::NotFound {
68 return Err(RepoError::NotFound {
69 at: current_directory,
69 at: current_directory,
70 });
70 });
71 }
71 }
72
72
73 /// Find a repository, either at the given path (which must contain a `.hg`
73 /// Find a repository, either at the given path (which must contain a `.hg`
74 /// sub-directory) or by searching the current directory and its
74 /// sub-directory) or by searching the current directory and its
75 /// ancestors.
75 /// ancestors.
76 ///
76 ///
77 /// A method with two very different "modes" like this usually a code smell
77 /// A method with two very different "modes" like this usually a code smell
78 /// to make two methods instead, but in this case an `Option` is what rhg
78 /// to make two methods instead, but in this case an `Option` is what rhg
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
80 /// Having two methods would just move that `if` to almost all callers.
80 /// Having two methods would just move that `if` to almost all callers.
81 pub fn find(
81 pub fn find(
82 config: &Config,
82 config: &Config,
83 explicit_path: Option<PathBuf>,
83 explicit_path: Option<PathBuf>,
84 ) -> Result<Self, RepoError> {
84 ) -> Result<Self, RepoError> {
85 if let Some(root) = explicit_path {
85 if let Some(root) = explicit_path {
86 if is_dir(root.join(".hg"))? {
86 if is_dir(root.join(".hg"))? {
87 Self::new_at_path(root.to_owned(), config)
87 Self::new_at_path(root.to_owned(), config)
88 } else if is_file(&root)? {
88 } else if is_file(&root)? {
89 Err(HgError::unsupported("bundle repository").into())
89 Err(HgError::unsupported("bundle repository").into())
90 } else {
90 } else {
91 Err(RepoError::NotFound {
91 Err(RepoError::NotFound {
92 at: root.to_owned(),
92 at: root.to_owned(),
93 })
93 })
94 }
94 }
95 } else {
95 } else {
96 let root = Self::find_repo_root()?;
96 let root = Self::find_repo_root()?;
97 Self::new_at_path(root, config)
97 Self::new_at_path(root, config)
98 }
98 }
99 }
99 }
100
100
101 /// To be called after checking that `.hg` is a sub-directory
101 /// To be called after checking that `.hg` is a sub-directory
102 fn new_at_path(
102 fn new_at_path(
103 working_directory: PathBuf,
103 working_directory: PathBuf,
104 config: &Config,
104 config: &Config,
105 ) -> Result<Self, RepoError> {
105 ) -> Result<Self, RepoError> {
106 let dot_hg = working_directory.join(".hg");
106 let dot_hg = working_directory.join(".hg");
107
107
108 let mut repo_config_files = Vec::new();
108 let mut repo_config_files = Vec::new();
109 repo_config_files.push(dot_hg.join("hgrc"));
109 repo_config_files.push(dot_hg.join("hgrc"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
111
111
112 let hg_vfs = Vfs { base: &dot_hg };
112 let hg_vfs = Vfs { base: &dot_hg };
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 let relative =
114 let relative =
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 let shared =
116 let shared =
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118
118
119 // From `mercurial/localrepo.py`:
119 // From `mercurial/localrepo.py`:
120 //
120 //
121 // if .hg/requires contains the sharesafe requirement, it means
121 // if .hg/requires contains the sharesafe requirement, it means
122 // there exists a `.hg/store/requires` too and we should read it
122 // there exists a `.hg/store/requires` too and we should read it
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
124 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
125 // is not present, refer checkrequirementscompat() for that
125 // is not present, refer checkrequirementscompat() for that
126 //
126 //
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 // repository was shared the old way. We check the share source
128 // repository was shared the old way. We check the share source
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 // current repository needs to be reshared
130 // current repository needs to be reshared
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132
132
133 let store_path;
133 let store_path;
134 if !shared {
134 if !shared {
135 store_path = dot_hg.join("store");
135 store_path = dot_hg.join("store");
136 } else {
136 } else {
137 let bytes = hg_vfs.read("sharedpath")?;
137 let bytes = hg_vfs.read("sharedpath")?;
138 let mut shared_path =
138 let mut shared_path =
139 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
139 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
140 .to_owned();
140 .to_owned();
141 if relative {
141 if relative {
142 shared_path = dot_hg.join(shared_path)
142 shared_path = dot_hg.join(shared_path)
143 }
143 }
144 if !is_dir(&shared_path)? {
144 if !is_dir(&shared_path)? {
145 return Err(HgError::corrupted(format!(
145 return Err(HgError::corrupted(format!(
146 ".hg/sharedpath points to nonexistent directory {}",
146 ".hg/sharedpath points to nonexistent directory {}",
147 shared_path.display()
147 shared_path.display()
148 ))
148 ))
149 .into());
149 .into());
150 }
150 }
151
151
152 store_path = shared_path.join("store");
152 store_path = shared_path.join("store");
153
153
154 let source_is_share_safe =
154 let source_is_share_safe =
155 requirements::load(Vfs { base: &shared_path })?
155 requirements::load(Vfs { base: &shared_path })?
156 .contains(requirements::SHARESAFE_REQUIREMENT);
156 .contains(requirements::SHARESAFE_REQUIREMENT);
157
157
158 if share_safe && !source_is_share_safe {
158 if share_safe && !source_is_share_safe {
159 return Err(match config
159 return Err(match config
160 .get(b"share", b"safe-mismatch.source-not-safe")
160 .get(b"share", b"safe-mismatch.source-not-safe")
161 {
161 {
162 Some(b"abort") | None => HgError::abort(
162 Some(b"abort") | None => HgError::abort(
163 "abort: share source does not support share-safe requirement\n\
163 "abort: share source does not support share-safe requirement\n\
164 (see `hg help config.format.use-share-safe` for more information)",
164 (see `hg help config.format.use-share-safe` for more information)",
165 exit_codes::ABORT,
165 exit_codes::ABORT,
166 ),
166 ),
167 _ => HgError::unsupported("share-safe downgrade"),
167 _ => HgError::unsupported("share-safe downgrade"),
168 }
168 }
169 .into());
169 .into());
170 } else if source_is_share_safe && !share_safe {
170 } else if source_is_share_safe && !share_safe {
171 return Err(
171 return Err(
172 match config.get(b"share", b"safe-mismatch.source-safe") {
172 match config.get(b"share", b"safe-mismatch.source-safe") {
173 Some(b"abort") | None => HgError::abort(
173 Some(b"abort") | None => HgError::abort(
174 "abort: version mismatch: source uses share-safe \
174 "abort: version mismatch: source uses share-safe \
175 functionality while the current share does not\n\
175 functionality while the current share does not\n\
176 (see `hg help config.format.use-share-safe` for more information)",
176 (see `hg help config.format.use-share-safe` for more information)",
177 exit_codes::ABORT,
177 exit_codes::ABORT,
178 ),
178 ),
179 _ => HgError::unsupported("share-safe upgrade"),
179 _ => HgError::unsupported("share-safe upgrade"),
180 }
180 }
181 .into(),
181 .into(),
182 );
182 );
183 }
183 }
184
184
185 if share_safe {
185 if share_safe {
186 repo_config_files.insert(0, shared_path.join("hgrc"))
186 repo_config_files.insert(0, shared_path.join("hgrc"))
187 }
187 }
188 }
188 }
189 if share_safe {
189 if share_safe {
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
191 }
191 }
192
192
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
194 config.combine_with_repo(&repo_config_files)?
194 config.combine_with_repo(&repo_config_files)?
195 } else {
195 } else {
196 config.clone()
196 config.clone()
197 };
197 };
198
198
199 let repo = Self {
199 let repo = Self {
200 requirements: reqs,
200 requirements: reqs,
201 working_directory,
201 working_directory,
202 store: store_path,
202 store: store_path,
203 dot_hg,
203 dot_hg,
204 config: repo_config,
204 config: repo_config,
205 dirstate_parents: Cell::new(None),
205 dirstate_parents: Cell::new(None),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
207 changelog: LazyCell::new(Changelog::open),
207 changelog: LazyCell::new(Changelog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
209 };
209 };
210
210
211 requirements::check(&repo)?;
211 requirements::check(&repo)?;
212
212
213 Ok(repo)
213 Ok(repo)
214 }
214 }
215
215
216 pub fn working_directory_path(&self) -> &Path {
216 pub fn working_directory_path(&self) -> &Path {
217 &self.working_directory
217 &self.working_directory
218 }
218 }
219
219
220 pub fn requirements(&self) -> &HashSet<String> {
220 pub fn requirements(&self) -> &HashSet<String> {
221 &self.requirements
221 &self.requirements
222 }
222 }
223
223
224 pub fn config(&self) -> &Config {
224 pub fn config(&self) -> &Config {
225 &self.config
225 &self.config
226 }
226 }
227
227
228 /// For accessing repository files (in `.hg`), except for the store
228 /// For accessing repository files (in `.hg`), except for the store
229 /// (`.hg/store`).
229 /// (`.hg/store`).
230 pub fn hg_vfs(&self) -> Vfs<'_> {
230 pub fn hg_vfs(&self) -> Vfs<'_> {
231 Vfs { base: &self.dot_hg }
231 Vfs { base: &self.dot_hg }
232 }
232 }
233
233
234 /// For accessing repository store files (in `.hg/store`)
234 /// For accessing repository store files (in `.hg/store`)
235 pub fn store_vfs(&self) -> Vfs<'_> {
235 pub fn store_vfs(&self) -> Vfs<'_> {
236 Vfs { base: &self.store }
236 Vfs { base: &self.store }
237 }
237 }
238
238
239 /// For accessing the working copy
239 /// For accessing the working copy
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
241 Vfs {
241 Vfs {
242 base: &self.working_directory,
242 base: &self.working_directory,
243 }
243 }
244 }
244 }
245
245
246 pub fn has_dirstate_v2(&self) -> bool {
246 pub fn has_dirstate_v2(&self) -> bool {
247 self.requirements
247 self.requirements
248 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
248 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
249 }
249 }
250
250
251 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
251 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
252 Ok(self
252 Ok(self
253 .hg_vfs()
253 .hg_vfs()
254 .read("dirstate")
254 .read("dirstate")
255 .io_not_found_as_none()?
255 .io_not_found_as_none()?
256 .unwrap_or(Vec::new()))
256 .unwrap_or(Vec::new()))
257 }
257 }
258
258
259 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
259 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
260 if let Some(parents) = self.dirstate_parents.get() {
260 if let Some(parents) = self.dirstate_parents.get() {
261 return Ok(parents);
261 return Ok(parents);
262 }
262 }
263 let dirstate = self.dirstate_file_contents()?;
263 let dirstate = self.dirstate_file_contents()?;
264 let parents = if dirstate.is_empty() {
264 let parents = if dirstate.is_empty() {
265 DirstateParents::NULL
265 DirstateParents::NULL
266 } else if self.has_dirstate_v2() {
266 } else if self.has_dirstate_v2() {
267 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
267 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
268 } else {
268 } else {
269 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
269 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
270 .clone()
270 .clone()
271 };
271 };
272 self.dirstate_parents.set(Some(parents));
272 self.dirstate_parents.set(Some(parents));
273 Ok(parents)
273 Ok(parents)
274 }
274 }
275
275
276 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
276 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
277 let dirstate_file_contents = self.dirstate_file_contents()?;
277 let dirstate_file_contents = self.dirstate_file_contents()?;
278 if dirstate_file_contents.is_empty() {
278 if dirstate_file_contents.is_empty() {
279 self.dirstate_parents.set(Some(DirstateParents::NULL));
279 self.dirstate_parents.set(Some(DirstateParents::NULL));
280 Ok(OwningDirstateMap::new_empty(Vec::new()))
280 Ok(OwningDirstateMap::new_empty(Vec::new()))
281 } else if self.has_dirstate_v2() {
281 } else if self.has_dirstate_v2() {
282 let docket = crate::dirstate_tree::on_disk::read_docket(
282 let docket = crate::dirstate_tree::on_disk::read_docket(
283 &dirstate_file_contents,
283 &dirstate_file_contents,
284 )?;
284 )?;
285 self.dirstate_parents.set(Some(docket.parents()));
285 self.dirstate_parents.set(Some(docket.parents()));
286 let data_size = docket.data_size();
286 let data_size = docket.data_size();
287 let metadata = docket.tree_metadata();
287 let metadata = docket.tree_metadata();
288 let mut map = if let Some(data_mmap) = self
288 let mut map = if let Some(data_mmap) = self
289 .hg_vfs()
289 .hg_vfs()
290 .mmap_open(docket.data_filename())
290 .mmap_open(docket.data_filename())
291 .io_not_found_as_none()?
291 .io_not_found_as_none()?
292 {
292 {
293 OwningDirstateMap::new_empty(data_mmap)
293 OwningDirstateMap::new_empty(data_mmap)
294 } else {
294 } else {
295 OwningDirstateMap::new_empty(Vec::new())
295 OwningDirstateMap::new_empty(Vec::new())
296 };
296 };
297 let (on_disk, placeholder) = map.get_mut_pair();
297 let (on_disk, placeholder) = map.get_pair_mut();
298 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
298 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
299 Ok(map)
299 Ok(map)
300 } else {
300 } else {
301 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
301 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
302 let (on_disk, placeholder) = map.get_mut_pair();
302 let (on_disk, placeholder) = map.get_pair_mut();
303 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
303 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
304 self.dirstate_parents
304 self.dirstate_parents
305 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
305 .set(Some(parents.unwrap_or(DirstateParents::NULL)));
306 *placeholder = inner;
306 *placeholder = inner;
307 Ok(map)
307 Ok(map)
308 }
308 }
309 }
309 }
310
310
311 pub fn dirstate_map(
311 pub fn dirstate_map(
312 &self,
312 &self,
313 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
313 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
314 self.dirstate_map.get_or_init(self)
314 self.dirstate_map.get_or_init(self)
315 }
315 }
316
316
317 pub fn dirstate_map_mut(
317 pub fn dirstate_map_mut(
318 &self,
318 &self,
319 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
319 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
320 self.dirstate_map.get_mut_or_init(self)
320 self.dirstate_map.get_mut_or_init(self)
321 }
321 }
322
322
323 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
323 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
324 self.changelog.get_or_init(self)
324 self.changelog.get_or_init(self)
325 }
325 }
326
326
327 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
327 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
328 self.changelog.get_mut_or_init(self)
328 self.changelog.get_mut_or_init(self)
329 }
329 }
330
330
331 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
331 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
332 self.manifestlog.get_or_init(self)
332 self.manifestlog.get_or_init(self)
333 }
333 }
334
334
335 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
335 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
336 self.manifestlog.get_mut_or_init(self)
336 self.manifestlog.get_mut_or_init(self)
337 }
337 }
338
338
339 /// Returns the manifest of the *changeset* with the given node ID
339 /// Returns the manifest of the *changeset* with the given node ID
340 pub fn manifest_for_node(
340 pub fn manifest_for_node(
341 &self,
341 &self,
342 node: impl Into<NodePrefix>,
342 node: impl Into<NodePrefix>,
343 ) -> Result<Manifest, RevlogError> {
343 ) -> Result<Manifest, RevlogError> {
344 self.manifestlog()?.data_for_node(
344 self.manifestlog()?.data_for_node(
345 self.changelog()?
345 self.changelog()?
346 .data_for_node(node.into())?
346 .data_for_node(node.into())?
347 .manifest_node()?
347 .manifest_node()?
348 .into(),
348 .into(),
349 )
349 )
350 }
350 }
351
351
352 /// Returns the manifest of the *changeset* with the given revision number
352 /// Returns the manifest of the *changeset* with the given revision number
353 pub fn manifest_for_rev(
353 pub fn manifest_for_rev(
354 &self,
354 &self,
355 revision: Revision,
355 revision: Revision,
356 ) -> Result<Manifest, RevlogError> {
356 ) -> Result<Manifest, RevlogError> {
357 self.manifestlog()?.data_for_node(
357 self.manifestlog()?.data_for_node(
358 self.changelog()?
358 self.changelog()?
359 .data_for_rev(revision)?
359 .data_for_rev(revision)?
360 .manifest_node()?
360 .manifest_node()?
361 .into(),
361 .into(),
362 )
362 )
363 }
363 }
364
364
365 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
365 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
366 Filelog::open(self, path)
366 Filelog::open(self, path)
367 }
367 }
368 }
368 }
369
369
370 /// Lazily-initialized component of `Repo` with interior mutability
370 /// Lazily-initialized component of `Repo` with interior mutability
371 ///
371 ///
372 /// This differs from `OnceCell` in that the value can still be "deinitialized"
372 /// This differs from `OnceCell` in that the value can still be "deinitialized"
373 /// later by setting its inner `Option` to `None`.
373 /// later by setting its inner `Option` to `None`.
374 struct LazyCell<T, E> {
374 struct LazyCell<T, E> {
375 value: RefCell<Option<T>>,
375 value: RefCell<Option<T>>,
376 // `Fn`s that don’t capture environment are zero-size, so this box does
376 // `Fn`s that don’t capture environment are zero-size, so this box does
377 // not allocate:
377 // not allocate:
378 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
378 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
379 }
379 }
380
380
381 impl<T, E> LazyCell<T, E> {
381 impl<T, E> LazyCell<T, E> {
382 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
382 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
383 Self {
383 Self {
384 value: RefCell::new(None),
384 value: RefCell::new(None),
385 init: Box::new(init),
385 init: Box::new(init),
386 }
386 }
387 }
387 }
388
388
389 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
389 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
390 let mut borrowed = self.value.borrow();
390 let mut borrowed = self.value.borrow();
391 if borrowed.is_none() {
391 if borrowed.is_none() {
392 drop(borrowed);
392 drop(borrowed);
393 // Only use `borrow_mut` if it is really needed to avoid panic in
393 // Only use `borrow_mut` if it is really needed to avoid panic in
394 // case there is another outstanding borrow but mutation is not
394 // case there is another outstanding borrow but mutation is not
395 // needed.
395 // needed.
396 *self.value.borrow_mut() = Some((self.init)(repo)?);
396 *self.value.borrow_mut() = Some((self.init)(repo)?);
397 borrowed = self.value.borrow()
397 borrowed = self.value.borrow()
398 }
398 }
399 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
399 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
400 }
400 }
401
401
402 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
402 pub fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
403 let mut borrowed = self.value.borrow_mut();
403 let mut borrowed = self.value.borrow_mut();
404 if borrowed.is_none() {
404 if borrowed.is_none() {
405 *borrowed = Some((self.init)(repo)?);
405 *borrowed = Some((self.init)(repo)?);
406 }
406 }
407 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
407 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
408 }
408 }
409 }
409 }
@@ -1,505 +1,504 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::parsers::Timestamp,
25 dirstate::parsers::Timestamp,
26 dirstate::StateMapIter,
26 dirstate::StateMapIter,
27 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
27 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
28 dirstate_tree::dispatch::DirstateMapMethods,
29 dirstate_tree::on_disk::DirstateV2ParseError,
28 dirstate_tree::on_disk::DirstateV2ParseError,
30 dirstate_tree::owning::OwningDirstateMap,
29 dirstate_tree::owning::OwningDirstateMap,
31 revlog::Node,
30 revlog::Node,
32 utils::files::normalize_case,
31 utils::files::normalize_case,
33 utils::hg_path::{HgPath, HgPathBuf},
32 utils::hg_path::{HgPath, HgPathBuf},
34 DirstateEntry, DirstateError, DirstateParents, EntryState,
33 DirstateEntry, DirstateError, DirstateParents, EntryState,
35 };
34 };
36
35
37 // TODO
36 // TODO
38 // This object needs to share references to multiple members of its Rust
37 // This object needs to share references to multiple members of its Rust
39 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
38 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
40 // Right now `CopyMap` is done, but it needs to have an explicit reference
39 // Right now `CopyMap` is done, but it needs to have an explicit reference
41 // to `RustDirstateMap` which itself needs to have an encapsulation for
40 // to `RustDirstateMap` which itself needs to have an encapsulation for
42 // every method in `CopyMap` (copymapcopy, etc.).
41 // every method in `CopyMap` (copymapcopy, etc.).
43 // This is ugly and hard to maintain.
42 // This is ugly and hard to maintain.
44 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
43 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
45 // `py_class!` is already implemented and does not mention
44 // `py_class!` is already implemented and does not mention
46 // `RustDirstateMap`, rightfully so.
45 // `RustDirstateMap`, rightfully so.
47 // All attributes also have to have a separate refcount data attribute for
46 // All attributes also have to have a separate refcount data attribute for
48 // leaks, with all methods that go along for reference sharing.
47 // leaks, with all methods that go along for reference sharing.
49 py_class!(pub class DirstateMap |py| {
48 py_class!(pub class DirstateMap |py| {
50 @shared data inner: Box<dyn DirstateMapMethods + Send>;
49 @shared data inner: OwningDirstateMap;
51
50
52 /// Returns a `(dirstate_map, parents)` tuple
51 /// Returns a `(dirstate_map, parents)` tuple
53 @staticmethod
52 @staticmethod
54 def new_v1(
53 def new_v1(
55 on_disk: PyBytes,
54 on_disk: PyBytes,
56 ) -> PyResult<PyObject> {
55 ) -> PyResult<PyObject> {
57 let on_disk = PyBytesDeref::new(py, on_disk);
56 let on_disk = PyBytesDeref::new(py, on_disk);
58 let mut map = OwningDirstateMap::new_empty(on_disk);
57 let mut map = OwningDirstateMap::new_empty(on_disk);
59 let (on_disk, map_placeholder) = map.get_mut_pair();
58 let (on_disk, map_placeholder) = map.get_pair_mut();
60
59
61 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
60 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
62 .map_err(|e| dirstate_error(py, e))?;
61 .map_err(|e| dirstate_error(py, e))?;
63 *map_placeholder = actual_map;
62 *map_placeholder = actual_map;
64 let map = Self::create_instance(py, Box::new(map))?;
63 let map = Self::create_instance(py, map)?;
65 let parents = parents.map(|p| {
64 let parents = parents.map(|p| {
66 let p1 = PyBytes::new(py, p.p1.as_bytes());
65 let p1 = PyBytes::new(py, p.p1.as_bytes());
67 let p2 = PyBytes::new(py, p.p2.as_bytes());
66 let p2 = PyBytes::new(py, p.p2.as_bytes());
68 (p1, p2)
67 (p1, p2)
69 });
68 });
70 Ok((map, parents).to_py_object(py).into_object())
69 Ok((map, parents).to_py_object(py).into_object())
71 }
70 }
72
71
73 /// Returns a DirstateMap
72 /// Returns a DirstateMap
74 @staticmethod
73 @staticmethod
75 def new_v2(
74 def new_v2(
76 on_disk: PyBytes,
75 on_disk: PyBytes,
77 data_size: usize,
76 data_size: usize,
78 tree_metadata: PyBytes,
77 tree_metadata: PyBytes,
79 ) -> PyResult<PyObject> {
78 ) -> PyResult<PyObject> {
80 let dirstate_error = |e: DirstateError| {
79 let dirstate_error = |e: DirstateError| {
81 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
80 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
82 };
81 };
83 let on_disk = PyBytesDeref::new(py, on_disk);
82 let on_disk = PyBytesDeref::new(py, on_disk);
84 let mut map = OwningDirstateMap::new_empty(on_disk);
83 let mut map = OwningDirstateMap::new_empty(on_disk);
85 let (on_disk, map_placeholder) = map.get_mut_pair();
84 let (on_disk, map_placeholder) = map.get_pair_mut();
86 *map_placeholder = TreeDirstateMap::new_v2(
85 *map_placeholder = TreeDirstateMap::new_v2(
87 on_disk, data_size, tree_metadata.data(py),
86 on_disk, data_size, tree_metadata.data(py),
88 ).map_err(dirstate_error)?;
87 ).map_err(dirstate_error)?;
89 let map = Self::create_instance(py, Box::new(map))?;
88 let map = Self::create_instance(py, map)?;
90 Ok(map.into_object())
89 Ok(map.into_object())
91 }
90 }
92
91
93 def clear(&self) -> PyResult<PyObject> {
92 def clear(&self) -> PyResult<PyObject> {
94 self.inner(py).borrow_mut().clear();
93 self.inner(py).borrow_mut().clear();
95 Ok(py.None())
94 Ok(py.None())
96 }
95 }
97
96
98 def get(
97 def get(
99 &self,
98 &self,
100 key: PyObject,
99 key: PyObject,
101 default: Option<PyObject> = None
100 default: Option<PyObject> = None
102 ) -> PyResult<Option<PyObject>> {
101 ) -> PyResult<Option<PyObject>> {
103 let key = key.extract::<PyBytes>(py)?;
102 let key = key.extract::<PyBytes>(py)?;
104 match self
103 match self
105 .inner(py)
104 .inner(py)
106 .borrow()
105 .borrow()
107 .get(HgPath::new(key.data(py)))
106 .get(HgPath::new(key.data(py)))
108 .map_err(|e| v2_error(py, e))?
107 .map_err(|e| v2_error(py, e))?
109 {
108 {
110 Some(entry) => {
109 Some(entry) => {
111 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
110 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
112 },
111 },
113 None => Ok(default)
112 None => Ok(default)
114 }
113 }
115 }
114 }
116
115
117 def set_dirstate_item(
116 def set_dirstate_item(
118 &self,
117 &self,
119 path: PyObject,
118 path: PyObject,
120 item: DirstateItem
119 item: DirstateItem
121 ) -> PyResult<PyObject> {
120 ) -> PyResult<PyObject> {
122 let f = path.extract::<PyBytes>(py)?;
121 let f = path.extract::<PyBytes>(py)?;
123 let filename = HgPath::new(f.data(py));
122 let filename = HgPath::new(f.data(py));
124 self.inner(py)
123 self.inner(py)
125 .borrow_mut()
124 .borrow_mut()
126 .set_entry(filename, item.get_entry(py))
125 .set_entry(filename, item.get_entry(py))
127 .map_err(|e| v2_error(py, e))?;
126 .map_err(|e| v2_error(py, e))?;
128 Ok(py.None())
127 Ok(py.None())
129 }
128 }
130
129
131 def addfile(
130 def addfile(
132 &self,
131 &self,
133 f: PyBytes,
132 f: PyBytes,
134 item: DirstateItem,
133 item: DirstateItem,
135 ) -> PyResult<PyNone> {
134 ) -> PyResult<PyNone> {
136 let filename = HgPath::new(f.data(py));
135 let filename = HgPath::new(f.data(py));
137 let entry = item.get_entry(py);
136 let entry = item.get_entry(py);
138 self.inner(py)
137 self.inner(py)
139 .borrow_mut()
138 .borrow_mut()
140 .add_file(filename, entry)
139 .add_file(filename, entry)
141 .map_err(|e |dirstate_error(py, e))?;
140 .map_err(|e |dirstate_error(py, e))?;
142 Ok(PyNone)
141 Ok(PyNone)
143 }
142 }
144
143
145 def removefile(
144 def removefile(
146 &self,
145 &self,
147 f: PyObject,
146 f: PyObject,
148 in_merge: PyObject
147 in_merge: PyObject
149 ) -> PyResult<PyObject> {
148 ) -> PyResult<PyObject> {
150 self.inner(py).borrow_mut()
149 self.inner(py).borrow_mut()
151 .remove_file(
150 .remove_file(
152 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
151 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
153 in_merge.extract::<PyBool>(py)?.is_true(),
152 in_merge.extract::<PyBool>(py)?.is_true(),
154 )
153 )
155 .or_else(|_| {
154 .or_else(|_| {
156 Err(PyErr::new::<exc::OSError, _>(
155 Err(PyErr::new::<exc::OSError, _>(
157 py,
156 py,
158 "Dirstate error".to_string(),
157 "Dirstate error".to_string(),
159 ))
158 ))
160 })?;
159 })?;
161 Ok(py.None())
160 Ok(py.None())
162 }
161 }
163
162
164 def drop_item_and_copy_source(
163 def drop_item_and_copy_source(
165 &self,
164 &self,
166 f: PyBytes,
165 f: PyBytes,
167 ) -> PyResult<PyNone> {
166 ) -> PyResult<PyNone> {
168 self.inner(py)
167 self.inner(py)
169 .borrow_mut()
168 .borrow_mut()
170 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
169 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
171 .map_err(|e |dirstate_error(py, e))?;
170 .map_err(|e |dirstate_error(py, e))?;
172 Ok(PyNone)
171 Ok(PyNone)
173 }
172 }
174
173
175 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
174 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
176 let d = d.extract::<PyBytes>(py)?;
175 let d = d.extract::<PyBytes>(py)?;
177 Ok(self.inner(py).borrow_mut()
176 Ok(self.inner(py).borrow_mut()
178 .has_tracked_dir(HgPath::new(d.data(py)))
177 .has_tracked_dir(HgPath::new(d.data(py)))
179 .map_err(|e| {
178 .map_err(|e| {
180 PyErr::new::<exc::ValueError, _>(py, e.to_string())
179 PyErr::new::<exc::ValueError, _>(py, e.to_string())
181 })?
180 })?
182 .to_py_object(py))
181 .to_py_object(py))
183 }
182 }
184
183
185 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
184 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
186 let d = d.extract::<PyBytes>(py)?;
185 let d = d.extract::<PyBytes>(py)?;
187 Ok(self.inner(py).borrow_mut()
186 Ok(self.inner(py).borrow_mut()
188 .has_dir(HgPath::new(d.data(py)))
187 .has_dir(HgPath::new(d.data(py)))
189 .map_err(|e| {
188 .map_err(|e| {
190 PyErr::new::<exc::ValueError, _>(py, e.to_string())
189 PyErr::new::<exc::ValueError, _>(py, e.to_string())
191 })?
190 })?
192 .to_py_object(py))
191 .to_py_object(py))
193 }
192 }
194
193
195 def write_v1(
194 def write_v1(
196 &self,
195 &self,
197 p1: PyObject,
196 p1: PyObject,
198 p2: PyObject,
197 p2: PyObject,
199 now: PyObject
198 now: PyObject
200 ) -> PyResult<PyBytes> {
199 ) -> PyResult<PyBytes> {
201 let now = Timestamp(now.extract(py)?);
200 let now = Timestamp(now.extract(py)?);
202
201
203 let mut inner = self.inner(py).borrow_mut();
202 let mut inner = self.inner(py).borrow_mut();
204 let parents = DirstateParents {
203 let parents = DirstateParents {
205 p1: extract_node_id(py, &p1)?,
204 p1: extract_node_id(py, &p1)?,
206 p2: extract_node_id(py, &p2)?,
205 p2: extract_node_id(py, &p2)?,
207 };
206 };
208 let result = inner.pack_v1(parents, now);
207 let result = inner.pack_v1(parents, now);
209 match result {
208 match result {
210 Ok(packed) => Ok(PyBytes::new(py, &packed)),
209 Ok(packed) => Ok(PyBytes::new(py, &packed)),
211 Err(_) => Err(PyErr::new::<exc::OSError, _>(
210 Err(_) => Err(PyErr::new::<exc::OSError, _>(
212 py,
211 py,
213 "Dirstate error".to_string(),
212 "Dirstate error".to_string(),
214 )),
213 )),
215 }
214 }
216 }
215 }
217
216
218 /// Returns new data together with whether that data should be appended to
217 /// Returns new data together with whether that data should be appended to
219 /// the existing data file whose content is at `self.on_disk` (True),
218 /// the existing data file whose content is at `self.on_disk` (True),
220 /// instead of written to a new data file (False).
219 /// instead of written to a new data file (False).
221 def write_v2(
220 def write_v2(
222 &self,
221 &self,
223 now: PyObject,
222 now: PyObject,
224 can_append: bool,
223 can_append: bool,
225 ) -> PyResult<PyObject> {
224 ) -> PyResult<PyObject> {
226 let now = Timestamp(now.extract(py)?);
225 let now = Timestamp(now.extract(py)?);
227
226
228 let mut inner = self.inner(py).borrow_mut();
227 let mut inner = self.inner(py).borrow_mut();
229 let result = inner.pack_v2(now, can_append);
228 let result = inner.pack_v2(now, can_append);
230 match result {
229 match result {
231 Ok((packed, tree_metadata, append)) => {
230 Ok((packed, tree_metadata, append)) => {
232 let packed = PyBytes::new(py, &packed);
231 let packed = PyBytes::new(py, &packed);
233 let tree_metadata = PyBytes::new(py, &tree_metadata);
232 let tree_metadata = PyBytes::new(py, &tree_metadata);
234 let tuple = (packed, tree_metadata, append);
233 let tuple = (packed, tree_metadata, append);
235 Ok(tuple.to_py_object(py).into_object())
234 Ok(tuple.to_py_object(py).into_object())
236 },
235 },
237 Err(_) => Err(PyErr::new::<exc::OSError, _>(
236 Err(_) => Err(PyErr::new::<exc::OSError, _>(
238 py,
237 py,
239 "Dirstate error".to_string(),
238 "Dirstate error".to_string(),
240 )),
239 )),
241 }
240 }
242 }
241 }
243
242
244 def filefoldmapasdict(&self) -> PyResult<PyDict> {
243 def filefoldmapasdict(&self) -> PyResult<PyDict> {
245 let dict = PyDict::new(py);
244 let dict = PyDict::new(py);
246 for item in self.inner(py).borrow_mut().iter() {
245 for item in self.inner(py).borrow_mut().iter() {
247 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
246 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
248 if entry.state() != EntryState::Removed {
247 if entry.state() != EntryState::Removed {
249 let key = normalize_case(path);
248 let key = normalize_case(path);
250 let value = path;
249 let value = path;
251 dict.set_item(
250 dict.set_item(
252 py,
251 py,
253 PyBytes::new(py, key.as_bytes()).into_object(),
252 PyBytes::new(py, key.as_bytes()).into_object(),
254 PyBytes::new(py, value.as_bytes()).into_object(),
253 PyBytes::new(py, value.as_bytes()).into_object(),
255 )?;
254 )?;
256 }
255 }
257 }
256 }
258 Ok(dict)
257 Ok(dict)
259 }
258 }
260
259
261 def __len__(&self) -> PyResult<usize> {
260 def __len__(&self) -> PyResult<usize> {
262 Ok(self.inner(py).borrow().len())
261 Ok(self.inner(py).borrow().len())
263 }
262 }
264
263
265 def __contains__(&self, key: PyObject) -> PyResult<bool> {
264 def __contains__(&self, key: PyObject) -> PyResult<bool> {
266 let key = key.extract::<PyBytes>(py)?;
265 let key = key.extract::<PyBytes>(py)?;
267 self.inner(py)
266 self.inner(py)
268 .borrow()
267 .borrow()
269 .contains_key(HgPath::new(key.data(py)))
268 .contains_key(HgPath::new(key.data(py)))
270 .map_err(|e| v2_error(py, e))
269 .map_err(|e| v2_error(py, e))
271 }
270 }
272
271
273 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
272 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
274 let key = key.extract::<PyBytes>(py)?;
273 let key = key.extract::<PyBytes>(py)?;
275 let key = HgPath::new(key.data(py));
274 let key = HgPath::new(key.data(py));
276 match self
275 match self
277 .inner(py)
276 .inner(py)
278 .borrow()
277 .borrow()
279 .get(key)
278 .get(key)
280 .map_err(|e| v2_error(py, e))?
279 .map_err(|e| v2_error(py, e))?
281 {
280 {
282 Some(entry) => {
281 Some(entry) => {
283 Ok(DirstateItem::new_as_pyobject(py, entry)?)
282 Ok(DirstateItem::new_as_pyobject(py, entry)?)
284 },
283 },
285 None => Err(PyErr::new::<exc::KeyError, _>(
284 None => Err(PyErr::new::<exc::KeyError, _>(
286 py,
285 py,
287 String::from_utf8_lossy(key.as_bytes()),
286 String::from_utf8_lossy(key.as_bytes()),
288 )),
287 )),
289 }
288 }
290 }
289 }
291
290
292 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
291 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
293 let leaked_ref = self.inner(py).leak_immutable();
292 let leaked_ref = self.inner(py).leak_immutable();
294 DirstateMapKeysIterator::from_inner(
293 DirstateMapKeysIterator::from_inner(
295 py,
294 py,
296 unsafe { leaked_ref.map(py, |o| o.iter()) },
295 unsafe { leaked_ref.map(py, |o| o.iter()) },
297 )
296 )
298 }
297 }
299
298
300 def items(&self) -> PyResult<DirstateMapItemsIterator> {
299 def items(&self) -> PyResult<DirstateMapItemsIterator> {
301 let leaked_ref = self.inner(py).leak_immutable();
300 let leaked_ref = self.inner(py).leak_immutable();
302 DirstateMapItemsIterator::from_inner(
301 DirstateMapItemsIterator::from_inner(
303 py,
302 py,
304 unsafe { leaked_ref.map(py, |o| o.iter()) },
303 unsafe { leaked_ref.map(py, |o| o.iter()) },
305 )
304 )
306 }
305 }
307
306
308 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
307 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
309 let leaked_ref = self.inner(py).leak_immutable();
308 let leaked_ref = self.inner(py).leak_immutable();
310 DirstateMapKeysIterator::from_inner(
309 DirstateMapKeysIterator::from_inner(
311 py,
310 py,
312 unsafe { leaked_ref.map(py, |o| o.iter()) },
311 unsafe { leaked_ref.map(py, |o| o.iter()) },
313 )
312 )
314 }
313 }
315
314
316 // TODO all copymap* methods, see docstring above
315 // TODO all copymap* methods, see docstring above
317 def copymapcopy(&self) -> PyResult<PyDict> {
316 def copymapcopy(&self) -> PyResult<PyDict> {
318 let dict = PyDict::new(py);
317 let dict = PyDict::new(py);
319 for item in self.inner(py).borrow().copy_map_iter() {
318 for item in self.inner(py).borrow().copy_map_iter() {
320 let (key, value) = item.map_err(|e| v2_error(py, e))?;
319 let (key, value) = item.map_err(|e| v2_error(py, e))?;
321 dict.set_item(
320 dict.set_item(
322 py,
321 py,
323 PyBytes::new(py, key.as_bytes()),
322 PyBytes::new(py, key.as_bytes()),
324 PyBytes::new(py, value.as_bytes()),
323 PyBytes::new(py, value.as_bytes()),
325 )?;
324 )?;
326 }
325 }
327 Ok(dict)
326 Ok(dict)
328 }
327 }
329
328
330 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
329 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
331 let key = key.extract::<PyBytes>(py)?;
330 let key = key.extract::<PyBytes>(py)?;
332 match self
331 match self
333 .inner(py)
332 .inner(py)
334 .borrow()
333 .borrow()
335 .copy_map_get(HgPath::new(key.data(py)))
334 .copy_map_get(HgPath::new(key.data(py)))
336 .map_err(|e| v2_error(py, e))?
335 .map_err(|e| v2_error(py, e))?
337 {
336 {
338 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
337 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
339 None => Err(PyErr::new::<exc::KeyError, _>(
338 None => Err(PyErr::new::<exc::KeyError, _>(
340 py,
339 py,
341 String::from_utf8_lossy(key.data(py)),
340 String::from_utf8_lossy(key.data(py)),
342 )),
341 )),
343 }
342 }
344 }
343 }
345 def copymap(&self) -> PyResult<CopyMap> {
344 def copymap(&self) -> PyResult<CopyMap> {
346 CopyMap::from_inner(py, self.clone_ref(py))
345 CopyMap::from_inner(py, self.clone_ref(py))
347 }
346 }
348
347
349 def copymaplen(&self) -> PyResult<usize> {
348 def copymaplen(&self) -> PyResult<usize> {
350 Ok(self.inner(py).borrow().copy_map_len())
349 Ok(self.inner(py).borrow().copy_map_len())
351 }
350 }
352 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
351 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
353 let key = key.extract::<PyBytes>(py)?;
352 let key = key.extract::<PyBytes>(py)?;
354 self.inner(py)
353 self.inner(py)
355 .borrow()
354 .borrow()
356 .copy_map_contains_key(HgPath::new(key.data(py)))
355 .copy_map_contains_key(HgPath::new(key.data(py)))
357 .map_err(|e| v2_error(py, e))
356 .map_err(|e| v2_error(py, e))
358 }
357 }
359 def copymapget(
358 def copymapget(
360 &self,
359 &self,
361 key: PyObject,
360 key: PyObject,
362 default: Option<PyObject>
361 default: Option<PyObject>
363 ) -> PyResult<Option<PyObject>> {
362 ) -> PyResult<Option<PyObject>> {
364 let key = key.extract::<PyBytes>(py)?;
363 let key = key.extract::<PyBytes>(py)?;
365 match self
364 match self
366 .inner(py)
365 .inner(py)
367 .borrow()
366 .borrow()
368 .copy_map_get(HgPath::new(key.data(py)))
367 .copy_map_get(HgPath::new(key.data(py)))
369 .map_err(|e| v2_error(py, e))?
368 .map_err(|e| v2_error(py, e))?
370 {
369 {
371 Some(copy) => Ok(Some(
370 Some(copy) => Ok(Some(
372 PyBytes::new(py, copy.as_bytes()).into_object(),
371 PyBytes::new(py, copy.as_bytes()).into_object(),
373 )),
372 )),
374 None => Ok(default),
373 None => Ok(default),
375 }
374 }
376 }
375 }
377 def copymapsetitem(
376 def copymapsetitem(
378 &self,
377 &self,
379 key: PyObject,
378 key: PyObject,
380 value: PyObject
379 value: PyObject
381 ) -> PyResult<PyObject> {
380 ) -> PyResult<PyObject> {
382 let key = key.extract::<PyBytes>(py)?;
381 let key = key.extract::<PyBytes>(py)?;
383 let value = value.extract::<PyBytes>(py)?;
382 let value = value.extract::<PyBytes>(py)?;
384 self.inner(py)
383 self.inner(py)
385 .borrow_mut()
384 .borrow_mut()
386 .copy_map_insert(
385 .copy_map_insert(
387 HgPathBuf::from_bytes(key.data(py)),
386 HgPathBuf::from_bytes(key.data(py)),
388 HgPathBuf::from_bytes(value.data(py)),
387 HgPathBuf::from_bytes(value.data(py)),
389 )
388 )
390 .map_err(|e| v2_error(py, e))?;
389 .map_err(|e| v2_error(py, e))?;
391 Ok(py.None())
390 Ok(py.None())
392 }
391 }
393 def copymappop(
392 def copymappop(
394 &self,
393 &self,
395 key: PyObject,
394 key: PyObject,
396 default: Option<PyObject>
395 default: Option<PyObject>
397 ) -> PyResult<Option<PyObject>> {
396 ) -> PyResult<Option<PyObject>> {
398 let key = key.extract::<PyBytes>(py)?;
397 let key = key.extract::<PyBytes>(py)?;
399 match self
398 match self
400 .inner(py)
399 .inner(py)
401 .borrow_mut()
400 .borrow_mut()
402 .copy_map_remove(HgPath::new(key.data(py)))
401 .copy_map_remove(HgPath::new(key.data(py)))
403 .map_err(|e| v2_error(py, e))?
402 .map_err(|e| v2_error(py, e))?
404 {
403 {
405 Some(_) => Ok(None),
404 Some(_) => Ok(None),
406 None => Ok(default),
405 None => Ok(default),
407 }
406 }
408 }
407 }
409
408
410 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
409 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
411 let leaked_ref = self.inner(py).leak_immutable();
410 let leaked_ref = self.inner(py).leak_immutable();
412 CopyMapKeysIterator::from_inner(
411 CopyMapKeysIterator::from_inner(
413 py,
412 py,
414 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
413 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
415 )
414 )
416 }
415 }
417
416
418 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
417 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
419 let leaked_ref = self.inner(py).leak_immutable();
418 let leaked_ref = self.inner(py).leak_immutable();
420 CopyMapItemsIterator::from_inner(
419 CopyMapItemsIterator::from_inner(
421 py,
420 py,
422 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
421 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
423 )
422 )
424 }
423 }
425
424
426 def tracked_dirs(&self) -> PyResult<PyList> {
425 def tracked_dirs(&self) -> PyResult<PyList> {
427 let dirs = PyList::new(py, &[]);
426 let dirs = PyList::new(py, &[]);
428 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
427 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
429 .map_err(|e |dirstate_error(py, e))?
428 .map_err(|e |dirstate_error(py, e))?
430 {
429 {
431 let path = path.map_err(|e| v2_error(py, e))?;
430 let path = path.map_err(|e| v2_error(py, e))?;
432 let path = PyBytes::new(py, path.as_bytes());
431 let path = PyBytes::new(py, path.as_bytes());
433 dirs.append(py, path.into_object())
432 dirs.append(py, path.into_object())
434 }
433 }
435 Ok(dirs)
434 Ok(dirs)
436 }
435 }
437
436
438 def debug_iter(&self, all: bool) -> PyResult<PyList> {
437 def debug_iter(&self, all: bool) -> PyResult<PyList> {
439 let dirs = PyList::new(py, &[]);
438 let dirs = PyList::new(py, &[]);
440 for item in self.inner(py).borrow().debug_iter(all) {
439 for item in self.inner(py).borrow().debug_iter(all) {
441 let (path, (state, mode, size, mtime)) =
440 let (path, (state, mode, size, mtime)) =
442 item.map_err(|e| v2_error(py, e))?;
441 item.map_err(|e| v2_error(py, e))?;
443 let path = PyBytes::new(py, path.as_bytes());
442 let path = PyBytes::new(py, path.as_bytes());
444 let item = (path, state, mode, size, mtime);
443 let item = (path, state, mode, size, mtime);
445 dirs.append(py, item.to_py_object(py).into_object())
444 dirs.append(py, item.to_py_object(py).into_object())
446 }
445 }
447 Ok(dirs)
446 Ok(dirs)
448 }
447 }
449 });
448 });
450
449
451 impl DirstateMap {
450 impl DirstateMap {
452 pub fn get_inner_mut<'a>(
451 pub fn get_inner_mut<'a>(
453 &'a self,
452 &'a self,
454 py: Python<'a>,
453 py: Python<'a>,
455 ) -> RefMut<'a, Box<dyn DirstateMapMethods + Send>> {
454 ) -> RefMut<'a, OwningDirstateMap> {
456 self.inner(py).borrow_mut()
455 self.inner(py).borrow_mut()
457 }
456 }
458 fn translate_key(
457 fn translate_key(
459 py: Python,
458 py: Python,
460 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
459 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
461 ) -> PyResult<Option<PyBytes>> {
460 ) -> PyResult<Option<PyBytes>> {
462 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
461 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
463 Ok(Some(PyBytes::new(py, f.as_bytes())))
462 Ok(Some(PyBytes::new(py, f.as_bytes())))
464 }
463 }
465 fn translate_key_value(
464 fn translate_key_value(
466 py: Python,
465 py: Python,
467 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
466 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
468 ) -> PyResult<Option<(PyBytes, PyObject)>> {
467 ) -> PyResult<Option<(PyBytes, PyObject)>> {
469 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
468 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
470 Ok(Some((
469 Ok(Some((
471 PyBytes::new(py, f.as_bytes()),
470 PyBytes::new(py, f.as_bytes()),
472 DirstateItem::new_as_pyobject(py, entry)?,
471 DirstateItem::new_as_pyobject(py, entry)?,
473 )))
472 )))
474 }
473 }
475 }
474 }
476
475
477 py_shared_iterator!(
476 py_shared_iterator!(
478 DirstateMapKeysIterator,
477 DirstateMapKeysIterator,
479 UnsafePyLeaked<StateMapIter<'static>>,
478 UnsafePyLeaked<StateMapIter<'static>>,
480 DirstateMap::translate_key,
479 DirstateMap::translate_key,
481 Option<PyBytes>
480 Option<PyBytes>
482 );
481 );
483
482
484 py_shared_iterator!(
483 py_shared_iterator!(
485 DirstateMapItemsIterator,
484 DirstateMapItemsIterator,
486 UnsafePyLeaked<StateMapIter<'static>>,
485 UnsafePyLeaked<StateMapIter<'static>>,
487 DirstateMap::translate_key_value,
486 DirstateMap::translate_key_value,
488 Option<(PyBytes, PyObject)>
487 Option<(PyBytes, PyObject)>
489 );
488 );
490
489
491 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
490 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
492 let bytes = obj.extract::<PyBytes>(py)?;
491 let bytes = obj.extract::<PyBytes>(py)?;
493 match bytes.data(py).try_into() {
492 match bytes.data(py).try_into() {
494 Ok(s) => Ok(s),
493 Ok(s) => Ok(s),
495 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
494 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
496 }
495 }
497 }
496 }
498
497
499 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
498 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
500 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
499 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
501 }
500 }
502
501
503 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
502 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
504 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
503 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
505 }
504 }
@@ -1,281 +1,280 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use clap::{Arg, SubCommand};
10 use clap::{Arg, SubCommand};
11 use hg;
11 use hg;
12 use hg::dirstate_tree::dispatch::DirstateMapMethods;
13 use hg::errors::HgError;
12 use hg::errors::HgError;
14 use hg::manifest::Manifest;
13 use hg::manifest::Manifest;
15 use hg::matchers::AlwaysMatcher;
14 use hg::matchers::AlwaysMatcher;
16 use hg::repo::Repo;
15 use hg::repo::Repo;
17 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
16 use hg::utils::hg_path::{hg_path_to_os_string, HgPath};
18 use hg::{HgPathCow, StatusOptions};
17 use hg::{HgPathCow, StatusOptions};
19 use log::{info, warn};
18 use log::{info, warn};
20
19
21 pub const HELP_TEXT: &str = "
20 pub const HELP_TEXT: &str = "
22 Show changed files in the working directory
21 Show changed files in the working directory
23
22
24 This is a pure Rust version of `hg status`.
23 This is a pure Rust version of `hg status`.
25
24
26 Some options might be missing, check the list below.
25 Some options might be missing, check the list below.
27 ";
26 ";
28
27
29 pub fn args() -> clap::App<'static, 'static> {
28 pub fn args() -> clap::App<'static, 'static> {
30 SubCommand::with_name("status")
29 SubCommand::with_name("status")
31 .alias("st")
30 .alias("st")
32 .about(HELP_TEXT)
31 .about(HELP_TEXT)
33 .arg(
32 .arg(
34 Arg::with_name("all")
33 Arg::with_name("all")
35 .help("show status of all files")
34 .help("show status of all files")
36 .short("-A")
35 .short("-A")
37 .long("--all"),
36 .long("--all"),
38 )
37 )
39 .arg(
38 .arg(
40 Arg::with_name("modified")
39 Arg::with_name("modified")
41 .help("show only modified files")
40 .help("show only modified files")
42 .short("-m")
41 .short("-m")
43 .long("--modified"),
42 .long("--modified"),
44 )
43 )
45 .arg(
44 .arg(
46 Arg::with_name("added")
45 Arg::with_name("added")
47 .help("show only added files")
46 .help("show only added files")
48 .short("-a")
47 .short("-a")
49 .long("--added"),
48 .long("--added"),
50 )
49 )
51 .arg(
50 .arg(
52 Arg::with_name("removed")
51 Arg::with_name("removed")
53 .help("show only removed files")
52 .help("show only removed files")
54 .short("-r")
53 .short("-r")
55 .long("--removed"),
54 .long("--removed"),
56 )
55 )
57 .arg(
56 .arg(
58 Arg::with_name("clean")
57 Arg::with_name("clean")
59 .help("show only clean files")
58 .help("show only clean files")
60 .short("-c")
59 .short("-c")
61 .long("--clean"),
60 .long("--clean"),
62 )
61 )
63 .arg(
62 .arg(
64 Arg::with_name("deleted")
63 Arg::with_name("deleted")
65 .help("show only deleted files")
64 .help("show only deleted files")
66 .short("-d")
65 .short("-d")
67 .long("--deleted"),
66 .long("--deleted"),
68 )
67 )
69 .arg(
68 .arg(
70 Arg::with_name("unknown")
69 Arg::with_name("unknown")
71 .help("show only unknown (not tracked) files")
70 .help("show only unknown (not tracked) files")
72 .short("-u")
71 .short("-u")
73 .long("--unknown"),
72 .long("--unknown"),
74 )
73 )
75 .arg(
74 .arg(
76 Arg::with_name("ignored")
75 Arg::with_name("ignored")
77 .help("show only ignored files")
76 .help("show only ignored files")
78 .short("-i")
77 .short("-i")
79 .long("--ignored"),
78 .long("--ignored"),
80 )
79 )
81 }
80 }
82
81
83 /// Pure data type allowing the caller to specify file states to display
82 /// Pure data type allowing the caller to specify file states to display
84 #[derive(Copy, Clone, Debug)]
83 #[derive(Copy, Clone, Debug)]
85 pub struct DisplayStates {
84 pub struct DisplayStates {
86 pub modified: bool,
85 pub modified: bool,
87 pub added: bool,
86 pub added: bool,
88 pub removed: bool,
87 pub removed: bool,
89 pub clean: bool,
88 pub clean: bool,
90 pub deleted: bool,
89 pub deleted: bool,
91 pub unknown: bool,
90 pub unknown: bool,
92 pub ignored: bool,
91 pub ignored: bool,
93 }
92 }
94
93
95 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
94 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
96 modified: true,
95 modified: true,
97 added: true,
96 added: true,
98 removed: true,
97 removed: true,
99 clean: false,
98 clean: false,
100 deleted: true,
99 deleted: true,
101 unknown: true,
100 unknown: true,
102 ignored: false,
101 ignored: false,
103 };
102 };
104
103
105 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
104 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
106 modified: true,
105 modified: true,
107 added: true,
106 added: true,
108 removed: true,
107 removed: true,
109 clean: true,
108 clean: true,
110 deleted: true,
109 deleted: true,
111 unknown: true,
110 unknown: true,
112 ignored: true,
111 ignored: true,
113 };
112 };
114
113
115 impl DisplayStates {
114 impl DisplayStates {
116 pub fn is_empty(&self) -> bool {
115 pub fn is_empty(&self) -> bool {
117 !(self.modified
116 !(self.modified
118 || self.added
117 || self.added
119 || self.removed
118 || self.removed
120 || self.clean
119 || self.clean
121 || self.deleted
120 || self.deleted
122 || self.unknown
121 || self.unknown
123 || self.ignored)
122 || self.ignored)
124 }
123 }
125 }
124 }
126
125
127 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
126 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
128 let status_enabled_default = false;
127 let status_enabled_default = false;
129 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
128 let status_enabled = invocation.config.get_option(b"rhg", b"status")?;
130 if !status_enabled.unwrap_or(status_enabled_default) {
129 if !status_enabled.unwrap_or(status_enabled_default) {
131 return Err(CommandError::unsupported(
130 return Err(CommandError::unsupported(
132 "status is experimental in rhg (enable it with 'rhg.status = true' \
131 "status is experimental in rhg (enable it with 'rhg.status = true' \
133 or enable fallback with 'rhg.on-unsupported = fallback')"
132 or enable fallback with 'rhg.on-unsupported = fallback')"
134 ));
133 ));
135 }
134 }
136
135
137 let ui = invocation.ui;
136 let ui = invocation.ui;
138 let args = invocation.subcommand_args;
137 let args = invocation.subcommand_args;
139 let display_states = if args.is_present("all") {
138 let display_states = if args.is_present("all") {
140 // TODO when implementing `--quiet`: it excludes clean files
139 // TODO when implementing `--quiet`: it excludes clean files
141 // from `--all`
140 // from `--all`
142 ALL_DISPLAY_STATES
141 ALL_DISPLAY_STATES
143 } else {
142 } else {
144 let requested = DisplayStates {
143 let requested = DisplayStates {
145 modified: args.is_present("modified"),
144 modified: args.is_present("modified"),
146 added: args.is_present("added"),
145 added: args.is_present("added"),
147 removed: args.is_present("removed"),
146 removed: args.is_present("removed"),
148 clean: args.is_present("clean"),
147 clean: args.is_present("clean"),
149 deleted: args.is_present("deleted"),
148 deleted: args.is_present("deleted"),
150 unknown: args.is_present("unknown"),
149 unknown: args.is_present("unknown"),
151 ignored: args.is_present("ignored"),
150 ignored: args.is_present("ignored"),
152 };
151 };
153 if requested.is_empty() {
152 if requested.is_empty() {
154 DEFAULT_DISPLAY_STATES
153 DEFAULT_DISPLAY_STATES
155 } else {
154 } else {
156 requested
155 requested
157 }
156 }
158 };
157 };
159
158
160 let repo = invocation.repo?;
159 let repo = invocation.repo?;
161 let mut dmap = repo.dirstate_map_mut()?;
160 let mut dmap = repo.dirstate_map_mut()?;
162
161
163 let options = StatusOptions {
162 let options = StatusOptions {
164 // TODO should be provided by the dirstate parsing and
163 // TODO should be provided by the dirstate parsing and
165 // hence be stored on dmap. Using a value that assumes we aren't
164 // hence be stored on dmap. Using a value that assumes we aren't
166 // below the time resolution granularity of the FS and the
165 // below the time resolution granularity of the FS and the
167 // dirstate.
166 // dirstate.
168 last_normal_time: 0,
167 last_normal_time: 0,
169 // we're currently supporting file systems with exec flags only
168 // we're currently supporting file systems with exec flags only
170 // anyway
169 // anyway
171 check_exec: true,
170 check_exec: true,
172 list_clean: display_states.clean,
171 list_clean: display_states.clean,
173 list_unknown: display_states.unknown,
172 list_unknown: display_states.unknown,
174 list_ignored: display_states.ignored,
173 list_ignored: display_states.ignored,
175 collect_traversed_dirs: false,
174 collect_traversed_dirs: false,
176 };
175 };
177 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
176 let ignore_file = repo.working_directory_vfs().join(".hgignore"); // TODO hardcoded
178 let (mut ds_status, pattern_warnings) = dmap.status(
177 let (mut ds_status, pattern_warnings) = dmap.status(
179 &AlwaysMatcher,
178 &AlwaysMatcher,
180 repo.working_directory_path().to_owned(),
179 repo.working_directory_path().to_owned(),
181 vec![ignore_file],
180 vec![ignore_file],
182 options,
181 options,
183 )?;
182 )?;
184 if !pattern_warnings.is_empty() {
183 if !pattern_warnings.is_empty() {
185 warn!("Pattern warnings: {:?}", &pattern_warnings);
184 warn!("Pattern warnings: {:?}", &pattern_warnings);
186 }
185 }
187
186
188 if !ds_status.bad.is_empty() {
187 if !ds_status.bad.is_empty() {
189 warn!("Bad matches {:?}", &(ds_status.bad))
188 warn!("Bad matches {:?}", &(ds_status.bad))
190 }
189 }
191 if !ds_status.unsure.is_empty() {
190 if !ds_status.unsure.is_empty() {
192 info!(
191 info!(
193 "Files to be rechecked by retrieval from filelog: {:?}",
192 "Files to be rechecked by retrieval from filelog: {:?}",
194 &ds_status.unsure
193 &ds_status.unsure
195 );
194 );
196 }
195 }
197 if !ds_status.unsure.is_empty()
196 if !ds_status.unsure.is_empty()
198 && (display_states.modified || display_states.clean)
197 && (display_states.modified || display_states.clean)
199 {
198 {
200 let p1 = repo.dirstate_parents()?.p1;
199 let p1 = repo.dirstate_parents()?.p1;
201 let manifest = repo.manifest_for_node(p1).map_err(|e| {
200 let manifest = repo.manifest_for_node(p1).map_err(|e| {
202 CommandError::from((e, &*format!("{:x}", p1.short())))
201 CommandError::from((e, &*format!("{:x}", p1.short())))
203 })?;
202 })?;
204 for to_check in ds_status.unsure {
203 for to_check in ds_status.unsure {
205 if cat_file_is_modified(repo, &manifest, &to_check)? {
204 if cat_file_is_modified(repo, &manifest, &to_check)? {
206 if display_states.modified {
205 if display_states.modified {
207 ds_status.modified.push(to_check);
206 ds_status.modified.push(to_check);
208 }
207 }
209 } else {
208 } else {
210 if display_states.clean {
209 if display_states.clean {
211 ds_status.clean.push(to_check);
210 ds_status.clean.push(to_check);
212 }
211 }
213 }
212 }
214 }
213 }
215 }
214 }
216 if display_states.modified {
215 if display_states.modified {
217 display_status_paths(ui, &mut ds_status.modified, b"M")?;
216 display_status_paths(ui, &mut ds_status.modified, b"M")?;
218 }
217 }
219 if display_states.added {
218 if display_states.added {
220 display_status_paths(ui, &mut ds_status.added, b"A")?;
219 display_status_paths(ui, &mut ds_status.added, b"A")?;
221 }
220 }
222 if display_states.removed {
221 if display_states.removed {
223 display_status_paths(ui, &mut ds_status.removed, b"R")?;
222 display_status_paths(ui, &mut ds_status.removed, b"R")?;
224 }
223 }
225 if display_states.deleted {
224 if display_states.deleted {
226 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
225 display_status_paths(ui, &mut ds_status.deleted, b"!")?;
227 }
226 }
228 if display_states.unknown {
227 if display_states.unknown {
229 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
228 display_status_paths(ui, &mut ds_status.unknown, b"?")?;
230 }
229 }
231 if display_states.ignored {
230 if display_states.ignored {
232 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
231 display_status_paths(ui, &mut ds_status.ignored, b"I")?;
233 }
232 }
234 if display_states.clean {
233 if display_states.clean {
235 display_status_paths(ui, &mut ds_status.clean, b"C")?;
234 display_status_paths(ui, &mut ds_status.clean, b"C")?;
236 }
235 }
237 Ok(())
236 Ok(())
238 }
237 }
239
238
240 // Probably more elegant to use a Deref or Borrow trait rather than
239 // Probably more elegant to use a Deref or Borrow trait rather than
241 // harcode HgPathBuf, but probably not really useful at this point
240 // harcode HgPathBuf, but probably not really useful at this point
242 fn display_status_paths(
241 fn display_status_paths(
243 ui: &Ui,
242 ui: &Ui,
244 paths: &mut [HgPathCow],
243 paths: &mut [HgPathCow],
245 status_prefix: &[u8],
244 status_prefix: &[u8],
246 ) -> Result<(), CommandError> {
245 ) -> Result<(), CommandError> {
247 paths.sort_unstable();
246 paths.sort_unstable();
248 for path in paths {
247 for path in paths {
249 // Same TODO as in commands::root
248 // Same TODO as in commands::root
250 let bytes: &[u8] = path.as_bytes();
249 let bytes: &[u8] = path.as_bytes();
251 // TODO optim, probably lots of unneeded copies here, especially
250 // TODO optim, probably lots of unneeded copies here, especially
252 // if out stream is buffered
251 // if out stream is buffered
253 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
252 ui.write_stdout(&[status_prefix, b" ", bytes, b"\n"].concat())?;
254 }
253 }
255 Ok(())
254 Ok(())
256 }
255 }
257
256
258 /// Check if a file is modified by comparing actual repo store and file system.
257 /// Check if a file is modified by comparing actual repo store and file system.
259 ///
258 ///
260 /// This meant to be used for those that the dirstate cannot resolve, due
259 /// This meant to be used for those that the dirstate cannot resolve, due
261 /// to time resolution limits.
260 /// to time resolution limits.
262 ///
261 ///
263 /// TODO: detect permission bits and similar metadata modifications
262 /// TODO: detect permission bits and similar metadata modifications
264 fn cat_file_is_modified(
263 fn cat_file_is_modified(
265 repo: &Repo,
264 repo: &Repo,
266 manifest: &Manifest,
265 manifest: &Manifest,
267 hg_path: &HgPath,
266 hg_path: &HgPath,
268 ) -> Result<bool, HgError> {
267 ) -> Result<bool, HgError> {
269 let file_node = manifest
268 let file_node = manifest
270 .find_file(hg_path)?
269 .find_file(hg_path)?
271 .expect("ambgious file not in p1");
270 .expect("ambgious file not in p1");
272 let filelog = repo.filelog(hg_path)?;
271 let filelog = repo.filelog(hg_path)?;
273 let filelog_entry = filelog.data_for_node(file_node).map_err(|_| {
272 let filelog_entry = filelog.data_for_node(file_node).map_err(|_| {
274 HgError::corrupted("filelog missing node from manifest")
273 HgError::corrupted("filelog missing node from manifest")
275 })?;
274 })?;
276 let contents_in_p1 = filelog_entry.data()?;
275 let contents_in_p1 = filelog_entry.data()?;
277
276
278 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
277 let fs_path = hg_path_to_os_string(hg_path).expect("HgPath conversion");
279 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
278 let fs_contents = repo.working_directory_vfs().read(fs_path)?;
280 return Ok(contents_in_p1 == &*fs_contents);
279 return Ok(contents_in_p1 == &*fs_contents);
281 }
280 }
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now