[Updated] D12124: dirstate: introduce a "tracked-key" feature

marmoute (Pierre-Yves David) phabricator at mercurial-scm.org
Thu Feb 3 16:55:08 UTC 2022


Closed by commit rHG568f63b5a30f: dirstate: introduce a "tracked-key" feature (authored by marmoute).
This revision was automatically updated to reflect the committed changes.

CHANGED PRIOR TO COMMIT
  https://phab.mercurial-scm.org/D12124?vs=32020&id=32042#toc

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D12124?vs=32020&id=32042

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D12124/new/

REVISION DETAIL
  https://phab.mercurial-scm.org/D12124

AFFECTED FILES
  mercurial/configitems.py
  mercurial/dirstate.py
  mercurial/helptext/config.txt
  mercurial/localrepo.py
  mercurial/requirements.py
  mercurial/transaction.py
  tests/test-help.t
  tests/test-status-tracked-key.t

CHANGE DETAILS

diff --git a/tests/test-status-tracked-key.t b/tests/test-status-tracked-key.t
new file mode 100644
--- /dev/null
+++ b/tests/test-status-tracked-key.t
@@ -0,0 +1,163 @@
+==============================
+Test the "tracked key" feature
+==============================
+
+The tracked key feature provide a file that get updated when the set of tracked
+files get updated.
+
+basic setup
+
+  $ cat << EOF >> $HGRCPATH
+  > [format]
+  > exp-dirstate-tracked-key-version=1
+  > EOF
+
+  $ hg init tracked-key-test
+  $ cd tracked-key-test
+  $ hg debugbuilddag '.+10' -n
+  $ hg log -G -T '{rev} {desc} {files}\n'
+  o  10 r10 nf10
+  |
+  o  9 r9 nf9
+  |
+  o  8 r8 nf8
+  |
+  o  7 r7 nf7
+  |
+  o  6 r6 nf6
+  |
+  o  5 r5 nf5
+  |
+  o  4 r4 nf4
+  |
+  o  3 r3 nf3
+  |
+  o  2 r2 nf2
+  |
+  o  1 r1 nf1
+  |
+  o  0 r0 nf0
+  
+  $ hg up tip
+  11 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg files
+  nf0
+  nf1
+  nf10
+  nf2
+  nf3
+  nf4
+  nf5
+  nf6
+  nf7
+  nf8
+  nf9
+
+key-file exists
+-----------
+
+The tracked key file should exist
+
+  $ ls -1 .hg/dirstate*
+  .hg/dirstate
+  .hg/dirstate-tracked-key
+
+key-file stay the same if the tracked set is unchanged
+------------------------------------------------------
+
+(copy its content for later comparison)
+
+  $ cp .hg/dirstate-tracked-key ../key-bck
+  $ echo foo >> nf0
+  $ sleep 1
+  $ hg status
+  M nf0
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
+  $ hg revert -C nf0
+  $ sleep 1
+  $ hg status
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
+
+key-file change if the tracked set is changed manually
+------------------------------------------------------
+
+adding a file to tracking
+
+  $ cp .hg/dirstate-tracked-key ../key-bck
+  $ echo x > x
+  $ hg add x
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
+  Files .hg/dirstate-tracked-key and ../key-bck differ
+  [1]
+
+remove a file from tracking
+(forget)
+
+  $ cp .hg/dirstate-tracked-key ../key-bck
+  $ hg forget x
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
+  Files .hg/dirstate-tracked-key and ../key-bck differ
+  [1]
+
+(remove)
+
+  $ cp .hg/dirstate-tracked-key ../key-bck
+  $ hg remove nf1
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
+  Files .hg/dirstate-tracked-key and ../key-bck differ
+  [1]
+
+key-file changes on revert (when applicable)
+--------------------------------------------
+
+  $ cp .hg/dirstate-tracked-key ../key-bck
+  $ hg status
+  R nf1
+  ? x
+  $ hg revert --all
+  undeleting nf1
+  $ hg status
+  ? x
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
+  Files .hg/dirstate-tracked-key and ../key-bck differ
+  [1]
+
+
+`hg update` does affect the key-file (when needed)
+--------------------------------------------------
+
+update changing the tracked set
+
+(removing)
+
+  $ cp .hg/dirstate-tracked-key ../key-bck
+  $ hg status --rev . --rev '.#generations[-1]'
+  R nf10
+  $ hg up '.#generations[-1]'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
+  Files .hg/dirstate-tracked-key and ../key-bck differ
+  [1]
+
+(adding)
+
+  $ cp .hg/dirstate-tracked-key ../key-bck
+  $ hg status --rev . --rev '.#generations[1]'
+  A nf10
+  $ hg up '.#generations[1]'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
+  Files .hg/dirstate-tracked-key and ../key-bck differ
+  [1]
+
+update not affecting the tracked set
+
+  $ echo foo >> nf0
+  $ hg commit -m foo
+
+  $ cp .hg/dirstate-tracked-key ../key-bck
+  $ hg status --rev . --rev '.#generations[-1]'
+  M nf0
+  $ hg up '.#generations[-1]'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ diff --brief .hg/dirstate-tracked-key ../key-bck
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -1599,6 +1599,8 @@
   
       "use-dirstate-v2"
   
