Mercurial > hg
view mercurial/help.py @ 46582:b0a3ca02d17a
copies-rust: implement PartialEqual manually
Now that we know that each (dest, rev) pair has at most a unique CopySource, we
can simplify comparison a lot.
This "simple" step buy a good share of the previous slowdown back in some case:
Repo Case Source-Rev Dest-Rev # of revisions old time new time Difference Factor time per rev
---------------------------------------------------------------------------------------------------------------------------------------------------------------
mozilla-try x00000_revs_x00000_added_x000_copies 9b2a99adc05e 8e29777b48e6 : 382065 revs, 43.304637 s, 34.443661 s, -8.860976 s, × 0.7954, 90 µs/rev
Full benchmark:
Repo Case Source-Rev Dest-Rev # of revisions old time new time Difference Factor time per rev
---------------------------------------------------------------------------------------------------------------------------------------------------------------
mercurial x_revs_x_added_0_copies ad6b123de1c7 39cfcef4f463 : 1 revs, 0.000043 s, 0.000043 s, +0.000000 s, × 1.0000, 43 µs/rev
mercurial x_revs_x_added_x_copies 2b1c78674230 0c1d10351869 : 6 revs, 0.000114 s, 0.000117 s, +0.000003 s, × 1.0263, 19 µs/rev
mercurial x000_revs_x000_added_x_copies 81f8ff2a9bf2 dd3267698d84 : 1032 revs, 0.004937 s, 0.004892 s, -0.000045 s, × 0.9909, 4 µs/rev
pypy x_revs_x_added_0_copies aed021ee8ae8 099ed31b181b : 9 revs, 0.000339 s, 0.000196 s, -0.000143 s, × 0.5782, 21 µs/rev
pypy x_revs_x000_added_0_copies 4aa4e1f8e19a 359343b9ac0e : 1 revs, 0.000049 s, 0.000050 s, +0.000001 s, × 1.0204, 50 µs/rev
pypy x_revs_x_added_x_copies ac52eb7bbbb0 72e022663155 : 7 revs, 0.000202 s, 0.000117 s, -0.000085 s, × 0.5792, 16 µs/rev
pypy x_revs_x00_added_x_copies c3b14617fbd7 ace7255d9a26 : 1 revs, 0.000409 s, 0.6f1f4a s, -0.000087 s, × 0.7873, 322 µs/rev
pypy x_revs_x000_added_x000_copies df6f7a526b60 a83dc6a2d56f : 6 revs, 0.011984 s, 0.011949 s, -0.000035 s, × 0.9971, 1991 µs/rev
pypy x000_revs_xx00_added_0_copies 89a76aede314 2f22446ff07e : 4785 revs, 0.050820 s, 0.050802 s, -0.000018 s, × 0.9996, 10 µs/rev
pypy x000_revs_x000_added_x_copies 8a3b5bfd266e 2c68e87c3efe : 6780 revs, 0.087953 s, 0.088090 s, +0.000137 s, × 1.0016, 12 µs/rev
pypy x000_revs_x000_added_x000_copies 89a76aede314 7b3dda341c84 : 5441 revs, 0.062902 s, 0.062079 s, -0.000823 s, × 0.9869, 11 µs/rev
pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 43645 revs, 0.679234 s, 0.635337 s, -0.043897 s, × 0.9354, 14 µs/rev
pypy x0000_revs_xx000_added_0_copies bf2c629d0071 4ffed77c095c : 2 revs, 0.013095 s, 0.013262 s, +0.000167 s, × 1.0128, 6631 µs/rev
pypy x0000_revs_xx000_added_x000_copies 08ea3258278e d9fa043f30c0 : 11316 revs, 0.120910 s, 0.120085 s, -0.000825 s, × 0.9932, 10 µs/rev
netbeans x_revs_x_added_0_copies fb0955ffcbcd a01e9239f9e7 : 2 revs, 0.000087 s, 0.000085 s, -0.000002 s, × 0.9770, 42 µs/rev
netbeans x_revs_x000_added_0_copies 6f360122949f 20eb231cc7d0 : 2 revs, 0.000107 s, 0.000110 s, +0.000003 s, × 1.0280, 55 µs/rev
netbeans x_revs_x_added_x_copies 1ada3faf6fb6 5a39d12eecf4 : 3 revs, 0.000186 s, 0.000177 s, -0.000009 s, × 0.9516, 59 µs/rev
netbeans x_revs_x00_added_x_copies 35be93ba1e2c 9eec5e90c05f : 9 revs, 0.000754 s, 0.000743 s, -0.000011 s, × 0.9854, 82 µs/rev
netbeans x000_revs_xx00_added_0_copies eac3045b4fdd 51d4ae7f1290 : 1421 revs, 0.010443 s, 0.010168 s, -0.000275 s, × 0.9737, 7 µs/rev
netbeans x000_revs_x000_added_x_copies e2063d266acd 6081d72689dc : 1533 revs, 0.015697 s, 0.015946 s, +0.000249 s, × 1.0159, 10 µs/rev
netbeans x000_revs_x000_added_x000_copies ff453e9fee32 411350406ec2 : 5750 revs, 0.063528 s, 0.062712 s, -0.000816 s, × 0.9872, 10 µs/rev
netbeans x0000_revs_xx000_added_x000_copies 588c2d1ced70 1aad62e59ddd : 66949 revs, 0.545515 s, 0.523832 s, -0.021683 s, × 0.9603, 7 µs/rev
mozilla-central x_revs_x_added_0_copies 3697f962bb7b 7015fcdd43a2 : 2 revs, 0.000089 s, 0.000090 s, +0.000001 s, × 1.0112, 45 µs/rev
mozilla-central x_revs_x000_added_0_copies dd390860c6c9 40d0c5bed75d : 8 revs, 0.000265 s, 0.000264 s, -0.000001 s, × 0.9962, 33 µs/rev
mozilla-central x_revs_x_added_x_copies 8d198483ae3b 14207ffc2b2f : 9 revs, 0.000381 s, 0.000187 s, -0.000194 s, × 0.4908, 20 µs/rev
mozilla-central x_revs_x00_added_x_copies 98cbc58cc6bc 446a150332c3 : 7 revs, 0.000672 s, 0.000665 s, -0.000007 s, × 0.9896, 95 µs/rev
mozilla-central x_revs_x000_added_x000_copies 3c684b4b8f68 0a5e72d1b479 : 3 revs, 0.003497 s, 0.003556 s, +0.000059 s, × 1.0169, 1185 µs/rev
mozilla-central x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 6 revs, 0.073204 s, 0.071345 s, -0.001859 s, × 0.9746, 11890 µs/rev
mozilla-central x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 1593 revs, 0.006482 s, 0.006551 s, +0.000069 s, × 1.0106, 4 µs/rev
mozilla-central x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 41 revs, 0.005066 s, 0.005078 s, +0.000012 s, × 1.0024, 123 µs/rev
mozilla-central x000_revs_x000_added_x000_copies 7c97034feb78 4407bd0c6330 : 7839 revs, 0.065707 s, 0.065823 s, +0.000116 s, × 1.0018, 8 µs/rev
mozilla-central x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 615 revs, 0.026800 s, 0.027050 s, +0.000250 s, × 1.0093, 43 µs/rev
mozilla-central x0000_revs_xx000_added_x000_copies f78c615a656c 96a38b690156 : 30263 revs, 0.203856 s, 0.202443 s, -0.001413 s, × 0.9931, 6 µs/rev
mozilla-central x00000_revs_x0000_added_x0000_copies 6832ae71433c 4c222a1d9a00 : 153721 revs, 1.293394 s, 1.261583 s, -0.031811 s, × 0.9754, 8 µs/rev
mozilla-central x00000_revs_x00000_added_x000_copies 76caed42cf7c 1daa622bbe42 : 204976 revs, 1.698239 s, 1.643869 s, -0.054370 s, × 0.9680, 8 µs/rev
mozilla-try x_revs_x_added_0_copies aaf6dde0deb8 9790f499805a : 2 revs, 0.000875 s, 0.000868 s, -0.000007 s, × 0.9920, 434 µs/rev
mozilla-try x_revs_x000_added_0_copies d8d0222927b4 5bb8ce8c7450 : 2 revs, 0.000891 s, 0.000887 s, -0.000004 s, × 0.9955, 443 µs/rev
mozilla-try x_revs_x_added_x_copies 092fcca11bdb 936255a0384a : 4 revs, 0.000292 s, 0.000168 s, -0.000124 s, × 0.5753, 42 µs/rev
mozilla-try x_revs_x00_added_x_copies b53d2fadbdb5 017afae788ec : 2 revs, 0.003939 s, 0.001160 s, -0.002779 s, × 0.2945, 580 µs/rev
mozilla-try x_revs_x000_added_x000_copies 20408ad61ce5 6f0ee96e21ad : 1 revs, 0.033027 s, 0.033016 s, -0.000011 s, × 0.9997, 33016 µs/rev
mozilla-try x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 6 revs, 0.073703 s, 0.073312 s, -0.39ae31 s, × 0.9947, 12218 µs/rev
mozilla-try x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 1593 revs, 0.006469 s, 0.006485 s, +0.000016 s, × 1.0025, 4 µs/rev
mozilla-try x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 41 revs, 0.005278 s, 0.005494 s, +0.000216 s, × 1.0409, 134 µs/rev
mozilla-try x000_revs_x000_added_x000_copies 1346fd0130e4 4c65cbdabc1f : 6657 revs, 0.064995 s, 0.064879 s, -0.000116 s, × 0.9982, 9 µs/rev
mozilla-try x0000_revs_x_added_0_copies 63519bfd42ee a36a2a865d92 : 40314 revs, 0.301041 s, 0.301469 s, +0.000428 s, × 1.0014, 7 µs/rev
mozilla-try x0000_revs_x_added_x_copies 9fe69ff0762d bcabf2a78927 : 38690 revs, 0.285575 s, 0.297113 s, +0.011538 s, × 1.0404, 7 µs/rev
mozilla-try x0000_revs_xx000_added_x_copies 156f6e2674f2 4d0f2c178e66 : 8598 revs, 0.085597 s, 0.085890 s, +0.000293 s, × 1.0034, 9 µs/rev
mozilla-try x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 615 revs, 0.027118 s, 0.027718 s, +0.000600 s, × 1.0221, 45 µs/rev
mozilla-try x0000_revs_xx000_added_x000_copies 89294cd501d9 7ccb2fc7ccb5 : 97052 revs, 2.119204 s, 2.048949 s, -0.070255 s, × 0.9668, 21 µs/rev
mozilla-try x0000_revs_x0000_added_x0000_copies e928c65095ed e951f4ad123a : 52031 revs, 0.701479 s, 0.685924 s, -0.015555 s, × 0.9778, 13 µs/rev
mozilla-try x00000_revs_x_added_0_copies 6a320851d377 1ebb79acd503 : 363753 revs, 4.482399 s, 4.482891 s, +0.000492 s, × 1.0001, 12 µs/rev
mozilla-try x00000_revs_x00000_added_0_copies dc8a3ca7010e d16fde900c9c : 34414 revs, 0.574082 s, 0.577633 s, +0.003551 s, × 1.0062, 16 µs/rev
mozilla-try x00000_revs_x_added_x_copies 5173c4b6f97c 95d83ee7242d : 362229 revs, 4.480366 s, 4.397816 s, -0.082550 s, × 0.9816, 12 µs/rev
mozilla-try x00000_revs_x000_added_x_copies 9126823d0e9c ca82787bb23c : 359344 revs, 4.369070 s, 4.370538 s, +0.001468 s, × 1.0003, 12 µs/rev
mozilla-try x00000_revs_x0000_added_x0000_copies 8d3fafa80d4b eb884023b810 : 192665 revs, 1.592506 s, 1.570439 s, -0.022067 s, × 0.9861, 8 µs/rev
mozilla-try x00000_revs_x00000_added_x0000_copies 1b661134e2ca 1ae03d022d6d : 228985 revs, 87.824489 s, 88.388512 s, +0.564023 s, × 1.0064, 386 µs/rev
mozilla-try x00000_revs_x00000_added_x000_copies 9b2a99adc05e 8e29777b48e6 : 382065 revs, 43.304637 s, 34.443661 s, -8.860976 s, × 0.7954, 90 µs/rev
private : 459513 revs, 33.853687 s, 27.370148 s, -6.483539 s, × 0.8085, 59 µs/rev
Differential Revision: https://phab.mercurial-scm.org/D9653
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Wed, 16 Dec 2020 11:11:05 +0100 |
parents | d481f30ea8e3 |
children | d4ba4d51f85f |
line wrap: on
line source
# help.py - help data for mercurial # # Copyright 2006 Matt Mackall <mpm@selenic.com> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import import itertools import re import textwrap from .i18n import ( _, gettext, ) from .pycompat import getattr from . import ( cmdutil, encoding, error, extensions, fancyopts, filemerge, fileset, minirst, pycompat, registrar, revset, templatefilters, templatefuncs, templatekw, ui as uimod, util, ) from .hgweb import webcommands from .utils import ( compression, resourceutil, ) _exclkeywords = { b"(ADVANCED)", b"(DEPRECATED)", b"(EXPERIMENTAL)", # i18n: "(ADVANCED)" is a keyword, must be translated consistently _(b"(ADVANCED)"), # i18n: "(DEPRECATED)" is a keyword, must be translated consistently _(b"(DEPRECATED)"), # i18n: "(EXPERIMENTAL)" is a keyword, must be translated consistently _(b"(EXPERIMENTAL)"), } # The order in which command categories will be displayed. # Extensions with custom categories should insert them into this list # after/before the appropriate item, rather than replacing the list or # assuming absolute positions. CATEGORY_ORDER = [ registrar.command.CATEGORY_REPO_CREATION, registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT, registrar.command.CATEGORY_COMMITTING, registrar.command.CATEGORY_CHANGE_MANAGEMENT, registrar.command.CATEGORY_CHANGE_ORGANIZATION, registrar.command.CATEGORY_FILE_CONTENTS, registrar.command.CATEGORY_CHANGE_NAVIGATION, registrar.command.CATEGORY_WORKING_DIRECTORY, registrar.command.CATEGORY_IMPORT_EXPORT, registrar.command.CATEGORY_MAINTENANCE, registrar.command.CATEGORY_HELP, registrar.command.CATEGORY_MISC, registrar.command.CATEGORY_NONE, ] # Human-readable category names. These are translated. # Extensions with custom categories should add their names here. CATEGORY_NAMES = { registrar.command.CATEGORY_REPO_CREATION: b'Repository creation', registrar.command.CATEGORY_REMOTE_REPO_MANAGEMENT: b'Remote repository management', registrar.command.CATEGORY_COMMITTING: b'Change creation', registrar.command.CATEGORY_CHANGE_NAVIGATION: b'Change navigation', registrar.command.CATEGORY_CHANGE_MANAGEMENT: b'Change manipulation', registrar.command.CATEGORY_CHANGE_ORGANIZATION: b'Change organization', registrar.command.CATEGORY_WORKING_DIRECTORY: b'Working directory management', registrar.command.CATEGORY_FILE_CONTENTS: b'File content management', registrar.command.CATEGORY_IMPORT_EXPORT: b'Change import/export', registrar.command.CATEGORY_MAINTENANCE: b'Repository maintenance', registrar.command.CATEGORY_HELP: b'Help', registrar.command.CATEGORY_MISC: b'Miscellaneous commands', registrar.command.CATEGORY_NONE: b'Uncategorized commands', } # Topic categories. TOPIC_CATEGORY_IDS = b'ids' TOPIC_CATEGORY_OUTPUT = b'output' TOPIC_CATEGORY_CONFIG = b'config' TOPIC_CATEGORY_CONCEPTS = b'concepts' TOPIC_CATEGORY_MISC = b'misc' TOPIC_CATEGORY_NONE = b'none' # The order in which topic categories will be displayed. # Extensions with custom categories should insert them into this list # after/before the appropriate item, rather than replacing the list or # assuming absolute positions. TOPIC_CATEGORY_ORDER = [ TOPIC_CATEGORY_IDS, TOPIC_CATEGORY_OUTPUT, TOPIC_CATEGORY_CONFIG, TOPIC_CATEGORY_CONCEPTS, TOPIC_CATEGORY_MISC, TOPIC_CATEGORY_NONE, ] # Human-readable topic category names. These are translated. TOPIC_CATEGORY_NAMES = { TOPIC_CATEGORY_IDS: b'Mercurial identifiers', TOPIC_CATEGORY_OUTPUT: b'Mercurial output', TOPIC_CATEGORY_CONFIG: b'Mercurial configuration', TOPIC_CATEGORY_CONCEPTS: b'Concepts', TOPIC_CATEGORY_MISC: b'Miscellaneous', TOPIC_CATEGORY_NONE: b'Uncategorized topics', } def listexts(header, exts, indent=1, showdeprecated=False): '''return a text listing of the given extensions''' rst = [] if exts: for name, desc in sorted(pycompat.iteritems(exts)): if not showdeprecated and any(w in desc for w in _exclkeywords): continue rst.append(b'%s:%s: %s\n' % (b' ' * indent, name, desc)) if rst: rst.insert(0, b'\n%s\n\n' % header) return rst def extshelp(ui): rst = loaddoc(b'extensions')(ui).splitlines(True) rst.extend( listexts( _(b'enabled extensions:'), extensions.enabled(), showdeprecated=True ) ) rst.extend( listexts( _(b'disabled extensions:'), extensions.disabled(), showdeprecated=ui.verbose, ) ) doc = b''.join(rst) return doc def parsedefaultmarker(text): """given a text 'abc (DEFAULT: def.ghi)', returns (b'abc', (b'def', b'ghi')). Otherwise return None""" if text[-1:] == b')': marker = b' (DEFAULT: ' pos = text.find(marker) if pos >= 0: item = text[pos + len(marker) : -1] return text[:pos], item.split(b'.', 2) def optrst(header, options, verbose, ui): data = [] multioccur = False for option in options: if len(option) == 5: shortopt, longopt, default, desc, optlabel = option else: shortopt, longopt, default, desc = option optlabel = _(b"VALUE") # default label if not verbose and any(w in desc for w in _exclkeywords): continue defaultstrsuffix = b'' if default is None: parseresult = parsedefaultmarker(desc) if parseresult is not None: (desc, (section, name)) = parseresult if ui.configbool(section, name): default = True defaultstrsuffix = _(b' from config') so = b'' if shortopt: so = b'-' + shortopt lo = b'--' + longopt if default is True: lo = b'--[no-]' + longopt if isinstance(default, fancyopts.customopt): default = default.getdefaultvalue() if default and not callable(default): # default is of unknown type, and in Python 2 we abused # the %s-shows-repr property to handle integers etc. To # match that behavior on Python 3, we do str(default) and # then convert it to bytes. defaultstr = pycompat.bytestr(default) if default is True: defaultstr = _(b"on") desc += _(b" (default: %s)") % (defaultstr + defaultstrsuffix) if isinstance(default, list): lo += b" %s [+]" % optlabel multioccur = True elif (default is not None) and not isinstance(default, bool): lo += b" %s" % optlabel data.append((so, lo, desc)) if multioccur: header += _(b" ([+] can be repeated)") rst = [b'\n%s:\n\n' % header] rst.extend(minirst.maketable(data, 1)) return b''.join(rst) def indicateomitted(rst, omitted, notomitted=None): rst.append(b'\n\n.. container:: omitted\n\n %s\n\n' % omitted) if notomitted: rst.append(b'\n\n.. container:: notomitted\n\n %s\n\n' % notomitted) def filtercmd(ui, cmd, func, kw, doc): if not ui.debugflag and cmd.startswith(b"debug") and kw != b"debug": # Debug command, and user is not looking for those. return True if not ui.verbose: if not kw and not doc: # Command had no documentation, no point in showing it by default. return True if getattr(func, 'alias', False) and not getattr(func, 'owndoc', False): # Alias didn't have its own documentation. return True if doc and any(w in doc for w in _exclkeywords): # Documentation has excluded keywords. return True if kw == b"shortlist" and not getattr(func, 'helpbasic', False): # We're presenting the short list but the command is not basic. return True if ui.configbool(b'help', b'hidden-command.%s' % cmd): # Configuration explicitly hides the command. return True return False def filtertopic(ui, topic): return ui.configbool(b'help', b'hidden-topic.%s' % topic, False) def topicmatch(ui, commands, kw): """Return help topics matching kw. Returns {'section': [(name, summary), ...], ...} where section is one of topics, commands, extensions, or extensioncommands. """ kw = encoding.lower(kw) def lowercontains(container): return kw in encoding.lower(container) # translated in helptable results = { b'topics': [], b'commands': [], b'extensions': [], b'extensioncommands': [], } for topic in helptable: names, header, doc = topic[0:3] # Old extensions may use a str as doc. if ( sum(map(lowercontains, names)) or lowercontains(header) or (callable(doc) and lowercontains(doc(ui))) ): name = names[0] if not filtertopic(ui, name): results[b'topics'].append((names[0], header)) for cmd, entry in pycompat.iteritems(commands.table): if len(entry) == 3: summary = entry[2] else: summary = b'' # translate docs *before* searching there func = entry[0] docs = _(pycompat.getdoc(func)) or b'' if kw in cmd or lowercontains(summary) or lowercontains(docs): doclines = docs.splitlines() if doclines: summary = doclines[0] cmdname = cmdutil.parsealiases(cmd)[0] if filtercmd(ui, cmdname, func, kw, docs): continue results[b'commands'].append((cmdname, summary)) for name, docs in itertools.chain( pycompat.iteritems(extensions.enabled(False)), pycompat.iteritems(extensions.disabled()), ): if not docs: continue name = name.rpartition(b'.')[-1] if lowercontains(name) or lowercontains(docs): # extension docs are already translated results[b'extensions'].append((name, docs.splitlines()[0])) try: mod = extensions.load(ui, name, b'') except ImportError: # debug message would be printed in extensions.load() continue for cmd, entry in pycompat.iteritems(getattr(mod, 'cmdtable', {})): if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])): cmdname = cmdutil.parsealiases(cmd)[0] func = entry[0] cmddoc = pycompat.getdoc(func) if cmddoc: cmddoc = gettext(cmddoc).splitlines()[0] else: cmddoc = _(b'(no help text available)') if filtercmd(ui, cmdname, func, kw, cmddoc): continue results[b'extensioncommands'].append((cmdname, cmddoc)) return results def loaddoc(topic, subdir=None): """Return a delayed loader for help/topic.txt.""" def loader(ui): package = b'mercurial.helptext' if subdir: package += b'.' + subdir with resourceutil.open_resource(package, topic + b'.txt') as fp: doc = gettext(fp.read()) for rewriter in helphooks.get(topic, []): doc = rewriter(ui, topic, doc) return doc return loader internalstable = sorted( [ ( [b'bid-merge'], _(b'Bid Merge Algorithm'), loaddoc(b'bid-merge', subdir=b'internals'), ), ([b'bundle2'], _(b'Bundle2'), loaddoc(b'bundle2', subdir=b'internals')), ([b'bundles'], _(b'Bundles'), loaddoc(b'bundles', subdir=b'internals')), ([b'cbor'], _(b'CBOR'), loaddoc(b'cbor', subdir=b'internals')), ([b'censor'], _(b'Censor'), loaddoc(b'censor', subdir=b'internals')), ( [b'changegroups'], _(b'Changegroups'), loaddoc(b'changegroups', subdir=b'internals'), ), ( [b'config'], _(b'Config Registrar'), loaddoc(b'config', subdir=b'internals'), ), ( [b'extensions', b'extension'], _(b'Extension API'), loaddoc(b'extensions', subdir=b'internals'), ), ( [b'mergestate'], _(b'Mergestate'), loaddoc(b'mergestate', subdir=b'internals'), ), ( [b'requirements'], _(b'Repository Requirements'), loaddoc(b'requirements', subdir=b'internals'), ), ( [b'revlogs'], _(b'Revision Logs'), loaddoc(b'revlogs', subdir=b'internals'), ), ( [b'wireprotocol'], _(b'Wire Protocol'), loaddoc(b'wireprotocol', subdir=b'internals'), ), ( [b'wireprotocolrpc'], _(b'Wire Protocol RPC'), loaddoc(b'wireprotocolrpc', subdir=b'internals'), ), ( [b'wireprotocolv2'], _(b'Wire Protocol Version 2'), loaddoc(b'wireprotocolv2', subdir=b'internals'), ), ] ) def internalshelp(ui): """Generate the index for the "internals" topic.""" lines = [ b'To access a subtopic, use "hg help internals.{subtopic-name}"\n', b'\n', ] for names, header, doc in internalstable: lines.append(b' :%s: %s\n' % (names[0], header)) return b''.join(lines) helptable = sorted( [ ( [b'bundlespec'], _(b"Bundle File Formats"), loaddoc(b'bundlespec'), TOPIC_CATEGORY_CONCEPTS, ), ( [b'color'], _(b"Colorizing Outputs"), loaddoc(b'color'), TOPIC_CATEGORY_OUTPUT, ), ( [b"config", b"hgrc"], _(b"Configuration Files"), loaddoc(b'config'), TOPIC_CATEGORY_CONFIG, ), ( [b'deprecated'], _(b"Deprecated Features"), loaddoc(b'deprecated'), TOPIC_CATEGORY_MISC, ), ( [b"dates"], _(b"Date Formats"), loaddoc(b'dates'), TOPIC_CATEGORY_OUTPUT, ), ( [b"flags"], _(b"Command-line flags"), loaddoc(b'flags'), TOPIC_CATEGORY_CONFIG, ), ( [b"patterns"], _(b"File Name Patterns"), loaddoc(b'patterns'), TOPIC_CATEGORY_IDS, ), ( [b'environment', b'env'], _(b'Environment Variables'), loaddoc(b'environment'), TOPIC_CATEGORY_CONFIG, ), ( [ b'revisions', b'revs', b'revsets', b'revset', b'multirevs', b'mrevs', ], _(b'Specifying Revisions'), loaddoc(b'revisions'), TOPIC_CATEGORY_IDS, ), ( [b'filesets', b'fileset'], _(b"Specifying File Sets"), loaddoc(b'filesets'), TOPIC_CATEGORY_IDS, ), ( [b'diffs'], _(b'Diff Formats'), loaddoc(b'diffs'), TOPIC_CATEGORY_OUTPUT, ), ( [b'merge-tools', b'mergetools', b'mergetool'], _(b'Merge Tools'), loaddoc(b'merge-tools'), TOPIC_CATEGORY_CONFIG, ), ( [b'templating', b'templates', b'template', b'style'], _(b'Template Usage'), loaddoc(b'templates'), TOPIC_CATEGORY_OUTPUT, ), ([b'urls'], _(b'URL Paths'), loaddoc(b'urls'), TOPIC_CATEGORY_IDS), ( [b"extensions"], _(b"Using Additional Features"), extshelp, TOPIC_CATEGORY_CONFIG, ), ( [b"subrepos", b"subrepo"], _(b"Subrepositories"), loaddoc(b'subrepos'), TOPIC_CATEGORY_CONCEPTS, ), ( [b"hgweb"], _(b"Configuring hgweb"), loaddoc(b'hgweb'), TOPIC_CATEGORY_CONFIG, ), ( [b"glossary"], _(b"Glossary"), loaddoc(b'glossary'), TOPIC_CATEGORY_CONCEPTS, ), ( [b"hgignore", b"ignore"], _(b"Syntax for Mercurial Ignore Files"), loaddoc(b'hgignore'), TOPIC_CATEGORY_IDS, ), ( [b"phases"], _(b"Working with Phases"), loaddoc(b'phases'), TOPIC_CATEGORY_CONCEPTS, ), ( [b'scripting'], _(b'Using Mercurial from scripts and automation'), loaddoc(b'scripting'), TOPIC_CATEGORY_MISC, ), ( [b'internals'], _(b"Technical implementation topics"), internalshelp, TOPIC_CATEGORY_MISC, ), ( [b'pager'], _(b"Pager Support"), loaddoc(b'pager'), TOPIC_CATEGORY_CONFIG, ), ] ) # Maps topics with sub-topics to a list of their sub-topics. subtopics = { b'internals': internalstable, } # Map topics to lists of callable taking the current topic help and # returning the updated version helphooks = {} def addtopichook(topic, rewriter): helphooks.setdefault(topic, []).append(rewriter) def makeitemsdoc(ui, topic, doc, marker, items, dedent=False): """Extract docstring from the items key to function mapping, build a single documentation block and use it to overwrite the marker in doc. """ entries = [] for name in sorted(items): text = (pycompat.getdoc(items[name]) or b'').rstrip() if not text or not ui.verbose and any(w in text for w in _exclkeywords): continue text = gettext(text) if dedent: # Abuse latin1 to use textwrap.dedent() on bytes. text = textwrap.dedent(text.decode('latin1')).encode('latin1') lines = text.splitlines() doclines = [(lines[0])] for l in lines[1:]: # Stop once we find some Python doctest if l.strip().startswith(b'>>>'): break if dedent: doclines.append(l.rstrip()) else: doclines.append(b' ' + l.strip()) entries.append(b'\n'.join(doclines)) entries = b'\n\n'.join(entries) return doc.replace(marker, entries) def addtopicsymbols(topic, marker, symbols, dedent=False): def add(ui, topic, doc): return makeitemsdoc(ui, topic, doc, marker, symbols, dedent=dedent) addtopichook(topic, add) addtopicsymbols( b'bundlespec', b'.. bundlecompressionmarker', compression.bundlecompressiontopics(), ) addtopicsymbols(b'filesets', b'.. predicatesmarker', fileset.symbols) addtopicsymbols( b'merge-tools', b'.. internaltoolsmarker', filemerge.internalsdoc ) addtopicsymbols(b'revisions', b'.. predicatesmarker', revset.symbols) addtopicsymbols(b'templates', b'.. keywordsmarker', templatekw.keywords) addtopicsymbols(b'templates', b'.. filtersmarker', templatefilters.filters) addtopicsymbols(b'templates', b'.. functionsmarker', templatefuncs.funcs) addtopicsymbols( b'hgweb', b'.. webcommandsmarker', webcommands.commands, dedent=True ) def inserttweakrc(ui, topic, doc): marker = b'.. tweakdefaultsmarker' repl = uimod.tweakrc def sub(m): lines = [m.group(1) + s for s in repl.splitlines()] return b'\n'.join(lines) return re.sub(br'( *)%s' % re.escape(marker), sub, doc) def _getcategorizedhelpcmds(ui, cmdtable, name, select=None): # Category -> list of commands cats = {} # Command -> short description h = {} # Command -> string showing synonyms syns = {} for c, e in pycompat.iteritems(cmdtable): fs = cmdutil.parsealiases(c) f = fs[0] syns[f] = fs func = e[0] if select and not select(f): continue doc = pycompat.getdoc(func) if filtercmd(ui, f, func, name, doc): continue doc = gettext(doc) if not doc: doc = _(b"(no help text available)") h[f] = doc.splitlines()[0].rstrip() cat = getattr(func, 'helpcategory', None) or ( registrar.command.CATEGORY_NONE ) cats.setdefault(cat, []).append(f) return cats, h, syns def _getcategorizedhelptopics(ui, topictable): # Group commands by category. topiccats = {} syns = {} for topic in topictable: names, header, doc = topic[0:3] if len(topic) > 3 and topic[3]: category = topic[3] else: category = TOPIC_CATEGORY_NONE topicname = names[0] syns[topicname] = list(names) if not filtertopic(ui, topicname): topiccats.setdefault(category, []).append((topicname, header)) return topiccats, syns addtopichook(b'config', inserttweakrc) def help_( ui, commands, name, unknowncmd=False, full=True, subtopic=None, fullname=None, **opts ): """ Generate the help for 'name' as unformatted restructured text. If 'name' is None, describe the commands available. """ opts = pycompat.byteskwargs(opts) def helpcmd(name, subtopic=None): try: aliases, entry = cmdutil.findcmd( name, commands.table, strict=unknowncmd ) except error.AmbiguousCommand as inst: # py3 fix: except vars can't be used outside the scope of the # except block, nor can be used inside a lambda. python issue4617 prefix = inst.prefix select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix) rst = helplist(select) return rst rst = [] # check if it's an invalid alias and display its error if it is if getattr(entry[0], 'badalias', None): rst.append(entry[0].badalias + b'\n') if entry[0].unknowncmd: try: rst.extend(helpextcmd(entry[0].cmdname)) except error.UnknownCommand: pass return rst # synopsis if len(entry) > 2: if entry[2].startswith(b'hg'): rst.append(b"%s\n" % entry[2]) else: rst.append(b'hg %s %s\n' % (aliases[0], entry[2])) else: rst.append(b'hg %s\n' % aliases[0]) # aliases if full and not ui.quiet and len(aliases) > 1: rst.append(_(b"\naliases: %s\n") % b', '.join(aliases[1:])) rst.append(b'\n') # description doc = gettext(pycompat.getdoc(entry[0])) if not doc: doc = _(b"(no help text available)") if util.safehasattr(entry[0], b'definition'): # aliased command source = entry[0].source if entry[0].definition.startswith(b'!'): # shell alias doc = _(b'shell alias for: %s\n\n%s\n\ndefined by: %s\n') % ( entry[0].definition[1:], doc, source, ) else: doc = _(b'alias for: hg %s\n\n%s\n\ndefined by: %s\n') % ( entry[0].definition, doc, source, ) doc = doc.splitlines(True) if ui.quiet or not full: rst.append(doc[0]) else: rst.extend(doc) rst.append(b'\n') # check if this command shadows a non-trivial (multi-line) # extension help text try: mod = extensions.find(name) doc = gettext(pycompat.getdoc(mod)) or b'' if b'\n' in doc.strip(): msg = _( b"(use 'hg help -e %s' to show help for " b"the %s extension)" ) % (name, name) rst.append(b'\n%s\n' % msg) except KeyError: pass # options if not ui.quiet and entry[1]: rst.append(optrst(_(b"options"), entry[1], ui.verbose, ui)) if ui.verbose: rst.append( optrst( _(b"global options"), commands.globalopts, ui.verbose, ui ) ) if not ui.verbose: if not full: rst.append(_(b"\n(use 'hg %s -h' to show more help)\n") % name) elif not ui.quiet: rst.append( _( b'\n(some details hidden, use --verbose ' b'to show complete help)' ) ) return rst def helplist(select=None, **opts): cats, h, syns = _getcategorizedhelpcmds( ui, commands.table, name, select ) rst = [] if not h: if not ui.quiet: rst.append(_(b'no commands defined\n')) return rst # Output top header. if not ui.quiet: if name == b"shortlist": rst.append(_(b'basic commands:\n\n')) elif name == b"debug": rst.append(_(b'debug commands (internal and unsupported):\n\n')) else: rst.append(_(b'list of commands:\n')) def appendcmds(cmds): cmds = sorted(cmds) for c in cmds: display_cmd = c if ui.verbose: display_cmd = b', '.join(syns[c]) display_cmd = display_cmd.replace(b':', br'\:') rst.append(b' :%s: %s\n' % (display_cmd, h[c])) if name in (b'shortlist', b'debug'): # List without categories. appendcmds(h) else: # Check that all categories have an order. missing_order = set(cats.keys()) - set(CATEGORY_ORDER) if missing_order: ui.develwarn( b'help categories missing from CATEGORY_ORDER: %s' % missing_order ) # List per category. for cat in CATEGORY_ORDER: catfns = cats.get(cat, []) if catfns: if len(cats) > 1: catname = gettext(CATEGORY_NAMES[cat]) rst.append(b"\n%s:\n" % catname) rst.append(b"\n") appendcmds(catfns) ex = opts.get anyopts = ex('keyword') or not (ex('command') or ex('extension')) if not name and anyopts: exts = listexts( _(b'enabled extensions:'), extensions.enabled(), showdeprecated=ui.verbose, ) if exts: rst.append(b'\n') rst.extend(exts) rst.append(_(b"\nadditional help topics:\n")) topiccats, topicsyns = _getcategorizedhelptopics(ui, helptable) # Check that all categories have an order. missing_order = set(topiccats.keys()) - set(TOPIC_CATEGORY_ORDER) if missing_order: ui.develwarn( b'help categories missing from TOPIC_CATEGORY_ORDER: %s' % missing_order ) # Output topics per category. for cat in TOPIC_CATEGORY_ORDER: topics = topiccats.get(cat, []) if topics: if len(topiccats) > 1: catname = gettext(TOPIC_CATEGORY_NAMES[cat]) rst.append(b"\n%s:\n" % catname) rst.append(b"\n") for t, desc in topics: rst.append(b" :%s: %s\n" % (t, desc)) if ui.quiet: pass elif ui.verbose: rst.append( b'\n%s\n' % optrst( _(b"global options"), commands.globalopts, ui.verbose, ui ) ) if name == b'shortlist': rst.append( _(b"\n(use 'hg help' for the full list of commands)\n") ) else: if name == b'shortlist': rst.append( _( b"\n(use 'hg help' for the full list of commands " b"or 'hg -v' for details)\n" ) ) elif name and not full: rst.append( _(b"\n(use 'hg help %s' to show the full help text)\n") % name ) elif name and syns and name in syns.keys(): rst.append( _( b"\n(use 'hg help -v -e %s' to show built-in " b"aliases and global options)\n" ) % name ) else: rst.append( _( b"\n(use 'hg help -v%s' to show built-in aliases " b"and global options)\n" ) % (name and b" " + name or b"") ) return rst def helptopic(name, subtopic=None): # Look for sub-topic entry first. header, doc = None, None if subtopic and name in subtopics: for names, header, doc in subtopics[name]: if subtopic in names: break if not any(subtopic in s[0] for s in subtopics[name]): raise error.UnknownCommand(name) if not header: for topic in helptable: names, header, doc = topic[0:3] if name in names: break else: raise error.UnknownCommand(name) rst = [minirst.section(header)] # description if not doc: rst.append(b" %s\n" % _(b"(no help text available)")) if callable(doc): rst += [b" %s\n" % l for l in doc(ui).splitlines()] if not ui.verbose: omitted = _( b'(some details hidden, use --verbose' b' to show complete help)' ) indicateomitted(rst, omitted) try: cmdutil.findcmd(name, commands.table) rst.append( _(b"\nuse 'hg help -c %s' to see help for the %s command\n") % (name, name) ) except error.UnknownCommand: pass return rst def helpext(name, subtopic=None): try: mod = extensions.find(name) doc = gettext(pycompat.getdoc(mod)) or _(b'no help text available') except KeyError: mod = None doc = extensions.disabled_help(name) if not doc: raise error.UnknownCommand(name) if b'\n' not in doc: head, tail = doc, b"" else: head, tail = doc.split(b'\n', 1) rst = [_(b'%s extension - %s\n\n') % (name.rpartition(b'.')[-1], head)] if tail: rst.extend(tail.splitlines(True)) rst.append(b'\n') if not ui.verbose: omitted = _( b'(some details hidden, use --verbose' b' to show complete help)' ) indicateomitted(rst, omitted) if mod: try: ct = mod.cmdtable except AttributeError: ct = {} modcmds = {c.partition(b'|')[0] for c in ct} rst.extend(helplist(modcmds.__contains__)) else: rst.append( _( b"(use 'hg help extensions' for information on enabling" b" extensions)\n" ) ) return rst def helpextcmd(name, subtopic=None): cmd, ext, doc = extensions.disabledcmd( ui, name, ui.configbool(b'ui', b'strict') ) doc = doc.splitlines()[0] rst = listexts( _(b"'%s' is provided by the following extension:") % cmd, {ext: doc}, indent=4, showdeprecated=True, ) rst.append(b'\n') rst.append( _( b"(use 'hg help extensions' for information on enabling " b"extensions)\n" ) ) return rst rst = [] kw = opts.get(b'keyword') if kw or name is None and any(opts[o] for o in opts): matches = topicmatch(ui, commands, name or b'') helpareas = [] if opts.get(b'extension'): helpareas += [(b'extensions', _(b'Extensions'))] if opts.get(b'command'): helpareas += [(b'commands', _(b'Commands'))] if not helpareas: helpareas = [ (b'topics', _(b'Topics')), (b'commands', _(b'Commands')), (b'extensions', _(b'Extensions')), (b'extensioncommands', _(b'Extension Commands')), ] for t, title in helpareas: if matches[t]: rst.append(b'%s:\n\n' % title) rst.extend(minirst.maketable(sorted(matches[t]), 1)) rst.append(b'\n') if not rst: msg = _(b'no matches') hint = _(b"try 'hg help' for a list of topics") raise error.InputError(msg, hint=hint) elif name and name != b'shortlist': queries = [] if unknowncmd: queries += [helpextcmd] if opts.get(b'extension'): queries += [helpext] if opts.get(b'command'): queries += [helpcmd] if not queries: queries = (helptopic, helpcmd, helpext, helpextcmd) for f in queries: try: rst = f(name, subtopic) break except error.UnknownCommand: pass else: if unknowncmd: raise error.UnknownCommand(name) else: if fullname: formatname = fullname else: formatname = name if subtopic: hintname = subtopic else: hintname = name msg = _(b'no such help topic: %s') % formatname hint = _(b"try 'hg help --keyword %s'") % hintname raise error.InputError(msg, hint=hint) else: # program name if not ui.quiet: rst = [_(b"Mercurial Distributed SCM\n"), b'\n'] rst.extend(helplist(None, **pycompat.strkwargs(opts))) return b''.join(rst) def formattedhelp( ui, commands, fullname, keep=None, unknowncmd=False, full=True, **opts ): """get help for a given topic (as a dotted name) as rendered rst Either returns the rendered help text or raises an exception. """ if keep is None: keep = [] else: keep = list(keep) # make a copy so we can mutate this later # <fullname> := <name>[.<subtopic][.<section>] name = subtopic = section = None if fullname is not None: nameparts = fullname.split(b'.') name = nameparts.pop(0) if nameparts and name in subtopics: subtopic = nameparts.pop(0) if nameparts: section = encoding.lower(b'.'.join(nameparts)) textwidth = ui.configint(b'ui', b'textwidth') termwidth = ui.termwidth() - 2 if textwidth <= 0 or termwidth < textwidth: textwidth = termwidth text = help_( ui, commands, name, fullname=fullname, subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts ) blocks, pruned = minirst.parse(text, keep=keep) if b'verbose' in pruned: keep.append(b'omitted') else: keep.append(b'notomitted') blocks, pruned = minirst.parse(text, keep=keep) if section: blocks = minirst.filtersections(blocks, section) # We could have been given a weird ".foo" section without a name # to look for, or we could have simply failed to found "foo.bar" # because bar isn't a section of foo if section and not (blocks and name): raise error.InputError(_(b"help section not found: %s") % fullname) return minirst.formatplain(blocks, textwidth)