rebase: use one dirstateguard for when using rebase.singletransaction
authorDurham Goode <durham@fb.com>
Thu, 20 Jul 2017 01:30:41 -0700
changeset 33619 609606d21765
parent 33610 658524d45af0
child 33664 f3407d56a6e8
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
hgext/rebase.py
mercurial/dirstateguard.py
mercurial/util.py
tests/test-rebase-base.t
--- 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 ..