##// END OF EJS Templates
localrepo: automatically load lfs extension when required (BC)...
localrepo: automatically load lfs extension when required (BC) If an unrecognized requirement is present (possibly due to an unloaded extension), the user will get an error message telling them to go to https://mercurial-scm.org/wiki/MissingRequirement for more info. And some requirements clearly map to known extensions shipped by Mercurial. This commit teaches repository loading to automatically map requirements to extensions. We implement support for loading the lfs extension when the "lfs" requirement is present. This behavior feels more user-friendly to me and I'm having trouble coming up with a compelling reason to not do it. The strongest argument I have against is that - strictly speaking - requirements are general repository features and there could be N providers of that feature. e.g. in the case of LFS, there could be another extension implementing LFS support. And the user would want to use this non-official extension rather than the built-in one. The way this patch implements things, the non-official extension could be missing and Mercurial would load the official lfs extension, leading to unexpected behavior. But this feels like a highly marginal use case to me and doesn't outweigh the user benefit of "it just works." If someone really wanted to e.g. use a custom LFS extension, they could prevent the built-in one from being loaded by either defining "extensions.lfs=/path/to/custom/extension" or "extensions.lfs=!", as the automatic extension loading only occurs if there is no config entry for that extension. Differential Revision: https://phab.mercurial-scm.org/D4711

File last commit:

