##// END OF EJS Templates
dirstate: introduce a "tracked-key" feature...
marmoute -
r49533:568f63b5 default
parent child Browse files
Show More
@@ -0,0 +1,163 b''
1 ==============================
2 Test the "tracked key" feature
3 ==============================
4
5 The tracked key feature provide a file that get updated when the set of tracked
6 files get updated.
7
8 basic setup
9
10 $ cat << EOF >> $HGRCPATH
11 > [format]
12 > exp-dirstate-tracked-key-version=1
13 > EOF
14
15 $ hg init tracked-key-test
16 $ cd tracked-key-test
17 $ hg debugbuilddag '.+10' -n
18 $ hg log -G -T '{rev} {desc} {files}\n'
19 o 10 r10 nf10
20 |
21 o 9 r9 nf9
22 |
23 o 8 r8 nf8
24 |
25 o 7 r7 nf7
26 |
27 o 6 r6 nf6
28 |
29 o 5 r5 nf5
30 |
31 o 4 r4 nf4
32 |
33 o 3 r3 nf3
34 |
35 o 2 r2 nf2
36 |
37 o 1 r1 nf1
38 |
39 o 0 r0 nf0
40
41 $ hg up tip
42 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 $ hg files
44 nf0
45 nf1
46 nf10
47 nf2
48 nf3
49 nf4
50 nf5
51 nf6
52 nf7
53 nf8
54 nf9
55
56 key-file exists
57 -----------
58
59 The tracked key file should exist
60
61 $ ls -1 .hg/dirstate*
62 .hg/dirstate
63 .hg/dirstate-tracked-key
64
65 key-file stay the same if the tracked set is unchanged
66 ------------------------------------------------------
67
68 (copy its content for later comparison)
69
70 $ cp .hg/dirstate-tracked-key ../key-bck
71 $ echo foo >> nf0
72 $ sleep 1
73 $ hg status
74 M nf0
75 $ diff --brief .hg/dirstate-tracked-key ../key-bck
76 $ hg revert -C nf0
77 $ sleep 1
78 $ hg status
79 $ diff --brief .hg/dirstate-tracked-key ../key-bck
80
81 key-file change if the tracked set is changed manually
82 ------------------------------------------------------
83
84 adding a file to tracking
85
86 $ cp .hg/dirstate-tracked-key ../key-bck
87 $ echo x > x
88 $ hg add x
89 $ diff --brief .hg/dirstate-tracked-key ../key-bck
90 Files .hg/dirstate-tracked-key and ../key-bck differ
91 [1]
92
93 remove a file from tracking
94 (forget)
95
96 $ cp .hg/dirstate-tracked-key ../key-bck
97 $ hg forget x
98 $ diff --brief .hg/dirstate-tracked-key ../key-bck
99 Files .hg/dirstate-tracked-key and ../key-bck differ
100 [1]
101
102 (remove)
103
104 $ cp .hg/dirstate-tracked-key ../key-bck
105 $ hg remove nf1
106 $ diff --brief .hg/dirstate-tracked-key ../key-bck
107 Files .hg/dirstate-tracked-key and ../key-bck differ
108 [1]
109
110 key-file changes on revert (when applicable)
111 --------------------------------------------
112
113 $ cp .hg/dirstate-tracked-key ../key-bck
114 $ hg status
115 R nf1
116 ? x
117 $ hg revert --all
118 undeleting nf1
119 $ hg status
120 ? x
121 $ diff --brief .hg/dirstate-tracked-key ../key-bck
122 Files .hg/dirstate-tracked-key and ../key-bck differ
123 [1]
124
125
126 `hg update` does affect the key-file (when needed)
127 --------------------------------------------------
128
129 update changing the tracked set
130
131 (removing)
132
133 $ cp .hg/dirstate-tracked-key ../key-bck
134 $ hg status --rev . --rev '.#generations[-1]'
135 R nf10
136 $ hg up '.#generations[-1]'
137 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
138 $ diff --brief .hg/dirstate-tracked-key ../key-bck
139 Files .hg/dirstate-tracked-key and ../key-bck differ
140 [1]
141
142 (adding)
143
144 $ cp .hg/dirstate-tracked-key ../key-bck
145 $ hg status --rev . --rev '.#generations[1]'
146 A nf10
147 $ hg up '.#generations[1]'
148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 $ diff --brief .hg/dirstate-tracked-key ../key-bck
150 Files .hg/dirstate-tracked-key and ../key-bck differ
151 [1]
152
153 update not affecting the tracked set
154
155 $ echo foo >> nf0
156 $ hg commit -m foo
157
158 $ cp .hg/dirstate-tracked-key ../key-bck
159 $ hg status --rev . --rev '.#generations[-1]'
160 M nf0
161 $ hg up '.#generations[-1]'
162 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 $ diff --brief .hg/dirstate-tracked-key ../key-bck
@@ -1284,6 +1284,12 b' coreconfigitem('
1284 )
1284 )
1285 coreconfigitem(
1285 coreconfigitem(
1286 b'format',
1286 b'format',
1287 b'exp-dirstate-tracked-key-version',
1288 default=0,
1289 experimental=True,
1290 )
1291 coreconfigitem(
1292 b'format',
1287 b'dotencode',
1293 b'dotencode',
1288 default=True,
1294 default=True,
1289 )
1295 )
@@ -12,6 +12,7 b' import contextlib'
12 import errno
12 import errno
13 import os
13 import os
14 import stat
14 import stat
15 import uuid
15
16
16 from .i18n import _
17 from .i18n import _
17 from .pycompat import delattr
18 from .pycompat import delattr
@@ -23,6 +24,7 b' from . import ('
23 encoding,
24 encoding,
24 error,
25 error,
25 match as matchmod,
26 match as matchmod,
27 node,
26 pathutil,
28 pathutil,
27 policy,
29 policy,
28 pycompat,
30 pycompat,
@@ -99,6 +101,7 b' class dirstate(object):'
99 sparsematchfn,
101 sparsematchfn,
100 nodeconstants,
102 nodeconstants,
101 use_dirstate_v2,
103 use_dirstate_v2,
104 use_tracked_key=False,
102 ):
105 ):
103 """Create a new dirstate object.
106 """Create a new dirstate object.
104
107
@@ -107,6 +110,7 b' class dirstate(object):'
107 the dirstate.
110 the dirstate.
108 """
111 """
109 self._use_dirstate_v2 = use_dirstate_v2
112 self._use_dirstate_v2 = use_dirstate_v2
113 self._use_tracked_key = use_tracked_key
110 self._nodeconstants = nodeconstants
114 self._nodeconstants = nodeconstants
111 self._opener = opener
115 self._opener = opener
112 self._validate = validate
116 self._validate = validate
@@ -115,11 +119,15 b' class dirstate(object):'
115 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
119 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
116 # UNC path pointing to root share (issue4557)
120 # UNC path pointing to root share (issue4557)
117 self._rootdir = pathutil.normasprefix(root)
121 self._rootdir = pathutil.normasprefix(root)
122 # True is any internal state may be different
118 self._dirty = False
123 self._dirty = False
124 # True if the set of tracked file may be different
125 self._dirty_tracked_set = False
119 self._ui = ui
126 self._ui = ui
120 self._filecache = {}
127 self._filecache = {}
121 self._parentwriters = 0
128 self._parentwriters = 0
122 self._filename = b'dirstate'
129 self._filename = b'dirstate'
130 self._filename_tk = b'dirstate-tracked-key'
123 self._pendingfilename = b'%s.pending' % self._filename
131 self._pendingfilename = b'%s.pending' % self._filename
124 self._plchangecallbacks = {}
132 self._plchangecallbacks = {}
125 self._origpl = None
133 self._origpl = None
@@ -409,6 +417,7 b' class dirstate(object):'
409 if a in self.__dict__:
417 if a in self.__dict__:
410 delattr(self, a)
418 delattr(self, a)
411 self._dirty = False
419 self._dirty = False
420 self._dirty_tracked_set = False
412 self._parentwriters = 0
421 self._parentwriters = 0
413 self._origpl = None
422 self._origpl = None
414
423
@@ -446,6 +455,8 b' class dirstate(object):'
446 pre_tracked = self._map.set_tracked(filename)
455 pre_tracked = self._map.set_tracked(filename)
447 if reset_copy:
456 if reset_copy:
448 self._map.copymap.pop(filename, None)
457 self._map.copymap.pop(filename, None)
458 if pre_tracked:
459 self._dirty_tracked_set = True
449 return pre_tracked
460 return pre_tracked
450
461
451 @requires_no_parents_change
462 @requires_no_parents_change
@@ -460,6 +471,7 b' class dirstate(object):'
460 ret = self._map.set_untracked(filename)
471 ret = self._map.set_untracked(filename)
461 if ret:
472 if ret:
462 self._dirty = True
473 self._dirty = True
474 self._dirty_tracked_set = True
463 return ret
475 return ret
464
476
465 @requires_no_parents_change
477 @requires_no_parents_change
@@ -544,6 +556,13 b' class dirstate(object):'
544 # this. The test agrees
556 # this. The test agrees
545
557
546 self._dirty = True
558 self._dirty = True
559 old_entry = self._map.get(filename)
560 if old_entry is None:
561 prev_tracked = False
562 else:
563 prev_tracked = old_entry.tracked
564 if prev_tracked != wc_tracked:
565 self._dirty_tracked_set = True
547
566
548 self._map.reset_state(
567 self._map.reset_state(
549 filename,
568 filename,
@@ -702,20 +721,44 b' class dirstate(object):'
702 if not self._dirty:
721 if not self._dirty:
703 return
722 return
704
723
705 filename = self._filename
724 write_key = self._use_tracked_key and self._dirty_tracked_set
706 if tr:
725 if tr:
707 # delay writing in-memory changes out
726 # delay writing in-memory changes out
727 if write_key:
728 tr.addfilegenerator(
729 b'dirstate-0-key-pre',
730 (self._filename_tk,),
731 lambda f: self._write_tracked_key(tr, f),
732 location=b'plain',
733 )
708 tr.addfilegenerator(
734 tr.addfilegenerator(
709 b'dirstate-1-main',
735 b'dirstate-1-main',
710 (self._filename,),
736 (self._filename,),
711 lambda f: self._writedirstate(tr, f),
737 lambda f: self._writedirstate(tr, f),
712 location=b'plain',
738 location=b'plain',
713 )
739 )
740 if write_key:
741 tr.addfilegenerator(
742 b'dirstate-2-key-post',
743 (self._filename_tk,),
744 lambda f: self._write_tracked_key(tr, f),
745 location=b'plain',
746 )
714 return
747 return
715
748
716 file = lambda f: self._opener(f, b"w", atomictemp=True, checkambig=True)
749 file = lambda f: self._opener(f, b"w", atomictemp=True, checkambig=True)
750 if write_key:
751 # we change the key-file before changing the dirstate to make sure
752 # reading invalidate there cache before we start writing
753 with file(self._filename_tk) as f:
754 self._write_tracked_key(tr, f)
717 with file(self._filename) as f:
755 with file(self._filename) as f:
718 self._writedirstate(tr, f)
756 self._writedirstate(tr, f)
757 if write_key:
758 # we update the key-file after writing to make sure reader have a
759 # key that match the newly written content
760 with file(self._filename_tk) as f:
761 self._write_tracked_key(tr, f)
719
762
720 def addparentchangecallback(self, category, callback):
763 def addparentchangecallback(self, category, callback):
721 """add a callback to be called when the wd parents are changed
764 """add a callback to be called when the wd parents are changed
@@ -736,9 +779,13 b' class dirstate(object):'
736 ):
779 ):
737 callback(self, self._origpl, self._pl)
780 callback(self, self._origpl, self._pl)
738 self._origpl = None
781 self._origpl = None
739
740 self._map.write(tr, st)
782 self._map.write(tr, st)
741 self._dirty = False
783 self._dirty = False
784 self._dirty_tracked_set = False
785
786 def _write_tracked_key(self, tr, f):
787 key = node.hex(uuid.uuid4().bytes)
788 f.write(b"1\n%s\n" % key) # 1 is the format version
742
789
743 def _dirignore(self, f):
790 def _dirignore(self, f):
744 if self._ignore(f):
791 if self._ignore(f):
@@ -944,6 +944,42 b' https://www.mercurial-scm.org/wiki/Missi'
944
944
945 For a more comprehensive guide, see :hg:`help internals.dirstate-v2`.
945 For a more comprehensive guide, see :hg:`help internals.dirstate-v2`.
946
946
947 ``exp-dirstate-tracked-key-version``
948 Enable or disable the writing of "tracked key" file alongside the dirstate.
949
950 That "tracked-key" can help external automations to detect changes to the
951 set of tracked files.
952
953 Two values are currently supported:
954 - 0: no file is written (the default),
955 - 1: a file in version "1" is written.
956
957 The tracked-key is written in a new `.hg/dirstate-tracked-key`. That file
958 contains two lines:
959 - the first line is the file version (currently: 1),
960 - the second line contains the "tracked-key".
961
962 The tracked-key changes whenever the set of file tracked in the dirstate
963 changes. The general guarantees are:
964 - if the tracked key is identical, the set of tracked file MUST not have changed,
965 - if the tracked key is different, the set of tracked file MIGHT differ.
966
967 They are two "ways" to use this feature:
968
969 1) monitoring changes to the `.hg/dirstate-tracked-key`, if the file changes
970 the tracked set might have changed.
971
972 2) storing the value and comparing it to a later value. Beware that it is
973 impossible to achieve atomic writing or reading of the two files involved
974 files (`.hg/dirstate` and `.hg/dirstate-tracked-key`). So it is needed to
975 read the `tracked-key` files twice: before and after reading the tracked
976 set. The `tracked-key` is only usable as a cache key if it had the same
977 value in both cases and must be discarded otherwise.
978
979 To enforce that the `tracked-key` value can be used race-free (with double
980 reading as explained in (2)), the `.hg/dirstate-tracked-key` is written
981 twice: before and after we change the associated `.hg/dirstate` file.
982
947 ``use-persistent-nodemap``
983 ``use-persistent-nodemap``
948 Enable or disable the "persistent-nodemap" feature which improves
984 Enable or disable the "persistent-nodemap" feature which improves
949 performance if the Rust extensions are available.
985 performance if the Rust extensions are available.
@@ -1278,6 +1278,7 b' class localrepository(object):'
1278 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1278 requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
1279 requirementsmod.CHANGELOGV2_REQUIREMENT,
1279 requirementsmod.CHANGELOGV2_REQUIREMENT,
1280 requirementsmod.COPIESSDC_REQUIREMENT,
1280 requirementsmod.COPIESSDC_REQUIREMENT,
1281 requirementsmod.DIRSTATE_TRACKED_KEY_V1,
1281 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1282 requirementsmod.DIRSTATE_V2_REQUIREMENT,
1282 requirementsmod.DOTENCODE_REQUIREMENT,
1283 requirementsmod.DOTENCODE_REQUIREMENT,
1283 requirementsmod.FNCACHE_REQUIREMENT,
1284 requirementsmod.FNCACHE_REQUIREMENT,
@@ -1742,7 +1743,9 b' class localrepository(object):'
1742 """Extension point for wrapping the dirstate per-repo."""
1743 """Extension point for wrapping the dirstate per-repo."""
1743 sparsematchfn = lambda: sparse.matcher(self)
1744 sparsematchfn = lambda: sparse.matcher(self)
1744 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1745 v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
1746 tk = requirementsmod.DIRSTATE_TRACKED_KEY_V1
1745 use_dirstate_v2 = v2_req in self.requirements
1747 use_dirstate_v2 = v2_req in self.requirements
1748 use_tracked_key = tk in self.requirements
1746
1749
1747 return dirstate.dirstate(
1750 return dirstate.dirstate(
1748 self.vfs,
1751 self.vfs,
@@ -1752,6 +1755,7 b' class localrepository(object):'
1752 sparsematchfn,
1755 sparsematchfn,
1753 self.nodeconstants,
1756 self.nodeconstants,
1754 use_dirstate_v2,
1757 use_dirstate_v2,
1758 use_tracked_key=use_tracked_key,
1755 )
1759 )
1756
1760
1757 def _dirstatevalidate(self, node):
1761 def _dirstatevalidate(self, node):
@@ -3691,6 +3695,17 b' def newreporequirements(ui, createopts):'
3691 else:
3695 else:
3692 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3696 requirements.add(requirementsmod.SHARED_REQUIREMENT)
3693
3697
3698 tracked_key = ui.configint(b'format', b'exp-dirstate-tracked-key-version')
3699 if tracked_key:
3700 if tracked_key != 1:
3701 msg = _("ignoring unknown tracked key version: %d\n")
3702 hint = _(
3703 "see `hg help config.format.exp-dirstate-tracked-key-version"
3704 )
3705 ui.warn(msg % tracked_key, hint=hint)
3706 else:
3707 requirements.add(requirementsmod.DIRSTATE_TRACKED_KEY_V1)
3708
3694 return requirements
3709 return requirements
3695
3710
3696
3711
@@ -18,6 +18,7 b" DOTENCODE_REQUIREMENT = b'dotencode'"
18 STORE_REQUIREMENT = b'store'
18 STORE_REQUIREMENT = b'store'
19 FNCACHE_REQUIREMENT = b'fncache'
19 FNCACHE_REQUIREMENT = b'fncache'
20
20
21 DIRSTATE_TRACKED_KEY_V1 = b'exp-dirstate-tracked-key-v1'
21 DIRSTATE_V2_REQUIREMENT = b'dirstate-v2'
22 DIRSTATE_V2_REQUIREMENT = b'dirstate-v2'
22
23
23 # When narrowing is finalized and no longer subject to format changes,
24 # When narrowing is finalized and no longer subject to format changes,
@@ -96,6 +97,7 b' WORKING_DIR_REQUIREMENTS = {'
96 SHARED_REQUIREMENT,
97 SHARED_REQUIREMENT,
97 RELATIVE_SHARED_REQUIREMENT,
98 RELATIVE_SHARED_REQUIREMENT,
98 SHARESAFE_REQUIREMENT,
99 SHARESAFE_REQUIREMENT,
100 DIRSTATE_TRACKED_KEY_V1,
99 DIRSTATE_V2_REQUIREMENT,
101 DIRSTATE_V2_REQUIREMENT,
100 }
102 }
101
103
@@ -30,7 +30,9 b' version = 2'
30 # the changelog having been written).
30 # the changelog having been written).
31 postfinalizegenerators = {
31 postfinalizegenerators = {
32 b'bookmarks',
32 b'bookmarks',
33 b'dirstate-0-key-pre',
33 b'dirstate-1-main',
34 b'dirstate-1-main',
35 b'dirstate-2-key-post',
34 }
36 }
35
37
36 GEN_GROUP_ALL = b'all'
38 GEN_GROUP_ALL = b'all'
@@ -1599,6 +1599,8 b' Separate sections from subsections'
1599
1599
1600 "use-dirstate-v2"
1600 "use-dirstate-v2"
1601
1601
1602 "exp-dirstate-tracked-key-version"
1603
1602 "use-persistent-nodemap"
1604 "use-persistent-nodemap"
1603
1605
1604 "use-share-safe"
1606 "use-share-safe"
General Comments 0
You need to be logged in to leave comments. Login now