changeset 26634:09bb1ee7e73e

dirstate: make writing in-memory changes aware of transaction activity This patch delays writing in-memory changes out, if transaction is running. '_getfsnow()' is defined as a function, to hook it easily for ambiguous timestamp tests (see also fakedirstatewritetime.py) 'if tr:' code path in this patch is still disabled at this revision, because there is no client invoking 'dirstate.write()' with repo object. BTW, this patch changes 'dirstate.invalidate()' semantics around 'dirstate.write()' in a transaction scope: before: with repo.transaction(): dirstate.CHANGE('A') dirstate.write() # change for A is written out here dirstate.CHANGE('B') dirstate.invalidate() # discards only change for B after: with repo.transaction(): dirstate.CHANGE('A') dirstate.write() # change for A is still kept in memory dirstate.CHANGE('B') dirstate.invalidate() # discards changes for A and B Fortunately, there is no code path expecting the former, at least, in Mercurial itself, because 'dirstateguard' was introduced to remove such 'dirstate.invalidate()'.
author FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
date Wed, 14 Oct 2015 02:49:17 +0900
parents 020b12d591f3
children 79d86ab65c9d
files mercurial/dirstate.py mercurial/localrepo.py tests/fakedirstatewritetime.py
diffstat 3 files changed, 53 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/dirstate.py	Wed Oct 14 02:49:17 2015 +0900
+++ b/mercurial/dirstate.py	Wed Oct 14 02:49:17 2015 +0900
@@ -27,6 +27,15 @@
     def join(self, obj, fname):
         return obj._join(fname)
 
+def _getfsnow(vfs):
+    '''Get "now" timestamp on filesystem'''
+    tmpfd, tmpname = vfs.mkstemp()
+    try:
+        return util.statmtimesec(os.fstat(tmpfd))
+    finally:
+        os.close(tmpfd)
+        vfs.unlink(tmpname)
+
 class dirstate(object):
 
     def __init__(self, opener, ui, root, validate):
@@ -611,7 +620,7 @@
         self._pl = (parent, nullid)
         self._dirty = True
 
-    def write(self):
+    def write(self, repo=None):
         if not self._dirty:
             return
 
@@ -622,7 +631,40 @@
             import time # to avoid useless import
             time.sleep(delaywrite)
 
-        st = self._opener(self._filename, "w", atomictemp=True)
+        filename = self._filename
+        if not repo:
+            tr = None
+            if self._opener.lexists(self._pendingfilename):
+                # if pending file already exists, in-memory changes
+                # should be written into it, because it has priority
+                # to '.hg/dirstate' at reading under HG_PENDING mode
+                filename = self._pendingfilename
+        else:
+            tr = repo.currenttransaction()
+
+        if tr:
+            # 'dirstate.write()' is not only for writing in-memory
+            # changes out, but also for dropping ambiguous timestamp.
+            # delayed writing re-raise "ambiguous timestamp issue".
+            # See also the wiki page below for detail:
+            # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
+
+            # emulate dropping timestamp in 'parsers.pack_dirstate'
+            now = _getfsnow(repo.vfs)
+            dmap = self._map
+            for f, e in dmap.iteritems():
+                if e[0] == 'n' and e[3] == now:
+                    dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
+
+            # emulate that all 'dirstate.normal' results are written out
+            self._lastnormaltime = 0
+
+            # delay writing in-memory changes out
+            tr.addfilegenerator('dirstate', (self._filename,),
+                                self._writedirstate, location='plain')
+            return
+
+        st = self._opener(filename, "w", atomictemp=True)
         self._writedirstate(st)
 
     def _writedirstate(self, st):
--- a/mercurial/localrepo.py	Wed Oct 14 02:49:17 2015 +0900
+++ b/mercurial/localrepo.py	Wed Oct 14 02:49:17 2015 +0900
@@ -1000,6 +1000,11 @@
         def releasefn(tr, success):
             repo = reporef()
             if success:
+                # this should be explicitly invoked here, because
+                # in-memory changes aren't written out at closing
+                # transaction, if tr.addfilegenerator (via
+                # dirstate.write or so) isn't invoked while
+                # transaction running
                 repo.dirstate.write()
             else:
                 # prevent in-memory changes from being written out at
--- a/tests/fakedirstatewritetime.py	Wed Oct 14 02:49:17 2015 +0900
+++ b/tests/fakedirstatewritetime.py	Wed Oct 14 02:49:17 2015 +0900
@@ -5,7 +5,7 @@
 #   - 'workingctx._checklookup()' (= 'repo.status()')
 #   - 'committablectx.markcommitted()'
 
-from mercurial import context, extensions, parsers, util
+from mercurial import context, dirstate, extensions, parsers, util
 
 def pack_dirstate(fakenow, orig, dmap, copymap, pl, now):
     # execute what original parsers.pack_dirstate should do actually
@@ -34,13 +34,16 @@
     fakenow = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0]
 
     orig_pack_dirstate = parsers.pack_dirstate
+    orig_dirstate_getfsnow = dirstate._getfsnow
     wrapper = lambda *args: pack_dirstate(fakenow, orig_pack_dirstate, *args)
 
     parsers.pack_dirstate = wrapper
+    dirstate._getfsnow = lambda *args: fakenow
     try:
         return func()
     finally:
         parsers.pack_dirstate = orig_pack_dirstate
+        dirstate._getfsnow = orig_dirstate_getfsnow
 
 def _checklookup(orig, workingctx, files):
     ui = workingctx.repo().ui