+      "exp-dirstate-tracked-key-version"
+  
       "use-persistent-nodemap"
   
       "use-share-safe"
diff --git a/mercurial/transaction.py b/mercurial/transaction.py
--- a/mercurial/transaction.py
+++ b/mercurial/transaction.py
@@ -30,7 +30,9 @@
 # the changelog having been written).
 postfinalizegenerators = {
     b'bookmarks',
+    b'dirstate-0-key-pre',
     b'dirstate-1-main',
+    b'dirstate-2-key-post',
 }
 
 GEN_GROUP_ALL = b'all'
diff --git a/mercurial/requirements.py b/mercurial/requirements.py
--- a/mercurial/requirements.py
+++ b/mercurial/requirements.py
@@ -18,6 +18,7 @@
 STORE_REQUIREMENT = b'store'
 FNCACHE_REQUIREMENT = b'fncache'
 
+DIRSTATE_TRACKED_KEY_V1 = b'exp-dirstate-tracked-key-v1'
 DIRSTATE_V2_REQUIREMENT = b'dirstate-v2'
 
 # When narrowing is finalized and no longer subject to format changes,
@@ -96,6 +97,7 @@
     SHARED_REQUIREMENT,
     RELATIVE_SHARED_REQUIREMENT,
     SHARESAFE_REQUIREMENT,
+    DIRSTATE_TRACKED_KEY_V1,
     DIRSTATE_V2_REQUIREMENT,
 }
 
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1278,6 +1278,7 @@
         requirementsmod.BOOKMARKS_IN_STORE_REQUIREMENT,
         requirementsmod.CHANGELOGV2_REQUIREMENT,
         requirementsmod.COPIESSDC_REQUIREMENT,
+        requirementsmod.DIRSTATE_TRACKED_KEY_V1,
         requirementsmod.DIRSTATE_V2_REQUIREMENT,
         requirementsmod.DOTENCODE_REQUIREMENT,
         requirementsmod.FNCACHE_REQUIREMENT,
@@ -1742,7 +1743,9 @@
         """Extension point for wrapping the dirstate per-repo."""
         sparsematchfn = lambda: sparse.matcher(self)
         v2_req = requirementsmod.DIRSTATE_V2_REQUIREMENT
+        tk = requirementsmod.DIRSTATE_TRACKED_KEY_V1
         use_dirstate_v2 = v2_req in self.requirements
+        use_tracked_key = tk in self.requirements
 
         return dirstate.dirstate(
             self.vfs,
@@ -1752,6 +1755,7 @@
             sparsematchfn,
             self.nodeconstants,
             use_dirstate_v2,
+            use_tracked_key=use_tracked_key,
         )
 
     def _dirstatevalidate(self, node):
