# HG changeset patch # User Durham Goode # Date 1489189949 28800 # Node ID 68474b72ea63a3ddfaa4c3c95357c5f462052d61 # Parent 16f8107a489c7204ee4e2eb86385a1b48af76c5b 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. diff -r 16f8107a489c -r 68474b72ea63 hgext/histedit.py --- 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)