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()'.
This commit is contained in:
FUJIWARA Katsunori 2015-10-14 02:49:17 +09:00
parent bde9721b18
commit 2ef2ab5d6d
3 changed files with 53 additions and 3 deletions

View File

@ -27,6 +27,15 @@ class rootcache(filecache):
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 @@ class dirstate(object):
self._pl = (parent, nullid)
self._dirty = True
def write(self):
def write(self, repo=None):
if not self._dirty:
return
@ -622,7 +631,40 @@ class dirstate(object):
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):

View File

@ -1000,6 +1000,11 @@ class localrepository(object):
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

View File

@ -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 @@ def fakewrite(ui, func):
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