r37727:7b295562 default
r39888:2c2fadbc default
Show More
show.py
469 lines | 15.4 KiB | text/x-python | PythonLexer
Gregory Szorc
show: new extension for displaying various repository data...
r31765 # show.py - Extension implementing `hg show`
#
# Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""unified command to show various repository information (EXPERIMENTAL)
This extension provides the :hg:`show` command, which provides a central
command for displaying commonly-accessed repository data and views of that
data.
Gregory Szorc
show: config option to register aliases for views...
r33132
The following config options can influence operation.
``commands``
------------
``show.aliasprefix``
List of strings that will register aliases for views. e.g. ``s`` will
effectively set config options ``alias.s<view> = show <view>`` for all
views. i.e. `hg swork` would execute `hg show work`.
Aliases that would conflict with existing registrations will not be
performed.
Gregory Szorc
show: new extension for displaying various repository data...
r31765 """
from __future__ import absolute_import
from mercurial.i18n import _
Yuya Nishihara
show: use revlog function to compute length of the longest shortest node...
r35514 from mercurial.node import (
nullrev,
)
Gregory Szorc
show: new extension for displaying various repository data...
r31765 from mercurial import (
cmdutil,
Gregory Szorc
show: config option to register aliases for views...
r33132 commands,
Gregory Szorc
show: implement "stack" view...
r33194 destutil,
Gregory Szorc
show: new extension for displaying various repository data...
r31765 error,
Ryan McElroy
show: fix corrupt json output with no bookmarks
r31859 formatter,
Gregory Szorc
show: implement underway view...
r31944 graphmod,
Yuya Nishihara
cmdutil: drop aliases for logcmdutil functions (API)...
r35906 logcmdutil,
Gregory Szorc
show: implement "stack" view...
r33194 phases,
Yuya Nishihara
py3: have registrar process docstrings in bytes...
r31820 pycompat,
Gregory Szorc
show: new extension for displaying various repository data...
r31765 registrar,
Gregory Szorc
show: implement underway view...
r31944 revset,
revsetlang,
Martin von Zweigbergk
scmutil: introduce shortesthexnodeidprefix()...
r37698 scmutil,
Gregory Szorc
show: new extension for displaying various repository data...
r31765 )
# 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 = 'ships-with-hg-core'
cmdtable = {}
Yuya Nishihara
registrar: move cmdutil.command to registrar module (API)...
r32337 command = registrar.command(cmdtable)
Boris Feld
configitems: register the 'commands.show.aliasprefix' config
r34519
Gregory Szorc
show: implement underway view...
r31944 revsetpredicate = registrar.revsetpredicate()
Gregory Szorc
show: new extension for displaying various repository data...
r31765
class showcmdfunc(registrar._funcregistrarbase):
"""Register a function to be invoked for an `hg show <thing>`."""
# Used by _formatdoc().
_docformat = '%s -- %s'
Gregory Szorc
show: construct changeset templater during dispatch...
r33046 def _extrasetup(self, name, func, fmtopic=None, csettopic=None):
Gregory Szorc
show: new extension for displaying various repository data...
r31765 """Called with decorator arguments to register a show view.
``name`` is the sub-command name.
``func`` is the function being decorated.
``fmtopic`` is the topic in the style that will be rendered for
this view.
Gregory Szorc
show: construct changeset templater during dispatch...
r33046
``csettopic`` is the topic in the style to be used for a changeset
printer.
If ``fmtopic`` is specified, the view function will receive a
formatter instance. If ``csettopic`` is specified, the view
function will receive a changeset printer.
Gregory Szorc
show: new extension for displaying various repository data...
r31765 """
func._fmtopic = fmtopic
Gregory Szorc
show: construct changeset templater during dispatch...
r33046 func._csettopic = csettopic
Gregory Szorc
show: new extension for displaying various repository data...
r31765
showview = showcmdfunc()
Ryan McElroy
show: make template option actually show up in help...
r31945 @command('show', [
Yuya Nishihara
commands: move templates of common command options to cmdutil (API)...
r32375 # TODO: Switch this template flag to use cmdutil.formatteropts if
Ryan McElroy
show: make template option actually show up in help...
r31945 # 'hg show' becomes stable before --template/-T is stable. For now,
# we are putting it here without the '(EXPERIMENTAL)' flag because it
# is an important part of the 'hg show' user experience and the entire
# 'hg show' experience is experimental.
('T', 'template', '', ('display with template'), _('TEMPLATE')),
], _('VIEW'))
Gregory Szorc
show: new extension for displaying various repository data...
r31765 def show(ui, repo, view=None, template=None):
"""show various repository information
A requested view of repository data is displayed.
If no view is requested, the list of available views is shown and the
command aborts.
.. note::
There are no backwards compatibility guarantees for the output of this
command. Output may change in any future Mercurial release.
Consumers wanting stable command output should specify a template via
``-T/--template``.
List of available views:
"""
if ui.plain() and not template:
Ryan McElroy
show: tweak plain abort language for clarity
r31858 hint = _('invoke with -T/--template to control output format')
raise error.Abort(_('must specify a template in plain mode'), hint=hint)
Gregory Szorc
show: new extension for displaying various repository data...
r31765
views = showview._table
if not view:
ui.pager('show')
# TODO consider using formatter here so available views can be
# rendered to custom format.
ui.write(_('available views:\n'))
ui.write('\n')
for name, func in sorted(views.items()):
Gregory Szorc
py3: convert __doc__ to bytes...
r36058 ui.write(('%s\n') % pycompat.sysbytes(func.__doc__))
Gregory Szorc
show: new extension for displaying various repository data...
r31765
ui.write('\n')
raise error.Abort(_('no view requested'),
hint=_('use "hg show VIEW" to choose a view'))
# TODO use same logic as dispatch to perform prefix matching.
if view not in views:
raise error.Abort(_('unknown view: %s') % view,
hint=_('run "hg show" to see available views'))
template = template or 'show'
Gregory Szorc
show: construct changeset templater during dispatch...
r33046 fn = views[view]
Gregory Szorc
show: new extension for displaying various repository data...
r31765 ui.pager('show')
Gregory Szorc
show: construct changeset templater during dispatch...
r33046
if fn._fmtopic:
fmtopic = 'show%s' % fn._fmtopic
with ui.formatter(fmtopic, {'template': template}) as fm:
return fn(ui, repo, fm)
elif fn._csettopic:
ref = 'show%s' % fn._csettopic
spec = formatter.lookuptemplate(ui, ref, template)
Yuya Nishihara
cmdutil: drop aliases for logcmdutil functions (API)...
r35906 displayer = logcmdutil.changesettemplater(ui, repo, spec, buffered=True)
Gregory Szorc
show: construct changeset templater during dispatch...
r33046 return fn(ui, repo, displayer)
else:
return fn(ui, repo)
Gregory Szorc
show: new extension for displaying various repository data...
r31765
@showview('bookmarks', fmtopic='bookmarks')
def showbookmarks(ui, repo, fm):
"""bookmarks and their associated changeset"""
marks = repo._bookmarks
if not len(marks):
Ryan McElroy
show: fix corrupt json output with no bookmarks
r31859 # This is a bit hacky. Ideally, templates would have a way to
# specify an empty output, but we shouldn't corrupt JSON while
# waiting for this functionality.
if not isinstance(fm, formatter.jsonformatter):
ui.write(_('(no bookmarks set)\n'))
Gregory Szorc
show: new extension for displaying various repository data...
r31765 return
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 revs = [repo[node].rev() for node in marks.values()]
Gregory Szorc
show: new extension for displaying various repository data...
r31765 active = repo._activebookmark
longestname = max(len(b) for b in marks)
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 nodelen = longestshortest(repo, revs)
Gregory Szorc
show: new extension for displaying various repository data...
r31765
for bm, node in sorted(marks.items()):
fm.startitem()
fm.context(ctx=repo[node])
fm.write('bookmark', '%s', bm)
fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
fm.data(active=bm == active,
Gregory Szorc
show: pass the minimum length for nodes as a template keyword...
r34191 longestbookmarklen=longestname,
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 nodelen=nodelen)
Gregory Szorc
show: new extension for displaying various repository data...
r31765
Gregory Szorc
show: implement "stack" view...
r33194 @showview('stack', csettopic='stack')
def showstack(ui, repo, displayer):
"""current line of work"""
wdirctx = repo['.']
if wdirctx.rev() == nullrev:
raise error.Abort(_('stack view only available when there is a '
'working directory'))
if wdirctx.phase() == phases.public:
Gregory Szorc
show: tweak warning message...
r33205 ui.write(_('(empty stack; working directory parent is a published '
Gregory Szorc
show: implement "stack" view...
r33194 'changeset)\n'))
return
# TODO extract "find stack" into a function to facilitate
# customization and reuse.
baserev = destutil.stackbase(ui, repo)
basectx = None
if baserev is None:
baserev = wdirctx.rev()
stackrevs = {wdirctx.rev()}
else:
stackrevs = set(repo.revs('%d::.', baserev))
ctx = repo[baserev]
if ctx.p1().rev() != nullrev:
basectx = ctx.p1()
# And relevant descendants.
branchpointattip = False
cl = repo.changelog
for rev in cl.descendants([wdirctx.rev()]):
ctx = repo[rev]
# Will only happen if . is public.
if ctx.phase() == phases.public:
break
stackrevs.add(ctx.rev())
Gregory Szorc
show: document why accidentally quadratic is (probably) acceptable
r33208 # ctx.children() within a function iterating on descandants
# potentially has severe performance concerns because revlog.children()
# iterates over all revisions after ctx's node. However, the number of
# draft changesets should be a reasonably small number. So even if
# this is quadratic, the perf impact should be minimal.
Gregory Szorc
show: implement "stack" view...
r33194 if len(ctx.children()) > 1:
branchpointattip = True
break
Gregory Szorc
show: avoid extra list operations
r33206 stackrevs = list(sorted(stackrevs, reverse=True))
Gregory Szorc
show: implement "stack" view...
r33194
# Find likely target heads for the current stack. These are likely
# merge or rebase targets.
if basectx:
# TODO make this customizable?
newheads = set(repo.revs('heads(%d::) - %ld - not public()',
basectx.rev(), stackrevs))
else:
newheads = set()
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 allrevs = set(stackrevs) | newheads | set([baserev])
nodelen = longestshortest(repo, allrevs)
Gregory Szorc
show: implement "stack" view...
r33194 try:
cmdutil.findcmd('rebase', commands.table)
haverebase = True
Gregory Szorc
show: also catch AmbiguousCommand...
r33207 except (error.AmbiguousCommand, error.UnknownCommand):
Gregory Szorc
show: implement "stack" view...
r33194 haverebase = False
# TODO use templating.
# TODO consider using graphmod. But it may not be necessary given
# our simplicity and the customizations required.
# TODO use proper graph symbols from graphmod
Yuya Nishihara
templater: move repo, ui and cache to per-engine resources
r35485 tres = formatter.templateresources(ui, repo)
shortesttmpl = formatter.maketemplater(ui, '{shortest(node, %d)}' % nodelen,
resources=tres)
Gregory Szorc
show: implement "stack" view...
r33194 def shortest(ctx):
Yuya Nishihara
templater: rename .render(mapping) to .renderdefault(mapping) (API)...
r37003 return shortesttmpl.renderdefault({'ctx': ctx, 'node': ctx.hex()})
Gregory Szorc
show: implement "stack" view...
r33194
# We write out new heads to aid in DAG awareness and to help with decision
# making on how the stack should be reconciled with commits made since the
# branch point.
if newheads:
# Calculate distance from base so we can render the count and so we can
# sort display order by commit distance.
revdistance = {}
for head in newheads:
# There is some redundancy in DAG traversal here and therefore
# room to optimize.
ancestors = cl.ancestors([head], stoprev=basectx.rev())
revdistance[head] = len(list(ancestors))
sourcectx = repo[stackrevs[-1]]
sortedheads = sorted(newheads, key=lambda x: revdistance[x],
reverse=True)
for i, rev in enumerate(sortedheads):
ctx = repo[rev]
if i:
ui.write(': ')
else:
ui.write(' ')
ui.write(('o '))
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 displayer.show(ctx, nodelen=nodelen)
Gregory Szorc
show: implement "stack" view...
r33194 displayer.flush(ctx)
ui.write('\n')
if i:
ui.write(':/')
else:
ui.write(' /')
ui.write(' (')
ui.write(_('%d commits ahead') % revdistance[rev],
label='stack.commitdistance')
if haverebase:
# TODO may be able to omit --source in some scenarios
ui.write('; ')
ui.write(('hg rebase --source %s --dest %s' % (
shortest(sourcectx), shortest(ctx))),
label='stack.rebasehint')
ui.write(')\n')
ui.write(':\n: ')
ui.write(_('(stack head)\n'), label='stack.label')
if branchpointattip:
ui.write(' \\ / ')
ui.write(_('(multiple children)\n'), label='stack.label')
ui.write(' |\n')
for rev in stackrevs:
ctx = repo[rev]
symbol = '@' if rev == wdirctx.rev() else 'o'
if newheads:
ui.write(': ')
else:
ui.write(' ')
ui.write(symbol, ' ')
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 displayer.show(ctx, nodelen=nodelen)
Gregory Szorc
show: implement "stack" view...
r33194 displayer.flush(ctx)
ui.write('\n')
# TODO display histedit hint?
if basectx:
# Vertically and horizontally separate stack base from parent
# to reinforce stack boundary.
if newheads:
ui.write(':/ ')
else:
ui.write(' / ')
ui.write(_('(stack base)'), '\n', label='stack.label')
ui.write(('o '))
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 displayer.show(basectx, nodelen=nodelen)
Gregory Szorc
show: implement "stack" view...
r33194 displayer.flush(basectx)
ui.write('\n')
Gregory Szorc
show: implement underway view...
r31944 @revsetpredicate('_underway([commitage[, headage]])')
def underwayrevset(repo, subset, x):
args = revset.getargsdict(x, 'underway', 'commitage headage')
if 'commitage' not in args:
args['commitage'] = None
if 'headage' not in args:
args['headage'] = None
# We assume callers of this revset add a topographical sort on the
# result. This means there is no benefit to making the revset lazy
# since the topographical sort needs to consume all revs.
#
# With this in mind, we build up the set manually instead of constructing
# a complex revset. This enables faster execution.
# Mutable changesets (non-public) are the most important changesets
# to return. ``not public()`` will also pull in obsolete changesets if
# there is a non-obsolete changeset with obsolete ancestors. This is
# why we exclude obsolete changesets from this query.
rs = 'not public() and not obsolete()'
rsargs = []
if args['commitage']:
rs += ' and date(%s)'
rsargs.append(revsetlang.getstring(args['commitage'],
_('commitage requires a string')))
mutable = repo.revs(rs, *rsargs)
relevant = revset.baseset(mutable)
# Add parents of mutable changesets to provide context.
relevant += repo.revs('parents(%ld)', mutable)
# We also pull in (public) heads if they a) aren't closing a branch
# b) are recent.
rs = 'head() and not closed()'
rsargs = []
if args['headage']:
rs += ' and date(%s)'
rsargs.append(revsetlang.getstring(args['headage'],
_('headage requires a string')))
relevant += repo.revs(rs, *rsargs)
# Add working directory parent.
wdirrev = repo['.'].rev()
if wdirrev != nullrev:
Martin von Zweigbergk
cleanup: use set literals...
r32291 relevant += revset.baseset({wdirrev})
Gregory Szorc
show: implement underway view...
r31944
return subset & relevant
Gregory Szorc
show: construct changeset templater during dispatch...
r33046 @showview('work', csettopic='work')
def showwork(ui, repo, displayer):
Gregory Szorc
show: implement underway view...
r31944 """changesets that aren't finished"""
# TODO support date-based limiting when calling revset.
revs = repo.revs('sort(_underway(), topo)')
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 nodelen = longestshortest(repo, revs)
Gregory Szorc
show: implement underway view...
r31944
revdag = graphmod.dagwalker(repo, revs)
ui.setconfig('experimental', 'graphshorten', True)
Yuya Nishihara
cmdutil: drop aliases for logcmdutil functions (API)...
r35906 logcmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
props={'nodelen': nodelen})
Gregory Szorc
show: implement underway view...
r31944
Gregory Szorc
show: config option to register aliases for views...
r33132 def extsetup(ui):
# Alias `hg <prefix><view>` to `hg show <view>`.
for prefix in ui.configlist('commands', 'show.aliasprefix'):
for view in showview._table:
name = '%s%s' % (prefix, view)
choice, allcommands = cmdutil.findpossible(name, commands.table,
strict=True)
# This alias is already a command name. Don't set it.
if name in choice:
continue
# Same for aliases.
Rodrigo Damazio
help: supporting both help and doc for aliases...
r37152 if ui.config('alias', name, None):
Gregory Szorc
show: config option to register aliases for views...
r33132 continue
ui.setconfig('alias', name, 'show %s' % view, source='show')
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192 def longestshortest(repo, revs, minlen=4):
"""Return the length of the longest shortest node to identify revisions.
The result of this function can be used with the ``shortest()`` template
function to ensure that a value is unique and unambiguous for a given
set of nodes.
The number of revisions in the repo is taken into account to prevent
a numeric node prefix from conflicting with an integer revision number.
If we fail to do this, a value of e.g. ``10023`` could mean either
revision 10023 or node ``10023abc...``.
"""
Yuya Nishihara
show: use revlog function to compute length of the longest shortest node...
r35514 if not revs:
return minlen
Martin von Zweigbergk
scmutil: introduce shortesthexnodeidprefix()...
r37698 cl = repo.changelog
Martin von Zweigbergk
scmutil: make shortesthexnodeidprefix() take a full binary nodeid...
r37727 return max(len(scmutil.shortesthexnodeidprefix(repo, cl.node(r), minlen))
for r in revs)
Gregory Szorc
show: use consistent (and possibly shorter) node lengths...
r34192
Gregory Szorc
show: new extension for displaying various repository data...
r31765 # Adjust the docstring of the show command so it shows all registered views.
# This is a bit hacky because it runs at the end of module load. When moved
# into core or when another extension wants to provide a view, we'll need
# to do this more robustly.
# TODO make this more robust.
Gregory Szorc
show: fix formatting of multiple commands...
r31943 def _updatedocstring():
longest = max(map(len, showview._table.keys()))
entries = []
for key in sorted(showview._table.keys()):
entries.append(pycompat.sysstr(' %s %s' % (
key.ljust(longest), showview._table[key]._origdoc)))
cmdtable['show'][0].__doc__ = pycompat.sysstr('%s\n\n%s\n ') % (
cmdtable['show'][0].__doc__.rstrip(),
pycompat.sysstr('\n\n').join(entries))
_updatedocstring()