@@ -3691,6 +3695,17 @@
         else:
             requirements.add(requirementsmod.SHARED_REQUIREMENT)
 
+    tracked_key = ui.configint(b'format', b'exp-dirstate-tracked-key-version')
+    if tracked_key:
+        if tracked_key != 1:
+            msg = _("ignoring unknown tracked key version: %d\n")
+            hint = _(
+                "see `hg help config.format.exp-dirstate-tracked-key-version"
+            )
+            ui.warn(msg % tracked_key, hint=hint)
+        else:
+            requirements.add(requirementsmod.DIRSTATE_TRACKED_KEY_V1)
+
     return requirements
 
 
diff --git a/mercurial/helptext/config.txt b/mercurial/helptext/config.txt
--- a/mercurial/helptext/config.txt
+++ b/mercurial/helptext/config.txt
@@ -944,6 +944,42 @@
 
     For a more comprehensive guide, see :hg:`help internals.dirstate-v2`.
 
+``exp-dirstate-tracked-key-version``
+    Enable or disable the writing of "tracked key" file alongside the dirstate.
+
+    That "tracked-key" can help external automations to detect changes to the
+    set of tracked files.
+
+    Two values are currently supported:
+    - 0: no file is written (the default),
+    - 1: a file in version "1" is written.
+
+    The tracked-key is written in a new `.hg/dirstate-tracked-key`. That file
+    contains two lines:
+    - the first line is the file version (currently: 1),
+    - the second line contains the "tracked-key".
+
+    The tracked-key changes whenever the set of file tracked in the dirstate
+    changes. The general guarantees are:
+    - if the tracked key is identical, the set of tracked file MUST not have changed,
+    - if the tracked key is different, the set of tracked file MIGHT differ.
+
+    They are two "ways" to use this feature:
+
+    1) monitoring changes to the `.hg/dirstate-tracked-key`, if the file changes
+    the tracked set might have changed.
+
+    2) storing the value and comparing it to a later value. Beware that it is
+    impossible to achieve atomic writing or reading of the two files involved
+    files (`.hg/dirstate` and `.hg/dirstate-tracked-key`). So it is needed to
+    read the `tracked-key` files twice: before and after reading the tracked
+    set. The `tracked-key` is only usable as a cache key if it had the same
+    value in both cases and must be discarded otherwise.
+
+    To enforce that the `tracked-key` value can be used race-free (with double
+    reading as explained in (2)), the `.hg/dirstate-tracked-key` is written
+    twice: before and after we change the associated `.hg/dirstate` file.
+
 ``use-persistent-nodemap``
     Enable or disable the "persistent-nodemap" feature which improves
     performance if the Rust extensions are available.
diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -12,6 +12,7 @@
 import errno
 import os
 import stat
+import uuid
 
 from .i18n import _
 from .pycompat import delattr
@@ -23,6 +24,7 @@
     encoding,
     error,
     match as matchmod,
+    node,
     pathutil,
     policy,
     pycompat,
@@ -99,6 +101,7 @@
         sparsematchfn,
         nodeconstants,
         use_dirstate_v2,
+        use_tracked_key=False,
     ):
         """Create a new dirstate object.
 
@@ -107,6 +110,7 @@
         the dirstate.
         """
         self._use_dirstate_v2 = use_dirstate_v2
+        self._use_tracked_key = use_tracked_key
         self._nodeconstants = nodeconstants
         self._opener = opener
         self._validate = validate
@@ -115,11 +119,15 @@
         # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
         # UNC path pointing to root share (issue4557)
         self._rootdir = pathutil.normasprefix(root)
+        # True is any internal state may be different
         self._dirty = False
+        # True if the set of tracked file may be different
+        self._dirty_tracked_set = False
         self._ui = ui
         self._filecache = {}
         self._parentwriters = 0
         self._filename = b'dirstate'
