##// END OF EJS Templates
rust-clippy: merge "revlog" module definition and struct implementation...
Raphaël Gomès -
r50832:75040950 default
parent child Browse files
Show More
@@ -118,7 +118,7 b' fn test_combine_changeset_copies() {'
118 // keys to copy source values. Note: the arrows for map literal syntax
118 // keys to copy source values. Note: the arrows for map literal syntax
119 // point **backwards** compared to the logical direction of copy!
119 // point **backwards** compared to the logical direction of copy!
120
120
121 use crate::NULL_REVISION as NULL;
121 use crate::revlog::NULL_REVISION as NULL;
122 use Action::*;
122 use Action::*;
123 use MergeCase::*;
123 use MergeCase::*;
124
124
@@ -6,8 +6,8 b''
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::repo::Repo;
8 use crate::repo::Repo;
9 use crate::revlog::revlog::RevlogError;
10 use crate::revlog::Node;
9 use crate::revlog::Node;
10 use crate::revlog::RevlogError;
11
11
12 use crate::utils::hg_path::HgPath;
12 use crate::utils::hg_path::HgPath;
13
13
@@ -7,7 +7,7 b''
7
7
8 use crate::repo::Repo;
8 use crate::repo::Repo;
9 use crate::requirements;
9 use crate::requirements;
10 use crate::revlog::revlog::{Revlog, RevlogError};
10 use crate::revlog::{Revlog, RevlogError};
11
11
12 /// Kind of data to debug
12 /// Kind of data to debug
13 #[derive(Debug, Copy, Clone)]
13 #[derive(Debug, Copy, Clone)]
@@ -10,7 +10,7 b' use crate::dirstate_tree::on_disk::{for_'
10 use crate::errors::HgError;
10 use crate::errors::HgError;
11 use crate::repo::Repo;
11 use crate::repo::Repo;
12 use crate::revlog::manifest::Manifest;
12 use crate::revlog::manifest::Manifest;
13 use crate::revlog::revlog::RevlogError;
13 use crate::revlog::RevlogError;
14 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
15 use crate::DirstateError;
15 use crate::DirstateError;
16 use rayon::prelude::*;
16 use rayon::prelude::*;
@@ -8,7 +8,7 b' use crate::errors::{HgError, IoResultExt'
8 use crate::lock::{try_with_lock_no_wait, LockError};
8 use crate::lock::{try_with_lock_no_wait, LockError};
9 use crate::manifest::{Manifest, Manifestlog};
9 use crate::manifest::{Manifest, Manifestlog};
10 use crate::revlog::filelog::Filelog;
10 use crate::revlog::filelog::Filelog;
11 use crate::revlog::revlog::RevlogError;
11 use crate::revlog::RevlogError;
12 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPath;
14 use crate::utils::SliceExt;
14 use crate::utils::SliceExt;
@@ -1,7 +1,7 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::revlog::revlog::{Revlog, RevlogEntry, RevlogError};
3 use crate::revlog::Revision;
2 use crate::revlog::Revision;
4 use crate::revlog::{Node, NodePrefix};
3 use crate::revlog::{Node, NodePrefix};
4 use crate::revlog::{Revlog, RevlogEntry, RevlogError};
5 use crate::utils::hg_path::HgPath;
5 use crate::utils::hg_path::HgPath;
6 use crate::vfs::Vfs;
6 use crate::vfs::Vfs;
7 use itertools::Itertools;
7 use itertools::Itertools;
@@ -1,10 +1,10 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::repo::Repo;
2 use crate::repo::Repo;
3 use crate::revlog::path_encode::path_encode;
3 use crate::revlog::path_encode::path_encode;
4 use crate::revlog::revlog::RevlogEntry;
5 use crate::revlog::revlog::{Revlog, RevlogError};
6 use crate::revlog::NodePrefix;
4 use crate::revlog::NodePrefix;
7 use crate::revlog::Revision;
5 use crate::revlog::Revision;
6 use crate::revlog::RevlogEntry;
7 use crate::revlog::{Revlog, RevlogError};
8 use crate::utils::files::get_path_from_bytes;
8 use crate::utils::files::get_path_from_bytes;
9 use crate::utils::hg_path::HgPath;
9 use crate::utils::hg_path::HgPath;
10 use crate::utils::SliceExt;
10 use crate::utils::SliceExt;
@@ -1,7 +1,7 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use crate::revlog::revlog::{Revlog, RevlogError};
3 use crate::revlog::Revision;
2 use crate::revlog::Revision;
4 use crate::revlog::{Node, NodePrefix};
3 use crate::revlog::{Node, NodePrefix};
4 use crate::revlog::{Revlog, RevlogError};
5 use crate::utils::hg_path::HgPath;
5 use crate::utils::hg_path::HgPath;
6 use crate::utils::SliceExt;
6 use crate::utils::SliceExt;
7 use crate::vfs::Vfs;
7 use crate::vfs::Vfs;
This diff has been collapsed as it changes many lines, (642 lines changed) Show them Hide them
@@ -1,4 +1,4 b''
1 // Copyright 2018-2020 Georges Racinet <georges.racinet@octobus.net>
1 // Copyright 2018-2023 Georges Racinet <georges.racinet@octobus.net>
2 // and Mercurial contributors
2 // and Mercurial contributors
3 //
3 //
4 // This software may be used and distributed according to the terms of the
4 // This software may be used and distributed according to the terms of the
@@ -15,7 +15,22 b' pub mod filelog;'
15 pub mod index;
15 pub mod index;
16 pub mod manifest;
16 pub mod manifest;
17 pub mod patch;
17 pub mod patch;
18 pub mod revlog;
18
19 use std::borrow::Cow;
20 use std::io::Read;
21 use std::ops::Deref;
22 use std::path::Path;
23
24 use flate2::read::ZlibDecoder;
25 use sha1::{Digest, Sha1};
26 use zstd;
27
28 use self::node::{NODE_BYTES_LENGTH, NULL_NODE};
29 use self::nodemap_docket::NodeMapDocket;
30 use super::index::Index;
31 use super::nodemap::{NodeMap, NodeMapError};
32 use crate::errors::HgError;
33 use crate::vfs::Vfs;
19
34
20 /// Mercurial revision numbers
35 /// Mercurial revision numbers
21 ///
36 ///
@@ -70,3 +85,626 b' pub trait RevlogIndex {'
70 /// `NULL_REVISION` is not considered to be out of bounds.
85 /// `NULL_REVISION` is not considered to be out of bounds.
71 fn node(&self, rev: Revision) -> Option<&Node>;
86 fn node(&self, rev: Revision) -> Option<&Node>;
72 }
87 }
88
89 const REVISION_FLAG_CENSORED: u16 = 1 << 15;
90 const REVISION_FLAG_ELLIPSIS: u16 = 1 << 14;
91 const REVISION_FLAG_EXTSTORED: u16 = 1 << 13;
92 const REVISION_FLAG_HASCOPIESINFO: u16 = 1 << 12;
93
94 // Keep this in sync with REVIDX_KNOWN_FLAGS in
95 // mercurial/revlogutils/flagutil.py
96 const REVIDX_KNOWN_FLAGS: u16 = REVISION_FLAG_CENSORED
97 | REVISION_FLAG_ELLIPSIS
98 | REVISION_FLAG_EXTSTORED
99 | REVISION_FLAG_HASCOPIESINFO;
100
101 const NULL_REVLOG_ENTRY_FLAGS: u16 = 0;
102
103 #[derive(Debug, derive_more::From)]
104 pub enum RevlogError {
105 InvalidRevision,
106 /// Working directory is not supported
107 WDirUnsupported,
108 /// Found more than one entry whose ID match the requested prefix
109 AmbiguousPrefix,
110 #[from]
111 Other(HgError),
112 }
113
114 impl From<NodeMapError> for RevlogError {
115 fn from(error: NodeMapError) -> Self {
116 match error {
117 NodeMapError::MultipleResults => RevlogError::AmbiguousPrefix,
118 NodeMapError::RevisionNotInIndex(rev) => RevlogError::corrupted(
119 format!("nodemap point to revision {} not in index", rev),
120 ),
121 }
122 }
123 }
124
125 fn corrupted<S: AsRef<str>>(context: S) -> HgError {
126 HgError::corrupted(format!("corrupted revlog, {}", context.as_ref()))
127 }
128
129 impl RevlogError {
130 fn corrupted<S: AsRef<str>>(context: S) -> Self {
131 RevlogError::Other(corrupted(context))
132 }
133 }
134
135 /// Read only implementation of revlog.
136 pub struct Revlog {
137 /// When index and data are not interleaved: bytes of the revlog index.
138 /// When index and data are interleaved: bytes of the revlog index and
139 /// data.
140 index: Index,
141 /// When index and data are not interleaved: bytes of the revlog data
142 data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>>,
143 /// When present on disk: the persistent nodemap for this revlog
144 nodemap: Option<nodemap::NodeTree>,
145 }
146
147 impl Revlog {
148 /// Open a revlog index file.
149 ///
150 /// It will also open the associated data file if index and data are not
151 /// interleaved.
152 pub fn open(
153 store_vfs: &Vfs,
154 index_path: impl AsRef<Path>,
155 data_path: Option<&Path>,
156 use_nodemap: bool,
157 ) -> Result<Self, HgError> {
158 let index_path = index_path.as_ref();
159 let index = {
160 match store_vfs.mmap_open_opt(&index_path)? {
161 None => Index::new(Box::new(vec![])),
162 Some(index_mmap) => {
163 let index = Index::new(Box::new(index_mmap))?;
164 Ok(index)
165 }
166 }
167 }?;
168
169 let default_data_path = index_path.with_extension("d");
170
171 // type annotation required
172 // won't recognize Mmap as Deref<Target = [u8]>
173 let data_bytes: Option<Box<dyn Deref<Target = [u8]> + Send>> =
174 if index.is_inline() {
175 None
176 } else {
177 let data_path = data_path.unwrap_or(&default_data_path);
178 let data_mmap = store_vfs.mmap_open(data_path)?;
179 Some(Box::new(data_mmap))
180 };
181
182 let nodemap = if index.is_inline() || !use_nodemap {
183 None
184 } else {
185 NodeMapDocket::read_from_file(store_vfs, index_path)?.map(
186 |(docket, data)| {
187 nodemap::NodeTree::load_bytes(
188 Box::new(data),
189 docket.data_length,
190 )
191 },
192 )
193 };
194
195 Ok(Revlog {
196 index,
197 data_bytes,
198 nodemap,
199 })
200 }
201
202 /// Return number of entries of the `Revlog`.
203 pub fn len(&self) -> usize {
204 self.index.len()
205 }
206
207 /// Returns `true` if the `Revlog` has zero `entries`.
208 pub fn is_empty(&self) -> bool {
209 self.index.is_empty()
210 }
211
212 /// Returns the node ID for the given revision number, if it exists in this
213 /// revlog
214 pub fn node_from_rev(&self, rev: Revision) -> Option<&Node> {
215 if rev == NULL_REVISION {
216 return Some(&NULL_NODE);
217 }
218 Some(self.index.get_entry(rev)?.hash())
219 }
220
221 /// Return the revision number for the given node ID, if it exists in this
222 /// revlog
223 pub fn rev_from_node(
224 &self,
225 node: NodePrefix,
226 ) -> Result<Revision, RevlogError> {
227 if node.is_prefix_of(&NULL_NODE) {
228 return Ok(NULL_REVISION);
229 }
230
231 if let Some(nodemap) = &self.nodemap {
232 return nodemap
233 .find_bin(&self.index, node)?
234 .ok_or(RevlogError::InvalidRevision);
235 }
236
237 // Fallback to linear scan when a persistent nodemap is not present.
238 // This happens when the persistent-nodemap experimental feature is not
239 // enabled, or for small revlogs.
240 //
241 // TODO: consider building a non-persistent nodemap in memory to
242 // optimize these cases.
243 let mut found_by_prefix = None;
244 for rev in (0..self.len() as Revision).rev() {
245 let index_entry = self.index.get_entry(rev).ok_or_else(|| {
246 HgError::corrupted(
247 "revlog references a revision not in the index",
248 )
249 })?;
250 if node == *index_entry.hash() {
251 return Ok(rev);
252 }
253 if node.is_prefix_of(index_entry.hash()) {
254 if found_by_prefix.is_some() {
255 return Err(RevlogError::AmbiguousPrefix);
256 }
257 found_by_prefix = Some(rev)
258 }
259 }
260 found_by_prefix.ok_or(RevlogError::InvalidRevision)
261 }
262
263 /// Returns whether the given revision exists in this revlog.
264 pub fn has_rev(&self, rev: Revision) -> bool {
265 self.index.get_entry(rev).is_some()
266 }
267
268 /// Return the full data associated to a revision.
269 ///
270 /// All entries required to build the final data out of deltas will be
271 /// retrieved as needed, and the deltas will be applied to the inital
272 /// snapshot to rebuild the final data.
273 pub fn get_rev_data(
274 &self,
275 rev: Revision,
276 ) -> Result<Cow<[u8]>, RevlogError> {
277 if rev == NULL_REVISION {
278 return Ok(Cow::Borrowed(&[]));
279 };
280 Ok(self.get_entry(rev)?.data()?)
281 }
282
283 /// Check the hash of some given data against the recorded hash.
284 pub fn check_hash(
285 &self,
286 p1: Revision,
287 p2: Revision,
288 expected: &[u8],
289 data: &[u8],
290 ) -> bool {
291 let e1 = self.index.get_entry(p1);
292 let h1 = match e1 {
293 Some(ref entry) => entry.hash(),
294 None => &NULL_NODE,
295 };
296 let e2 = self.index.get_entry(p2);
297 let h2 = match e2 {
298 Some(ref entry) => entry.hash(),
299 None => &NULL_NODE,
300 };
301
302 hash(data, h1.as_bytes(), h2.as_bytes()) == expected
303 }
304
305 /// Build the full data of a revision out its snapshot
306 /// and its deltas.
307 fn build_data_from_deltas(
308 snapshot: RevlogEntry,
309 deltas: &[RevlogEntry],
310 ) -> Result<Vec<u8>, HgError> {
311 let snapshot = snapshot.data_chunk()?;
312 let deltas = deltas
313 .iter()
314 .rev()
315 .map(RevlogEntry::data_chunk)
316 .collect::<Result<Vec<_>, _>>()?;
317 let patches: Vec<_> =
318 deltas.iter().map(|d| patch::PatchList::new(d)).collect();
319 let patch = patch::fold_patch_lists(&patches);
320 Ok(patch.apply(&snapshot))
321 }
322
323 /// Return the revlog data.
324 fn data(&self) -> &[u8] {
325 match &self.data_bytes {
326 Some(data_bytes) => data_bytes,
327 None => panic!(
328 "forgot to load the data or trying to access inline data"
329 ),
330 }
331 }
332
333 pub fn make_null_entry(&self) -> RevlogEntry {
334 RevlogEntry {
335 revlog: self,
336 rev: NULL_REVISION,
337 bytes: b"",
338 compressed_len: 0,
339 uncompressed_len: 0,
340 base_rev_or_base_of_delta_chain: None,
341 p1: NULL_REVISION,
342 p2: NULL_REVISION,
343 flags: NULL_REVLOG_ENTRY_FLAGS,
344 hash: NULL_NODE,
345 }
346 }
347
348 /// Get an entry of the revlog.
349 pub fn get_entry(
350 &self,
351 rev: Revision,
352 ) -> Result<RevlogEntry, RevlogError> {
353 if rev == NULL_REVISION {
354 return Ok(self.make_null_entry());
355 }
356 let index_entry = self
357 .index
358 .get_entry(rev)
359 .ok_or(RevlogError::InvalidRevision)?;
360 let start = index_entry.offset();
361 let end = start + index_entry.compressed_len() as usize;
362 let data = if self.index.is_inline() {
363 self.index.data(start, end)
364 } else {
365 &self.data()[start..end]
366 };
367 let entry = RevlogEntry {
368 revlog: self,
369 rev,
370 bytes: data,
371 compressed_len: index_entry.compressed_len(),
372 uncompressed_len: index_entry.uncompressed_len(),
373 base_rev_or_base_of_delta_chain: if index_entry
374 .base_revision_or_base_of_delta_chain()
375 == rev
376 {
377 None
378 } else {
379 Some(index_entry.base_revision_or_base_of_delta_chain())
380 },
381 p1: index_entry.p1(),
382 p2: index_entry.p2(),
383 flags: index_entry.flags(),
384 hash: *index_entry.hash(),
385 };
386 Ok(entry)
387 }
388
389 /// when resolving internal references within revlog, any errors
390 /// should be reported as corruption, instead of e.g. "invalid revision"
391 fn get_entry_internal(
392 &self,
393 rev: Revision,
394 ) -> Result<RevlogEntry, HgError> {
395 self.get_entry(rev)
396 .map_err(|_| corrupted(format!("revision {} out of range", rev)))
397 }
398 }
399
400 /// The revlog entry's bytes and the necessary informations to extract
401 /// the entry's data.
402 #[derive(Clone)]
403 pub struct RevlogEntry<'a> {
404 revlog: &'a Revlog,
405 rev: Revision,
406 bytes: &'a [u8],
407 compressed_len: u32,
408 uncompressed_len: i32,
409 base_rev_or_base_of_delta_chain: Option<Revision>,
410 p1: Revision,
411 p2: Revision,
412 flags: u16,
413 hash: Node,
414 }
415
416 impl<'a> RevlogEntry<'a> {
417 pub fn revision(&self) -> Revision {
418 self.rev
419 }
420
421 pub fn node(&self) -> &Node {
422 &self.hash
423 }
424
425 pub fn uncompressed_len(&self) -> Option<u32> {
426 u32::try_from(self.uncompressed_len).ok()
427 }
428
429 pub fn has_p1(&self) -> bool {
430 self.p1 != NULL_REVISION
431 }
432
433 pub fn p1_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
434 if self.p1 == NULL_REVISION {
435 Ok(None)
436 } else {
437 Ok(Some(self.revlog.get_entry(self.p1)?))
438 }
439 }
440
441 pub fn p2_entry(&self) -> Result<Option<RevlogEntry>, RevlogError> {
442 if self.p2 == NULL_REVISION {
443 Ok(None)
444 } else {
445 Ok(Some(self.revlog.get_entry(self.p2)?))
446 }
447 }
448
449 pub fn p1(&self) -> Option<Revision> {
450 if self.p1 == NULL_REVISION {
451 None
452 } else {
453 Some(self.p1)
454 }
455 }
456
457 pub fn p2(&self) -> Option<Revision> {
458 if self.p2 == NULL_REVISION {
459 None
460 } else {
461 Some(self.p2)
462 }
463 }
464
465 pub fn is_censored(&self) -> bool {
466 (self.flags & REVISION_FLAG_CENSORED) != 0
467 }
468
469 pub fn has_length_affecting_flag_processor(&self) -> bool {
470 // Relevant Python code: revlog.size()
471 // note: ELLIPSIS is known to not change the content
472 (self.flags & (REVIDX_KNOWN_FLAGS ^ REVISION_FLAG_ELLIPSIS)) != 0
473 }
474
475 /// The data for this entry, after resolving deltas if any.
476 pub fn rawdata(&self) -> Result<Cow<'a, [u8]>, HgError> {
477 let mut entry = self.clone();
478 let mut delta_chain = vec![];
479
480 // The meaning of `base_rev_or_base_of_delta_chain` depends on
481 // generaldelta. See the doc on `ENTRY_DELTA_BASE` in
482 // `mercurial/revlogutils/constants.py` and the code in
483 // [_chaininfo] and in [index_deltachain].
484 let uses_generaldelta = self.revlog.index.uses_generaldelta();
485 while let Some(base_rev) = entry.base_rev_or_base_of_delta_chain {
486 let base_rev = if uses_generaldelta {
487 base_rev
488 } else {
489 entry.rev - 1
490 };
491 delta_chain.push(entry);
492 entry = self.revlog.get_entry_internal(base_rev)?;
493 }
494
495 let data = if delta_chain.is_empty() {
496 entry.data_chunk()?
497 } else {
498 Revlog::build_data_from_deltas(entry, &delta_chain)?.into()
499 };
500
501 Ok(data)
502 }
503
504 fn check_data(
505 &self,
506 data: Cow<'a, [u8]>,
507 ) -> Result<Cow<'a, [u8]>, HgError> {
508 if self.revlog.check_hash(
509 self.p1,
510 self.p2,
511 self.hash.as_bytes(),
512 &data,
513 ) {
514 Ok(data)
515 } else {
516 if (self.flags & REVISION_FLAG_ELLIPSIS) != 0 {
517 return Err(HgError::unsupported(
518 "ellipsis revisions are not supported by rhg",
519 ));
520 }
521 Err(corrupted(format!(
522 "hash check failed for revision {}",
523 self.rev
524 )))
525 }
526 }
527
528 pub fn data(&self) -> Result<Cow<'a, [u8]>, HgError> {
529 let data = self.rawdata()?;
530 if self.is_censored() {
531 return Err(HgError::CensoredNodeError);
532 }
533 self.check_data(data)
534 }
535
536 /// Extract the data contained in the entry.
537 /// This may be a delta. (See `is_delta`.)
538 fn data_chunk(&self) -> Result<Cow<'a, [u8]>, HgError> {
539 if self.bytes.is_empty() {
540 return Ok(Cow::Borrowed(&[]));
541 }
542 match self.bytes[0] {
543 // Revision data is the entirety of the entry, including this
544 // header.
545 b'\0' => Ok(Cow::Borrowed(self.bytes)),
546 // Raw revision data follows.
547 b'u' => Ok(Cow::Borrowed(&self.bytes[1..])),
548 // zlib (RFC 1950) data.
549 b'x' => Ok(Cow::Owned(self.uncompressed_zlib_data()?)),
550 // zstd data.
551 b'\x28' => Ok(Cow::Owned(self.uncompressed_zstd_data()?)),
552 // A proper new format should have had a repo/store requirement.
553 format_type => Err(corrupted(format!(
554 "unknown compression header '{}'",
555 format_type
556 ))),
557 }
558 }
559
560 fn uncompressed_zlib_data(&self) -> Result<Vec<u8>, HgError> {
561 let mut decoder = ZlibDecoder::new(self.bytes);
562 if self.is_delta() {
563 let mut buf = Vec::with_capacity(self.compressed_len as usize);
564 decoder
565 .read_to_end(&mut buf)
566 .map_err(|e| corrupted(e.to_string()))?;
567 Ok(buf)
568 } else {
569 let cap = self.uncompressed_len.max(0) as usize;
570 let mut buf = vec![0; cap];
571 decoder
572 .read_exact(&mut buf)
573 .map_err(|e| corrupted(e.to_string()))?;
574 Ok(buf)
575 }
576 }
577
578 fn uncompressed_zstd_data(&self) -> Result<Vec<u8>, HgError> {
579 if self.is_delta() {
580 let mut buf = Vec::with_capacity(self.compressed_len as usize);
581 zstd::stream::copy_decode(self.bytes, &mut buf)
582 .map_err(|e| corrupted(e.to_string()))?;
583 Ok(buf)
584 } else {
585 let cap = self.uncompressed_len.max(0) as usize;
586 let mut buf = vec![0; cap];
587 let len = zstd::bulk::decompress_to_buffer(self.bytes, &mut buf)
588 .map_err(|e| corrupted(e.to_string()))?;
589 if len != self.uncompressed_len as usize {
590 Err(corrupted("uncompressed length does not match"))
591 } else {
592 Ok(buf)
593 }
594 }
595 }
596
597 /// Tell if the entry is a snapshot or a delta
598 /// (influences on decompression).
599 fn is_delta(&self) -> bool {
600 self.base_rev_or_base_of_delta_chain.is_some()
601 }
602 }
603
604 /// Calculate the hash of a revision given its data and its parents.
605 fn hash(
606 data: &[u8],
607 p1_hash: &[u8],
608 p2_hash: &[u8],
609 ) -> [u8; NODE_BYTES_LENGTH] {
610 let mut hasher = Sha1::new();
611 let (a, b) = (p1_hash, p2_hash);
612 if a > b {
613 hasher.update(b);
614 hasher.update(a);
615 } else {
616 hasher.update(a);
617 hasher.update(b);
618 }
619 hasher.update(data);
620 *hasher.finalize().as_ref()
621 }
622
623 #[cfg(test)]
624 mod tests {
625 use super::*;
626 use crate::index::{IndexEntryBuilder, INDEX_ENTRY_SIZE};
627 use itertools::Itertools;
628
629 #[test]
630 fn test_empty() {
631 let temp = tempfile::tempdir().unwrap();
632 let vfs = Vfs { base: temp.path() };
633 std::fs::write(temp.path().join("foo.i"), b"").unwrap();
634 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
635 assert!(revlog.is_empty());
636 assert_eq!(revlog.len(), 0);
637 assert!(revlog.get_entry(0).is_err());
638 assert!(!revlog.has_rev(0));
639 }
640
641 #[test]
642 fn test_inline() {
643 let temp = tempfile::tempdir().unwrap();
644 let vfs = Vfs { base: temp.path() };
645 let node0 = Node::from_hex("2ed2a3912a0b24502043eae84ee4b279c18b90dd")
646 .unwrap();
647 let node1 = Node::from_hex("b004912a8510032a0350a74daa2803dadfb00e12")
648 .unwrap();
649 let node2 = Node::from_hex("dd6ad206e907be60927b5a3117b97dffb2590582")
650 .unwrap();
651 let entry0_bytes = IndexEntryBuilder::new()
652 .is_first(true)
653 .with_version(1)
654 .with_inline(true)
655 .with_offset(INDEX_ENTRY_SIZE)
656 .with_node(node0)
657 .build();
658 let entry1_bytes = IndexEntryBuilder::new()
659 .with_offset(INDEX_ENTRY_SIZE)
660 .with_node(node1)
661 .build();
662 let entry2_bytes = IndexEntryBuilder::new()
663 .with_offset(INDEX_ENTRY_SIZE)
664 .with_p1(0)
665 .with_p2(1)
666 .with_node(node2)
667 .build();
668 let contents = vec![entry0_bytes, entry1_bytes, entry2_bytes]
669 .into_iter()
670 .flatten()
671 .collect_vec();
672 std::fs::write(temp.path().join("foo.i"), contents).unwrap();
673 let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
674
675 let entry0 = revlog.get_entry(0).ok().unwrap();
676 assert_eq!(entry0.revision(), 0);
677 assert_eq!(*entry0.node(), node0);
678 assert!(!entry0.has_p1());
679 assert_eq!(entry0.p1(), None);
680 assert_eq!(entry0.p2(), None);
681 let p1_entry = entry0.p1_entry().unwrap();
682 assert!(p1_entry.is_none());
683 let p2_entry = entry0.p2_entry().unwrap();
684 assert!(p2_entry.is_none());
685
686 let entry1 = revlog.get_entry(1).ok().unwrap();
687 assert_eq!(entry1.revision(), 1);
688 assert_eq!(*entry1.node(), node1);
689 assert!(!entry1.has_p1());
690 assert_eq!(entry1.p1(), None);
691 assert_eq!(entry1.p2(), None);
692 let p1_entry = entry1.p1_entry().unwrap();
693 assert!(p1_entry.is_none());
694 let p2_entry = entry1.p2_entry().unwrap();
695 assert!(p2_entry.is_none());
696
697 let entry2 = revlog.get_entry(2).ok().unwrap();
698 assert_eq!(entry2.revision(), 2);
699 assert_eq!(*entry2.node(), node2);
700 assert!(entry2.has_p1());
701 assert_eq!(entry2.p1(), Some(0));
702 assert_eq!(entry2.p2(), Some(1));
703 let p1_entry = entry2.p1_entry().unwrap();
704 assert!(p1_entry.is_some());
705 assert_eq!(p1_entry.unwrap().revision(), 0);
706 let p2_entry = entry2.p2_entry().unwrap();
707 assert!(p2_entry.is_some());
708 assert_eq!(p2_entry.unwrap().revision(), 1);
709 }
710 }
@@ -4,9 +4,9 b''
4
4
5 use crate::errors::HgError;
5 use crate::errors::HgError;
6 use crate::repo::Repo;
6 use crate::repo::Repo;
7 use crate::revlog::revlog::{Revlog, RevlogError};
8 use crate::revlog::NodePrefix;
7 use crate::revlog::NodePrefix;
9 use crate::revlog::{Revision, NULL_REVISION, WORKING_DIRECTORY_HEX};
8 use crate::revlog::{Revision, NULL_REVISION, WORKING_DIRECTORY_HEX};
9 use crate::revlog::{Revlog, RevlogError};
10 use crate::Node;
10 use crate::Node;
11
11
12 /// Resolve a query string into a single revision.
12 /// Resolve a query string into a single revision.
@@ -7,7 +7,7 b' use hg::dirstate_tree::on_disk::Dirstate'
7 use hg::errors::HgError;
7 use hg::errors::HgError;
8 use hg::exit_codes;
8 use hg::exit_codes;
9 use hg::repo::RepoError;
9 use hg::repo::RepoError;
10 use hg::revlog::revlog::RevlogError;
10 use hg::revlog::RevlogError;
11 use hg::sparse::SparseConfigError;
11 use hg::sparse::SparseConfigError;
12 use hg::utils::files::get_bytes_from_path;
12 use hg::utils::files::get_bytes_from_path;
13 use hg::{DirstateError, DirstateMapError, StatusError};
13 use hg::{DirstateError, DirstateMapError, StatusError};
1 NO CONTENT: file was removed
NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (642 lines changed) Show them Hide them
General Comments 0
You need to be logged in to leave comments. Login now