# HG changeset patch # User FUJIWARA Katsunori # Date 1430968030 -32400 # Node ID 7df090c9c9fefe5ac9a66a9e82a36ccab11bddd7 # Parent 4169a4f8354861d3a04a4f8a8911a06488837496 localrepo: use changelog.hasnode instead of self.__contains__ Before this patch, releasing the store lock implies the actions below, when the transaction is aborted: 1. "commithook()" scheduled in "localrepository.commit()" is invoked 2. "changectx.__init__()" is invoked via "self.__contains__()" 3. specified ID is examined against "repo.dirstate.p1()" 4. validation function is invoked in "dirstate.p1()" In subsequent patches, "dirstate.invalidate()" invocations for discarding changes are replaced with "dirstateguard", but discarding changes by "dirstateguard" is executed after releasing the store lock: resources are acquired in "wlock => dirstateguard => store lock" order, and are released in reverse order. This may cause that "dirstate.p1()" still refers to the changeset to be rolled-back at (4) above: pushing multiple patches by "hg qpush" is a typical case. When releasing the store lock, such changesets are: - not contained in "repo.changelog", if it is reloaded from ".hg/00changelog.i", as that file was already truncated by "transaction.abort()" - still contained in it, otherwise (this "dirty read" problem is discussed in "Transaction Plan" http://mercurial.selenic.com/wiki/TransactionPlan) Validation function shows "unknown working parent" warning in the former case, but reloading "repo.changelog" depends on the timestamp of ".hg/00changelog.i". This causes occasional test failures. In the case of scheduled "commithook()", it just wants to examine whether "node ID" of committed changeset is still valid or not. Other examinations implied in "changectx.__init__()" are meaningless. To avoid showing the "unknown working parent" warning irregularly, this patch uses "changelog.hasnode()" instead of "node in self" to examine existence of committed changeset. diff -r 4169a4f83548 -r 7df090c9c9fe mercurial/localrepo.py --- a/mercurial/localrepo.py Thu May 07 12:07:10 2015 +0900 +++ b/mercurial/localrepo.py Thu May 07 12:07:10 2015 +0900 @@ -1517,7 +1517,7 @@ def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2): # hack for command that use a temporary commit (eg: histedit) # temporary commit got stripped before hook release - if node in self: + if self.changelog.hasnode(ret): self.hook("commit", node=node, parent1=parent1, parent2=parent2) self._afterlock(commithook) diff -r 4169a4f83548 -r 7df090c9c9fe tests/test-mq-qpush-fail.t --- a/tests/test-mq-qpush-fail.t Thu May 07 12:07:10 2015 +0900 +++ b/tests/test-mq-qpush-fail.t Thu May 07 12:07:10 2015 +0900 @@ -34,7 +34,23 @@ $ $PYTHON -c 'print "\xe9"' > message $ cat .hg/patches/bad-patch >> message $ mv message .hg/patches/bad-patch - $ hg qpush -a && echo 'qpush succeeded?!' + $ cat > $TESTTMP/wrapplayback.py < import os + > from mercurial import extensions, transaction + > def wrapplayback(orig, + > journal, report, opener, vfsmap, entries, backupentries, + > unlink=True): + > orig(journal, report, opener, vfsmap, entries, backupentries, unlink) + > # Touching files truncated at "transaction.abort" causes + > # forcible re-loading invalidated filecache properties + > # (including repo.changelog) + > for f, o, _ignore in entries: + > if o or not unlink: + > os.utime(opener.join(f), (0.0, 0.0)) + > def extsetup(ui): + > extensions.wrapfunction(transaction, '_playback', wrapplayback) + > EOF + $ hg qpush -a --config extensions.wrapplayback=$TESTTMP/wrapplayback.py && echo 'qpush succeeded?!' applying patch1 applying patch2 applying bad-patch