# HG changeset patch # User FUJIWARA Katsunori # Date 1444758557 -32400 # Node ID 09bb1ee7e73e057179964ae51c9217da4dc76e61 # Parent 020b12d591f35ba6c65554bb36533070a9ba3985 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()'. diff -r 020b12d591f3 -r 09bb1ee7e73e mercurial/dirstate.py --- 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): diff -r 020b12d591f3 -r 09bb1ee7e73e mercurial/localrepo.py --- 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 diff -r 020b12d591f3 -r 09bb1ee7e73e tests/fakedirstatewritetime.py --- 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