rebase: use one dirstateguard for when using rebase.singletransaction
This was previously landed as
2519994d25ca but backed out in
b63351f6a2 because
it broke hooks mid-rebase and caused conflict resolution data loss in the event
of unexpected exceptions. This new version adds the behavior back but behind a
config flag, since the performance improvement is notable in large repositories.
The old commit message was:
Recently we switched rebases to run the entire rebase inside a single
transaction, which dramatically improved the speed of rebases in repos with
large working copies. Let's also move the dirstate into a single dirstateguard
to get the same benefits. This let's us avoid serializing the dirstate after
each commit.
In a large repo, rebasing 27 commits is sped up by about 20%.
I believe the test changes are because us touching the dirstate gave the
transaction something to actually rollback.
(grafted from
9e3dc3a1638b9754b58a0cb26aaa75d868058109)
(grafted from
7d38b41d2266d9a02a15c64229fae0da5738dcec)
Differential Revision: https://phab.mercurial-scm.org/D135
--- a/hgext/rebase.py Tue Aug 01 10:14:25 2017 -0400
+++ b/hgext/rebase.py Thu Jul 20 01:30:41 2017 -0700
@@ -479,12 +479,17 @@
editopt = True
editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
revtoreuse = max(self.state)
- newnode = concludenode(repo, revtoreuse, p1, self.external,
- commitmsg=commitmsg,
- extrafn=_makeextrafn(self.extrafns),
- editor=editor,
- keepbranches=self.keepbranchesf,
- date=self.date)
+
+ dsguard = None
+ if ui.configbool('rebase', 'singletransaction'):
+ dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+ with util.acceptintervention(dsguard):
+ newnode = concludenode(repo, revtoreuse, p1, self.external,
+ commitmsg=commitmsg,
+ extrafn=_makeextrafn(self.extrafns),
+ editor=editor,
+ keepbranches=self.keepbranchesf,
+ date=self.date)
if newnode is None:
newrev = self.dest
else:
@@ -711,10 +716,16 @@
return retcode
tr = None
- if ui.configbool('rebase', 'singletransaction'):
+ dsguard = None
+
+ singletr = ui.configbool('rebase', 'singletransaction')
+ if singletr:
tr = repo.transaction('rebase')
with util.acceptintervention(tr):
- rbsrt._performrebase(tr)
+ if singletr:
+ dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+ with util.acceptintervention(dsguard):
+ rbsrt._performrebase(tr)
rbsrt._finishrebase()
@@ -841,8 +852,10 @@
'''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
but also store useful information in extra.
Return node of committed revision.'''
- dsguard = dirstateguard.dirstateguard(repo, 'rebase')
- try:
+ dsguard = util.nullcontextmanager()
+ if not repo.ui.configbool('rebase', 'singletransaction'):
+ dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+ with dsguard:
repo.setparents(repo[p1].node(), repo[p2].node())
ctx = repo[rev]
if commitmsg is None:
@@ -864,10 +877,7 @@
date=date, extra=extra, editor=editor)
repo.dirstate.setbranch(repo[newnode].branch())
- dsguard.close()
return newnode
- finally:
- release(dsguard)
def rebasenode(repo, rev, p1, base, state, collapse, dest):
'Rebase a single revision rev on top of p1 using base as merge ancestor'
--- a/mercurial/dirstateguard.py Tue Aug 01 10:14:25 2017 -0400
+++ b/mercurial/dirstateguard.py Thu Jul 20 01:30:41 2017 -0700
@@ -43,6 +43,16 @@
# ``release(tr, ....)``.
self._abort()
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ try:
+ if exc_type is None:
+ self.close()
+ finally:
+ self.release()
+
def close(self):
if not self._active: # already inactivated
msg = (_("can't close already inactivated backup: %s")
--- a/mercurial/util.py Tue Aug 01 10:14:25 2017 -0400
+++ b/mercurial/util.py Thu Jul 20 01:30:41 2017 -0700
@@ -602,6 +602,10 @@
finally:
tr.release()
+@contextlib.contextmanager
+def nullcontextmanager():
+ yield
+
class _lrucachenode(object):
"""A node in a doubly linked list.
--- a/tests/test-rebase-base.t Tue Aug 01 10:14:25 2017 -0400
+++ b/tests/test-rebase-base.t Thu Jul 20 01:30:41 2017 -0700
@@ -379,3 +379,40 @@
/
o 0: A
+Rebasing using a single transaction
+
+ $ hg init singletr && cd singletr
+ $ cat >> .hg/hgrc <<EOF
+ > [rebase]
+ > singletransaction=True
+ > EOF
+ $ hg debugdrawdag <<'EOF'
+ > Z
+ > |
+ > | D
+ > | |
+ > | C
+ > | |
+ > Y B
+ > |/
+ > A
+ > EOF
+- We should only see two status stored messages. One from the start, one from
+- the end.
+ $ hg rebase --debug -b D -d Z | grep 'status stored'
+ rebase status stored
+ rebase status stored
+ $ hg tglog
+ o 5: D
+ |
+ o 4: C
+ |
+ o 3: B
+ |
+ o 2: Z
+ |
+ o 1: Y
+ |
+ o 0: A
+
+ $ cd ..