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 |
|
|
592 | read_len = self._nodelen * 2 | |
578 | read_len = offset + self._nodelen * 2 |
|
593 | st = self._readdirstatefile(read_len) | |
579 |
|
|
594 | l = len(st) | |
580 |
f |
|
595 | if l == read_len: | |
581 |
st = |
|
596 | self._parents = ( | |
582 | fp.close() |
|
597 | st[: self._nodelen], | |
583 | except IOError as err: |
|
598 | st[self._nodelen : 2 * self._nodelen], | |
584 |
|
|
599 | ) | |
585 |
|
|
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 |
|
|
604 | ) | |
590 |
|
|
605 | else: | |
591 |
|
|
606 | raise error.Abort( | |
592 | self._parents = ( |
|
607 | _(b'working directory state appears damaged!') | |
593 |
|
|
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 |
f |
|
635 | if self.docket.uuid: | |
620 | try: |
|
636 | # TODO: use mmap when possible | |
621 |
|
|
637 | data = self._opener.read(self.docket.data_filename()) | |
622 |
|
|
638 | else: | |
623 |
|
|
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 |
|
|
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 |
|
|
660 | self.parents(), len(packed) | |
647 |
|
|
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 |
|
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; 8 |
|
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 |
|
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:: |
|
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 |
|
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 |
|
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 = i |
|
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) = |
|
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 |
|
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