##// END OF EJS Templates
rust: don't swallow valuable error information...
Raphaël Gomès -
r50269:455fce57 stable
parent child Browse files
Show More
@@ -1,631 +1,644 b''
1 use std::borrow::Cow;
1 use std::borrow::Cow;
2 use std::convert::TryFrom;
2 use std::convert::TryFrom;
3 use std::io::Read;
3 use std::io::Read;
4 use std::ops::Deref;
4 use std::ops::Deref;
5 use std::path::Path;
5 use std::path::Path;
6
6
7 use flate2::read::ZlibDecoder;
7 use flate2::read::ZlibDecoder;
8 use micro_timer::timed;
8 use micro_timer::timed;
9 use sha1::{Digest, Sha1};
9 use sha1::{Digest, Sha1};
10 use zstd;
10 use zstd;
11
11
12 use super::index::Index;
12 use super::index::Index;
13 use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE};
13 use super::node::{NodePrefix, NODE_BYTES_LENGTH, NULL_NODE};
14 use super::nodemap;
14 use super::nodemap;
15 use super::nodemap::{NodeMap, NodeMapError};
15 use super::nodemap::{NodeMap, NodeMapError};
16 use super::nodemap_docket::NodeMapDocket;
16 use super::nodemap_docket::NodeMapDocket;
17 use super::patch;
17 use super::patch;
18 use crate::errors::HgError;
18 use crate::errors::HgError;
19 use crate::revlog::Revision;
19 use crate::revlog::Revision;
20 use crate::vfs::Vfs;
20 use crate::vfs::Vfs;
21 use crate::{Node, NULL_REVISION};
21 use crate::{Node, NULL_REVISION};
22
22
23 const REVISION_FLAG_CENSORED: u16 = 1 << 15;
23 const REVISION_FLAG_CENSORED: u16 = 1 << 15;
24 const REVISION_FLAG_ELLIPSIS: u16 = 1 << 14;
24 const REVISION_FLAG_ELLIPSIS: u16 = 1 << 14;
25 const REVISION_FLAG_EXTSTORED: u16 = 1 << 13;
25 const REVISION_FLAG_EXTSTORED: u16 = 1 << 13;
26 const REVISION_FLAG_HASCOPIESINFO: u16 = 1 << 12;
26 const REVISION_FLAG_HASCOPIESINFO: u16 = 1 << 12;
27
27
28 // Keep this in sync with REVIDX_KNOWN_FLAGS in
28 // Keep this in sync with REVIDX_KNOWN_FLAGS in
29 // mercurial/revlogutils/flagutil.py
29 // mercurial/revlogutils/flagutil.py
30 const REVIDX_KNOWN_FLAGS: u16 = REVISION_FLAG_CENSORED
30 const REVIDX_KNOWN_FLAGS: u16 = REVISION_FLAG_CENSORED
31 | REVISION_FLAG_ELLIPSIS
31 | REVISION_FLAG_ELLIPSIS
32 | REVISION_FLAG_EXTSTORED
32 | REVISION_FLAG_EXTSTORED
33 | REVISION_FLAG_HASCOPIESINFO;
33 | REVISION_FLAG_HASCOPIESINFO;
34
34
35 const NULL_REVLOG_ENTRY_FLAGS: u16 = 0;
35 const NULL_REVLOG_ENTRY_FLAGS: u16 = 0;
36
36
37 #[derive(Debug, derive_more::From)]
37 #[derive(Debug, derive_more::From)]
38 pub enum RevlogError {
38 pub enum RevlogError {
39 InvalidRevision,
39 InvalidRevision,
40 /// Working directory is not supported
40 /// Working directory is not supported
41 WDirUnsupported,
41 WDirUnsupported,
42 /// Found more than one entry whose ID match the requested prefix
42 /// Found more than one entry whose ID match the requested prefix
43 AmbiguousPrefix,
43 AmbiguousPrefix,
44 #[from]
44 #[from]
45 Other(HgError),
45 Other(HgError),
46 }
46 }
47
47
48 impl From<NodeMapError> for RevlogError {
48 impl From<NodeMapError> for RevlogError {
49 fn from(error: NodeMapError) -> Self {
49 fn from(error: NodeMapError) -> Self {
50 match error {
50 match error {
51 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
51 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
52 NodeMapError::RevisionNotInIndex(_) => RevlogError::corrupted(),
52 NodeMapError::RevisionNotInIndex(rev) => RevlogError::corrupted(
53 format!("nodemap point to revision {} not in index", rev),
54 ),
53 }
55 }
54 }
56 }
55 }
57 }
56
58
57 fn corrupted() -> HgError {
59 fn corrupted<S: AsRef<str>>(context: S) -> HgError {
58 HgError::corrupted("corrupted revlog")
60 HgError::corrupted(format!("corrupted revlog, {}", context.as_ref()))
59 }
61 }
60
62
61 impl RevlogError {
63 impl RevlogError {
62 fn corrupted() -> Self {
64 fn corrupted<S: AsRef<str>>(context: S) -> Self {
63 RevlogError::Other(corrupted())
65 RevlogError::Other(corrupted(context))
64 }
66 }
65 }
67 }
66
68
67 /// Read only implementation of revlog.
69 /// Read only implementation of revlog.
68 pub struct Revlog {
70 pub struct Revlog {
69 /// When index and data are not interleaved: bytes of the revlog index.
71 /// When index and data are not interleaved: bytes of the revlog index.
70 /// When index and data are interleaved: bytes of the revlog index and
72 /// When index and data are interleaved: bytes of the revlog index and
71 /// data.
73 /// data.
72 index: Index,
74 index: Index,
73 /// When index and data are not interleaved: bytes of the revlog data
75 /// When index and data are not interleaved: bytes of the revlog data
74 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
76 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
75 /// When present on disk: the persistent nodemap for this revlog
77 /// When present on disk: the persistent nodemap for this revlog
76 nodemap: Option<nodemap::NodeTree>,
78 nodemap: Option<nodemap::NodeTree>,
77 }
79 }
78
80
79 impl Revlog {
81 impl Revlog {
80 /// Open a revlog index file.
82 /// Open a revlog index file.
81 ///
83 ///
82 /// It will also open the associated data file if index and data are not
84 /// It will also open the associated data file if index and data are not
83 /// interleaved.
85 /// interleaved.
84 #[timed]
86 #[timed]
85 pub fn open(
87 pub fn open(
86 store_vfs: &Vfs,
88 store_vfs: &Vfs,
87 index_path: impl AsRef<Path>,
89 index_path: impl AsRef<Path>,
88 data_path: Option<&Path>,
90 data_path: Option<&Path>,
89 use_nodemap: bool,
91 use_nodemap: bool,
90 ) -> Result<Self, HgError> {
92 ) -> Result<Self, HgError> {
91 let index_path = index_path.as_ref();
93 let index_path = index_path.as_ref();
92 let index = {
94 let index = {
93 match store_vfs.mmap_open_opt(&index_path)? {
95 match store_vfs.mmap_open_opt(&index_path)? {
94 None => Index::new(Box::new(vec![])),
96 None => Index::new(Box::new(vec![])),
95 Some(index_mmap) => {
97 Some(index_mmap) => {
96 let index = Index::new(Box::new(index_mmap))?;
98 let index = Index::new(Box::new(index_mmap))?;
97 Ok(index)
99 Ok(index)
98 }
100 }
99 }
101 }
100 }?;
102 }?;
101
103
102 let default_data_path = index_path.with_extension("d");
104 let default_data_path = index_path.with_extension("d");
103
105
104 // type annotation required
106 // type annotation required
105 // won't recognize Mmap as Deref<Target = [u8]>
107 // won't recognize Mmap as Deref<Target = [u8]>
106 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
108 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
107 if index.is_inline() {
109 if index.is_inline() {
108 None
110 None
109 } else {
111 } else {
110 let data_path = data_path.unwrap_or(&default_data_path);
112 let data_path = data_path.unwrap_or(&default_data_path);
111 let data_mmap = store_vfs.mmap_open(data_path)?;
113 let data_mmap = store_vfs.mmap_open(data_path)?;
112 Some(Box::new(data_mmap))
114 Some(Box::new(data_mmap))
113 };
115 };
114
116
115 let nodemap = if index.is_inline() {
117 let nodemap = if index.is_inline() {
116 None
118 None
117 } else if !use_nodemap {
119 } else if !use_nodemap {
118 None
120 None
119 } else {
121 } else {
120 NodeMapDocket::read_from_file(store_vfs, index_path)?.map(
122 NodeMapDocket::read_from_file(store_vfs, index_path)?.map(
121 |(docket, data)| {
123 |(docket, data)| {
122 nodemap::NodeTree::load_bytes(
124 nodemap::NodeTree::load_bytes(
123 Box::new(data),
125 Box::new(data),
124 docket.data_length,
126 docket.data_length,
125 )
127 )
126 },
128 },
127 )
129 )
128 };
130 };
129
131
130 Ok(Revlog {
132 Ok(Revlog {
131 index,
133 index,
132 data_bytes,
134 data_bytes,
133 nodemap,
135 nodemap,
134 })
136 })
135 }
137 }
136
138
137 /// Return number of entries of the `Revlog`.
139 /// Return number of entries of the `Revlog`.
138 pub fn len(&self) -> usize {
140 pub fn len(&self) -> usize {
139 self.index.len()
141 self.index.len()
140 }
142 }
141
143
142 /// Returns `true` if the `Revlog` has zero `entries`.
144 /// Returns `true` if the `Revlog` has zero `entries`.
143 pub fn is_empty(&self) -> bool {
145 pub fn is_empty(&self) -> bool {
144 self.index.is_empty()
146 self.index.is_empty()
145 }
147 }
146
148
147 /// Returns the node ID for the given revision number, if it exists in this
149 /// Returns the node ID for the given revision number, if it exists in this
148 /// revlog
150 /// revlog
149 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
151 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
150 if rev == NULL_REVISION {
152 if rev == NULL_REVISION {
151 return Some(&NULL_NODE);
153 return Some(&NULL_NODE);
152 }
154 }
153 Some(self.index.get_entry(rev)?.hash())
155 Some(self.index.get_entry(rev)?.hash())
154 }
156 }
155
157
156 /// Return the revision number for the given node ID, if it exists in this
158 /// Return the revision number for the given node ID, if it exists in this
157 /// revlog
159 /// revlog
158 #[timed]
160 #[timed]
159 pub fn rev_from_node(
161 pub fn rev_from_node(
160 &self,
162 &self,
161 node: NodePrefix,
163 node: NodePrefix,
162 ) -> Result<Revision, RevlogError> {
164 ) -> Result<Revision, RevlogError> {
163 if node.is_prefix_of(&NULL_NODE) {
165 if node.is_prefix_of(&NULL_NODE) {
164 return Ok(NULL_REVISION);
166 return Ok(NULL_REVISION);
165 }
167 }
166
168
167 if let Some(nodemap) = &self.nodemap {
169 if let Some(nodemap) = &self.nodemap {
168 return nodemap
170 return nodemap
169 .find_bin(&self.index, node)?
171 .find_bin(&self.index, node)?
170 .ok_or(RevlogError::InvalidRevision);
172 .ok_or(RevlogError::InvalidRevision);
171 }
173 }
172
174
173 // Fallback to linear scan when a persistent nodemap is not present.
175 // Fallback to linear scan when a persistent nodemap is not present.
174 // This happens when the persistent-nodemap experimental feature is not
176 // This happens when the persistent-nodemap experimental feature is not
175 // enabled, or for small revlogs.
177 // enabled, or for small revlogs.
176 //
178 //
177 // TODO: consider building a non-persistent nodemap in memory to
179 // TODO: consider building a non-persistent nodemap in memory to
178 // optimize these cases.
180 // optimize these cases.
179 let mut found_by_prefix = None;
181 let mut found_by_prefix = None;
180 for rev in (0..self.len() as Revision).rev() {
182 for rev in (0..self.len() as Revision).rev() {
181 let index_entry =
183 let index_entry =
182 self.index.get_entry(rev).ok_or(HgError::corrupted(
184 self.index.get_entry(rev).ok_or(HgError::corrupted(
183 "revlog references a revision not in the index",
185 "revlog references a revision not in the index",
184 ))?;
186 ))?;
185 if node == *index_entry.hash() {
187 if node == *index_entry.hash() {
186 return Ok(rev);
188 return Ok(rev);
187 }
189 }
188 if node.is_prefix_of(index_entry.hash()) {
190 if node.is_prefix_of(index_entry.hash()) {
189 if found_by_prefix.is_some() {
191 if found_by_prefix.is_some() {
190 return Err(RevlogError::AmbiguousPrefix);
192 return Err(RevlogError::AmbiguousPrefix);
191 }
193 }
192 found_by_prefix = Some(rev)
194 found_by_prefix = Some(rev)
193 }
195 }
194 }
196 }
195 found_by_prefix.ok_or(RevlogError::InvalidRevision)
197 found_by_prefix.ok_or(RevlogError::InvalidRevision)
196 }
198 }
197
199
198 /// Returns whether the given revision exists in this revlog.
200 /// Returns whether the given revision exists in this revlog.
199 pub fn has_rev(&self, rev: Revision) -> bool {
201 pub fn has_rev(&self, rev: Revision) -> bool {
200 self.index.get_entry(rev).is_some()
202 self.index.get_entry(rev).is_some()
201 }
203 }
202
204
203 /// Return the full data associated to a revision.
205 /// Return the full data associated to a revision.
204 ///
206 ///
205 /// All entries required to build the final data out of deltas will be
207 /// All entries required to build the final data out of deltas will be
206 /// retrieved as needed, and the deltas will be applied to the inital
208 /// retrieved as needed, and the deltas will be applied to the inital
207 /// snapshot to rebuild the final data.
209 /// snapshot to rebuild the final data.
208 #[timed]
210 #[timed]
209 pub fn get_rev_data(
211 pub fn get_rev_data(
210 &self,
212 &self,
211 rev: Revision,
213 rev: Revision,
212 ) -> Result<Cow<[u8]>, RevlogError> {
214 ) -> Result<Cow<[u8]>, RevlogError> {
213 if rev == NULL_REVISION {
215 if rev == NULL_REVISION {
214 return Ok(Cow::Borrowed(&[]));
216 return Ok(Cow::Borrowed(&[]));
215 };
217 };
216 Ok(self.get_entry(rev)?.data()?)
218 Ok(self.get_entry(rev)?.data()?)
217 }
219 }
218
220
219 /// Check the hash of some given data against the recorded hash.
221 /// Check the hash of some given data against the recorded hash.
220 pub fn check_hash(
222 pub fn check_hash(
221 &self,
223 &self,
222 p1: Revision,
224 p1: Revision,
223 p2: Revision,
225 p2: Revision,
224 expected: &[u8],
226 expected: &[u8],
225 data: &[u8],
227 data: &[u8],
226 ) -> bool {
228 ) -> bool {
227 let e1 = self.index.get_entry(p1);
229 let e1 = self.index.get_entry(p1);
228 let h1 = match e1 {
230 let h1 = match e1 {
229 Some(ref entry) => entry.hash(),
231 Some(ref entry) => entry.hash(),
230 None => &NULL_NODE,
232 None => &NULL_NODE,
231 };
233 };
232 let e2 = self.index.get_entry(p2);
234 let e2 = self.index.get_entry(p2);
233 let h2 = match e2 {
235 let h2 = match e2 {
234 Some(ref entry) => entry.hash(),
236 Some(ref entry) => entry.hash(),
235 None => &NULL_NODE,
237 None => &NULL_NODE,
236 };
238 };
237
239
238 &hash(data, h1.as_bytes(), h2.as_bytes()) == expected
240 &hash(data, h1.as_bytes(), h2.as_bytes()) == expected
239 }
241 }
240
242
241 /// Build the full data of a revision out its snapshot
243 /// Build the full data of a revision out its snapshot
242 /// and its deltas.
244 /// and its deltas.
243 #[timed]
245 #[timed]
244 fn build_data_from_deltas(
246 fn build_data_from_deltas(
245 snapshot: RevlogEntry,
247 snapshot: RevlogEntry,
246 deltas: &[RevlogEntry],
248 deltas: &[RevlogEntry],
247 ) -> Result<Vec<u8>, HgError> {
249 ) -> Result<Vec<u8>, HgError> {
248 let snapshot = snapshot.data_chunk()?;
250 let snapshot = snapshot.data_chunk()?;
249 let deltas = deltas
251 let deltas = deltas
250 .iter()
252 .iter()
251 .rev()
253 .rev()
252 .map(RevlogEntry::data_chunk)
254 .map(RevlogEntry::data_chunk)
253 .collect::<Result<Vec<_>, _>>()?;
255 .collect::<Result<Vec<_>, _>>()?;
254 let patches: Vec<_> =
256 let patches: Vec<_> =
255 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
257 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
256 let patch = patch::fold_patch_lists(&patches);
258 let patch = patch::fold_patch_lists(&patches);
257 Ok(patch.apply(&snapshot))
259 Ok(patch.apply(&snapshot))
258 }
260 }
259
261
260 /// Return the revlog data.
262 /// Return the revlog data.
261 fn data(&self) -> &[u8] {
263 fn data(&self) -> &[u8] {
262 match self.data_bytes {
264 match self.data_bytes {
263 Some(ref data_bytes) => &data_bytes,
265 Some(ref data_bytes) => &data_bytes,
264 None => panic!(
266 None => panic!(
265 "forgot to load the data or trying to access inline data"
267 "forgot to load the data or trying to access inline data"
266 ),
268 ),
267 }
269 }
268 }
270 }
269
271
270 pub fn make_null_entry(&self) -> RevlogEntry {
272 pub fn make_null_entry(&self) -> RevlogEntry {
271 RevlogEntry {
273 RevlogEntry {
272 revlog: self,
274 revlog: self,
273 rev: NULL_REVISION,
275 rev: NULL_REVISION,
274 bytes: b"",
276 bytes: b"",
275 compressed_len: 0,
277 compressed_len: 0,
276 uncompressed_len: 0,
278 uncompressed_len: 0,
277 base_rev_or_base_of_delta_chain: None,
279 base_rev_or_base_of_delta_chain: None,
278 p1: NULL_REVISION,
280 p1: NULL_REVISION,
279 p2: NULL_REVISION,
281 p2: NULL_REVISION,
280 flags: NULL_REVLOG_ENTRY_FLAGS,
282 flags: NULL_REVLOG_ENTRY_FLAGS,
281 hash: NULL_NODE,
283 hash: NULL_NODE,
282 }
284 }
283 }
285 }
284
286
285 /// Get an entry of the revlog.
287 /// Get an entry of the revlog.
286 pub fn get_entry(
288 pub fn get_entry(
287 &self,
289 &self,
288 rev: Revision,
290 rev: Revision,
289 ) -> Result<RevlogEntry, RevlogError> {
291 ) -> Result<RevlogEntry, RevlogError> {
290 if rev == NULL_REVISION {
292 if rev == NULL_REVISION {
291 return Ok(self.make_null_entry());
293 return Ok(self.make_null_entry());
292 }
294 }
293 let index_entry = self
295 let index_entry = self
294 .index
296 .index
295 .get_entry(rev)
297 .get_entry(rev)
296 .ok_or(RevlogError::InvalidRevision)?;
298 .ok_or(RevlogError::InvalidRevision)?;
297 let start = index_entry.offset();
299 let start = index_entry.offset();
298 let end = start + index_entry.compressed_len() as usize;
300 let end = start + index_entry.compressed_len() as usize;
299 let data = if self.index.is_inline() {
301 let data = if self.index.is_inline() {
300 self.index.data(start, end)
302 self.index.data(start, end)
301 } else {
303 } else {
302 &self.data()[start..end]
304 &self.data()[start..end]
303 };
305 };
304 let entry = RevlogEntry {
306 let entry = RevlogEntry {
305 revlog: self,
307 revlog: self,
306 rev,
308 rev,
307 bytes: data,
309 bytes: data,
308 compressed_len: index_entry.compressed_len(),
310 compressed_len: index_entry.compressed_len(),
309 uncompressed_len: index_entry.uncompressed_len(),
311 uncompressed_len: index_entry.uncompressed_len(),
310 base_rev_or_base_of_delta_chain: if index_entry
312 base_rev_or_base_of_delta_chain: if index_entry
311 .base_revision_or_base_of_delta_chain()
313 .base_revision_or_base_of_delta_chain()
312 == rev
314 == rev
313 {
315 {
314 None
316 None
315 } else {
317 } else {
316 Some(index_entry.base_revision_or_base_of_delta_chain())
318 Some(index_entry.base_revision_or_base_of_delta_chain())
317 },
319 },
318 p1: index_entry.p1(),
320 p1: index_entry.p1(),
319 p2: index_entry.p2(),
321 p2: index_entry.p2(),
320 flags: index_entry.flags(),
322 flags: index_entry.flags(),
321 hash: *index_entry.hash(),
323 hash: *index_entry.hash(),
322 };
324 };
323 Ok(entry)
325 Ok(entry)
324 }
326 }
325
327
326 /// when resolving internal references within revlog, any errors
328 /// when resolving internal references within revlog, any errors
327 /// should be reported as corruption, instead of e.g. "invalid revision"
329 /// should be reported as corruption, instead of e.g. "invalid revision"
328 fn get_entry_internal(
330 fn get_entry_internal(
329 &self,
331 &self,
330 rev: Revision,
332 rev: Revision,
331 ) -> Result<RevlogEntry, HgError> {
333 ) -> Result<RevlogEntry, HgError> {
332 return self.get_entry(rev).map_err(|_| corrupted());
334 self.get_entry(rev)
335 .map_err(|_| corrupted(format!("revision {} out of range", rev)))
333 }
336 }
334 }
337 }
335
338
336 /// The revlog entry's bytes and the necessary informations to extract
339 /// The revlog entry's bytes and the necessary informations to extract
337 /// the entry's data.
340 /// the entry's data.
338 #[derive(Clone)]
341 #[derive(Clone)]
339 pub struct RevlogEntry<'a> {
342 pub struct RevlogEntry<'a> {
340 revlog: &'a Revlog,
343 revlog: &'a Revlog,
341 rev: Revision,
344 rev: Revision,
342 bytes: &'a [u8],
345 bytes: &'a [u8],
343 compressed_len: u32,
346 compressed_len: u32,
344 uncompressed_len: i32,
347 uncompressed_len: i32,
345 base_rev_or_base_of_delta_chain: Option<Revision>,
348 base_rev_or_base_of_delta_chain: Option<Revision>,
346 p1: Revision,
349 p1: Revision,
347 p2: Revision,
350 p2: Revision,
348 flags: u16,
351 flags: u16,
349 hash: Node,
352 hash: Node,
350 }
353 }
351
354
352 impl<'a> RevlogEntry<'a> {
355 impl<'a> RevlogEntry<'a> {
353 pub fn revision(&self) -> Revision {
356 pub fn revision(&self) -> Revision {
354 self.rev
357 self.rev
355 }
358 }
356
359
357 pub fn node(&self) -> &Node {
360 pub fn node(&self) -> &Node {
358 &self.hash
361 &self.hash
359 }
362 }
360
363
361 pub fn uncompressed_len(&self) -> Option<u32> {
364 pub fn uncompressed_len(&self) -> Option<u32> {
362 u32::try_from(self.uncompressed_len).ok()
365 u32::try_from(self.uncompressed_len).ok()
363 }
366 }
364
367
365 pub fn has_p1(&self) -> bool {
368 pub fn has_p1(&self) -> bool {
366 self.p1 != NULL_REVISION
369 self.p1 != NULL_REVISION
367 }
370 }
368
371
369 pub fn p1_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
372 pub fn p1_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
370 if self.p1 == NULL_REVISION {
373 if self.p1 == NULL_REVISION {
371 Ok(None)
374 Ok(None)
372 } else {
375 } else {
373 Ok(Some(self.revlog.get_entry(self.p1)?))
376 Ok(Some(self.revlog.get_entry(self.p1)?))
374 }
377 }
375 }
378 }
376
379
377 pub fn p2_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
380 pub fn p2_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
378 if self.p2 == NULL_REVISION {
381 if self.p2 == NULL_REVISION {
379 Ok(None)
382 Ok(None)
380 } else {
383 } else {
381 Ok(Some(self.revlog.get_entry(self.p2)?))
384 Ok(Some(self.revlog.get_entry(self.p2)?))
382 }
385 }
383 }
386 }
384
387
385 pub fn p1(&self) -> Option<Revision> {
388 pub fn p1(&self) -> Option<Revision> {
386 if self.p1 == NULL_REVISION {
389 if self.p1 == NULL_REVISION {
387 None
390 None
388 } else {
391 } else {
389 Some(self.p1)
392 Some(self.p1)
390 }
393 }
391 }
394 }
392
395
393 pub fn p2(&self) -> Option<Revision> {
396 pub fn p2(&self) -> Option<Revision> {
394 if self.p2 == NULL_REVISION {
397 if self.p2 == NULL_REVISION {
395 None
398 None
396 } else {
399 } else {
397 Some(self.p2)
400 Some(self.p2)
398 }
401 }
399 }
402 }
400
403
401 pub fn is_censored(&self) -> bool {
404 pub fn is_censored(&self) -> bool {
402 (self.flags & REVISION_FLAG_CENSORED) != 0
405 (self.flags & REVISION_FLAG_CENSORED) != 0
403 }
406 }
404
407
405 pub fn has_length_affecting_flag_processor(&self) -> bool {
408 pub fn has_length_affecting_flag_processor(&self) -> bool {
406 // Relevant Python code: revlog.size()
409 // Relevant Python code: revlog.size()
407 // note: ELLIPSIS is known to not change the content
410 // note: ELLIPSIS is known to not change the content
408 (self.flags & (REVIDX_KNOWN_FLAGS ^ REVISION_FLAG_ELLIPSIS)) != 0
411 (self.flags & (REVIDX_KNOWN_FLAGS ^ REVISION_FLAG_ELLIPSIS)) != 0
409 }
412 }
410
413
411 /// The data for this entry, after resolving deltas if any.
414 /// The data for this entry, after resolving deltas if any.
412 pub fn rawdata(&self) -> Result<Cow<'a, [u8]>, HgError> {
415 pub fn rawdata(&self) -> Result<Cow<'a, [u8]>, HgError> {
413 let mut entry = self.clone();
416 let mut entry = self.clone();
414 let mut delta_chain = vec![];
417 let mut delta_chain = vec![];
415
418
416 // The meaning of `base_rev_or_base_of_delta_chain` depends on
419 // The meaning of `base_rev_or_base_of_delta_chain` depends on
417 // generaldelta. See the doc on `ENTRY_DELTA_BASE` in
420 // generaldelta. See the doc on `ENTRY_DELTA_BASE` in
418 // `mercurial/revlogutils/constants.py` and the code in
421 // `mercurial/revlogutils/constants.py` and the code in
419 // [_chaininfo] and in [index_deltachain].
422 // [_chaininfo] and in [index_deltachain].
420 let uses_generaldelta = self.revlog.index.uses_generaldelta();
423 let uses_generaldelta = self.revlog.index.uses_generaldelta();
421 while let Some(base_rev) = entry.base_rev_or_base_of_delta_chain {
424 while let Some(base_rev) = entry.base_rev_or_base_of_delta_chain {
422 let base_rev = if uses_generaldelta {
425 let base_rev = if uses_generaldelta {
423 base_rev
426 base_rev
424 } else {
427 } else {
425 entry.rev - 1
428 entry.rev - 1
426 };
429 };
427 delta_chain.push(entry);
430 delta_chain.push(entry);
428 entry = self.revlog.get_entry_internal(base_rev)?;
431 entry = self.revlog.get_entry_internal(base_rev)?;
429 }
432 }
430
433
431 let data = if delta_chain.is_empty() {
434 let data = if delta_chain.is_empty() {
432 entry.data_chunk()?
435 entry.data_chunk()?
433 } else {
436 } else {
434 Revlog::build_data_from_deltas(entry, &delta_chain)?.into()
437 Revlog::build_data_from_deltas(entry, &delta_chain)?.into()
435 };
438 };
436
439
437 Ok(data)
440 Ok(data)
438 }
441 }
439
442
440 fn check_data(
443 fn check_data(
441 &self,
444 &self,
442 data: Cow<'a, [u8]>,
445 data: Cow<'a, [u8]>,
443 ) -> Result<Cow<'a, [u8]>, HgError> {
446 ) -> Result<Cow<'a, [u8]>, HgError> {
444 if self.revlog.check_hash(
447 if self.revlog.check_hash(
445 self.p1,
448 self.p1,
446 self.p2,
449 self.p2,
447 self.hash.as_bytes(),
450 self.hash.as_bytes(),
448 &data,
451 &data,
449 ) {
452 ) {
450 Ok(data)
453 Ok(data)
451 } else {
454 } else {
452 Err(corrupted())
455 Err(corrupted(format!(
456 "hash check failed for revision {}",
457 self.rev
458 )))
453 }
459 }
454 }
460 }
455
461
456 pub fn data(&self) -> Result<Cow<'a, [u8]>, HgError> {
462 pub fn data(&self) -> Result<Cow<'a, [u8]>, HgError> {
457 let data = self.rawdata()?;
463 let data = self.rawdata()?;
458 if self.is_censored() {
464 if self.is_censored() {
459 return Err(HgError::CensoredNodeError);
465 return Err(HgError::CensoredNodeError);
460 }
466 }
461 self.check_data(data)
467 self.check_data(data)
462 }
468 }
463
469
464 /// Extract the data contained in the entry.
470 /// Extract the data contained in the entry.
465 /// This may be a delta. (See `is_delta`.)
471 /// This may be a delta. (See `is_delta`.)
466 fn data_chunk(&self) -> Result<Cow<'a, [u8]>, HgError> {
472 fn data_chunk(&self) -> Result<Cow<'a, [u8]>, HgError> {
467 if self.bytes.is_empty() {
473 if self.bytes.is_empty() {
468 return Ok(Cow::Borrowed(&[]));
474 return Ok(Cow::Borrowed(&[]));
469 }
475 }
470 match self.bytes[0] {
476 match self.bytes[0] {
471 // Revision data is the entirety of the entry, including this
477 // Revision data is the entirety of the entry, including this
472 // header.
478 // header.
473 b'\0' => Ok(Cow::Borrowed(self.bytes)),
479 b'\0' => Ok(Cow::Borrowed(self.bytes)),
474 // Raw revision data follows.
480 // Raw revision data follows.
475 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
481 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
476 // zlib (RFC 1950) data.
482 // zlib (RFC 1950) data.
477 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
483 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
478 // zstd data.
484 // zstd data.
479 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
485 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
480 // A proper new format should have had a repo/store requirement.
486 // A proper new format should have had a repo/store requirement.
481 _format_type => Err(corrupted()),
487 format_type => Err(corrupted(format!(
488 "unknown compression header '{}'",
489 format_type
490 ))),
482 }
491 }
483 }
492 }
484
493
485 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, HgError> {
494 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, HgError> {
486 let mut decoder = ZlibDecoder::new(self.bytes);
495 let mut decoder = ZlibDecoder::new(self.bytes);
487 if self.is_delta() {
496 if self.is_delta() {
488 let mut buf = Vec::with_capacity(self.compressed_len as usize);
497 let mut buf = Vec::with_capacity(self.compressed_len as usize);
489 decoder.read_to_end(&mut buf).map_err(|_| corrupted())?;
498 decoder
499 .read_to_end(&mut buf)
500 .map_err(|e| corrupted(e.to_string()))?;
490 Ok(buf)
501 Ok(buf)
491 } else {
502 } else {
492 let cap = self.uncompressed_len.max(0) as usize;
503 let cap = self.uncompressed_len.max(0) as usize;
493 let mut buf = vec![0; cap];
504 let mut buf = vec![0; cap];
494 decoder.read_exact(&mut buf).map_err(|_| corrupted())?;
505 decoder
506 .read_exact(&mut buf)
507 .map_err(|e| corrupted(e.to_string()))?;
495 Ok(buf)
508 Ok(buf)
496 }
509 }
497 }
510 }
498
511
499 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, HgError> {
512 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, HgError> {
500 if self.is_delta() {
513 if self.is_delta() {
501 let mut buf = Vec::with_capacity(self.compressed_len as usize);
514 let mut buf = Vec::with_capacity(self.compressed_len as usize);
502 zstd::stream::copy_decode(self.bytes, &mut buf)
515 zstd::stream::copy_decode(self.bytes, &mut buf)
503 .map_err(|_| corrupted())?;
516 .map_err(|e| corrupted(e.to_string()))?;
504 Ok(buf)
517 Ok(buf)
505 } else {
518 } else {
506 let cap = self.uncompressed_len.max(0) as usize;
519 let cap = self.uncompressed_len.max(0) as usize;
507 let mut buf = vec![0; cap];
520 let mut buf = vec![0; cap];
508 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
521 let len = zstd::block::decompress_to_buffer(self.bytes, &mut buf)
509 .map_err(|_| corrupted())?;
522 .map_err(|e| corrupted(e.to_string()))?;
510 if len != self.uncompressed_len as usize {
523 if len != self.uncompressed_len as usize {
511 Err(corrupted())
524 Err(corrupted("uncompressed length does not match"))
512 } else {
525 } else {
513 Ok(buf)
526 Ok(buf)
514 }
527 }
515 }
528 }
516 }
529 }
517
530
518 /// Tell if the entry is a snapshot or a delta
531 /// Tell if the entry is a snapshot or a delta
519 /// (influences on decompression).
532 /// (influences on decompression).
520 fn is_delta(&self) -> bool {
533 fn is_delta(&self) -> bool {
521 self.base_rev_or_base_of_delta_chain.is_some()
534 self.base_rev_or_base_of_delta_chain.is_some()
522 }
535 }
523 }
536 }
524
537
525 /// Calculate the hash of a revision given its data and its parents.
538 /// Calculate the hash of a revision given its data and its parents.
526 fn hash(
539 fn hash(
527 data: &[u8],
540 data: &[u8],
528 p1_hash: &[u8],
541 p1_hash: &[u8],
529 p2_hash: &[u8],
542 p2_hash: &[u8],
530 ) -> [u8; NODE_BYTES_LENGTH] {
543 ) -> [u8; NODE_BYTES_LENGTH] {
531 let mut hasher = Sha1::new();
544 let mut hasher = Sha1::new();
532 let (a, b) = (p1_hash, p2_hash);
545 let (a, b) = (p1_hash, p2_hash);
533 if a > b {
546 if a > b {
534 hasher.update(b);
547 hasher.update(b);
535 hasher.update(a);
548 hasher.update(a);
536 } else {
549 } else {
537 hasher.update(a);
550 hasher.update(a);
538 hasher.update(b);
551 hasher.update(b);
539 }
552 }
540 hasher.update(data);
553 hasher.update(data);
541 *hasher.finalize().as_ref()
554 *hasher.finalize().as_ref()
542 }
555 }
543
556
544 #[cfg(test)]
557 #[cfg(test)]
545 mod tests {
558 mod tests {
546 use super::*;
559 use super::*;
547 use crate::index::{IndexEntryBuilder, INDEX_ENTRY_SIZE};
560 use crate::index::{IndexEntryBuilder, INDEX_ENTRY_SIZE};
548 use itertools::Itertools;
561 use itertools::Itertools;
549
562
550 #[test]
563 #[test]
551 fn test_empty() {
564 fn test_empty() {
552 let temp = tempfile::tempdir().unwrap();
565 let temp = tempfile::tempdir().unwrap();
553 let vfs = Vfs { base: temp.path() };
566 let vfs = Vfs { base: temp.path() };
554 std::fs::write(temp.path().join("foo.i"), b"").unwrap();
567 std::fs::write(temp.path().join("foo.i"), b"").unwrap();
555 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
568 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
556 assert!(revlog.is_empty());
569 assert!(revlog.is_empty());
557 assert_eq!(revlog.len(), 0);
570 assert_eq!(revlog.len(), 0);
558 assert!(revlog.get_entry(0).is_err());
571 assert!(revlog.get_entry(0).is_err());
559 assert!(!revlog.has_rev(0));
572 assert!(!revlog.has_rev(0));
560 }
573 }
561
574
562 #[test]
575 #[test]
563 fn test_inline() {
576 fn test_inline() {
564 let temp = tempfile::tempdir().unwrap();
577 let temp = tempfile::tempdir().unwrap();
565 let vfs = Vfs { base: temp.path() };
578 let vfs = Vfs { base: temp.path() };
566 let node0 = Node::from_hex("2ed2a3912a0b24502043eae84ee4b279c18b90dd")
579 let node0 = Node::from_hex("2ed2a3912a0b24502043eae84ee4b279c18b90dd")
567 .unwrap();
580 .unwrap();
568 let node1 = Node::from_hex("b004912a8510032a0350a74daa2803dadfb00e12")
581 let node1 = Node::from_hex("b004912a8510032a0350a74daa2803dadfb00e12")
569 .unwrap();
582 .unwrap();
570 let node2 = Node::from_hex("dd6ad206e907be60927b5a3117b97dffb2590582")
583 let node2 = Node::from_hex("dd6ad206e907be60927b5a3117b97dffb2590582")
571 .unwrap();
584 .unwrap();
572 let entry0_bytes = IndexEntryBuilder::new()
585 let entry0_bytes = IndexEntryBuilder::new()
573 .is_first(true)
586 .is_first(true)
574 .with_version(1)
587 .with_version(1)
575 .with_inline(true)
588 .with_inline(true)
576 .with_offset(INDEX_ENTRY_SIZE)
589 .with_offset(INDEX_ENTRY_SIZE)
577 .with_node(node0)
590 .with_node(node0)
578 .build();
591 .build();
579 let entry1_bytes = IndexEntryBuilder::new()
592 let entry1_bytes = IndexEntryBuilder::new()
580 .with_offset(INDEX_ENTRY_SIZE)
593 .with_offset(INDEX_ENTRY_SIZE)
581 .with_node(node1)
594 .with_node(node1)
582 .build();
595 .build();
583 let entry2_bytes = IndexEntryBuilder::new()
596 let entry2_bytes = IndexEntryBuilder::new()
584 .with_offset(INDEX_ENTRY_SIZE)
597 .with_offset(INDEX_ENTRY_SIZE)
585 .with_p1(0)
598 .with_p1(0)
586 .with_p2(1)
599 .with_p2(1)
587 .with_node(node2)
600 .with_node(node2)
588 .build();
601 .build();
589 let contents = vec![entry0_bytes, entry1_bytes, entry2_bytes]
602 let contents = vec![entry0_bytes, entry1_bytes, entry2_bytes]
590 .into_iter()
603 .into_iter()
591 .flatten()
604 .flatten()
592 .collect_vec();
605 .collect_vec();
593 std::fs::write(temp.path().join("foo.i"), contents).unwrap();
606 std::fs::write(temp.path().join("foo.i"), contents).unwrap();
594 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
607 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
595
608
596 let entry0 = revlog.get_entry(0).ok().unwrap();
609 let entry0 = revlog.get_entry(0).ok().unwrap();
597 assert_eq!(entry0.revision(), 0);
610 assert_eq!(entry0.revision(), 0);
598 assert_eq!(*entry0.node(), node0);
611 assert_eq!(*entry0.node(), node0);
599 assert!(!entry0.has_p1());
612 assert!(!entry0.has_p1());
600 assert_eq!(entry0.p1(), None);
613 assert_eq!(entry0.p1(), None);
601 assert_eq!(entry0.p2(), None);
614 assert_eq!(entry0.p2(), None);
602 let p1_entry = entry0.p1_entry().unwrap();
615 let p1_entry = entry0.p1_entry().unwrap();
603 assert!(p1_entry.is_none());
616 assert!(p1_entry.is_none());
604 let p2_entry = entry0.p2_entry().unwrap();
617 let p2_entry = entry0.p2_entry().unwrap();
605 assert!(p2_entry.is_none());
618 assert!(p2_entry.is_none());
606
619
607 let entry1 = revlog.get_entry(1).ok().unwrap();
620 let entry1 = revlog.get_entry(1).ok().unwrap();
608 assert_eq!(entry1.revision(), 1);
621 assert_eq!(entry1.revision(), 1);
609 assert_eq!(*entry1.node(), node1);
622 assert_eq!(*entry1.node(), node1);
610 assert!(!entry1.has_p1());
623 assert!(!entry1.has_p1());
611 assert_eq!(entry1.p1(), None);
624 assert_eq!(entry1.p1(), None);
612 assert_eq!(entry1.p2(), None);
625 assert_eq!(entry1.p2(), None);
613 let p1_entry = entry1.p1_entry().unwrap();
626 let p1_entry = entry1.p1_entry().unwrap();
614 assert!(p1_entry.is_none());
627 assert!(p1_entry.is_none());
615 let p2_entry = entry1.p2_entry().unwrap();
628 let p2_entry = entry1.p2_entry().unwrap();
616 assert!(p2_entry.is_none());
629 assert!(p2_entry.is_none());
617
630
618 let entry2 = revlog.get_entry(2).ok().unwrap();
631 let entry2 = revlog.get_entry(2).ok().unwrap();
619 assert_eq!(entry2.revision(), 2);
632 assert_eq!(entry2.revision(), 2);
620 assert_eq!(*entry2.node(), node2);
633 assert_eq!(*entry2.node(), node2);
621 assert!(entry2.has_p1());
634 assert!(entry2.has_p1());
622 assert_eq!(entry2.p1(), Some(0));
635 assert_eq!(entry2.p1(), Some(0));
623 assert_eq!(entry2.p2(), Some(1));
636 assert_eq!(entry2.p2(), Some(1));
624 let p1_entry = entry2.p1_entry().unwrap();
637 let p1_entry = entry2.p1_entry().unwrap();
625 assert!(p1_entry.is_some());
638 assert!(p1_entry.is_some());
626 assert_eq!(p1_entry.unwrap().revision(), 0);
639 assert_eq!(p1_entry.unwrap().revision(), 0);
627 let p2_entry = entry2.p2_entry().unwrap();
640 let p2_entry = entry2.p2_entry().unwrap();
628 assert!(p2_entry.is_some());
641 assert!(p2_entry.is_some());
629 assert_eq!(p2_entry.unwrap().revision(), 1);
642 assert_eq!(p2_entry.unwrap().revision(), 1);
630 }
643 }
631 }
644 }
@@ -1,512 +1,515 b''
1 // revlog.rs
1 // revlog.rs
2 //
2 //
3 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
3 // Copyright 2019-2020 Georges Racinet <georges.racinet@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::{
8 use crate::{
9 cindex,
9 cindex,
10 utils::{node_from_py_bytes, node_from_py_object},
10 utils::{node_from_py_bytes, node_from_py_object},
11 };
11 };
12 use cpython::{
12 use cpython::{
13 buffer::{Element, PyBuffer},
13 buffer::{Element, PyBuffer},
14 exc::{IndexError, ValueError},
14 exc::{IndexError, ValueError},
15 ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyInt, PyModule,
15 ObjectProtocol, PyBytes, PyClone, PyDict, PyErr, PyInt, PyModule,
16 PyObject, PyResult, PyString, PyTuple, Python, PythonObject, ToPyObject,
16 PyObject, PyResult, PyString, PyTuple, Python, PythonObject, ToPyObject,
17 };
17 };
18 use hg::{
18 use hg::{
19 nodemap::{Block, NodeMapError, NodeTree},
19 nodemap::{Block, NodeMapError, NodeTree},
20 revlog::{nodemap::NodeMap, NodePrefix, RevlogIndex},
20 revlog::{nodemap::NodeMap, NodePrefix, RevlogIndex},
21 Revision,
21 Revision,
22 };
22 };
23 use std::cell::RefCell;
23 use std::cell::RefCell;
24
24
25 /// Return a Struct implementing the Graph trait
25 /// Return a Struct implementing the Graph trait
26 pub(crate) fn pyindex_to_graph(
26 pub(crate) fn pyindex_to_graph(
27 py: Python,
27 py: Python,
28 index: PyObject,
28 index: PyObject,
29 ) -> PyResult<cindex::Index> {
29 ) -> PyResult<cindex::Index> {
30 match index.extract::<MixedIndex>(py) {
30 match index.extract::<MixedIndex>(py) {
31 Ok(midx) => Ok(midx.clone_cindex(py)),
31 Ok(midx) => Ok(midx.clone_cindex(py)),
32 Err(_) => cindex::Index::new(py, index),
32 Err(_) => cindex::Index::new(py, index),
33 }
33 }
34 }
34 }
35
35
36 py_class!(pub class MixedIndex |py| {
36 py_class!(pub class MixedIndex |py| {
37 data cindex: RefCell<cindex::Index>;
37 data cindex: RefCell<cindex::Index>;
38 data nt: RefCell<Option<NodeTree>>;
38 data nt: RefCell<Option<NodeTree>>;
39 data docket: RefCell<Option<PyObject>>;
39 data docket: RefCell<Option<PyObject>>;
40 // Holds a reference to the mmap'ed persistent nodemap data
40 // Holds a reference to the mmap'ed persistent nodemap data
41 data mmap: RefCell<Option<PyBuffer>>;
41 data mmap: RefCell<Option<PyBuffer>>;
42
42
43 def __new__(_cls, cindex: PyObject) -> PyResult<MixedIndex> {
43 def __new__(_cls, cindex: PyObject) -> PyResult<MixedIndex> {
44 Self::new(py, cindex)
44 Self::new(py, cindex)
45 }
45 }
46
46
47 /// Compatibility layer used for Python consumers needing access to the C index
47 /// Compatibility layer used for Python consumers needing access to the C index
48 ///
48 ///
49 /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
49 /// Only use case so far is `scmutil.shortesthexnodeidprefix`,
50 /// that may need to build a custom `nodetree`, based on a specified revset.
50 /// that may need to build a custom `nodetree`, based on a specified revset.
51 /// With a Rust implementation of the nodemap, we will be able to get rid of
51 /// With a Rust implementation of the nodemap, we will be able to get rid of
52 /// this, by exposing our own standalone nodemap class,
52 /// this, by exposing our own standalone nodemap class,
53 /// ready to accept `MixedIndex`.
53 /// ready to accept `MixedIndex`.
54 def get_cindex(&self) -> PyResult<PyObject> {
54 def get_cindex(&self) -> PyResult<PyObject> {
55 Ok(self.cindex(py).borrow().inner().clone_ref(py))
55 Ok(self.cindex(py).borrow().inner().clone_ref(py))
56 }
56 }
57
57
58 // Index API involving nodemap, as defined in mercurial/pure/parsers.py
58 // Index API involving nodemap, as defined in mercurial/pure/parsers.py
59
59
60 /// Return Revision if found, raises a bare `error.RevlogError`
60 /// Return Revision if found, raises a bare `error.RevlogError`
61 /// in case of ambiguity, same as C version does
61 /// in case of ambiguity, same as C version does
62 def get_rev(&self, node: PyBytes) -> PyResult<Option<Revision>> {
62 def get_rev(&self, node: PyBytes) -> PyResult<Option<Revision>> {
63 let opt = self.get_nodetree(py)?.borrow();
63 let opt = self.get_nodetree(py)?.borrow();
64 let nt = opt.as_ref().unwrap();
64 let nt = opt.as_ref().unwrap();
65 let idx = &*self.cindex(py).borrow();
65 let idx = &*self.cindex(py).borrow();
66 let node = node_from_py_bytes(py, &node)?;
66 let node = node_from_py_bytes(py, &node)?;
67 nt.find_bin(idx, node.into()).map_err(|e| nodemap_error(py, e))
67 nt.find_bin(idx, node.into()).map_err(|e| nodemap_error(py, e))
68 }
68 }
69
69
70 /// same as `get_rev()` but raises a bare `error.RevlogError` if node
70 /// same as `get_rev()` but raises a bare `error.RevlogError` if node
71 /// is not found.
71 /// is not found.
72 ///
72 ///
73 /// No need to repeat `node` in the exception, `mercurial/revlog.py`
73 /// No need to repeat `node` in the exception, `mercurial/revlog.py`
74 /// will catch and rewrap with it
74 /// will catch and rewrap with it
75 def rev(&self, node: PyBytes) -> PyResult<Revision> {
75 def rev(&self, node: PyBytes) -> PyResult<Revision> {
76 self.get_rev(py, node)?.ok_or_else(|| revlog_error(py))
76 self.get_rev(py, node)?.ok_or_else(|| revlog_error(py))
77 }
77 }
78
78
79 /// return True if the node exist in the index
79 /// return True if the node exist in the index
80 def has_node(&self, node: PyBytes) -> PyResult<bool> {
80 def has_node(&self, node: PyBytes) -> PyResult<bool> {
81 self.get_rev(py, node).map(|opt| opt.is_some())
81 self.get_rev(py, node).map(|opt| opt.is_some())
82 }
82 }
83
83
84 /// find length of shortest hex nodeid of a binary ID
84 /// find length of shortest hex nodeid of a binary ID
85 def shortest(&self, node: PyBytes) -> PyResult<usize> {
85 def shortest(&self, node: PyBytes) -> PyResult<usize> {
86 let opt = self.get_nodetree(py)?.borrow();
86 let opt = self.get_nodetree(py)?.borrow();
87 let nt = opt.as_ref().unwrap();
87 let nt = opt.as_ref().unwrap();
88 let idx = &*self.cindex(py).borrow();
88 let idx = &*self.cindex(py).borrow();
89 match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
89 match nt.unique_prefix_len_node(idx, &node_from_py_bytes(py, &node)?)
90 {
90 {
91 Ok(Some(l)) => Ok(l),
91 Ok(Some(l)) => Ok(l),
92 Ok(None) => Err(revlog_error(py)),
92 Ok(None) => Err(revlog_error(py)),
93 Err(e) => Err(nodemap_error(py, e)),
93 Err(e) => Err(nodemap_error(py, e)),
94 }
94 }
95 }
95 }
96
96
97 def partialmatch(&self, node: PyObject) -> PyResult<Option<PyBytes>> {
97 def partialmatch(&self, node: PyObject) -> PyResult<Option<PyBytes>> {
98 let opt = self.get_nodetree(py)?.borrow();
98 let opt = self.get_nodetree(py)?.borrow();
99 let nt = opt.as_ref().unwrap();
99 let nt = opt.as_ref().unwrap();
100 let idx = &*self.cindex(py).borrow();
100 let idx = &*self.cindex(py).borrow();
101
101
102 let node_as_string = if cfg!(feature = "python3-sys") {
102 let node_as_string = if cfg!(feature = "python3-sys") {
103 node.cast_as::<PyString>(py)?.to_string(py)?.to_string()
103 node.cast_as::<PyString>(py)?.to_string(py)?.to_string()
104 }
104 }
105 else {
105 else {
106 let node = node.extract::<PyBytes>(py)?;
106 let node = node.extract::<PyBytes>(py)?;
107 String::from_utf8_lossy(node.data(py)).to_string()
107 String::from_utf8_lossy(node.data(py)).to_string()
108 };
108 };
109
109
110 let prefix = NodePrefix::from_hex(&node_as_string).map_err(|_| PyErr::new::<ValueError, _>(py, "Invalid node or prefix"))?;
110 let prefix = NodePrefix::from_hex(&node_as_string)
111 .map_err(|_| PyErr::new::<ValueError, _>(
112 py, format!("Invalid node or prefix '{}'", node_as_string))
113 )?;
111
114
112 nt.find_bin(idx, prefix)
115 nt.find_bin(idx, prefix)
113 // TODO make an inner API returning the node directly
116 // TODO make an inner API returning the node directly
114 .map(|opt| opt.map(
117 .map(|opt| opt.map(
115 |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes())))
118 |rev| PyBytes::new(py, idx.node(rev).unwrap().as_bytes())))
116 .map_err(|e| nodemap_error(py, e))
119 .map_err(|e| nodemap_error(py, e))
117
120
118 }
121 }
119
122
120 /// append an index entry
123 /// append an index entry
121 def append(&self, tup: PyTuple) -> PyResult<PyObject> {
124 def append(&self, tup: PyTuple) -> PyResult<PyObject> {
122 if tup.len(py) < 8 {
125 if tup.len(py) < 8 {
123 // this is better than the panic promised by tup.get_item()
126 // this is better than the panic promised by tup.get_item()
124 return Err(
127 return Err(
125 PyErr::new::<IndexError, _>(py, "tuple index out of range"))
128 PyErr::new::<IndexError, _>(py, "tuple index out of range"))
126 }
129 }
127 let node_bytes = tup.get_item(py, 7).extract(py)?;
130 let node_bytes = tup.get_item(py, 7).extract(py)?;
128 let node = node_from_py_object(py, &node_bytes)?;
131 let node = node_from_py_object(py, &node_bytes)?;
129
132
130 let mut idx = self.cindex(py).borrow_mut();
133 let mut idx = self.cindex(py).borrow_mut();
131 let rev = idx.len() as Revision;
134 let rev = idx.len() as Revision;
132
135
133 idx.append(py, tup)?;
136 idx.append(py, tup)?;
134 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap()
137 self.get_nodetree(py)?.borrow_mut().as_mut().unwrap()
135 .insert(&*idx, &node, rev)
138 .insert(&*idx, &node, rev)
136 .map_err(|e| nodemap_error(py, e))?;
139 .map_err(|e| nodemap_error(py, e))?;
137 Ok(py.None())
140 Ok(py.None())
138 }
141 }
139
142
140 def __delitem__(&self, key: PyObject) -> PyResult<()> {
143 def __delitem__(&self, key: PyObject) -> PyResult<()> {
141 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
144 // __delitem__ is both for `del idx[r]` and `del idx[r1:r2]`
142 self.cindex(py).borrow().inner().del_item(py, key)?;
145 self.cindex(py).borrow().inner().del_item(py, key)?;
143 let mut opt = self.get_nodetree(py)?.borrow_mut();
146 let mut opt = self.get_nodetree(py)?.borrow_mut();
144 let mut nt = opt.as_mut().unwrap();
147 let mut nt = opt.as_mut().unwrap();
145 nt.invalidate_all();
148 nt.invalidate_all();
146 self.fill_nodemap(py, &mut nt)?;
149 self.fill_nodemap(py, &mut nt)?;
147 Ok(())
150 Ok(())
148 }
151 }
149
152
150 //
153 //
151 // Reforwarded C index API
154 // Reforwarded C index API
152 //
155 //
153
156
154 // index_methods (tp_methods). Same ordering as in revlog.c
157 // index_methods (tp_methods). Same ordering as in revlog.c
155
158
156 /// return the gca set of the given revs
159 /// return the gca set of the given revs
157 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
160 def ancestors(&self, *args, **kw) -> PyResult<PyObject> {
158 self.call_cindex(py, "ancestors", args, kw)
161 self.call_cindex(py, "ancestors", args, kw)
159 }
162 }
160
163
161 /// return the heads of the common ancestors of the given revs
164 /// return the heads of the common ancestors of the given revs
162 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
165 def commonancestorsheads(&self, *args, **kw) -> PyResult<PyObject> {
163 self.call_cindex(py, "commonancestorsheads", args, kw)
166 self.call_cindex(py, "commonancestorsheads", args, kw)
164 }
167 }
165
168
166 /// Clear the index caches and inner py_class data.
169 /// Clear the index caches and inner py_class data.
167 /// It is Python's responsibility to call `update_nodemap_data` again.
170 /// It is Python's responsibility to call `update_nodemap_data` again.
168 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
171 def clearcaches(&self, *args, **kw) -> PyResult<PyObject> {
169 self.nt(py).borrow_mut().take();
172 self.nt(py).borrow_mut().take();
170 self.docket(py).borrow_mut().take();
173 self.docket(py).borrow_mut().take();
171 self.mmap(py).borrow_mut().take();
174 self.mmap(py).borrow_mut().take();
172 self.call_cindex(py, "clearcaches", args, kw)
175 self.call_cindex(py, "clearcaches", args, kw)
173 }
176 }
174
177
175 /// return the raw binary string representing a revision
178 /// return the raw binary string representing a revision
176 def entry_binary(&self, *args, **kw) -> PyResult<PyObject> {
179 def entry_binary(&self, *args, **kw) -> PyResult<PyObject> {
177 self.call_cindex(py, "entry_binary", args, kw)
180 self.call_cindex(py, "entry_binary", args, kw)
178 }
181 }
179
182
180 /// return a binary packed version of the header
183 /// return a binary packed version of the header
181 def pack_header(&self, *args, **kw) -> PyResult<PyObject> {
184 def pack_header(&self, *args, **kw) -> PyResult<PyObject> {
182 self.call_cindex(py, "pack_header", args, kw)
185 self.call_cindex(py, "pack_header", args, kw)
183 }
186 }
184
187
185 /// get an index entry
188 /// get an index entry
186 def get(&self, *args, **kw) -> PyResult<PyObject> {
189 def get(&self, *args, **kw) -> PyResult<PyObject> {
187 self.call_cindex(py, "get", args, kw)
190 self.call_cindex(py, "get", args, kw)
188 }
191 }
189
192
190 /// compute phases
193 /// compute phases
191 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
194 def computephasesmapsets(&self, *args, **kw) -> PyResult<PyObject> {
192 self.call_cindex(py, "computephasesmapsets", args, kw)
195 self.call_cindex(py, "computephasesmapsets", args, kw)
193 }
196 }
194
197
195 /// reachableroots
198 /// reachableroots
196 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
199 def reachableroots2(&self, *args, **kw) -> PyResult<PyObject> {
197 self.call_cindex(py, "reachableroots2", args, kw)
200 self.call_cindex(py, "reachableroots2", args, kw)
198 }
201 }
199
202
200 /// get head revisions
203 /// get head revisions
201 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
204 def headrevs(&self, *args, **kw) -> PyResult<PyObject> {
202 self.call_cindex(py, "headrevs", args, kw)
205 self.call_cindex(py, "headrevs", args, kw)
203 }
206 }
204
207
205 /// get filtered head revisions
208 /// get filtered head revisions
206 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
209 def headrevsfiltered(&self, *args, **kw) -> PyResult<PyObject> {
207 self.call_cindex(py, "headrevsfiltered", args, kw)
210 self.call_cindex(py, "headrevsfiltered", args, kw)
208 }
211 }
209
212
210 /// True if the object is a snapshot
213 /// True if the object is a snapshot
211 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
214 def issnapshot(&self, *args, **kw) -> PyResult<PyObject> {
212 self.call_cindex(py, "issnapshot", args, kw)
215 self.call_cindex(py, "issnapshot", args, kw)
213 }
216 }
214
217
215 /// Gather snapshot data in a cache dict
218 /// Gather snapshot data in a cache dict
216 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
219 def findsnapshots(&self, *args, **kw) -> PyResult<PyObject> {
217 self.call_cindex(py, "findsnapshots", args, kw)
220 self.call_cindex(py, "findsnapshots", args, kw)
218 }
221 }
219
222
220 /// determine revisions with deltas to reconstruct fulltext
223 /// determine revisions with deltas to reconstruct fulltext
221 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
224 def deltachain(&self, *args, **kw) -> PyResult<PyObject> {
222 self.call_cindex(py, "deltachain", args, kw)
225 self.call_cindex(py, "deltachain", args, kw)
223 }
226 }
224
227
225 /// slice planned chunk read to reach a density threshold
228 /// slice planned chunk read to reach a density threshold
226 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
229 def slicechunktodensity(&self, *args, **kw) -> PyResult<PyObject> {
227 self.call_cindex(py, "slicechunktodensity", args, kw)
230 self.call_cindex(py, "slicechunktodensity", args, kw)
228 }
231 }
229
232
230 /// stats for the index
233 /// stats for the index
231 def stats(&self, *args, **kw) -> PyResult<PyObject> {
234 def stats(&self, *args, **kw) -> PyResult<PyObject> {
232 self.call_cindex(py, "stats", args, kw)
235 self.call_cindex(py, "stats", args, kw)
233 }
236 }
234
237
235 // index_sequence_methods and index_mapping_methods.
238 // index_sequence_methods and index_mapping_methods.
236 //
239 //
237 // Since we call back through the high level Python API,
240 // Since we call back through the high level Python API,
238 // there's no point making a distinction between index_get
241 // there's no point making a distinction between index_get
239 // and index_getitem.
242 // and index_getitem.
240
243
241 def __len__(&self) -> PyResult<usize> {
244 def __len__(&self) -> PyResult<usize> {
242 self.cindex(py).borrow().inner().len(py)
245 self.cindex(py).borrow().inner().len(py)
243 }
246 }
244
247
245 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
248 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
246 // this conversion seems needless, but that's actually because
249 // this conversion seems needless, but that's actually because
247 // `index_getitem` does not handle conversion from PyLong,
250 // `index_getitem` does not handle conversion from PyLong,
248 // which expressions such as [e for e in index] internally use.
251 // which expressions such as [e for e in index] internally use.
249 // Note that we don't seem to have a direct way to call
252 // Note that we don't seem to have a direct way to call
250 // PySequence_GetItem (does the job), which would possibly be better
253 // PySequence_GetItem (does the job), which would possibly be better
251 // for performance
254 // for performance
252 let key = match key.extract::<Revision>(py) {
255 let key = match key.extract::<Revision>(py) {
253 Ok(rev) => rev.to_py_object(py).into_object(),
256 Ok(rev) => rev.to_py_object(py).into_object(),
254 Err(_) => key,
257 Err(_) => key,
255 };
258 };
256 self.cindex(py).borrow().inner().get_item(py, key)
259 self.cindex(py).borrow().inner().get_item(py, key)
257 }
260 }
258
261
259 def __setitem__(&self, key: PyObject, value: PyObject) -> PyResult<()> {
262 def __setitem__(&self, key: PyObject, value: PyObject) -> PyResult<()> {
260 self.cindex(py).borrow().inner().set_item(py, key, value)
263 self.cindex(py).borrow().inner().set_item(py, key, value)
261 }
264 }
262
265
263 def __contains__(&self, item: PyObject) -> PyResult<bool> {
266 def __contains__(&self, item: PyObject) -> PyResult<bool> {
264 // ObjectProtocol does not seem to provide contains(), so
267 // ObjectProtocol does not seem to provide contains(), so
265 // this is an equivalent implementation of the index_contains()
268 // this is an equivalent implementation of the index_contains()
266 // defined in revlog.c
269 // defined in revlog.c
267 let cindex = self.cindex(py).borrow();
270 let cindex = self.cindex(py).borrow();
268 match item.extract::<Revision>(py) {
271 match item.extract::<Revision>(py) {
269 Ok(rev) => {
272 Ok(rev) => {
270 Ok(rev >= -1 && rev < cindex.inner().len(py)? as Revision)
273 Ok(rev >= -1 && rev < cindex.inner().len(py)? as Revision)
271 }
274 }
272 Err(_) => {
275 Err(_) => {
273 cindex.inner().call_method(
276 cindex.inner().call_method(
274 py,
277 py,
275 "has_node",
278 "has_node",
276 PyTuple::new(py, &[item]),
279 PyTuple::new(py, &[item]),
277 None)?
280 None)?
278 .extract(py)
281 .extract(py)
279 }
282 }
280 }
283 }
281 }
284 }
282
285
283 def nodemap_data_all(&self) -> PyResult<PyBytes> {
286 def nodemap_data_all(&self) -> PyResult<PyBytes> {
284 self.inner_nodemap_data_all(py)
287 self.inner_nodemap_data_all(py)
285 }
288 }
286
289
287 def nodemap_data_incremental(&self) -> PyResult<PyObject> {
290 def nodemap_data_incremental(&self) -> PyResult<PyObject> {
288 self.inner_nodemap_data_incremental(py)
291 self.inner_nodemap_data_incremental(py)
289 }
292 }
290 def update_nodemap_data(
293 def update_nodemap_data(
291 &self,
294 &self,
292 docket: PyObject,
295 docket: PyObject,
293 nm_data: PyObject
296 nm_data: PyObject
294 ) -> PyResult<PyObject> {
297 ) -> PyResult<PyObject> {
295 self.inner_update_nodemap_data(py, docket, nm_data)
298 self.inner_update_nodemap_data(py, docket, nm_data)
296 }
299 }
297
300
298 @property
301 @property
299 def entry_size(&self) -> PyResult<PyInt> {
302 def entry_size(&self) -> PyResult<PyInt> {
300 self.cindex(py).borrow().inner().getattr(py, "entry_size")?.extract::<PyInt>(py)
303 self.cindex(py).borrow().inner().getattr(py, "entry_size")?.extract::<PyInt>(py)
301 }
304 }
302
305
303 @property
306 @property
304 def rust_ext_compat(&self) -> PyResult<PyInt> {
307 def rust_ext_compat(&self) -> PyResult<PyInt> {
305 self.cindex(py).borrow().inner().getattr(py, "rust_ext_compat")?.extract::<PyInt>(py)
308 self.cindex(py).borrow().inner().getattr(py, "rust_ext_compat")?.extract::<PyInt>(py)
306 }
309 }
307
310
308 });
311 });
309
312
310 impl MixedIndex {
313 impl MixedIndex {
311 fn new(py: Python, cindex: PyObject) -> PyResult<MixedIndex> {
314 fn new(py: Python, cindex: PyObject) -> PyResult<MixedIndex> {
312 Self::create_instance(
315 Self::create_instance(
313 py,
316 py,
314 RefCell::new(cindex::Index::new(py, cindex)?),
317 RefCell::new(cindex::Index::new(py, cindex)?),
315 RefCell::new(None),
318 RefCell::new(None),
316 RefCell::new(None),
319 RefCell::new(None),
317 RefCell::new(None),
320 RefCell::new(None),
318 )
321 )
319 }
322 }
320
323
321 /// This is scaffolding at this point, but it could also become
324 /// This is scaffolding at this point, but it could also become
322 /// a way to start a persistent nodemap or perform a
325 /// a way to start a persistent nodemap or perform a
323 /// vacuum / repack operation
326 /// vacuum / repack operation
324 fn fill_nodemap(
327 fn fill_nodemap(
325 &self,
328 &self,
326 py: Python,
329 py: Python,
327 nt: &mut NodeTree,
330 nt: &mut NodeTree,
328 ) -> PyResult<PyObject> {
331 ) -> PyResult<PyObject> {
329 let index = self.cindex(py).borrow();
332 let index = self.cindex(py).borrow();
330 for r in 0..index.len() {
333 for r in 0..index.len() {
331 let rev = r as Revision;
334 let rev = r as Revision;
332 // in this case node() won't ever return None
335 // in this case node() won't ever return None
333 nt.insert(&*index, index.node(rev).unwrap(), rev)
336 nt.insert(&*index, index.node(rev).unwrap(), rev)
334 .map_err(|e| nodemap_error(py, e))?
337 .map_err(|e| nodemap_error(py, e))?
335 }
338 }
336 Ok(py.None())
339 Ok(py.None())
337 }
340 }
338
341
339 fn get_nodetree<'a>(
342 fn get_nodetree<'a>(
340 &'a self,
343 &'a self,
341 py: Python<'a>,
344 py: Python<'a>,
342 ) -> PyResult<&'a RefCell<Option<NodeTree>>> {
345 ) -> PyResult<&'a RefCell<Option<NodeTree>>> {
343 if self.nt(py).borrow().is_none() {
346 if self.nt(py).borrow().is_none() {
344 let readonly = Box::new(Vec::new());
347 let readonly = Box::new(Vec::new());
345 let mut nt = NodeTree::load_bytes(readonly, 0);
348 let mut nt = NodeTree::load_bytes(readonly, 0);
346 self.fill_nodemap(py, &mut nt)?;
349 self.fill_nodemap(py, &mut nt)?;
347 self.nt(py).borrow_mut().replace(nt);
350 self.nt(py).borrow_mut().replace(nt);
348 }
351 }
349 Ok(self.nt(py))
352 Ok(self.nt(py))
350 }
353 }
351
354
352 /// forward a method call to the underlying C index
355 /// forward a method call to the underlying C index
353 fn call_cindex(
356 fn call_cindex(
354 &self,
357 &self,
355 py: Python,
358 py: Python,
356 name: &str,
359 name: &str,
357 args: &PyTuple,
360 args: &PyTuple,
358 kwargs: Option<&PyDict>,
361 kwargs: Option<&PyDict>,
359 ) -> PyResult<PyObject> {
362 ) -> PyResult<PyObject> {
360 self.cindex(py)
363 self.cindex(py)
361 .borrow()
364 .borrow()
362 .inner()
365 .inner()
363 .call_method(py, name, args, kwargs)
366 .call_method(py, name, args, kwargs)
364 }
367 }
365
368
366 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
369 pub fn clone_cindex(&self, py: Python) -> cindex::Index {
367 self.cindex(py).borrow().clone_ref(py)
370 self.cindex(py).borrow().clone_ref(py)
368 }
371 }
369
372
370 /// Returns the full nodemap bytes to be written as-is to disk
373 /// Returns the full nodemap bytes to be written as-is to disk
371 fn inner_nodemap_data_all(&self, py: Python) -> PyResult<PyBytes> {
374 fn inner_nodemap_data_all(&self, py: Python) -> PyResult<PyBytes> {
372 let nodemap = self.get_nodetree(py)?.borrow_mut().take().unwrap();
375 let nodemap = self.get_nodetree(py)?.borrow_mut().take().unwrap();
373 let (readonly, bytes) = nodemap.into_readonly_and_added_bytes();
376 let (readonly, bytes) = nodemap.into_readonly_and_added_bytes();
374
377
375 // If there's anything readonly, we need to build the data again from
378 // If there's anything readonly, we need to build the data again from
376 // scratch
379 // scratch
377 let bytes = if readonly.len() > 0 {
380 let bytes = if readonly.len() > 0 {
378 let mut nt = NodeTree::load_bytes(Box::new(vec![]), 0);
381 let mut nt = NodeTree::load_bytes(Box::new(vec![]), 0);
379 self.fill_nodemap(py, &mut nt)?;
382 self.fill_nodemap(py, &mut nt)?;
380
383
381 let (readonly, bytes) = nt.into_readonly_and_added_bytes();
384 let (readonly, bytes) = nt.into_readonly_and_added_bytes();
382 assert_eq!(readonly.len(), 0);
385 assert_eq!(readonly.len(), 0);
383
386
384 bytes
387 bytes
385 } else {
388 } else {
386 bytes
389 bytes
387 };
390 };
388
391
389 let bytes = PyBytes::new(py, &bytes);
392 let bytes = PyBytes::new(py, &bytes);
390 Ok(bytes)
393 Ok(bytes)
391 }
394 }
392
395
393 /// Returns the last saved docket along with the size of any changed data
396 /// Returns the last saved docket along with the size of any changed data
394 /// (in number of blocks), and said data as bytes.
397 /// (in number of blocks), and said data as bytes.
395 fn inner_nodemap_data_incremental(
398 fn inner_nodemap_data_incremental(
396 &self,
399 &self,
397 py: Python,
400 py: Python,
398 ) -> PyResult<PyObject> {
401 ) -> PyResult<PyObject> {
399 let docket = self.docket(py).borrow();
402 let docket = self.docket(py).borrow();
400 let docket = match docket.as_ref() {
403 let docket = match docket.as_ref() {
401 Some(d) => d,
404 Some(d) => d,
402 None => return Ok(py.None()),
405 None => return Ok(py.None()),
403 };
406 };
404
407
405 let node_tree = self.get_nodetree(py)?.borrow_mut().take().unwrap();
408 let node_tree = self.get_nodetree(py)?.borrow_mut().take().unwrap();
406 let masked_blocks = node_tree.masked_readonly_blocks();
409 let masked_blocks = node_tree.masked_readonly_blocks();
407 let (_, data) = node_tree.into_readonly_and_added_bytes();
410 let (_, data) = node_tree.into_readonly_and_added_bytes();
408 let changed = masked_blocks * std::mem::size_of::<Block>();
411 let changed = masked_blocks * std::mem::size_of::<Block>();
409
412
410 Ok((docket, changed, PyBytes::new(py, &data))
413 Ok((docket, changed, PyBytes::new(py, &data))
411 .to_py_object(py)
414 .to_py_object(py)
412 .into_object())
415 .into_object())
413 }
416 }
414
417
415 /// Update the nodemap from the new (mmaped) data.
418 /// Update the nodemap from the new (mmaped) data.
416 /// The docket is kept as a reference for later incremental calls.
419 /// The docket is kept as a reference for later incremental calls.
417 fn inner_update_nodemap_data(
420 fn inner_update_nodemap_data(
418 &self,
421 &self,
419 py: Python,
422 py: Python,
420 docket: PyObject,
423 docket: PyObject,
421 nm_data: PyObject,
424 nm_data: PyObject,
422 ) -> PyResult<PyObject> {
425 ) -> PyResult<PyObject> {
423 let buf = PyBuffer::get(py, &nm_data)?;
426 let buf = PyBuffer::get(py, &nm_data)?;
424 let len = buf.item_count();
427 let len = buf.item_count();
425
428
426 // Build a slice from the mmap'ed buffer data
429 // Build a slice from the mmap'ed buffer data
427 let cbuf = buf.buf_ptr();
430 let cbuf = buf.buf_ptr();
428 let bytes = if std::mem::size_of::<u8>() == buf.item_size()
431 let bytes = if std::mem::size_of::<u8>() == buf.item_size()
429 && buf.is_c_contiguous()
432 && buf.is_c_contiguous()
430 && u8::is_compatible_format(buf.format())
433 && u8::is_compatible_format(buf.format())
431 {
434 {
432 unsafe { std::slice::from_raw_parts(cbuf as *const u8, len) }
435 unsafe { std::slice::from_raw_parts(cbuf as *const u8, len) }
433 } else {
436 } else {
434 return Err(PyErr::new::<ValueError, _>(
437 return Err(PyErr::new::<ValueError, _>(
435 py,
438 py,
436 "Nodemap data buffer has an invalid memory representation"
439 "Nodemap data buffer has an invalid memory representation"
437 .to_string(),
440 .to_string(),
438 ));
441 ));
439 };
442 };
440
443
441 // Keep a reference to the mmap'ed buffer, otherwise we get a dangling
444 // Keep a reference to the mmap'ed buffer, otherwise we get a dangling
442 // pointer.
445 // pointer.
443 self.mmap(py).borrow_mut().replace(buf);
446 self.mmap(py).borrow_mut().replace(buf);
444
447
445 let mut nt = NodeTree::load_bytes(Box::new(bytes), len);
448 let mut nt = NodeTree::load_bytes(Box::new(bytes), len);
446
449
447 let data_tip =
450 let data_tip =
448 docket.getattr(py, "tip_rev")?.extract::<Revision>(py)?;
451 docket.getattr(py, "tip_rev")?.extract::<Revision>(py)?;
449 self.docket(py).borrow_mut().replace(docket.clone_ref(py));
452 self.docket(py).borrow_mut().replace(docket.clone_ref(py));
450 let idx = self.cindex(py).borrow();
453 let idx = self.cindex(py).borrow();
451 let current_tip = idx.len();
454 let current_tip = idx.len();
452
455
453 for r in (data_tip + 1)..current_tip as Revision {
456 for r in (data_tip + 1)..current_tip as Revision {
454 let rev = r as Revision;
457 let rev = r as Revision;
455 // in this case node() won't ever return None
458 // in this case node() won't ever return None
456 nt.insert(&*idx, idx.node(rev).unwrap(), rev)
459 nt.insert(&*idx, idx.node(rev).unwrap(), rev)
457 .map_err(|e| nodemap_error(py, e))?
460 .map_err(|e| nodemap_error(py, e))?
458 }
461 }
459
462
460 *self.nt(py).borrow_mut() = Some(nt);
463 *self.nt(py).borrow_mut() = Some(nt);
461
464
462 Ok(py.None())
465 Ok(py.None())
463 }
466 }
464 }
467 }
465
468
466 fn revlog_error(py: Python) -> PyErr {
469 fn revlog_error(py: Python) -> PyErr {
467 match py
470 match py
468 .import("mercurial.error")
471 .import("mercurial.error")
469 .and_then(|m| m.get(py, "RevlogError"))
472 .and_then(|m| m.get(py, "RevlogError"))
470 {
473 {
471 Err(e) => e,
474 Err(e) => e,
472 Ok(cls) => PyErr::from_instance(
475 Ok(cls) => PyErr::from_instance(
473 py,
476 py,
474 cls.call(py, (py.None(),), None).ok().into_py_object(py),
477 cls.call(py, (py.None(),), None).ok().into_py_object(py),
475 ),
478 ),
476 }
479 }
477 }
480 }
478
481
479 fn rev_not_in_index(py: Python, rev: Revision) -> PyErr {
482 fn rev_not_in_index(py: Python, rev: Revision) -> PyErr {
480 PyErr::new::<ValueError, _>(
483 PyErr::new::<ValueError, _>(
481 py,
484 py,
482 format!(
485 format!(
483 "Inconsistency: Revision {} found in nodemap \
486 "Inconsistency: Revision {} found in nodemap \
484 is not in revlog index",
487 is not in revlog index",
485 rev
488 rev
486 ),
489 ),
487 )
490 )
488 }
491 }
489
492
490 /// Standard treatment of NodeMapError
493 /// Standard treatment of NodeMapError
491 fn nodemap_error(py: Python, err: NodeMapError) -> PyErr {
494 fn nodemap_error(py: Python, err: NodeMapError) -> PyErr {
492 match err {
495 match err {
493 NodeMapError::MultipleResults => revlog_error(py),
496 NodeMapError::MultipleResults => revlog_error(py),
494 NodeMapError::RevisionNotInIndex(r) => rev_not_in_index(py, r),
497 NodeMapError::RevisionNotInIndex(r) => rev_not_in_index(py, r),
495 }
498 }
496 }
499 }
497
500
498 /// Create the module, with __package__ given from parent
501 /// Create the module, with __package__ given from parent
499 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
502 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
500 let dotted_name = &format!("{}.revlog", package);
503 let dotted_name = &format!("{}.revlog", package);
501 let m = PyModule::new(py, dotted_name)?;
504 let m = PyModule::new(py, dotted_name)?;
502 m.add(py, "__package__", package)?;
505 m.add(py, "__package__", package)?;
503 m.add(py, "__doc__", "RevLog - Rust implementations")?;
506 m.add(py, "__doc__", "RevLog - Rust implementations")?;
504
507
505 m.add_class::<MixedIndex>(py)?;
508 m.add_class::<MixedIndex>(py)?;
506
509
507 let sys = PyModule::import(py, "sys")?;
510 let sys = PyModule::import(py, "sys")?;
508 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
511 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
509 sys_modules.set_item(py, dotted_name, &m)?;
512 sys_modules.set_item(py, dotted_name, &m)?;
510
513
511 Ok(m)
514 Ok(m)
512 }
515 }
@@ -1,544 +1,547 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 crate::utils::path_utils::RelativizePaths;
10 use crate::utils::path_utils::RelativizePaths;
11 use clap::{Arg, SubCommand};
11 use clap::{Arg, SubCommand};
12 use format_bytes::format_bytes;
12 use format_bytes::format_bytes;
13 use hg;
13 use hg;
14 use hg::config::Config;
14 use hg::config::Config;
15 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::has_exec_bit;
16 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::TruncatedTimestamp;
17 use hg::dirstate::TruncatedTimestamp;
18 use hg::errors::{HgError, IoResultExt};
18 use hg::errors::{HgError, IoResultExt};
19 use hg::lock::LockError;
19 use hg::lock::LockError;
20 use hg::manifest::Manifest;
20 use hg::manifest::Manifest;
21 use hg::matchers::AlwaysMatcher;
21 use hg::matchers::AlwaysMatcher;
22 use hg::repo::Repo;
22 use hg::repo::Repo;
23 use hg::utils::files::get_bytes_from_os_string;
23 use hg::utils::files::get_bytes_from_os_string;
24 use hg::utils::files::get_bytes_from_path;
24 use hg::utils::files::get_bytes_from_path;
25 use hg::utils::files::get_path_from_bytes;
25 use hg::utils::files::get_path_from_bytes;
26 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
26 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
27 use hg::DirstateStatus;
27 use hg::DirstateStatus;
28 use hg::PatternFileWarning;
28 use hg::PatternFileWarning;
29 use hg::StatusError;
29 use hg::StatusError;
30 use hg::StatusOptions;
30 use hg::StatusOptions;
31 use log::info;
31 use log::info;
32 use std::io;
32 use std::io;
33 use std::path::PathBuf;
33 use std::path::PathBuf;
34
34
35 pub const HELP_TEXT: &str = "
35 pub const HELP_TEXT: &str = "
36 Show changed files in the working directory
36 Show changed files in the working directory
37
37
38 This is a pure Rust version of `hg status`.
38 This is a pure Rust version of `hg status`.
39
39
40 Some options might be missing, check the list below.
40 Some options might be missing, check the list below.
41 ";
41 ";
42
42
43 pub fn args() -> clap::App<'static, 'static> {
43 pub fn args() -> clap::App<'static, 'static> {
44 SubCommand::with_name("status")
44 SubCommand::with_name("status")
45 .alias("st")
45 .alias("st")
46 .about(HELP_TEXT)
46 .about(HELP_TEXT)
47 .arg(
47 .arg(
48 Arg::with_name("all")
48 Arg::with_name("all")
49 .help("show status of all files")
49 .help("show status of all files")
50 .short("-A")
50 .short("-A")
51 .long("--all"),
51 .long("--all"),
52 )
52 )
53 .arg(
53 .arg(
54 Arg::with_name("modified")
54 Arg::with_name("modified")
55 .help("show only modified files")
55 .help("show only modified files")
56 .short("-m")
56 .short("-m")
57 .long("--modified"),
57 .long("--modified"),
58 )
58 )
59 .arg(
59 .arg(
60 Arg::with_name("added")
60 Arg::with_name("added")
61 .help("show only added files")
61 .help("show only added files")
62 .short("-a")
62 .short("-a")
63 .long("--added"),
63 .long("--added"),
64 )
64 )
65 .arg(
65 .arg(
66 Arg::with_name("removed")
66 Arg::with_name("removed")
67 .help("show only removed files")
67 .help("show only removed files")
68 .short("-r")
68 .short("-r")
69 .long("--removed"),
69 .long("--removed"),
70 )
70 )
71 .arg(
71 .arg(
72 Arg::with_name("clean")
72 Arg::with_name("clean")
73 .help("show only clean files")
73 .help("show only clean files")
74 .short("-c")
74 .short("-c")
75 .long("--clean"),
75 .long("--clean"),
76 )
76 )
77 .arg(
77 .arg(
78 Arg::with_name("deleted")
78 Arg::with_name("deleted")
79 .help("show only deleted files")
79 .help("show only deleted files")
80 .short("-d")
80 .short("-d")
81 .long("--deleted"),
81 .long("--deleted"),
82 )
82 )
83 .arg(
83 .arg(
84 Arg::with_name("unknown")
84 Arg::with_name("unknown")
85 .help("show only unknown (not tracked) files")
85 .help("show only unknown (not tracked) files")
86 .short("-u")
86 .short("-u")
87 .long("--unknown"),
87 .long("--unknown"),
88 )
88 )
89 .arg(
89 .arg(
90 Arg::with_name("ignored")
90 Arg::with_name("ignored")
91 .help("show only ignored files")
91 .help("show only ignored files")
92 .short("-i")
92 .short("-i")
93 .long("--ignored"),
93 .long("--ignored"),
94 )
94 )
95 .arg(
95 .arg(
96 Arg::with_name("copies")
96 Arg::with_name("copies")
97 .help("show source of copied files (DEFAULT: ui.statuscopies)")
97 .help("show source of copied files (DEFAULT: ui.statuscopies)")
98 .short("-C")
98 .short("-C")
99 .long("--copies"),
99 .long("--copies"),
100 )
100 )
101 .arg(
101 .arg(
102 Arg::with_name("no-status")
102 Arg::with_name("no-status")
103 .help("hide status prefix")
103 .help("hide status prefix")
104 .short("-n")
104 .short("-n")
105 .long("--no-status"),
105 .long("--no-status"),
106 )
106 )
107 }
107 }
108
108
109 /// Pure data type allowing the caller to specify file states to display
109 /// Pure data type allowing the caller to specify file states to display
110 #[derive(Copy, Clone, Debug)]
110 #[derive(Copy, Clone, Debug)]
111 pub struct DisplayStates {
111 pub struct DisplayStates {
112 pub modified: bool,
112 pub modified: bool,
113 pub added: bool,
113 pub added: bool,
114 pub removed: bool,
114 pub removed: bool,
115 pub clean: bool,
115 pub clean: bool,
116 pub deleted: bool,
116 pub deleted: bool,
117 pub unknown: bool,
117 pub unknown: bool,
118 pub ignored: bool,
118 pub ignored: bool,
119 }
119 }
120
120
121 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
121 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
122 modified: true,
122 modified: true,
123 added: true,
123 added: true,
124 removed: true,
124 removed: true,
125 clean: false,
125 clean: false,
126 deleted: true,
126 deleted: true,
127 unknown: true,
127 unknown: true,
128 ignored: false,
128 ignored: false,
129 };
129 };
130
130
131 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
131 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
132 modified: true,
132 modified: true,
133 added: true,
133 added: true,
134 removed: true,
134 removed: true,
135 clean: true,
135 clean: true,
136 deleted: true,
136 deleted: true,
137 unknown: true,
137 unknown: true,
138 ignored: true,
138 ignored: true,
139 };
139 };
140
140
141 impl DisplayStates {
141 impl DisplayStates {
142 pub fn is_empty(&self) -> bool {
142 pub fn is_empty(&self) -> bool {
143 !(self.modified
143 !(self.modified
144 || self.added
144 || self.added
145 || self.removed
145 || self.removed
146 || self.clean
146 || self.clean
147 || self.deleted
147 || self.deleted
148 || self.unknown
148 || self.unknown
149 || self.ignored)
149 || self.ignored)
150 }
150 }
151 }
151 }
152
152
153 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
153 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
154 // TODO: lift these limitations
154 // TODO: lift these limitations
155 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
155 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
156 return Err(CommandError::unsupported(
156 return Err(CommandError::unsupported(
157 "ui.tweakdefaults is not yet supported with rhg status",
157 "ui.tweakdefaults is not yet supported with rhg status",
158 ));
158 ));
159 }
159 }
160 if invocation.config.get_bool(b"ui", b"statuscopies")? {
160 if invocation.config.get_bool(b"ui", b"statuscopies")? {
161 return Err(CommandError::unsupported(
161 return Err(CommandError::unsupported(
162 "ui.statuscopies is not yet supported with rhg status",
162 "ui.statuscopies is not yet supported with rhg status",
163 ));
163 ));
164 }
164 }
165 if invocation
165 if invocation
166 .config
166 .config
167 .get(b"commands", b"status.terse")
167 .get(b"commands", b"status.terse")
168 .is_some()
168 .is_some()
169 {
169 {
170 return Err(CommandError::unsupported(
170 return Err(CommandError::unsupported(
171 "status.terse is not yet supported with rhg status",
171 "status.terse is not yet supported with rhg status",
172 ));
172 ));
173 }
173 }
174
174
175 let ui = invocation.ui;
175 let ui = invocation.ui;
176 let config = invocation.config;
176 let config = invocation.config;
177 let args = invocation.subcommand_args;
177 let args = invocation.subcommand_args;
178
178
179 let verbose = !ui.plain(None)
179 let verbose = !ui.plain(None)
180 && !args.is_present("print0")
180 && !args.is_present("print0")
181 && (config.get_bool(b"ui", b"verbose")?
181 && (config.get_bool(b"ui", b"verbose")?
182 || config.get_bool(b"commands", b"status.verbose")?);
182 || config.get_bool(b"commands", b"status.verbose")?);
183 if verbose {
183 if verbose {
184 return Err(CommandError::unsupported(
184 return Err(CommandError::unsupported(
185 "verbose status is not supported yet",
185 "verbose status is not supported yet",
186 ));
186 ));
187 }
187 }
188
188
189 let all = args.is_present("all");
189 let all = args.is_present("all");
190 let display_states = if all {
190 let display_states = if all {
191 // TODO when implementing `--quiet`: it excludes clean files
191 // TODO when implementing `--quiet`: it excludes clean files
192 // from `--all`
192 // from `--all`
193 ALL_DISPLAY_STATES
193 ALL_DISPLAY_STATES
194 } else {
194 } else {
195 let requested = DisplayStates {
195 let requested = DisplayStates {
196 modified: args.is_present("modified"),
196 modified: args.is_present("modified"),
197 added: args.is_present("added"),
197 added: args.is_present("added"),
198 removed: args.is_present("removed"),
198 removed: args.is_present("removed"),
199 clean: args.is_present("clean"),
199 clean: args.is_present("clean"),
200 deleted: args.is_present("deleted"),
200 deleted: args.is_present("deleted"),
201 unknown: args.is_present("unknown"),
201 unknown: args.is_present("unknown"),
202 ignored: args.is_present("ignored"),
202 ignored: args.is_present("ignored"),
203 };
203 };
204 if requested.is_empty() {
204 if requested.is_empty() {
205 DEFAULT_DISPLAY_STATES
205 DEFAULT_DISPLAY_STATES
206 } else {
206 } else {
207 requested
207 requested
208 }
208 }
209 };
209 };
210 let no_status = args.is_present("no-status");
210 let no_status = args.is_present("no-status");
211 let list_copies = all
211 let list_copies = all
212 || args.is_present("copies")
212 || args.is_present("copies")
213 || config.get_bool(b"ui", b"statuscopies")?;
213 || config.get_bool(b"ui", b"statuscopies")?;
214
214
215 let repo = invocation.repo?;
215 let repo = invocation.repo?;
216
216
217 if repo.has_sparse() || repo.has_narrow() {
217 if repo.has_sparse() || repo.has_narrow() {
218 return Err(CommandError::unsupported(
218 return Err(CommandError::unsupported(
219 "rhg status is not supported for sparse checkouts or narrow clones yet"
219 "rhg status is not supported for sparse checkouts or narrow clones yet"
220 ));
220 ));
221 }
221 }
222
222
223 let mut dmap = repo.dirstate_map_mut()?;
223 let mut dmap = repo.dirstate_map_mut()?;
224
224
225 let options = StatusOptions {
225 let options = StatusOptions {
226 // we're currently supporting file systems with exec flags only
226 // we're currently supporting file systems with exec flags only
227 // anyway
227 // anyway
228 check_exec: true,
228 check_exec: true,
229 list_clean: display_states.clean,
229 list_clean: display_states.clean,
230 list_unknown: display_states.unknown,
230 list_unknown: display_states.unknown,
231 list_ignored: display_states.ignored,
231 list_ignored: display_states.ignored,
232 list_copies,
232 list_copies,
233 collect_traversed_dirs: false,
233 collect_traversed_dirs: false,
234 };
234 };
235
235
236 type StatusResult<'a> =
236 type StatusResult<'a> =
237 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
237 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
238
238
239 let after_status = |res: StatusResult| -> Result<_, CommandError> {
239 let after_status = |res: StatusResult| -> Result<_, CommandError> {
240 let (mut ds_status, pattern_warnings) = res?;
240 let (mut ds_status, pattern_warnings) = res?;
241 for warning in pattern_warnings {
241 for warning in pattern_warnings {
242 match warning {
242 match warning {
243 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
243 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
244 .write_stderr(&format_bytes!(
244 .write_stderr(&format_bytes!(
245 b"{}: ignoring invalid syntax '{}'\n",
245 b"{}: ignoring invalid syntax '{}'\n",
246 get_bytes_from_path(path),
246 get_bytes_from_path(path),
247 &*syntax
247 &*syntax
248 ))?,
248 ))?,
249 hg::PatternFileWarning::NoSuchFile(path) => {
249 hg::PatternFileWarning::NoSuchFile(path) => {
250 let path = if let Ok(relative) =
250 let path = if let Ok(relative) =
251 path.strip_prefix(repo.working_directory_path())
251 path.strip_prefix(repo.working_directory_path())
252 {
252 {
253 relative
253 relative
254 } else {
254 } else {
255 &*path
255 &*path
256 };
256 };
257 ui.write_stderr(&format_bytes!(
257 ui.write_stderr(&format_bytes!(
258 b"skipping unreadable pattern file '{}': \
258 b"skipping unreadable pattern file '{}': \
259 No such file or directory\n",
259 No such file or directory\n",
260 get_bytes_from_path(path),
260 get_bytes_from_path(path),
261 ))?
261 ))?
262 }
262 }
263 }
263 }
264 }
264 }
265
265
266 for (path, error) in ds_status.bad {
266 for (path, error) in ds_status.bad {
267 let error = match error {
267 let error = match error {
268 hg::BadMatch::OsError(code) => {
268 hg::BadMatch::OsError(code) => {
269 std::io::Error::from_raw_os_error(code).to_string()
269 std::io::Error::from_raw_os_error(code).to_string()
270 }
270 }
271 hg::BadMatch::BadType(ty) => {
271 hg::BadMatch::BadType(ty) => {
272 format!("unsupported file type (type is {})", ty)
272 format!("unsupported file type (type is {})", ty)
273 }
273 }
274 };
274 };
275 ui.write_stderr(&format_bytes!(
275 ui.write_stderr(&format_bytes!(
276 b"{}: {}\n",
276 b"{}: {}\n",
277 path.as_bytes(),
277 path.as_bytes(),
278 error.as_bytes()
278 error.as_bytes()
279 ))?
279 ))?
280 }
280 }
281 if !ds_status.unsure.is_empty() {
281 if !ds_status.unsure.is_empty() {
282 info!(
282 info!(
283 "Files to be rechecked by retrieval from filelog: {:?}",
283 "Files to be rechecked by retrieval from filelog: {:?}",
284 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
284 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
285 );
285 );
286 }
286 }
287 let mut fixup = Vec::new();
287 let mut fixup = Vec::new();
288 if !ds_status.unsure.is_empty()
288 if !ds_status.unsure.is_empty()
289 && (display_states.modified || display_states.clean)
289 && (display_states.modified || display_states.clean)
290 {
290 {
291 let p1 = repo.dirstate_parents()?.p1;
291 let p1 = repo.dirstate_parents()?.p1;
292 let manifest = repo.manifest_for_node(p1).map_err(|e| {
292 let manifest = repo.manifest_for_node(p1).map_err(|e| {
293 CommandError::from((e, &*format!("{:x}", p1.short())))
293 CommandError::from((e, &*format!("{:x}", p1.short())))
294 })?;
294 })?;
295 for to_check in ds_status.unsure {
295 for to_check in ds_status.unsure {
296 if unsure_is_modified(repo, &manifest, &to_check.path)? {
296 if unsure_is_modified(repo, &manifest, &to_check.path)? {
297 if display_states.modified {
297 if display_states.modified {
298 ds_status.modified.push(to_check);
298 ds_status.modified.push(to_check);
299 }
299 }
300 } else {
300 } else {
301 if display_states.clean {
301 if display_states.clean {
302 ds_status.clean.push(to_check.clone());
302 ds_status.clean.push(to_check.clone());
303 }
303 }
304 fixup.push(to_check.path.into_owned())
304 fixup.push(to_check.path.into_owned())
305 }
305 }
306 }
306 }
307 }
307 }
308 let relative_paths = (!ui.plain(None))
308 let relative_paths = (!ui.plain(None))
309 && config
309 && config
310 .get_option(b"commands", b"status.relative")?
310 .get_option(b"commands", b"status.relative")?
311 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
311 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
312 let output = DisplayStatusPaths {
312 let output = DisplayStatusPaths {
313 ui,
313 ui,
314 no_status,
314 no_status,
315 relativize: if relative_paths {
315 relativize: if relative_paths {
316 Some(RelativizePaths::new(repo)?)
316 Some(RelativizePaths::new(repo)?)
317 } else {
317 } else {
318 None
318 None
319 },
319 },
320 };
320 };
321 if display_states.modified {
321 if display_states.modified {
322 output.display(b"M ", "status.modified", ds_status.modified)?;
322 output.display(b"M ", "status.modified", ds_status.modified)?;
323 }
323 }
324 if display_states.added {
324 if display_states.added {
325 output.display(b"A ", "status.added", ds_status.added)?;
325 output.display(b"A ", "status.added", ds_status.added)?;
326 }
326 }
327 if display_states.removed {
327 if display_states.removed {
328 output.display(b"R ", "status.removed", ds_status.removed)?;
328 output.display(b"R ", "status.removed", ds_status.removed)?;
329 }
329 }
330 if display_states.deleted {
330 if display_states.deleted {
331 output.display(b"! ", "status.deleted", ds_status.deleted)?;
331 output.display(b"! ", "status.deleted", ds_status.deleted)?;
332 }
332 }
333 if display_states.unknown {
333 if display_states.unknown {
334 output.display(b"? ", "status.unknown", ds_status.unknown)?;
334 output.display(b"? ", "status.unknown", ds_status.unknown)?;
335 }
335 }
336 if display_states.ignored {
336 if display_states.ignored {
337 output.display(b"I ", "status.ignored", ds_status.ignored)?;
337 output.display(b"I ", "status.ignored", ds_status.ignored)?;
338 }
338 }
339 if display_states.clean {
339 if display_states.clean {
340 output.display(b"C ", "status.clean", ds_status.clean)?;
340 output.display(b"C ", "status.clean", ds_status.clean)?;
341 }
341 }
342
342
343 let dirstate_write_needed = ds_status.dirty;
343 let dirstate_write_needed = ds_status.dirty;
344 let filesystem_time_at_status_start =
344 let filesystem_time_at_status_start =
345 ds_status.filesystem_time_at_status_start;
345 ds_status.filesystem_time_at_status_start;
346
346
347 Ok((
347 Ok((
348 fixup,
348 fixup,
349 dirstate_write_needed,
349 dirstate_write_needed,
350 filesystem_time_at_status_start,
350 filesystem_time_at_status_start,
351 ))
351 ))
352 };
352 };
353 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
353 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
354 dmap.with_status(
354 dmap.with_status(
355 &AlwaysMatcher,
355 &AlwaysMatcher,
356 repo.working_directory_path().to_owned(),
356 repo.working_directory_path().to_owned(),
357 ignore_files(repo, config),
357 ignore_files(repo, config),
358 options,
358 options,
359 after_status,
359 after_status,
360 )?;
360 )?;
361
361
362 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
362 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
363 && !dirstate_write_needed
363 && !dirstate_write_needed
364 {
364 {
365 // Nothing to update
365 // Nothing to update
366 return Ok(());
366 return Ok(());
367 }
367 }
368
368
369 // Update the dirstate on disk if we can
369 // Update the dirstate on disk if we can
370 let with_lock_result =
370 let with_lock_result =
371 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
371 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
372 if let Some(mtime_boundary) = filesystem_time_at_status_start {
372 if let Some(mtime_boundary) = filesystem_time_at_status_start {
373 for hg_path in fixup {
373 for hg_path in fixup {
374 use std::os::unix::fs::MetadataExt;
374 use std::os::unix::fs::MetadataExt;
375 let fs_path = hg_path_to_path_buf(&hg_path)
375 let fs_path = hg_path_to_path_buf(&hg_path)
376 .expect("HgPath conversion");
376 .expect("HgPath conversion");
377 // Specifically do not reuse `fs_metadata` from
377 // Specifically do not reuse `fs_metadata` from
378 // `unsure_is_clean` which was needed before reading
378 // `unsure_is_clean` which was needed before reading
379 // contents. Here we access metadata again after reading
379 // contents. Here we access metadata again after reading
380 // content, in case it changed in the meantime.
380 // content, in case it changed in the meantime.
381 let fs_metadata = repo
381 let fs_metadata = repo
382 .working_directory_vfs()
382 .working_directory_vfs()
383 .symlink_metadata(&fs_path)?;
383 .symlink_metadata(&fs_path)?;
384 if let Some(mtime) =
384 if let Some(mtime) =
385 TruncatedTimestamp::for_reliable_mtime_of(
385 TruncatedTimestamp::for_reliable_mtime_of(
386 &fs_metadata,
386 &fs_metadata,
387 &mtime_boundary,
387 &mtime_boundary,
388 )
388 )
389 .when_reading_file(&fs_path)?
389 .when_reading_file(&fs_path)?
390 {
390 {
391 let mode = fs_metadata.mode();
391 let mode = fs_metadata.mode();
392 let size = fs_metadata.len();
392 let size = fs_metadata.len();
393 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
393 dmap.set_clean(&hg_path, mode, size as u32, mtime)?;
394 dirstate_write_needed = true
394 dirstate_write_needed = true
395 }
395 }
396 }
396 }
397 }
397 }
398 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
398 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
399 if dirstate_write_needed {
399 if dirstate_write_needed {
400 repo.write_dirstate()?
400 repo.write_dirstate()?
401 }
401 }
402 Ok(())
402 Ok(())
403 });
403 });
404 match with_lock_result {
404 match with_lock_result {
405 Ok(closure_result) => closure_result?,
405 Ok(closure_result) => closure_result?,
406 Err(LockError::AlreadyHeld) => {
406 Err(LockError::AlreadyHeld) => {
407 // Not updating the dirstate is not ideal but not critical:
407 // Not updating the dirstate is not ideal but not critical:
408 // don’t keep our caller waiting until some other Mercurial
408 // don’t keep our caller waiting until some other Mercurial
409 // process releases the lock.
409 // process releases the lock.
410 }
410 }
411 Err(LockError::Other(HgError::IoError { error, .. }))
411 Err(LockError::Other(HgError::IoError { error, .. }))
412 if error.kind() == io::ErrorKind::PermissionDenied =>
412 if error.kind() == io::ErrorKind::PermissionDenied =>
413 {
413 {
414 // `hg status` on a read-only repository is fine
414 // `hg status` on a read-only repository is fine
415 }
415 }
416 Err(LockError::Other(error)) => {
416 Err(LockError::Other(error)) => {
417 // Report other I/O errors
417 // Report other I/O errors
418 Err(error)?
418 Err(error)?
419 }
419 }
420 }
420 }
421 Ok(())
421 Ok(())
422 }
422 }
423
423
424 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
424 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
425 let mut ignore_files = Vec::new();
425 let mut ignore_files = Vec::new();
426 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
426 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
427 if repo_ignore.exists() {
427 if repo_ignore.exists() {
428 ignore_files.push(repo_ignore)
428 ignore_files.push(repo_ignore)
429 }
429 }
430 for (key, value) in config.iter_section(b"ui") {
430 for (key, value) in config.iter_section(b"ui") {
431 if key == b"ignore" || key.starts_with(b"ignore.") {
431 if key == b"ignore" || key.starts_with(b"ignore.") {
432 let path = get_path_from_bytes(value);
432 let path = get_path_from_bytes(value);
433 // TODO: expand "~/" and environment variable here, like Python
433 // TODO: expand "~/" and environment variable here, like Python
434 // does with `os.path.expanduser` and `os.path.expandvars`
434 // does with `os.path.expanduser` and `os.path.expandvars`
435
435
436 let joined = repo.working_directory_path().join(path);
436 let joined = repo.working_directory_path().join(path);
437 ignore_files.push(joined);
437 ignore_files.push(joined);
438 }
438 }
439 }
439 }
440 ignore_files
440 ignore_files
441 }
441 }
442
442
443 struct DisplayStatusPaths<'a> {
443 struct DisplayStatusPaths<'a> {
444 ui: &'a Ui,
444 ui: &'a Ui,
445 no_status: bool,
445 no_status: bool,
446 relativize: Option<RelativizePaths>,
446 relativize: Option<RelativizePaths>,
447 }
447 }
448
448
449 impl DisplayStatusPaths<'_> {
449 impl DisplayStatusPaths<'_> {
450 // Probably more elegant to use a Deref or Borrow trait rather than
450 // Probably more elegant to use a Deref or Borrow trait rather than
451 // harcode HgPathBuf, but probably not really useful at this point
451 // harcode HgPathBuf, but probably not really useful at this point
452 fn display(
452 fn display(
453 &self,
453 &self,
454 status_prefix: &[u8],
454 status_prefix: &[u8],
455 label: &'static str,
455 label: &'static str,
456 mut paths: Vec<StatusPath<'_>>,
456 mut paths: Vec<StatusPath<'_>>,
457 ) -> Result<(), CommandError> {
457 ) -> Result<(), CommandError> {
458 paths.sort_unstable();
458 paths.sort_unstable();
459 // TODO: get the stdout lock once for the whole loop
459 // TODO: get the stdout lock once for the whole loop
460 // instead of in each write
460 // instead of in each write
461 for StatusPath { path, copy_source } in paths {
461 for StatusPath { path, copy_source } in paths {
462 let relative;
462 let relative;
463 let path = if let Some(relativize) = &self.relativize {
463 let path = if let Some(relativize) = &self.relativize {
464 relative = relativize.relativize(&path);
464 relative = relativize.relativize(&path);
465 &*relative
465 &*relative
466 } else {
466 } else {
467 path.as_bytes()
467 path.as_bytes()
468 };
468 };
469 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
469 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
470 // in order to stream to stdout instead of allocating an
470 // in order to stream to stdout instead of allocating an
471 // itermediate `Vec<u8>`.
471 // itermediate `Vec<u8>`.
472 if !self.no_status {
472 if !self.no_status {
473 self.ui.write_stdout_labelled(status_prefix, label)?
473 self.ui.write_stdout_labelled(status_prefix, label)?
474 }
474 }
475 self.ui
475 self.ui
476 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
476 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
477 if let Some(source) = copy_source {
477 if let Some(source) = copy_source {
478 let label = "status.copied";
478 let label = "status.copied";
479 self.ui.write_stdout_labelled(
479 self.ui.write_stdout_labelled(
480 &format_bytes!(b" {}\n", source.as_bytes()),
480 &format_bytes!(b" {}\n", source.as_bytes()),
481 label,
481 label,
482 )?
482 )?
483 }
483 }
484 }
484 }
485 Ok(())
485 Ok(())
486 }
486 }
487 }
487 }
488
488
489 /// Check if a file is modified by comparing actual repo store and file system.
489 /// Check if a file is modified by comparing actual repo store and file system.
490 ///
490 ///
491 /// This meant to be used for those that the dirstate cannot resolve, due
491 /// This meant to be used for those that the dirstate cannot resolve, due
492 /// to time resolution limits.
492 /// to time resolution limits.
493 fn unsure_is_modified(
493 fn unsure_is_modified(
494 repo: &Repo,
494 repo: &Repo,
495 manifest: &Manifest,
495 manifest: &Manifest,
496 hg_path: &HgPath,
496 hg_path: &HgPath,
497 ) -> Result<bool, HgError> {
497 ) -> Result<bool, HgError> {
498 let vfs = repo.working_directory_vfs();
498 let vfs = repo.working_directory_vfs();
499 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
499 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
500 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
500 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
501 let is_symlink = fs_metadata.file_type().is_symlink();
501 let is_symlink = fs_metadata.file_type().is_symlink();
502 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
502 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
503 // dirstate
503 // dirstate
504 let fs_flags = if is_symlink {
504 let fs_flags = if is_symlink {
505 Some(b'l')
505 Some(b'l')
506 } else if has_exec_bit(&fs_metadata) {
506 } else if has_exec_bit(&fs_metadata) {
507 Some(b'x')
507 Some(b'x')
508 } else {
508 } else {
509 None
509 None
510 };
510 };
511
511
512 let entry = manifest
512 let entry = manifest
513 .find_by_path(hg_path)?
513 .find_by_path(hg_path)?
514 .expect("ambgious file not in p1");
514 .expect("ambgious file not in p1");
515 if entry.flags != fs_flags {
515 if entry.flags != fs_flags {
516 return Ok(true);
516 return Ok(true);
517 }
517 }
518 let filelog = repo.filelog(hg_path)?;
518 let filelog = repo.filelog(hg_path)?;
519 let fs_len = fs_metadata.len();
519 let fs_len = fs_metadata.len();
520 let filelog_entry =
520 let file_node = entry.node_id()?;
521 filelog.entry_for_node(entry.node_id()?).map_err(|_| {
521 let filelog_entry = filelog.entry_for_node(file_node).map_err(|_| {
522 HgError::corrupted("filelog missing node from manifest")
522 HgError::corrupted(format!(
523 "filelog missing node {:?} from manifest",
524 file_node
525 ))
523 })?;
526 })?;
524 if filelog_entry.file_data_len_not_equal_to(fs_len) {
527 if filelog_entry.file_data_len_not_equal_to(fs_len) {
525 // No need to read file contents:
528 // No need to read file contents:
526 // it cannot be equal if it has a different length.
529 // it cannot be equal if it has a different length.
527 return Ok(true);
530 return Ok(true);
528 }
531 }
529
532
530 let p1_filelog_data = filelog_entry.data()?;
533 let p1_filelog_data = filelog_entry.data()?;
531 let p1_contents = p1_filelog_data.file_data()?;
534 let p1_contents = p1_filelog_data.file_data()?;
532 if p1_contents.len() as u64 != fs_len {
535 if p1_contents.len() as u64 != fs_len {
533 // No need to read file contents:
536 // No need to read file contents:
534 // it cannot be equal if it has a different length.
537 // it cannot be equal if it has a different length.
535 return Ok(true);
538 return Ok(true);
536 }
539 }
537
540
538 let fs_contents = if is_symlink {
541 let fs_contents = if is_symlink {
539 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
542 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
540 } else {
543 } else {
541 vfs.read(fs_path)?
544 vfs.read(fs_path)?
542 };
545 };
543 Ok(p1_contents != &*fs_contents)
546 Ok(p1_contents != &*fs_contents)
544 }
547 }
General Comments 0
You need to be logged in to leave comments. Login now