Mercurial > public > mercurial-scm > hg
comparison mercurial/commands.py @ 37716:dfc51a482031
registrar: replace "cmdtype" with an intent-based mechanism (API)
Commands perform varied actions and repositories vary in their
capabilities.
Historically, the .hg/requires file has been used to lock out clients
lacking a requirement. But this is a very heavy-handed approach and
is typically reserved for cases where the on-disk storage format
changes and we want to prevent incompatible clients from operating on
a repo.
Outside of the .hg/requires file, we tend to deal with things like
optional, extension-provided features via checking at call sites.
We'll either have checks in core or extensions will monkeypatch
functions in core disabling incompatible features, enabling new
features, etc.
Things are somewhat tolerable today. But once we introduce alternate
storage backends with varying support for repository features and
vastly different modes of behavior, the current model will quickly
grow unwieldy. For example, the implementation of the "simple store"
required a lot of hacks to deal with stripping and verify because
various parts of core assume things are implemented a certain way.
Partial clone will require new ways of modeling file data retrieval,
because we can no longer assume that all file data is already local.
In this new world, some commands might not make any sense for certain
types of repositories.
What we need is a mechanism to affect the construction of repository
(and eventually peer) instances so the requirements/capabilities
needed for the current operation can be taken into account. "Current
operation" can almost certainly be defined by a command. So it makes
sense for commands to declare their intended actions.
This commit introduces the "intents" concept on the command registrar.
"intents" captures a set of strings that declare actions that are
anticipated to be taken, requirements the repository must possess, etc.
These intents will be passed into hg.repo(), which will pass them into
localrepository, where they can be used to influence the object being
created. Some use cases for this include:
* For read-only intents, constructing a repository object that doesn't
expose methods that can mutate the repository. Its VFS instances
don't even allow opening a file with write access.
* For read-only intents, constructing a repository object without
cache invalidation logic. If the repo never changes during its lifetime,
nothing ever needs to be invalidated and we don't need to do expensive
things like verify the changelog's hidden revisions state is accurate
every time we access repo.changelog.
* We can automatically hide commands from `hg help` when the current
repository doesn't provide that command. For example, an alternate
storage backend may not support `hg commit`, so we can hide that
command or anything else that would perform local commits.
We already kind of had an "intents" mechanism on the registrar in the
form of "cmdtype." However, it was never used. And it was limited to
a single value. We really need something that supports multiple
intents. And because intents may be defined by extensions and at this
point are advisory, I think it is best to define them in a set rather
than as separate arguments/attributes on the command.
Differential Revision: https://phab.mercurial-scm.org/D3376
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sat, 14 Apr 2018 09:23:48 -0700 |
parents | 5fc502e149f1 |
children | e7bf5a73e4e1 |
comparison
equal
deleted
inserted
replaced
37715:1859b9a7ddef | 37716:dfc51a482031 |
---|---|
71 | 71 |
72 table = {} | 72 table = {} |
73 table.update(debugcommandsmod.command._table) | 73 table.update(debugcommandsmod.command._table) |
74 | 74 |
75 command = registrar.command(table) | 75 command = registrar.command(table) |
76 readonly = registrar.command.readonly | 76 INTENT_READONLY = registrar.INTENT_READONLY |
77 | 77 |
78 # common command options | 78 # common command options |
79 | 79 |
80 globalopts = [ | 80 globalopts = [ |
81 ('R', 'repository', '', | 81 ('R', 'repository', '', |
1081 @command('branches', | 1081 @command('branches', |
1082 [('a', 'active', False, | 1082 [('a', 'active', False, |
1083 _('show only branches that have unmerged heads (DEPRECATED)')), | 1083 _('show only branches that have unmerged heads (DEPRECATED)')), |
1084 ('c', 'closed', False, _('show normal and closed branches')), | 1084 ('c', 'closed', False, _('show normal and closed branches')), |
1085 ] + formatteropts, | 1085 ] + formatteropts, |
1086 _('[-c]'), cmdtype=readonly) | 1086 _('[-c]'), |
1087 intents={INTENT_READONLY}) | |
1087 def branches(ui, repo, active=False, closed=False, **opts): | 1088 def branches(ui, repo, active=False, closed=False, **opts): |
1088 """list repository named branches | 1089 """list repository named branches |
1089 | 1090 |
1090 List the repository's named branches, indicating which ones are | 1091 List the repository's named branches, indicating which ones are |
1091 inactive. If -c/--closed is specified, also list branches which have | 1092 inactive. If -c/--closed is specified, also list branches which have |
1280 _('print output to file with formatted name'), _('FORMAT')), | 1281 _('print output to file with formatted name'), _('FORMAT')), |
1281 ('r', 'rev', '', _('print the given revision'), _('REV')), | 1282 ('r', 'rev', '', _('print the given revision'), _('REV')), |
1282 ('', 'decode', None, _('apply any matching decode filter')), | 1283 ('', 'decode', None, _('apply any matching decode filter')), |
1283 ] + walkopts + formatteropts, | 1284 ] + walkopts + formatteropts, |
1284 _('[OPTION]... FILE...'), | 1285 _('[OPTION]... FILE...'), |
1285 inferrepo=True, cmdtype=readonly) | 1286 inferrepo=True, |
1287 intents={INTENT_READONLY}) | |
1286 def cat(ui, repo, file1, *pats, **opts): | 1288 def cat(ui, repo, file1, *pats, **opts): |
1287 """output the current or given revision of files | 1289 """output the current or given revision of files |
1288 | 1290 |
1289 Print the specified files as they were at the given revision. If | 1291 Print the specified files as they were at the given revision. If |
1290 no revision is given, the parent of the working directory is used. | 1292 no revision is given, the parent of the working directory is used. |
1631 [('u', 'untrusted', None, _('show untrusted configuration options')), | 1633 [('u', 'untrusted', None, _('show untrusted configuration options')), |
1632 ('e', 'edit', None, _('edit user config')), | 1634 ('e', 'edit', None, _('edit user config')), |
1633 ('l', 'local', None, _('edit repository config')), | 1635 ('l', 'local', None, _('edit repository config')), |
1634 ('g', 'global', None, _('edit global config'))] + formatteropts, | 1636 ('g', 'global', None, _('edit global config'))] + formatteropts, |
1635 _('[-u] [NAME]...'), | 1637 _('[-u] [NAME]...'), |
1636 optionalrepo=True, cmdtype=readonly) | 1638 optionalrepo=True, |
1639 intents={INTENT_READONLY}) | |
1637 def config(ui, repo, *values, **opts): | 1640 def config(ui, repo, *values, **opts): |
1638 """show combined config settings from all hgrc files | 1641 """show combined config settings from all hgrc files |
1639 | 1642 |
1640 With no arguments, print names and values of all config items. | 1643 With no arguments, print names and values of all config items. |
1641 | 1644 |
1800 @command('^diff', | 1803 @command('^diff', |
1801 [('r', 'rev', [], _('revision'), _('REV')), | 1804 [('r', 'rev', [], _('revision'), _('REV')), |
1802 ('c', 'change', '', _('change made by revision'), _('REV')) | 1805 ('c', 'change', '', _('change made by revision'), _('REV')) |
1803 ] + diffopts + diffopts2 + walkopts + subrepoopts, | 1806 ] + diffopts + diffopts2 + walkopts + subrepoopts, |
1804 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'), | 1807 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'), |
1805 inferrepo=True, cmdtype=readonly) | 1808 inferrepo=True, |
1809 intents={INTENT_READONLY}) | |
1806 def diff(ui, repo, *pats, **opts): | 1810 def diff(ui, repo, *pats, **opts): |
1807 """diff repository (or selected files) | 1811 """diff repository (or selected files) |
1808 | 1812 |
1809 Show differences between revisions for the specified files. | 1813 Show differences between revisions for the specified files. |
1810 | 1814 |
1893 [('o', 'output', '', | 1897 [('o', 'output', '', |
1894 _('print output to file with formatted name'), _('FORMAT')), | 1898 _('print output to file with formatted name'), _('FORMAT')), |
1895 ('', 'switch-parent', None, _('diff against the second parent')), | 1899 ('', 'switch-parent', None, _('diff against the second parent')), |
1896 ('r', 'rev', [], _('revisions to export'), _('REV')), | 1900 ('r', 'rev', [], _('revisions to export'), _('REV')), |
1897 ] + diffopts + formatteropts, | 1901 ] + diffopts + formatteropts, |
1898 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly) | 1902 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), |
1903 intents={INTENT_READONLY}) | |
1899 def export(ui, repo, *changesets, **opts): | 1904 def export(ui, repo, *changesets, **opts): |
1900 """dump the header and diffs for one or more changesets | 1905 """dump the header and diffs for one or more changesets |
1901 | 1906 |
1902 Print the changeset header and diffs for one or more revisions. | 1907 Print the changeset header and diffs for one or more revisions. |
1903 If no revision is given, the parent of the working directory is used. | 1908 If no revision is given, the parent of the working directory is used. |
1988 | 1993 |
1989 @command('files', | 1994 @command('files', |
1990 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')), | 1995 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')), |
1991 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), | 1996 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), |
1992 ] + walkopts + formatteropts + subrepoopts, | 1997 ] + walkopts + formatteropts + subrepoopts, |
1993 _('[OPTION]... [FILE]...'), cmdtype=readonly) | 1998 _('[OPTION]... [FILE]...'), |
1999 intents={INTENT_READONLY}) | |
1994 def files(ui, repo, *pats, **opts): | 2000 def files(ui, repo, *pats, **opts): |
1995 """list tracked files | 2001 """list tracked files |
1996 | 2002 |
1997 Print files under Mercurial control in the working directory or | 2003 Print files under Mercurial control in the working directory or |
1998 specified revision for given files (excluding removed files). | 2004 specified revision for given files (excluding removed files). |
2369 _('only search files changed within revision range'), _('REV')), | 2375 _('only search files changed within revision range'), _('REV')), |
2370 ('u', 'user', None, _('list the author (long with -v)')), | 2376 ('u', 'user', None, _('list the author (long with -v)')), |
2371 ('d', 'date', None, _('list the date (short with -q)')), | 2377 ('d', 'date', None, _('list the date (short with -q)')), |
2372 ] + formatteropts + walkopts, | 2378 ] + formatteropts + walkopts, |
2373 _('[OPTION]... PATTERN [FILE]...'), | 2379 _('[OPTION]... PATTERN [FILE]...'), |
2374 inferrepo=True, cmdtype=readonly) | 2380 inferrepo=True, |
2381 intents={INTENT_READONLY}) | |
2375 def grep(ui, repo, pattern, *pats, **opts): | 2382 def grep(ui, repo, pattern, *pats, **opts): |
2376 """search revision history for a pattern in specified files | 2383 """search revision history for a pattern in specified files |
2377 | 2384 |
2378 Search revision history for a regular expression in the specified | 2385 Search revision history for a regular expression in the specified |
2379 files or the entire project. | 2386 files or the entire project. |
2615 _('show only heads which are descendants of STARTREV'), _('STARTREV')), | 2622 _('show only heads which are descendants of STARTREV'), _('STARTREV')), |
2616 ('t', 'topo', False, _('show topological heads only')), | 2623 ('t', 'topo', False, _('show topological heads only')), |
2617 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')), | 2624 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')), |
2618 ('c', 'closed', False, _('show normal and closed branch heads')), | 2625 ('c', 'closed', False, _('show normal and closed branch heads')), |
2619 ] + templateopts, | 2626 ] + templateopts, |
2620 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly) | 2627 _('[-ct] [-r STARTREV] [REV]...'), |
2628 intents={INTENT_READONLY}) | |
2621 def heads(ui, repo, *branchrevs, **opts): | 2629 def heads(ui, repo, *branchrevs, **opts): |
2622 """show branch heads | 2630 """show branch heads |
2623 | 2631 |
2624 With no arguments, show all open branch heads in the repository. | 2632 With no arguments, show all open branch heads in the repository. |
2625 Branch heads are changesets that have no descendants on the | 2633 Branch heads are changesets that have no descendants on the |
2691 ('c', 'command', None, _('show only help for commands')), | 2699 ('c', 'command', None, _('show only help for commands')), |
2692 ('k', 'keyword', None, _('show topics matching keyword')), | 2700 ('k', 'keyword', None, _('show topics matching keyword')), |
2693 ('s', 'system', [], _('show help for specific platform(s)')), | 2701 ('s', 'system', [], _('show help for specific platform(s)')), |
2694 ], | 2702 ], |
2695 _('[-ecks] [TOPIC]'), | 2703 _('[-ecks] [TOPIC]'), |
2696 norepo=True, cmdtype=readonly) | 2704 norepo=True, |
2705 intents={INTENT_READONLY}) | |
2697 def help_(ui, name=None, **opts): | 2706 def help_(ui, name=None, **opts): |
2698 """show help for a given topic or a help overview | 2707 """show help for a given topic or a help overview |
2699 | 2708 |
2700 With no arguments, print a list of commands with short help messages. | 2709 With no arguments, print a list of commands with short help messages. |
2701 | 2710 |
2733 ('b', 'branch', None, _('show branch')), | 2742 ('b', 'branch', None, _('show branch')), |
2734 ('t', 'tags', None, _('show tags')), | 2743 ('t', 'tags', None, _('show tags')), |
2735 ('B', 'bookmarks', None, _('show bookmarks')), | 2744 ('B', 'bookmarks', None, _('show bookmarks')), |
2736 ] + remoteopts + formatteropts, | 2745 ] + remoteopts + formatteropts, |
2737 _('[-nibtB] [-r REV] [SOURCE]'), | 2746 _('[-nibtB] [-r REV] [SOURCE]'), |
2738 optionalrepo=True, cmdtype=readonly) | 2747 optionalrepo=True, |
2748 intents={INTENT_READONLY}) | |
2739 def identify(ui, repo, source=None, rev=None, | 2749 def identify(ui, repo, source=None, rev=None, |
2740 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts): | 2750 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts): |
2741 """identify the working directory or specified revision | 2751 """identify the working directory or specified revision |
2742 | 2752 |
2743 Print a summary identifying the repository state at REV using one or | 2753 Print a summary identifying the repository state at REV using one or |
3310 _('show changesets within the given named branch'), _('BRANCH')), | 3320 _('show changesets within the given named branch'), _('BRANCH')), |
3311 ('P', 'prune', [], | 3321 ('P', 'prune', [], |
3312 _('do not display revision or any of its ancestors'), _('REV')), | 3322 _('do not display revision or any of its ancestors'), _('REV')), |
3313 ] + logopts + walkopts, | 3323 ] + logopts + walkopts, |
3314 _('[OPTION]... [FILE]'), | 3324 _('[OPTION]... [FILE]'), |
3315 inferrepo=True, cmdtype=readonly) | 3325 inferrepo=True, |
3326 intents={INTENT_READONLY}) | |
3316 def log(ui, repo, *pats, **opts): | 3327 def log(ui, repo, *pats, **opts): |
3317 """show revision history of entire repository or files | 3328 """show revision history of entire repository or files |
3318 | 3329 |
3319 Print the revision history of the specified files or the entire | 3330 Print the revision history of the specified files or the entire |
3320 project. | 3331 project. |
3478 | 3489 |
3479 @command('manifest', | 3490 @command('manifest', |
3480 [('r', 'rev', '', _('revision to display'), _('REV')), | 3491 [('r', 'rev', '', _('revision to display'), _('REV')), |
3481 ('', 'all', False, _("list files from all revisions"))] | 3492 ('', 'all', False, _("list files from all revisions"))] |
3482 + formatteropts, | 3493 + formatteropts, |
3483 _('[-r REV]'), cmdtype=readonly) | 3494 _('[-r REV]'), |
3495 intents={INTENT_READONLY}) | |
3484 def manifest(ui, repo, node=None, rev=None, **opts): | 3496 def manifest(ui, repo, node=None, rev=None, **opts): |
3485 """output the current or given revision of the project manifest | 3497 """output the current or given revision of the project manifest |
3486 | 3498 |
3487 Print a list of version controlled files for the given revision. | 3499 Print a list of version controlled files for the given revision. |
3488 If no revision is given, the first parent of the working directory | 3500 If no revision is given, the first parent of the working directory |
3756 if n != nullid: | 3768 if n != nullid: |
3757 displayer.show(repo[n]) | 3769 displayer.show(repo[n]) |
3758 displayer.close() | 3770 displayer.close() |
3759 | 3771 |
3760 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True, | 3772 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True, |
3761 cmdtype=readonly) | 3773 intents={INTENT_READONLY}) |
3762 def paths(ui, repo, search=None, **opts): | 3774 def paths(ui, repo, search=None, **opts): |
3763 """show aliases for remote repositories | 3775 """show aliases for remote repositories |
3764 | 3776 |
3765 Show definition of symbolic path name NAME. If no name is given, | 3777 Show definition of symbolic path name NAME. If no name is given, |
3766 show definition of all available names. | 3778 show definition of all available names. |
4694 raise error.Abort(_('rollback is disabled because it is unsafe'), | 4706 raise error.Abort(_('rollback is disabled because it is unsafe'), |
4695 hint=('see `hg help -v rollback` for information')) | 4707 hint=('see `hg help -v rollback` for information')) |
4696 return repo.rollback(dryrun=opts.get(r'dry_run'), | 4708 return repo.rollback(dryrun=opts.get(r'dry_run'), |
4697 force=opts.get(r'force')) | 4709 force=opts.get(r'force')) |
4698 | 4710 |
4699 @command('root', [], cmdtype=readonly) | 4711 @command('root', [], intents={INTENT_READONLY}) |
4700 def root(ui, repo): | 4712 def root(ui, repo): |
4701 """print the root (top) of the current working directory | 4713 """print the root (top) of the current working directory |
4702 | 4714 |
4703 Print the root directory of the current repository. | 4715 Print the root directory of the current repository. |
4704 | 4716 |
4788 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), | 4800 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), |
4789 ('', 'rev', [], _('show difference from revision'), _('REV')), | 4801 ('', 'rev', [], _('show difference from revision'), _('REV')), |
4790 ('', 'change', '', _('list the changed files of a revision'), _('REV')), | 4802 ('', 'change', '', _('list the changed files of a revision'), _('REV')), |
4791 ] + walkopts + subrepoopts + formatteropts, | 4803 ] + walkopts + subrepoopts + formatteropts, |
4792 _('[OPTION]... [FILE]...'), | 4804 _('[OPTION]... [FILE]...'), |
4793 inferrepo=True, cmdtype=readonly) | 4805 inferrepo=True, |
4806 intents={INTENT_READONLY}) | |
4794 def status(ui, repo, *pats, **opts): | 4807 def status(ui, repo, *pats, **opts): |
4795 """show changed files in the working directory | 4808 """show changed files in the working directory |
4796 | 4809 |
4797 Show status of files in the repository. If names are given, only | 4810 Show status of files in the repository. If names are given, only |
4798 files that match are shown. Files that are clean or ignored or | 4811 files that match are shown. Files that are clean or ignored or |
4956 cmdutil.morestatus(repo, fm) | 4969 cmdutil.morestatus(repo, fm) |
4957 fm.end() | 4970 fm.end() |
4958 | 4971 |
4959 @command('^summary|sum', | 4972 @command('^summary|sum', |
4960 [('', 'remote', None, _('check for push and pull'))], | 4973 [('', 'remote', None, _('check for push and pull'))], |
4961 '[--remote]', cmdtype=readonly) | 4974 '[--remote]', |
4975 intents={INTENT_READONLY}) | |
4962 def summary(ui, repo, **opts): | 4976 def summary(ui, repo, **opts): |
4963 """summarize working directory state | 4977 """summarize working directory state |
4964 | 4978 |
4965 This generates a brief summary of the working directory state, | 4979 This generates a brief summary of the working directory state, |
4966 including parents, branch, commit status, phase and available updates. | 4980 including parents, branch, commit status, phase and available updates. |
5357 tagsmod.tag(repo, names, node, message, opts.get('local'), | 5371 tagsmod.tag(repo, names, node, message, opts.get('local'), |
5358 opts.get('user'), date, editor=editor) | 5372 opts.get('user'), date, editor=editor) |
5359 finally: | 5373 finally: |
5360 release(lock, wlock) | 5374 release(lock, wlock) |
5361 | 5375 |
5362 @command('tags', formatteropts, '', cmdtype=readonly) | 5376 @command('tags', formatteropts, '', intents={INTENT_READONLY}) |
5363 def tags(ui, repo, **opts): | 5377 def tags(ui, repo, **opts): |
5364 """list repository tags | 5378 """list repository tags |
5365 | 5379 |
5366 This lists both regular and local tags. When the -v/--verbose | 5380 This lists both regular and local tags. When the -v/--verbose |
5367 switch is used, a third column "local" is printed for local tags. | 5381 switch is used, a third column "local" is printed for local tags. |
5594 | 5608 |
5595 Returns 0 on success, 1 if errors are encountered. | 5609 Returns 0 on success, 1 if errors are encountered. |
5596 """ | 5610 """ |
5597 return hg.verify(repo) | 5611 return hg.verify(repo) |
5598 | 5612 |
5599 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly) | 5613 @command('version', [] + formatteropts, norepo=True, |
5614 intents={INTENT_READONLY}) | |
5600 def version_(ui, **opts): | 5615 def version_(ui, **opts): |
5601 """output version and copyright information""" | 5616 """output version and copyright information""" |
5602 opts = pycompat.byteskwargs(opts) | 5617 opts = pycompat.byteskwargs(opts) |
5603 if ui.verbose: | 5618 if ui.verbose: |
5604 ui.pager('version') | 5619 ui.pager('version') |