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')