Mercurial > hg
view hgext/uncommit.py @ 46018:8b0a3ff5ed12
statprof: separate functions and "line", assume 4 digit line numbers
Previously, the profile output looked like this (I've removed many lines that
are mostly inconsequential):
```
| 100.0% 0.02s hg: <module> line 43: dispatch.run()
| 100.0% 0.02s dispatch.py: run line 115: status = dispatch(req)
| 100.0% 0.02s dispatch.py: _runcatchfunc line 432: return _dispatch(req)
\ 50.0% 0.01s dispatch.py: _dispatch line 1228: return runcommand(
| 50.0% 0.01s dispatch.py: runcommand line 883: ret = _runcommand(ui, optio...
| 50.0% 0.01s dispatch.py: _runcommand line 1240: return cmdfunc()
| 50.0% 0.01s localrepo.py: __getitem__ line 1670: quick_access = self._quick_...
| 50.0% 0.01s localrepo.py: _quick_access_changeidline 1650: return self._quick_access_c...
| 50.0% 0.01s localrepo.py: __get__ line 179: return getattr(unfi, self.n...
| 50.0% 0.01s util.py: __get__ line 1747: result = self.func(obj)
| 50.0% 0.01s localrepo.py: _quick_access_changeid_wcline 1611: cl = self.unfiltered().chan...
| 50.0% 0.01s localrepo.py: __get__ line 110: return super(_basefilecache...
| 50.0% 0.01s util.py: __getattribute__line 245: self.__spec__.loader.exec_m...
| 50.0% 0.01s <frozen importlib._bootstrap_external>: exec_moduleline 783:
| 50.0% 0.01s <frozen importlib._bootstrap>: _call_with_frames_removedline 219:
| 50.0% 0.01s changelog.py: <module> line 376: class changelog(revlog.revl...
| 50.0% 0.01s util.py: __getattribute__line 245: self.__spec__.loader.exec_m...
| 50.0% 0.01s <frozen importlib._bootstrap_external>: exec_moduleline 779:
| 50.0% 0.01s <frozen importlib._bootstrap_external>: get_codeline 868:
| 50.0% 0.01s <frozen importlib._bootstrap_external>: path_statsline 1012:
| 50.0% 0.01s <frozen importlib._bootstrap_external>: _path_statline 87:
```
This has a few problems, though I'm only addressing some of them.
1. If the stuff before "line ###" is long, there's no separation between the
function name and the "line" string.
2. If the stuff before "line ###" is really long, there's excessive separation
between the "line" string and the line number.
3. We frequently have 4-digit line numbers, the code on the right wasn't
dynamically indented and ended up quite messy looking.
To solve these problems, I've added a ", " prefix before "line" iff it would
otherwise not have any separation such as spaces. I've added a 'max' so that we
never use a negative width (which is the cause of problem #2 above), and I've
added a default assumption of 4 digit line numbers (but again using a 'max' so
this shouldn't cause problems if we go beyond that.
With these changes, it now looks like this:
```
| 100.0% 0.02s hg: <module> line 43: dispatch.run()
| 100.0% 0.02s dispatch.py: run line 115: status = dispatch(req)
| 100.0% 0.02s dispatch.py: _runcatchfunc line 432: return _dispatch(req)
\ 50.0% 0.01s dispatch.py: _dispatch line 1228: return runcommand(
| 50.0% 0.01s dispatch.py: runcommand line 883: ret = _runcommand(ui, optio...
| 50.0% 0.01s dispatch.py: _runcommand line 1240: return cmdfunc()
| 50.0% 0.01s localrepo.py: __getitem__ line 1670: quick_access = self._quick_...
| 50.0% 0.01s localrepo.py: _quick_access_changeid, line 1650: return self._quick_access_c...
| 50.0% 0.01s localrepo.py: __get__ line 179: return getattr(unfi, self.n...
| 50.0% 0.01s util.py: __get__ line 1747: result = self.func(obj)
| 50.0% 0.01s localrepo.py: _quick_access_changeid_wc, line 1611: cl = self.unfiltered().chan...
| 50.0% 0.01s localrepo.py: __get__ line 110: return super(_basefilecache...
| 50.0% 0.01s util.py: __getattribute__, line 245: self.__spec__.loader.exec_m...
| 50.0% 0.01s <frozen importlib._bootstrap_external>: exec_module, line 783:
| 50.0% 0.01s <frozen importlib._bootstrap>: _call_with_frames_removed, line 219:
| 50.0% 0.01s changelog.py: <module> line 376: class changelog(revlog.revl...
| 50.0% 0.01s util.py: __getattribute__, line 245: self.__spec__.loader.exec_m...
| 50.0% 0.01s <frozen importlib._bootstrap_external>: exec_module, line 779:
| 50.0% 0.01s <frozen importlib._bootstrap_external>: get_code, line 868:
| 50.0% 0.01s <frozen importlib._bootstrap_external>: path_stats, line 1012:
| 50.0% 0.01s <frozen importlib._bootstrap_external>: _path_stat, line 87:
```
Differential Revision: https://phab.mercurial-scm.org/D9511
author | Kyle Lippincott <spectral@google.com> |
---|---|
date | Wed, 02 Dec 2020 12:33:51 -0800 |
parents | 89a2afe31e82 |
children | 59fa3890d40a |
line wrap: on
line source
# uncommit - undo the actions of a commit # # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com> # Logilab SA <contact@logilab.fr> # Pierre-Yves David <pierre-yves.david@ens-lyon.org> # Patrick Mezard <patrick@mezard.eu> # Copyright 2016 Facebook, Inc. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. """uncommit part or all of a local changeset (EXPERIMENTAL) This command undoes the effect of a local commit, returning the affected files to their uncommitted state. This means that files modified, added or removed in the changeset will be left unchanged, and so will remain modified, added and removed in the working directory. """ from __future__ import absolute_import from mercurial.i18n import _ from mercurial import ( cmdutil, commands, context, copies as copiesmod, error, node, obsutil, pathutil, pycompat, registrar, rewriteutil, scmutil, ) cmdtable = {} command = registrar.command(cmdtable) configtable = {} configitem = registrar.configitem(configtable) configitem( b'experimental', b'uncommitondirtywdir', default=False, ) configitem( b'experimental', b'uncommit.keep', default=False, ) # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should # be specifying the version(s) of Mercurial they are tested with, or # leave the attribute unspecified. testedwith = b'ships-with-hg-core' def _commitfiltered( repo, ctx, match, keepcommit, message=None, user=None, date=None ): """Recommit ctx with changed files not in match. Return the new node identifier, or None if nothing changed. """ base = ctx.p1() # ctx initialfiles = set(ctx.files()) exclude = {f for f in initialfiles if match(f)} # No files matched commit, so nothing excluded if not exclude: return None # return the p1 so that we don't create an obsmarker later if not keepcommit: return ctx.p1().node() files = initialfiles - exclude # Filter copies copied = copiesmod.pathcopies(base, ctx) copied = { dst: src for dst, src in pycompat.iteritems(copied) if dst in files } def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()): if path not in contentctx: return None fctx = contentctx[path] mctx = context.memfilectx( repo, memctx, fctx.path(), fctx.data(), fctx.islink(), fctx.isexec(), copysource=copied.get(path), ) return mctx if not files: repo.ui.status(_(b"note: keeping empty commit\n")) if message is None: message = ctx.description() if not user: user = ctx.user() if not date: date = ctx.date() new = context.memctx( repo, parents=[base.node(), node.nullid], text=message, files=files, filectxfn=filectxfn, user=user, date=date, extra=ctx.extra(), ) return repo.commitctx(new) @command( b'uncommit', [ (b'', b'keep', None, _(b'allow an empty commit after uncommitting')), ( b'', b'allow-dirty-working-copy', False, _(b'allow uncommit with outstanding changes'), ), (b'n', b'note', b'', _(b'store a note on uncommit'), _(b'TEXT')), ] + commands.walkopts + commands.commitopts + commands.commitopts2 + commands.commitopts3, _(b'[OPTION]... [FILE]...'), helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, ) def uncommit(ui, repo, *pats, **opts): """uncommit part or all of a local changeset This command undoes the effect of a local commit, returning the affected files to their uncommitted state. This means that files modified or deleted in the changeset will be left unchanged, and so will remain modified in the working directory. If no files are specified, the commit will be pruned, unless --keep is given. """ opts = pycompat.byteskwargs(opts) cmdutil.checknotesize(ui, opts) cmdutil.resolvecommitoptions(ui, opts) with repo.wlock(), repo.lock(): st = repo.status() m, a, r, d = st.modified, st.added, st.removed, st.deleted isdirtypath = any(set(m + a + r + d) & set(pats)) allowdirtywcopy = opts[ b'allow_dirty_working_copy' ] or repo.ui.configbool(b'experimental', b'uncommitondirtywdir') if not allowdirtywcopy and (not pats or isdirtypath): cmdutil.bailifchanged( repo, hint=_(b'requires --allow-dirty-working-copy to uncommit'), ) old = repo[b'.'] rewriteutil.precheck(repo, [old.rev()], b'uncommit') if len(old.parents()) > 1: raise error.Abort(_(b"cannot uncommit merge changeset")) match = scmutil.match(old, pats, opts) # Check all explicitly given files; abort if there's a problem. if match.files(): s = old.status(old.p1(), match, listclean=True) eligible = set(s.added) | set(s.modified) | set(s.removed) badfiles = set(match.files()) - eligible # Naming a parent directory of an eligible file is OK, even # if not everything tracked in that directory can be # uncommitted. if badfiles: badfiles -= {f for f in pathutil.dirs(eligible)} for f in sorted(badfiles): if f in s.clean: hint = _( b"file was not changed in working directory parent" ) elif repo.wvfs.exists(f): hint = _(b"file was untracked in working directory parent") else: hint = _(b"file does not exist") raise error.Abort( _(b'cannot uncommit "%s"') % scmutil.getuipathfn(repo)(f), hint=hint, ) with repo.transaction(b'uncommit'): if not (opts[b'message'] or opts[b'logfile']): opts[b'message'] = old.description() message = cmdutil.logmessage(ui, opts) keepcommit = pats if not keepcommit: if opts.get(b'keep') is not None: keepcommit = opts.get(b'keep') else: keepcommit = ui.configbool( b'experimental', b'uncommit.keep' ) newid = _commitfiltered( repo, old, match, keepcommit, message=message, user=opts.get(b'user'), date=opts.get(b'date'), ) if newid is None: ui.status(_(b"nothing to uncommit\n")) return 1 mapping = {} if newid != old.p1().node(): # Move local changes on filtered changeset mapping[old.node()] = (newid,) else: # Fully removed the old commit mapping[old.node()] = () with repo.dirstate.parentchange(): scmutil.movedirstate(repo, repo[newid], match) scmutil.cleanupnodes(repo, mapping, b'uncommit', fixphase=True) def predecessormarkers(ctx): """yields the obsolete markers marking the given changeset as a successor""" for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()): yield obsutil.marker(ctx.repo(), data) @command( b'unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True, ) def unamend(ui, repo, **opts): """undo the most recent amend operation on a current changeset This command will roll back to the previous version of a changeset, leaving working directory in state in which it was before running `hg amend` (e.g. files modified as part of an amend will be marked as modified `hg status`) """ unfi = repo.unfiltered() with repo.wlock(), repo.lock(), repo.transaction(b'unamend'): # identify the commit from which to unamend curctx = repo[b'.'] rewriteutil.precheck(repo, [curctx.rev()], b'unamend') # identify the commit to which to unamend markers = list(predecessormarkers(curctx)) if len(markers) != 1: e = _(b"changeset must have one predecessor, found %i predecessors") raise error.Abort(e % len(markers)) prednode = markers[0].prednode() predctx = unfi[prednode] # add an extra so that we get a new hash # note: allowing unamend to undo an unamend is an intentional feature extras = predctx.extra() extras[b'unamend_source'] = curctx.hex() def filectxfn(repo, ctx_, path): try: return predctx.filectx(path) except KeyError: return None # Make a new commit same as predctx newctx = context.memctx( repo, parents=(predctx.p1(), predctx.p2()), text=predctx.description(), files=predctx.files(), filectxfn=filectxfn, user=predctx.user(), date=predctx.date(), extra=extras, ) newprednode = repo.commitctx(newctx) newpredctx = repo[newprednode] dirstate = repo.dirstate with dirstate.parentchange(): scmutil.movedirstate(repo, newpredctx) mapping = {curctx.node(): (newprednode,)} scmutil.cleanupnodes(repo, mapping, b'unamend', fixphase=True)