Mercurial > hg
changeset 31513:68474b72ea63
histedit: add histedit.singletransaction config option
This adds an option (which defaults to False) to run entire histedits in a
single transaction. This results in 20-25% faster histedits in large repos where
transaction startup cost is expensive.
I didn't want to enable this by default because it has some unfortunate side
effects. For instance, if a pretxncommit hook throws midway through the
histedit, it will rollback the entire histedit and lose any progress the user
had made. Same if the user aborts editting a commit message. It's still worth
turning this on for large repos, but probably not for normal sized repos.
Long term, once we have inmemory merging, we could do the entire histedit in
memory, without a transaction, then we could selectively rollback just parts of
it in the event of an exception.
Tested it by running the tests with
`--extra-config-opt=histedit.singletransaction=True`. The only failure was
related to the hook rollback issue I mention above.
author | Durham Goode <durham@fb.com> |
---|---|
date | Fri, 10 Mar 2017 15:52:29 -0800 |
parents | 16f8107a489c |
children | 2519994d25ca |
files | hgext/histedit.py |
diffstat | 1 files changed, 49 insertions(+), 12 deletions(-) [+] |
line wrap: on
line diff
--- a/hgext/histedit.py Fri Mar 10 15:43:31 2017 -0800 +++ b/hgext/histedit.py Fri Mar 10 15:52:29 2017 -0800 @@ -168,6 +168,15 @@ [histedit] dropmissing = True +By default, histedit will close the transaction after each action. For +performance purposes, you can configure histedit to use a single transaction +across the entire histedit. WARNING: This setting introduces a significant risk +of losing the work you've done in a histedit if the histedit aborts +unexpectedly:: + + [histedit] + singletransaction = True + """ from __future__ import absolute_import @@ -269,6 +278,7 @@ self.lock = lock self.wlock = wlock self.backupfile = None + self.tr = None if replacements is None: self.replacements = [] else: @@ -1098,18 +1108,45 @@ total = len(state.actions) pos = 0 - while state.actions: - state.write() - actobj = state.actions[0] - pos += 1 - ui.progress(_("editing"), pos, actobj.torule(), - _('changes'), total) - ui.debug('histedit: processing %s %s\n' % (actobj.verb,\ - actobj.torule())) - parentctx, replacement_ = actobj.run() - state.parentctxnode = parentctx.node() - state.replacements.extend(replacement_) - state.actions.pop(0) + state.tr = None + + # Force an initial state file write, so the user can run --abort/continue + # even if there's an exception before the first transaction serialize. + state.write() + try: + # Don't use singletransaction by default since it rolls the entire + # transaction back if an unexpected exception happens (like a + # pretxncommit hook throws, or the user aborts the commit msg editor). + if ui.configbool("histedit", "singletransaction", False): + # Don't use a 'with' for the transaction, since actions may close + # and reopen a transaction. For example, if the action executes an + # external process it may choose to commit the transaction first. + state.tr = repo.transaction('histedit') + + while state.actions: + state.write(tr=state.tr) + actobj = state.actions[0] + pos += 1 + ui.progress(_("editing"), pos, actobj.torule(), + _('changes'), total) + ui.debug('histedit: processing %s %s\n' % (actobj.verb,\ + actobj.torule())) + parentctx, replacement_ = actobj.run() + state.parentctxnode = parentctx.node() + state.replacements.extend(replacement_) + state.actions.pop(0) + + if state.tr is not None: + state.tr.close() + except error.InterventionRequired: + if state.tr is not None: + state.tr.close() + raise + except Exception: + if state.tr is not None: + state.tr.abort() + raise + state.write() ui.progress(_("editing"), None)