[PATCH 1 of 2] rebase: add config to move rebase into a single transaction

Durham Goode durham at fb.com
Tue Jul 11 17:41:19 EDT 2017



On 7/11/17 2:20 PM, Martin von Zweigbergk wrote:
> On Tue, Jul 11, 2017 at 1:54 PM, Durham Goode <durham at fb.com> wrote:
>> # HG changeset patch
>> # User Durham Goode <durham at fb.com>
>> # Date 1499805985 25200
>> #      Tue Jul 11 13:46:25 2017 -0700
>> # Node ID 46b1d80be3c6741ba69f5029a8a4315151552293
>> # Parent  062c1bde178118b7c4d5abb1ead16ac8ad494280
>> rebase: add config to move rebase into a single transaction
>>
>> This was previously landed as cf8ad0e6c0e4 but backed out in a5abaa81fa because
>> it broke hook 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 next patch adds a test covering this config.
>>
>> The old commit message was:
>>
>> Previously, rebasing would open several transaction over the course of rebasing
>> several commits. Opening a transaction can have notable overhead (like copying
>> the dirstate) which can add up when rebasing many commits.
>>
>> This patch adds a single large transaction around the actual commit rebase
>> operation, with a catch for intervention which serializes the current state if
>> we need to drop back to the terminal for user intervention. Amazingly, almost
>> all the tests seem to pass.
>>
>> On large repos with large working copies, this can speed up rebasing 7 commits
>> by 25%. I'd expect the percentage to be a bit larger for rebasing even more
>> commits.
>>
>> There are minor test changes because we're rolling back the entire transaction
>> during unexpected exceptions instead of just stopping mid-rebase, so there's no
>> more backup bundle. It also leave an unknown file in the working copy, since our
>> clean up 'hg update' doesn't delete unknown files.
>>
>> diff --git a/hgext/rebase.py b/hgext/rebase.py
>> --- a/hgext/rebase.py
>> +++ b/hgext/rebase.py
>> @@ -343,7 +343,7 @@ class rebaseruntime(object):
>>          if dest.closesbranch() and not self.keepbranchesf:
>>              self.ui.status(_('reopening closed branch head %s\n') % dest)
>>
>> -    def _performrebase(self):
>> +    def _performrebase(self, tr):
>>          repo, ui, opts = self.repo, self.ui, self.opts
>>          if self.keepbranchesf:
>>              # insert _savebranch at the start of extrafns so if
>> @@ -394,7 +394,7 @@ class rebaseruntime(object):
>>                                               self.state,
>>                                               self.destancestors,
>>                                               self.obsoletenotrebased)
>> -                self.storestatus()
>> +                self.storestatus(tr=tr)
>>                  storecollapsemsg(repo, self.collapsemsg)
>>                  if len(repo[None].parents()) == 2:
>>                      repo.ui.debug('resuming interrupted rebase\n')
>> @@ -641,6 +641,15 @@ def rebase(ui, repo, **opts):
>>        [commands]
>>        rebase.requiredest = True
>>
>> +    By default, rebase will close the transaction after each commit. For
>> +    performance purposes, you can configure rebase to use a single transaction
>> +    across the entire rebase. WARNING: This setting introduces a significant
>> +    risk of losing the work you've done in a rebase if the rebase aborts
>> +    unexpectedly::
>> +
>> +      [rebase]
>> +      singletransaction = True
>> +
>>      Return Values:
>>
>>      Returns 0 on success, 1 if nothing to rebase or there are
>> @@ -700,7 +709,21 @@ def rebase(ui, repo, **opts):
>>              if retcode is not None:
>>                  return retcode
>>
>> -        rbsrt._performrebase()
>> +        tr = None
>> +        try:
>> +            if ui.configbool('rebase', 'singletransaction'):
>> +                tr = repo.transaction('rebase')
>> +            rbsrt._performrebase(tr)
>> +            if tr:
>> +                tr.close()
>> +        except error.InterventionRequired:
>> +            if tr:
>> +                tr.close()
>> +            raise
>> +        finally:
>> +            if tr:
>> +                tr.release()
>> +
>
> Looks like you can move "if tr: tr.close()" to the finally-block?

We can't run close in the finally because if we're in the middle of an 
unhandled exception we don't know if it's safe to commit the transaction.

> Might be even clearer as (untested):
>
> # This could be in util.py
> @contextlib.contextmanager
> def nullcontextmanager():
>     yield

Hmm, that would be a convenient pattern.  I'll make that change.

> tr = nullcontextmanager()
> if ui.configbool('rebase', 'singletransaction'):
>     tr = repo.transaction('rebase')
> with tr:
>     try:
>         rbsrt._performrebase(tr)
>     except error.InterventionRequired:
>            raise
>
>
>
>>          rbsrt._finishrebase()
>>
>>  def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
>> _______________________________________________
>> Mercurial-devel mailing list
>> Mercurial-devel at mercurial-scm.org
>> https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=DwIBaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=nuarHzhP1wi1T9iURRCj1A&m=QZIPEbDD6weNDVQQGJeE-0IGuMoAwgrzj9nAJY5kjZI&s=-zqkTyghrBhFsmCYd75gqTg5TpxJVoOBzuJoi1CfPkE&e=


More information about the Mercurial-devel mailing list