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