##// END OF EJS Templates
dirstate-v2: Introduce a docket file...
Simon Sapin -
r48474:ff97e793 default
parent child Browse files
Show More
1 NO CONTENT: new file 100644
@@ -0,0 +1,62 b''
1 # dirstatedocket.py - docket file for dirstate-v2
2 #
3 # Copyright Mercurial Contributors
4 #
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.
7
8 from __future__ import absolute_import
9
10 import struct
11
12 from ..revlogutils import docket as docket_mod
13
14
15 V2_FORMAT_MARKER = b"dirstate-v2\n"
16
17 # * 12 bytes: format marker
18 # * 32 bytes: node ID of the working directory's first parent
19 # * 32 bytes: node ID of the working directory's second parent
20 # * 4 bytes: big-endian used size of the data file
21 # * 1 byte: length of the data file's UUID
22 # * variable: data file's UUID
23 #
24 # Node IDs are null-padded if shorter than 32 bytes.
25 # A data file shorter than the specified used size is corrupted (truncated)
26 HEADER = struct.Struct(">{}s32s32sLB".format(len(V2_FORMAT_MARKER)))
27
28
29 class DirstateDocket(object):
30 data_filename_pattern = b'dirstate.%s.d'
31
32 def __init__(self, parents, data_size, uuid):
33 self.parents = parents
34 self.data_size = data_size
35 self.uuid = uuid
36
37 @classmethod
38 def with_new_uuid(cls, parents, data):
39 return cls(parents, data, docket_mod.make_uid())
40
41 @classmethod
42 def parse(cls, data, nodeconstants):
43 if not data:
44 parents = (nodeconstants.nullid, nodeconstants.nullid)
45 return cls(parents, 0, None)
46 marker, p1, p2, data_size, uuid_size = HEADER.unpack_from(data)
47 if marker != V2_FORMAT_MARKER:
48 raise ValueError("expected dirstate-v2 marker")
49 uuid = data[HEADER.size : HEADER.size + uuid_size]
50 p1 = p1[: nodeconstants.nodelen]
51 p2 = p2[: nodeconstants.nodelen]
52 return cls((p1, p2), data_size, uuid)
53
54 def serialize(self):
55 p1, p2 = self.parents
56 header = HEADER.pack(
57 V2_FORMAT_MARKER, p1, p2, self.data_size, len(self.uuid)
58 )
59 return header + self.uuid
60
61 def data_filename(self):
62 return self.data_filename_pattern % self.uuid
@@ -37,12 +37,12 b' def checkconsistency(ui, orig, dmap, _no'
37 37 ui.develwarn(b"[map] %s\n" % b_nonnormalcomputed, config=b'dirstate')
38 38
39 39
40 def _checkdirstate(orig, self, arg):
40 def _checkdirstate(orig, self, *args, **kwargs):
41 41 """Check nonnormal set consistency before and after the call to orig"""
42 42 checkconsistency(
43 43 self._ui, orig, self._map, self._map.nonnormalset, b"before"
44 44 )
45 r = orig(self, arg)
45 r = orig(self, *args, **kwargs)
46 46 checkconsistency(
47 47 self._ui, orig, self._map, self._map.nonnormalset, b"after"
48 48 )
@@ -7,6 +7,7 b''
7 7
8 8 from __future__ import absolute_import
9 9
10 import binascii
10 11 import codecs
11 12 import collections
12 13 import contextlib
@@ -987,6 +988,24 b' def debugstate(ui, repo, **opts):'
987 988
988 989
989 990 @command(
991 b'debugdirstateignorepatternshash',
992 [],
993 _(b''),
994 )
995 def debugdirstateignorepatternshash(ui, repo, **opts):
996 """show the hash of ignore patterns stored in dirstate if v2,
997 or nothing for dirstate-v2
998 """
999 if repo.dirstate._use_dirstate_v2:
1000 hash_offset = 16 # Four 32-bit integers before this field
1001 hash_len = 20 # 160 bits for SHA-1
1002 data_filename = repo.dirstate._map.docket.data_filename()
1003 with repo.vfs(data_filename) as f:
1004 hash_bytes = f.read(hash_offset + hash_len)[-hash_len:]
1005 ui.write(binascii.hexlify(hash_bytes) + b'\n')
1006
1007
1008 @command(
990 1009 b'debugdiscovery',
991 1010 [
992 1011 (b'', b'old', None, _(b'use old-style discovery')),
@@ -906,13 +906,13 b' class dirstate(object):'
906 906 tr.addfilegenerator(
907 907 b'dirstate',
908 908 (self._filename,),
909 self._writedirstate,
909 lambda f: self._writedirstate(tr, f),
910 910 location=b'plain',
911 911 )
912 912 return
913 913
914 914 st = self._opener(filename, b"w", atomictemp=True, checkambig=True)
915 self._writedirstate(st)
915 self._writedirstate(tr, st)
916 916
917 917 def addparentchangecallback(self, category, callback):
918 918 """add a callback to be called when the wd parents are changed
@@ -925,7 +925,7 b' class dirstate(object):'
925 925 """
926 926 self._plchangecallbacks[category] = callback
927 927
928 def _writedirstate(self, st):
928 def _writedirstate(self, tr, st):
929 929 # notify callbacks about parents change
930 930 if self._origpl is not None and self._origpl != self._pl:
931 931 for c, callback in sorted(
@@ -955,7 +955,7 b' class dirstate(object):'
955 955 now = end # trust our estimate that the end is near now
956 956 break
957 957
958 self._map.write(st, now)
958 self._map.write(tr, st, now)
959 959 self._lastnormaltime = 0
960 960 self._dirty = False
961 961
@@ -1580,7 +1580,8 b' class dirstate(object):'
1580 1580 # output file will be used to create backup of dirstate at this point.
1581 1581 if self._dirty or not self._opener.exists(filename):
1582 1582 self._writedirstate(
1583 self._opener(filename, b"w", atomictemp=True, checkambig=True)
1583 tr,
1584 self._opener(filename, b"w", atomictemp=True, checkambig=True),
1584 1585 )
1585 1586
1586 1587 if tr:
@@ -1590,7 +1591,7 b' class dirstate(object):'
1590 1591 tr.addfilegenerator(
1591 1592 b'dirstate',
1592 1593 (self._filename,),
1593 self._writedirstate,
1594 lambda f: self._writedirstate(tr, f),
1594 1595 location=b'plain',
1595 1596 )
1596 1597
@@ -18,6 +18,10 b' from . import ('
18 18 util,
19 19 )
20 20
21 from .dirstateutils import (
22 docket as docketmod,
23 )
24
21 25 parsers = policy.importmod('parsers')
22 26 rustmod = policy.importrust('dirstate')
23 27
@@ -416,7 +420,7 b' class dirstatemap(object):'
416 420 self.__getitem__ = self._map.__getitem__
417 421 self.get = self._map.get
418 422
419 def write(self, st, now):
423 def write(self, _tr, st, now):
420 424 st.write(
421 425 parsers.pack_dirstate(self._map, self.copymap, self.parents(), now)
422 426 )
@@ -466,6 +470,7 b' if rustmod is not None:'
466 470 self._nodelen = 20 # Also update Rust code when changing this!
467 471 self._parents = None
468 472 self._dirtyparents = False
473 self._docket = None
469 474
470 475 # for consistent view between _pl() and _read() invocations
471 476 self._pendingmode = None
@@ -565,6 +570,16 b' if rustmod is not None:'
565 570 self._pendingmode = mode
566 571 return fp
567 572
573 def _readdirstatefile(self, size=-1):
574 try:
575 with self._opendirstatefile() as fp:
576 return fp.read(size)
577 except IOError as err:
578 if err.errno != errno.ENOENT:
579 raise
580 # File doesn't exist, so the current state is empty
581 return b''
582
568 583 def setparents(self, p1, p2):
569 584 self._parents = (p1, p2)
570 585 self._dirtyparents = True
@@ -572,23 +587,12 b' if rustmod is not None:'
572 587 def parents(self):
573 588 if not self._parents:
574 589 if self._use_dirstate_v2:
575 offset = len(rustmod.V2_FORMAT_MARKER)
590 self._parents = self.docket.parents
576 591 else:
577 offset = 0
578 read_len = offset + self._nodelen * 2
579 try:
580 fp = self._opendirstatefile()
581 st = fp.read(read_len)
582 fp.close()
583 except IOError as err:
584 if err.errno != errno.ENOENT:
585 raise
586 # File doesn't exist, so the current state is empty
587 st = b''
588
592 read_len = self._nodelen * 2
593 st = self._readdirstatefile(read_len)
589 594 l = len(st)
590 595 if l == read_len:
591 st = st[offset:]
592 596 self._parents = (
593 597 st[: self._nodelen],
594 598 st[self._nodelen : 2 * self._nodelen],
@@ -605,6 +609,18 b' if rustmod is not None:'
605 609
606 610 return self._parents
607 611
612 @property
613 def docket(self):
614 if not self._docket:
615 if not self._use_dirstate_v2:
616 raise error.ProgrammingError(
617 b'dirstate only has a docket in v2 format'
618 )
619 self._docket = docketmod.DirstateDocket.parse(
620 self._readdirstatefile(), self._nodeconstants
621 )
622 return self._docket
623
608 624 @propertycache
609 625 def _rustmap(self):
610 626 """
@@ -615,19 +631,17 b' if rustmod is not None:'
615 631 self._opener.join(self._filename)
616 632 )
617 633
618 try:
619 fp = self._opendirstatefile()
620 try:
621 st = fp.read()
622 finally:
623 fp.close()
624 except IOError as err:
625 if err.errno != errno.ENOENT:
626 raise
627 st = b''
628
629 self._rustmap, parents = rustmod.DirstateMap.new(
630 self._use_dirstate_tree, self._use_dirstate_v2, st
634 if self._use_dirstate_v2:
635 if self.docket.uuid:
636 # TODO: use mmap when possible
637 data = self._opener.read(self.docket.data_filename())
638 else:
639 data = b''
640 self._rustmap = rustmod.DirstateMap.new_v2(data)
641 parents = self.docket.parents
642 else:
643 self._rustmap, parents = rustmod.DirstateMap.new_v1(
644 self._use_dirstate_tree, self._readdirstatefile()
631 645 )
632 646
633 647 if parents and not self._dirtyparents:
@@ -638,11 +652,27 b' if rustmod is not None:'
638 652 self.get = self._rustmap.get
639 653 return self._rustmap
640 654
641 def write(self, st, now):
642 parents = self.parents()
643 packed = self._rustmap.write(
644 self._use_dirstate_v2, parents[0], parents[1], now
655 def write(self, tr, st, now):
656 if self._use_dirstate_v2:
657 packed = self._rustmap.write_v2(now)
658 old_docket = self.docket
659 new_docket = docketmod.DirstateDocket.with_new_uuid(
660 self.parents(), len(packed)
645 661 )
662 self._opener.write(new_docket.data_filename(), packed)
663 # Write the new docket after the new data file has been
664 # written. Because `st` was opened with `atomictemp=True`,
665 # the actual `.hg/dirstate` file is only affected on close.
666 st.write(new_docket.serialize())
667 st.close()
668 # Remove the old data file after the new docket pointing to
669 # the new data file was written.
670 if old_docket.uuid:
671 self._opener.unlink(old_docket.data_filename())
672 self._docket = new_docket
673 else:
674 p1, p2 = self.parents()
675 packed = self._rustmap.write_v1(p1, p2, now)
646 676 st.write(packed)
647 677 st.close()
648 678 self._dirtyparents = False
@@ -643,6 +643,7 b' def upgrade_dirstate(ui, srcrepo, upgrad'
643 643 srcrepo.dirstate._use_dirstate_v2 = new == b'v2'
644 644 srcrepo.dirstate._map._use_dirstate_v2 = srcrepo.dirstate._use_dirstate_v2
645 645 srcrepo.dirstate._dirty = True
646 srcrepo.vfs.unlink(b'dirstate')
646 647 srcrepo.dirstate.write(None)
647 648
648 649 scmutil.writereporequirements(srcrepo, upgrade_op.new_requirements)
@@ -410,9 +410,7 b" impl<'on_disk> DirstateMap<'on_disk> {"
410 410 }
411 411
412 412 #[timed]
413 pub fn new_v2(
414 on_disk: &'on_disk [u8],
415 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
413 pub fn new_v2(on_disk: &'on_disk [u8]) -> Result<Self, DirstateError> {
416 414 Ok(on_disk::read(on_disk)?)
417 415 }
418 416
@@ -1039,11 +1037,7 b" impl<'on_disk> super::dispatch::Dirstate"
1039 1037 }
1040 1038
1041 1039 #[timed]
1042 fn pack_v2(
1043 &mut self,
1044 parents: DirstateParents,
1045 now: Timestamp,
1046 ) -> Result<Vec<u8>, DirstateError> {
1040 fn pack_v2(&mut self, now: Timestamp) -> Result<Vec<u8>, DirstateError> {
1047 1041 // TODO: how do we want to handle this in 2038?
1048 1042 let now: i32 = now.0.try_into().expect("time overflow");
1049 1043 let mut paths = Vec::new();
@@ -1062,7 +1056,7 b" impl<'on_disk> super::dispatch::Dirstate"
1062 1056
1063 1057 self.clear_known_ambiguous_mtimes(&paths)?;
1064 1058
1065 on_disk::write(self, parents)
1059 on_disk::write(self)
1066 1060 }
1067 1061
1068 1062 fn status<'a>(
@@ -183,11 +183,7 b' pub trait DirstateMapMethods {'
183 183 /// format.
184 184 ///
185 185 /// Note: this is only supported by the tree dirstate map.
186 fn pack_v2(
187 &mut self,
188 parents: DirstateParents,
189 now: Timestamp,
190 ) -> Result<Vec<u8>, DirstateError>;
186 fn pack_v2(&mut self, now: Timestamp) -> Result<Vec<u8>, DirstateError>;
191 187
192 188 /// Run the status algorithm.
193 189 ///
@@ -387,11 +383,7 b' impl DirstateMapMethods for DirstateMap '
387 383 self.pack(parents, now)
388 384 }
389 385
390 fn pack_v2(
391 &mut self,
392 _parents: DirstateParents,
393 _now: Timestamp,
394 ) -> Result<Vec<u8>, DirstateError> {
386 fn pack_v2(&mut self, _now: Timestamp) -> Result<Vec<u8>, DirstateError> {
395 387 panic!(
396 388 "should have used dirstate_tree::DirstateMap to use the v2 format"
397 389 )
@@ -19,6 +19,7 b' use crate::DirstateParents;'
19 19 use crate::EntryState;
20 20 use bytes_cast::unaligned::{I32Be, I64Be, U32Be};
21 21 use bytes_cast::BytesCast;
22 use format_bytes::format_bytes;
22 23 use std::borrow::Cow;
23 24 use std::convert::TryFrom;
24 25 use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -28,18 +29,34 b' use std::time::{Duration, SystemTime, UN'
28 29 /// `.hg/requires` already governs which format should be used.
29 30 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
30 31
32 /// Keep space for 256-bit hashes
33 const STORED_NODE_ID_BYTES: usize = 32;
34
35 /// … even though only 160 bits are used for now, with SHA-1
36 const USED_NODE_ID_BYTES: usize = 20;
37
31 38 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
32 39 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
33 40
41 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
42 #[derive(BytesCast)]
43 #[repr(C)]
44 struct DocketHeader {
45 marker: [u8; V2_FORMAT_MARKER.len()],
46 parent_1: [u8; STORED_NODE_ID_BYTES],
47 parent_2: [u8; STORED_NODE_ID_BYTES],
48 data_size: Size,
49 uuid_size: u8,
50 }
51
52 pub struct Docket<'on_disk> {
53 header: &'on_disk DocketHeader,
54 uuid: &'on_disk [u8],
55 }
56
34 57 #[derive(BytesCast)]
35 58 #[repr(C)]
36 59 struct Header {
37 marker: [u8; V2_FORMAT_MARKER.len()],
38
39 /// `dirstatemap.parents()` in `mercurial/dirstate.py` relies on this
40 /// `parents` field being at this offset, immediately after `marker`.
41 parents: DirstateParents,
42
43 60 root: ChildNodes,
44 61 nodes_with_entry_count: Size,
45 62 nodes_with_copy_source_count: Size,
@@ -172,7 +189,8 b' type OptPathSlice = Slice;'
172 189
173 190 /// Make sure that size-affecting changes are made knowingly
174 191 fn _static_assert_size_of() {
175 let _ = std::mem::transmute::<Header, [u8; 88]>;
192 let _ = std::mem::transmute::<DocketHeader, [u8; 81]>;
193 let _ = std::mem::transmute::<Header, [u8; 36]>;
176 194 let _ = std::mem::transmute::<Node, [u8; 49]>;
177 195 }
178 196
@@ -194,11 +212,31 b' impl From<DirstateV2ParseError> for crat'
194 212 }
195 213 }
196 214
197 fn read_header(on_disk: &[u8]) -> Result<&Header, DirstateV2ParseError> {
198 let (header, _) =
199 Header::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
200 if header.marker == *V2_FORMAT_MARKER {
201 Ok(header)
215 impl<'on_disk> Docket<'on_disk> {
216 pub fn parents(&self) -> DirstateParents {
217 use crate::Node;
218 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
219 .unwrap()
220 .clone();
221 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
222 .unwrap()
223 .clone();
224 DirstateParents { p1, p2 }
225 }
226
227 pub fn data_filename(&self) -> String {
228 String::from_utf8(format_bytes!(b"dirstate.{}.d", self.uuid)).unwrap()
229 }
230 }
231
232 pub fn read_docket(
233 on_disk: &[u8],
234 ) -> Result<Docket<'_>, DirstateV2ParseError> {
235 let (header, uuid) =
236 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
237 let uuid_size = header.uuid_size as usize;
238 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
239 Ok(Docket { header, uuid })
202 240 } else {
203 241 Err(DirstateV2ParseError)
204 242 }
@@ -206,14 +244,12 b' fn read_header(on_disk: &[u8]) -> Result'
206 244
207 245 pub(super) fn read<'on_disk>(
208 246 on_disk: &'on_disk [u8],
209 ) -> Result<
210 (DirstateMap<'on_disk>, Option<DirstateParents>),
211 DirstateV2ParseError,
212 > {
247 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
213 248 if on_disk.is_empty() {
214 return Ok((DirstateMap::empty(on_disk), None));
249 return Ok(DirstateMap::empty(on_disk));
215 250 }
216 let header = read_header(on_disk)?;
251 let (header, _) =
252 Header::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
217 253 let dirstate_map = DirstateMap {
218 254 on_disk,
219 255 root: dirstate_map::ChildNodes::OnDisk(read_slice::<Node>(
@@ -226,8 +262,7 b" pub(super) fn read<'on_disk>("
226 262 .get(),
227 263 ignore_patterns_hash: header.ignore_patterns_hash,
228 264 };
229 let parents = Some(header.parents.clone());
230 Ok((dirstate_map, parents))
265 Ok(dirstate_map)
231 266 }
232 267
233 268 impl Node {
@@ -447,17 +482,12 b' where'
447 482 .ok_or_else(|| DirstateV2ParseError)
448 483 }
449 484
450 pub(crate) fn parse_dirstate_parents(
451 on_disk: &[u8],
452 ) -> Result<&DirstateParents, HgError> {
453 Ok(&read_header(on_disk)?.parents)
454 }
455
456 485 pub(crate) fn for_each_tracked_path<'on_disk>(
457 486 on_disk: &'on_disk [u8],
458 487 mut f: impl FnMut(&'on_disk HgPath),
459 488 ) -> Result<(), DirstateV2ParseError> {
460 let header = read_header(on_disk)?;
489 let (header, _) =
490 Header::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
461 491 fn recur<'on_disk>(
462 492 on_disk: &'on_disk [u8],
463 493 nodes: Slice,
@@ -478,7 +508,6 b" pub(crate) fn for_each_tracked_path<'on_"
478 508
479 509 pub(super) fn write(
480 510 dirstate_map: &mut DirstateMap,
481 parents: DirstateParents,
482 511 ) -> Result<Vec<u8>, DirstateError> {
483 512 let header_len = std::mem::size_of::<Header>();
484 513
@@ -497,8 +526,6 b' pub(super) fn write('
497 526 write_nodes(dirstate_map, dirstate_map.root.as_ref(), &mut out)?;
498 527
499 528 let header = Header {
500 marker: *V2_FORMAT_MARKER,
501 parents: parents,
502 529 root,
503 530 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
504 531 nodes_with_copy_source_count: dirstate_map
@@ -6,7 +6,7 b''
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 use crate::dirstate::parsers::parse_dirstate_entries;
9 use crate::dirstate_tree::on_disk::for_each_tracked_path;
9 use crate::dirstate_tree::on_disk::{for_each_tracked_path, read_docket};
10 10 use crate::errors::HgError;
11 11 use crate::repo::Repo;
12 12 use crate::revlog::changelog::Changelog;
@@ -27,8 +27,13 b' pub struct Dirstate {'
27 27
28 28 impl Dirstate {
29 29 pub fn new(repo: &Repo) -> Result<Self, HgError> {
30 let mut content = repo.hg_vfs().read("dirstate")?;
31 if repo.has_dirstate_v2() {
32 let docket = read_docket(&content)?;
33 content = repo.hg_vfs().read(docket.data_filename())?;
34 }
30 35 Ok(Self {
31 content: repo.hg_vfs().read("dirstate")?,
36 content,
32 37 dirstate_v2: repo.has_dirstate_v2(),
33 38 })
34 39 }
@@ -241,11 +241,12 b' impl Repo {'
241 241 return Ok(crate::dirstate::DirstateParents::NULL);
242 242 }
243 243 let parents = if self.has_dirstate_v2() {
244 crate::dirstate_tree::on_disk::parse_dirstate_parents(&dirstate)?
244 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
245 245 } else {
246 246 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
247 .clone()
247 248 };
248 Ok(parents.clone())
249 Ok(parents)
249 250 }
250 251 }
251 252
@@ -57,17 +57,15 b' py_class!(pub class DirstateMap |py| {'
57 57
58 58 /// Returns a `(dirstate_map, parents)` tuple
59 59 @staticmethod
60 def new(
60 def new_v1(
61 61 use_dirstate_tree: bool,
62 use_dirstate_v2: bool,
63 62 on_disk: PyBytes,
64 63 ) -> PyResult<PyObject> {
65 64 let dirstate_error = |e: DirstateError| {
66 65 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
67 66 };
68 let (inner, parents) = if use_dirstate_tree || use_dirstate_v2 {
69 let (map, parents) =
70 OwningDirstateMap::new(py, on_disk, use_dirstate_v2)
67 let (inner, parents) = if use_dirstate_tree {
68 let (map, parents) = OwningDirstateMap::new_v1(py, on_disk)
71 69 .map_err(dirstate_error)?;
72 70 (Box::new(map) as _, parents)
73 71 } else {
@@ -81,6 +79,20 b' py_class!(pub class DirstateMap |py| {'
81 79 Ok((map, parents).to_py_object(py).into_object())
82 80 }
83 81
82 /// Returns a DirstateMap
83 @staticmethod
84 def new_v2(
85 on_disk: PyBytes,
86 ) -> PyResult<PyObject> {
87 let dirstate_error = |e: DirstateError| {
88 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
89 };
90 let inner = OwningDirstateMap::new_v2(py, on_disk)
91 .map_err(dirstate_error)?;
92 let map = Self::create_instance(py, Box::new(inner))?;
93 Ok(map.into_object())
94 }
95
84 96 def clear(&self) -> PyResult<PyObject> {
85 97 self.inner(py).borrow_mut().clear();
86 98 Ok(py.None())
@@ -304,25 +316,37 b' py_class!(pub class DirstateMap |py| {'
304 316 .to_py_object(py))
305 317 }
306 318
307 def write(
319 def write_v1(
308 320 &self,
309 use_dirstate_v2: bool,
310 321 p1: PyObject,
311 322 p2: PyObject,
312 323 now: PyObject
313 324 ) -> PyResult<PyBytes> {
314 325 let now = Timestamp(now.extract(py)?);
326
327 let mut inner = self.inner(py).borrow_mut();
315 328 let parents = DirstateParents {
316 329 p1: extract_node_id(py, &p1)?,
317 330 p2: extract_node_id(py, &p2)?,
318 331 };
332 let result = inner.pack_v1(parents, now);
333 match result {
334 Ok(packed) => Ok(PyBytes::new(py, &packed)),
335 Err(_) => Err(PyErr::new::<exc::OSError, _>(
336 py,
337 "Dirstate error".to_string(),
338 )),
339 }
340 }
341
342 def write_v2(
343 &self,
344 now: PyObject
345 ) -> PyResult<PyBytes> {
346 let now = Timestamp(now.extract(py)?);
319 347
320 348 let mut inner = self.inner(py).borrow_mut();
321 let result = if use_dirstate_v2 {
322 inner.pack_v2(parents, now)
323 } else {
324 inner.pack_v1(parents, now)
325 };
349 let result = inner.pack_v2(now);
326 350 match result {
327 351 Ok(packed) => Ok(PyBytes::new(py, &packed)),
328 352 Err(_) => Err(PyErr::new::<exc::OSError, _>(
@@ -124,12 +124,8 b' impl DirstateMapMethods for OwningDirsta'
124 124 self.get_mut().pack_v1(parents, now)
125 125 }
126 126
127 fn pack_v2(
128 &mut self,
129 parents: DirstateParents,
130 now: Timestamp,
131 ) -> Result<Vec<u8>, DirstateError> {
132 self.get_mut().pack_v2(parents, now)
127 fn pack_v2(&mut self, now: Timestamp) -> Result<Vec<u8>, DirstateError> {
128 self.get_mut().pack_v2(now)
133 129 }
134 130
135 131 fn status<'a>(
@@ -28,17 +28,12 b' pub(super) struct OwningDirstateMap {'
28 28 }
29 29
30 30 impl OwningDirstateMap {
31 pub fn new(
31 pub fn new_v1(
32 32 py: Python,
33 33 on_disk: PyBytes,
34 use_dirstate_v2: bool,
35 34 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
36 35 let bytes: &'_ [u8] = on_disk.data(py);
37 let (map, parents) = if use_dirstate_v2 {
38 DirstateMap::new_v2(bytes)?
39 } else {
40 DirstateMap::new_v1(bytes)?
41 };
36 let (map, parents) = DirstateMap::new_v1(bytes)?;
42 37
43 38 // Like in `bytes` above, this `'_` lifetime parameter borrows from
44 39 // the bytes buffer owned by `on_disk`.
@@ -50,6 +45,23 b' impl OwningDirstateMap {'
50 45 Ok((Self { on_disk, ptr }, parents))
51 46 }
52 47
48 pub fn new_v2(
49 py: Python,
50 on_disk: PyBytes,
51 ) -> Result<Self, DirstateError> {
52 let bytes: &'_ [u8] = on_disk.data(py);
53 let map = DirstateMap::new_v2(bytes)?;
54
55 // Like in `bytes` above, this `'_` lifetime parameter borrows from
56 // the bytes buffer owned by `on_disk`.
57 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
58
59 // Erase the pointed type entirely in order to erase the lifetime.
60 let ptr: *mut () = ptr.cast();
61
62 Ok(Self { on_disk, ptr })
63 }
64
53 65 pub fn get_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
54 66 // SAFETY: We cast the type-erased pointer back to the same type it had
55 67 // in `new`, except with a different lifetime parameter. This time we
@@ -10,6 +10,7 b' use crate::ui::Ui;'
10 10 use clap::{Arg, SubCommand};
11 11 use hg;
12 12 use hg::dirstate_tree::dirstate_map::DirstateMap;
13 use hg::dirstate_tree::on_disk;
13 14 use hg::errors::HgResultExt;
14 15 use hg::errors::IoResultExt;
15 16 use hg::matchers::AlwaysMatcher;
@@ -165,17 +166,33 b' pub fn run(invocation: &crate::CliInvoca'
165 166 };
166 167
167 168 let repo = invocation.repo?;
168 let dirstate_data =
169 let dirstate_data_mmap;
170 let (mut dmap, parents) = if repo.has_dirstate_v2() {
171 let parents;
172 let dirstate_data;
173 if let Some(docket_data) =
174 repo.hg_vfs().read("dirstate").io_not_found_as_none()?
175 {
176 let docket = on_disk::read_docket(&docket_data)?;
177 parents = Some(docket.parents());
178 dirstate_data_mmap = repo
179 .hg_vfs()
180 .mmap_open(docket.data_filename())
181 .io_not_found_as_none()?;
182 dirstate_data = dirstate_data_mmap.as_deref().unwrap_or(b"");
183 } else {
184 parents = None;
185 dirstate_data = b"";
186 }
187 let dmap = DirstateMap::new_v2(dirstate_data)?;
188 (dmap, parents)
189 } else {
190 dirstate_data_mmap =
169 191 repo.hg_vfs().mmap_open("dirstate").io_not_found_as_none()?;
170 let dirstate_data = match &dirstate_data {
171 Some(mmap) => &**mmap,
172 None => b"",
173 };
174 let (mut dmap, parents) = if repo.has_dirstate_v2() {
175 DirstateMap::new_v2(dirstate_data)?
176 } else {
192 let dirstate_data = dirstate_data_mmap.as_deref().unwrap_or(b"");
177 193 DirstateMap::new_v1(dirstate_data)?
178 194 };
195
179 196 let options = StatusOptions {
180 197 // TODO should be provided by the dirstate parsing and
181 198 // hence be stored on dmap. Using a value that assumes we aren't
@@ -3,6 +3,7 b' use crate::ui::UiError;'
3 3 use crate::NoRepoInCwdError;
4 4 use format_bytes::format_bytes;
5 5 use hg::config::{ConfigError, ConfigParseError, ConfigValueParseError};
6 use hg::dirstate_tree::on_disk::DirstateV2ParseError;
6 7 use hg::errors::HgError;
7 8 use hg::exit_codes;
8 9 use hg::repo::RepoError;
@@ -199,3 +200,9 b' impl From<DirstateError> for CommandErro'
199 200 }
200 201 }
201 202 }
203
204 impl From<DirstateV2ParseError> for CommandError {
205 fn from(error: DirstateV2ParseError) -> Self {
206 HgError::from(error).into()
207 }
208 }
@@ -1291,6 +1291,7 b' packages = ['
1291 1291 'mercurial.cext',
1292 1292 'mercurial.cffi',
1293 1293 'mercurial.defaultrc',
1294 'mercurial.dirstateutils',
1294 1295 'mercurial.helptext',
1295 1296 'mercurial.helptext.internals',
1296 1297 'mercurial.hgweb',
@@ -67,8 +67,8 b' def fakewrite(ui, func):'
67 67 # The Rust implementation does not use public parse/pack dirstate
68 68 # to prevent conversion round-trips
69 69 orig_dirstatemap_write = dirstatemapmod.dirstatemap.write
70 wrapper = lambda self, st, now: orig_dirstatemap_write(
71 self, st, fakenow
70 wrapper = lambda self, tr, st, now: orig_dirstatemap_write(
71 self, tr, st, fakenow
72 72 )
73 73 dirstatemapmod.dirstatemap.write = wrapper
74 74
@@ -93,6 +93,7 b' Show debug commands if there are no othe'
93 93 debugdate
94 94 debugdeltachain
95 95 debugdirstate
96 debugdirstateignorepatternshash
96 97 debugdiscovery
97 98 debugdownload
98 99 debugextensions
@@ -282,6 +283,7 b' Show all commands + options'
282 283 debugdata: changelog, manifest, dir
283 284 debugdate: extended
284 285 debugdeltachain: changelog, manifest, dir, template
286 debugdirstateignorepatternshash:
285 287 debugdirstate: nodates, dates, datesort, dirs
286 288 debugdiscovery: old, nonheads, rev, seed, local-as-revs, remote-as-revs, ssh, remotecmd, insecure, template
287 289 debugdownload: output
@@ -1008,6 +1008,8 b' Test list of internal help commands'
1008 1008 dump information about delta chains in a revlog
1009 1009 debugdirstate
1010 1010 show the contents of the current dirstate
1011 debugdirstateignorepatternshash
1012 show the hash of ignore patterns stored in dirstate if v2,
1011 1013 debugdiscovery
1012 1014 runs the changeset discovery protocol in isolation
1013 1015 debugdownload
@@ -405,20 +405,19 b' Windows paths are accepted on input'
405 405
406 406 #if dirstate-v2
407 407
408 Check the hash of ignore patterns written in the dirstate at offset
409 12 + 20 + 20 + 4 + 4 + 4 + 4 = 68
408 Check the hash of ignore patterns written in the dirstate
410 409
411 410 $ hg status > /dev/null
412 411 $ cat .hg/testhgignore .hg/testhgignorerel .hgignore dir2/.hgignore dir1/.hgignore dir1/.hgignoretwo | $TESTDIR/f --sha1
413 412 sha1=6e315b60f15fb5dfa02be00f3e2c8f923051f5ff
414 >>> import binascii; print(binascii.hexlify(open(".hg/dirstate", "rb").read()[68:][:20]).decode())
413 $ hg debugdirstateignorepatternshash
415 414 6e315b60f15fb5dfa02be00f3e2c8f923051f5ff
416 415
417 416 $ echo rel > .hg/testhgignorerel
418 417 $ hg status > /dev/null
419 418 $ cat .hg/testhgignore .hg/testhgignorerel .hgignore dir2/.hgignore dir1/.hgignore dir1/.hgignoretwo | $TESTDIR/f --sha1
420 419 sha1=dea19cc7119213f24b6b582a4bae7b0cb063e34e
421 >>> import binascii; print(binascii.hexlify(open(".hg/dirstate", "rb").read()[68:][:20]).decode())
420 $ hg debugdirstateignorepatternshash
422 421 dea19cc7119213f24b6b582a4bae7b0cb063e34e
423 422
424 423 #endif
General Comments 0
You need to be logged in to leave comments. Login now