Patchwork [2,of,9,V2] localrepo: use changelog.rev instead of self.__contains__

login
register
mail settings
Submitter Katsunori FUJIWARA
Date May 2, 2015, 3:59 p.m.
Message ID <ea0832f5425148072ca0.1430582377@feefifofum>
Download mbox | patch
Permalink /patch/8853/
State Superseded
Headers show

Comments

Katsunori FUJIWARA - May 2, 2015, 3:59 p.m.
# HG changeset patch
# User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
# Date 1430582197 -32400
#      Sun May 03 00:56:37 2015 +0900
# Node ID ea0832f5425148072ca048997fb322615e90e805
# Parent  fac0b879377d014c4081940040d092469635447a
localrepo: use changelog.rev instead of self.__contains__

Before this patch, releasing store lock implies actions below, when
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 sotre lock:
resouces are acquired in "wlock => dirstateguard => store lock" order,
and are released in reversed order.

This may cause that "dirstate.p1()" still refers the changeset to be
rollback-ed at (4) above: pushing multiple patches by "hg qpush" is
typical case.

At releasing store lock, such changesets are:

  - not contained in "repo.changelog", if it is reloaded from
    ".hg/00changelog.i" 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". It prevents tests from running stably.

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 "unknown working parent" warning irregularly, this
patch uses "changelog.rev()" instead of "node in self" to examine
existence of committed changeset.
Yuya Nishihara - May 3, 2015, 10:17 a.m.
On Sun, 03 May 2015 00:59:37 +0900, FUJIWARA Katsunori wrote:
> # HG changeset patch
> # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
> # Date 1430582197 -32400
> #      Sun May 03 00:56:37 2015 +0900
> # Node ID ea0832f5425148072ca048997fb322615e90e805
> # Parent  fac0b879377d014c4081940040d092469635447a
> localrepo: use changelog.rev instead of self.__contains__
> 
> Before this patch, releasing store lock implies actions below, when
> 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 sotre lock:
> resouces are acquired in "wlock => dirstateguard => store lock" order,
> and are released in reversed order.
> 
> This may cause that "dirstate.p1()" still refers the changeset to be
> rollback-ed at (4) above: pushing multiple patches by "hg qpush" is
> typical case.
> 
> At releasing store lock, such changesets are:
> 
>   - not contained in "repo.changelog", if it is reloaded from
>     ".hg/00changelog.i" 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". It prevents tests from running stably.
> 
> 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 "unknown working parent" warning irregularly, this
> patch uses "changelog.rev()" instead of "node in self" to examine
> existence of committed changeset.
> 
> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
> --- a/mercurial/localrepo.py
> +++ b/mercurial/localrepo.py
> @@ -1521,9 +1521,12 @@
>          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:
> +            try:
> +                self.changelog.rev(ret) # check existence
>                  self.hook("commit", node=node, parent1=parent1,
>                            parent2=parent2)
> +            except error.LookupError:
> +                pass # changeset is already rollback-ed

Nit pick: perhaps changelog.hasnode() can be used here.
Katsunori FUJIWARA - May 5, 2015, 5:20 p.m.
At Sun, 3 May 2015 19:17:40 +0900,
Yuya Nishihara wrote:
> 
> On Sun, 03 May 2015 00:59:37 +0900, FUJIWARA Katsunori wrote:
> > # HG changeset patch
> > # User FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
> > # Date 1430582197 -32400
> > #      Sun May 03 00:56:37 2015 +0900
> > # Node ID ea0832f5425148072ca048997fb322615e90e805
> > # Parent  fac0b879377d014c4081940040d092469635447a
> > localrepo: use changelog.rev instead of self.__contains__
> > 
> > Before this patch, releasing store lock implies actions below, when
> > 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 sotre lock:
> > resouces are acquired in "wlock => dirstateguard => store lock" order,
> > and are released in reversed order.
> > 
> > This may cause that "dirstate.p1()" still refers the changeset to be
> > rollback-ed at (4) above: pushing multiple patches by "hg qpush" is
> > typical case.
> > 
> > At releasing store lock, such changesets are:
> > 
> >   - not contained in "repo.changelog", if it is reloaded from
> >     ".hg/00changelog.i" 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". It prevents tests from running stably.
> > 
> > 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 "unknown working parent" warning irregularly, this
> > patch uses "changelog.rev()" instead of "node in self" to examine
> > existence of committed changeset.
> > 
> > diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
> > --- a/mercurial/localrepo.py
> > +++ b/mercurial/localrepo.py
> > @@ -1521,9 +1521,12 @@
> >          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:
> > +            try:
> > +                self.changelog.rev(ret) # check existence
> >                  self.hook("commit", node=node, parent1=parent1,
> >                            parent2=parent2)
> > +            except error.LookupError:
> > +                pass # changeset is already rollback-ed
> 
> Nit pick: perhaps changelog.hasnode() can be used here.

I'll use "changelog.hasnode()" in revised series, thank you.

----------------------------------------------------------------------
[FUJIWARA Katsunori]                             foozy@lares.dti.ne.jp

Patch

diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -1521,9 +1521,12 @@ 
         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:
+            try:
+                self.changelog.rev(ret) # check existence
                 self.hook("commit", node=node, parent1=parent1,
                           parent2=parent2)
+            except error.LookupError:
+                pass # changeset is already rollback-ed
         self._afterlock(commithook)
         return ret
 
diff --git a/tests/test-mq-qpush-fail.t b/tests/test-mq-qpush-fail.t
--- a/tests/test-mq-qpush-fail.t
+++ b/tests/test-mq-qpush-fail.t
@@ -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 <<EOF
+  > 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