+        self._filename_tk = b'dirstate-tracked-key'
         self._pendingfilename = b'%s.pending' % self._filename
         self._plchangecallbacks = {}
         self._origpl = None
@@ -409,6 +417,7 @@
             if a in self.__dict__:
                 delattr(self, a)
         self._dirty = False
+        self._dirty_tracked_set = False
         self._parentwriters = 0
         self._origpl = None
 
@@ -446,6 +455,8 @@
         pre_tracked = self._map.set_tracked(filename)
         if reset_copy:
             self._map.copymap.pop(filename, None)
+        if pre_tracked:
+            self._dirty_tracked_set = True
         return pre_tracked
 
     @requires_no_parents_change
@@ -460,6 +471,7 @@
         ret = self._map.set_untracked(filename)
         if ret:
             self._dirty = True
+            self._dirty_tracked_set = True
         return ret
 
     @requires_no_parents_change
@@ -544,6 +556,13 @@
         # this. The test agrees
 
         self._dirty = True
+        old_entry = self._map.get(filename)
+        if old_entry is None:
+            prev_tracked = False
+        else:
+            prev_tracked = old_entry.tracked
+        if prev_tracked != wc_tracked:
+            self._dirty_tracked_set = True
 
         self._map.reset_state(
             filename,
@@ -702,20 +721,44 @@
         if not self._dirty:
             return
 
-        filename = self._filename
+        write_key = self._use_tracked_key and self._dirty_tracked_set
         if tr:
             # delay writing in-memory changes out
+            if write_key:
+                tr.addfilegenerator(
+                    b'dirstate-0-key-pre',
+                    (self._filename_tk,),
+                    lambda f: self._write_tracked_key(tr, f),
+                    location=b'plain',
+                )
             tr.addfilegenerator(
                 b'dirstate-1-main',
                 (self._filename,),
                 lambda f: self._writedirstate(tr, f),
                 location=b'plain',
             )
+            if write_key:
+                tr.addfilegenerator(
+                    b'dirstate-2-key-post',
+                    (self._filename_tk,),
+                    lambda f: self._write_tracked_key(tr, f),
+                    location=b'plain',
+                )
             return
 
         file = lambda f: self._opener(f, b"w", atomictemp=True, checkambig=True)
+        if write_key:
+            # we change the key-file before changing the dirstate to make sure
+            # reading invalidate there cache before we start writing
+            with file(self._filename_tk) as f:
+                self._write_tracked_key(tr, f)
         with file(self._filename) as f:
             self._writedirstate(tr, f)
+        if write_key:
+            # we update the key-file after writing to make sure reader have a
+            # key that match the newly written content
+            with file(self._filename_tk) as f:
+                self._write_tracked_key(tr, f)
 
     def addparentchangecallback(self, category, callback):
         """add a callback to be called when the wd parents are changed
@@ -736,9 +779,13 @@
             ):
                 callback(self, self._origpl, self._pl)
             self._origpl = None
-
         self._map.write(tr, st)
         self._dirty = False
+        self._dirty_tracked_set = False
+
+    def _write_tracked_key(self, tr, f):
+        key = node.hex(uuid.uuid4().bytes)
+        f.write(b"1\n%s\n" % key)  # 1 is the format version
 
     def _dirignore(self, f):
         if self._ignore(f):
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -1284,6 +1284,12 @@
 )
 coreconfigitem(
     b'format',
+    b'exp-dirstate-tracked-key-version',
+    default=0,
+    experimental=True,
+)
+coreconfigitem(
+    b'format',
     b'dotencode',
     default=True,
 )



To: marmoute, #hg-reviewers, Alphare
Cc: Alphare, mercurial-patches
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurial-scm.org/pipermail/mercurial-patches/attachments/20220203/c553fb60/attachment-0002.html>


More information about the Mercurial-patches mailing list