diff --git a/hgext3rd/undo.py b/hgext3rd/undo.py --- a/hgext3rd/undo.py +++ b/hgext3rd/undo.py @@ -21,6 +21,7 @@ hg, localrepo, lock as lockmod, + merge, obsolete, obsutil, phases, @@ -59,42 +60,66 @@ # Wrappers def _runcommandwrapper(orig, lui, repo, cmd, fullargs, *args): - # For chg, do not wrap the "serve" runcommand call + # For chg, do not wrap the "serve" runcommand call. Otherwise everything + # will be logged as side effects of a long "hg serve" command, no + # individual commands will be logged. if 'CHGINTERNALMARK' in os.environ: return orig(lui, repo, cmd, fullargs, *args) - # This wrapper executes whenever a command is run. - # Some commands (eg hg sl) don't actually modify anything - # ie can't be undone, but the command doesn't know this. + # For non-repo command, it's unnecessary to go through the undo logic + if repo is None: + return orig(lui, repo, cmd, fullargs, *args) + command = [cmd] + fullargs - # Check wether undolog is consistent - # ie check wether the undo ext was - # off before this command - if '_undologactive' not in os.environ: - changes = safelog(repo, [""]) - if changes: - _recordnewgap(repo) + # Whether something (transaction, or update) has triggered the writing of + # the *before* state to undolog or not. Possible values: + # - []: not triggered, should trigger if write operation happens + # - [True]: already triggered by this process, should also log end state + # - [False]: already triggered by a parent process, should skip logging + triggered = [] + + # '_undologactive' is set by a parent hg process with before state written + # to undolog. In this case, the current process should not write undolog. + if '_undologactive' in os.environ: + triggered.append(False) + + def log(orig, *args, **kwargs): + # trigger a log of the initial state of a repo before a command tries + # to modify that state. + if not triggered: + triggered.append(True) + os.environ['_undologactive'] = "active" - # prevent nested calls - if '_undologactive' not in os.environ: - os.environ['_undologactive'] = "active" - rootlog = True - else: - rootlog = False + # Check wether undolog is consistent + # ie check wether the undo ext was + # off before this command + changes = safelog(repo, [""]) + if changes: + _recordnewgap(repo) + + return orig(*args, **kwargs) - try: - result = orig(lui, repo, cmd, fullargs, *args) - finally: - if repo is not None: - # mercurial is bad with caches - # eg update to hidden commit will leave false cache + # Only write undo log if we know a command is going to do some writes. This + # saves time calculating visible heads if the command is read-only (ex. + # status). + # + # To detect a write command, wrap all possible entries: + # - transaction.__init__ + # - merge.update + w = extensions.wrappedfunction + with w(merge, 'update', log), w(transaction.transaction, '__init__', log): + try: + result = orig(lui, repo, cmd, fullargs, *args) + finally: + # record changes to repo + if triggered and triggered[0]: + # invalidatevolatilesets should really be done in Mercurial's + # transaction handling code. We workaround it here before that + # upstream change. repo.invalidatevolatilesets() - - # record changes to repo - if rootlog: - safelog(repo, command) - del os.environ['_undologactive'] + safelog(repo, command) + del os.environ['_undologactive'] return result diff --git a/tests/test-undo.t b/tests/test-undo.t --- a/tests/test-undo.t +++ b/tests/test-undo.t @@ -210,10 +210,29 @@ $ hg log -G -r 'olddraft(1) and public()' -T compact Test undolog lock - $ hg log --config hooks.duringundologlock="sleep 1" > /dev/null & - $ sleep 0.1 - $ hg st --time - time: real [1-9]*\..* (re) + $ cat > $TESTTMP/noopupdate.py < from __future__ import absolute_import + > from mercurial import registrar, merge, encoding + > cmdtable = {} + > command = registrar.command(cmdtable) + > def uisetup(ui): + > merge.update = lambda *args, **kwargs: None + > @command('noopupdate') + > def noopupdate(ui, repo): + > """do nothing, but triggers an undolog write""" + > merge.update(repo, repo['.'], False, False) + > EOF + $ hg noopupdate --config extensions.noopupdate=$TESTTMP/noopupdate.py --config hooks.duringundologlock="sleep 2" > /dev/null & + give "hg noop" some time to reach the code obtaining the undolog lock + $ sleep 0.2 + + "hg status" does not trigger undolog writing + $ hg status --time + time: real 0* (glob) + + "hg noopupdate" trigger undolog writing and it needs to wait + $ hg noopupdate --config extensions.noopupdate=$TESTTMP/noopupdate.py --time + time: real [1-9]\..* (re) hg undo command tests $ hg undo @@ -431,12 +450,8 @@ File corruption handling $ echo 111corruptedrevlog > .hg/undolog/index.i -#if chg -(note: chg has issues with the below test) -#else - $ hg st --debug + $ hg noopupdate --config extensions.noopupdate=$TESTTMP/noopupdate.py --debug caught revlog error. undolog/index.i was probably corrupted -#endif $ hg debugundohistory -l 0: -- gap in log --