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 |
|
|
|
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 |
f |
|
|
620 | try: | |
|
621 |
|
|
|
622 |
|
|
|
623 |
|
|
|
624 | except IOError as err: | |
|
625 | if err.errno != errno.ENOENT: | |
|
626 |
|
|
|
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 |
|
|
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; 8 |
|
|
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 |
|
|
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:: |
|
|
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 |
|
|
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 |
|
|
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 = i |
|
|
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) = |
|
|
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 = |
|
|
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 |
|
|
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