registrar.py
534 lines
| 17.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / registrar.py
FUJIWARA Katsunori
|
r27583 | # registrar.py - utilities to register function for specific purpose | ||
# | ||||
# Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others | ||||
# | ||||
# 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 | ||||
from . import ( | ||||
r33127 | configitems, | |||
Pierre-Yves David
|
r30608 | error, | ||
Augie Fackler
|
r30059 | pycompat, | ||
FUJIWARA Katsunori
|
r27583 | util, | ||
) | ||||
r33127 | # unlike the other registered items, config options are neither functions or | |||
# classes. Registering the option is just small function call. | ||||
# | ||||
# We still add the official API to the registrar module for consistency with | ||||
# the other items extensions want might to register. | ||||
configitem = configitems.getitemregister | ||||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28392 | class _funcregistrarbase(object): | ||
Mads Kiilerich
|
r30332 | """Base of decorator to register a function for specific purpose | ||
FUJIWARA Katsunori
|
r28392 | |||
This decorator stores decorated functions into own dict 'table'. | ||||
The least derived class can be defined by overriding 'formatdoc', | ||||
for example:: | ||||
class keyword(_funcregistrarbase): | ||||
_docformat = ":%s: %s" | ||||
This should be used as below: | ||||
keyword = registrar.keyword() | ||||
@keyword('bar') | ||||
def barfunc(*args, **kwargs): | ||||
'''Explanation of bar keyword .... | ||||
''' | ||||
pass | ||||
In this case: | ||||
- 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above | ||||
- 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword" | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28392 | def __init__(self, table=None): | ||
if table is None: | ||||
self._table = {} | ||||
else: | ||||
self._table = table | ||||
def __call__(self, decl, *args, **kwargs): | ||||
return lambda func: self._doregister(func, decl, *args, **kwargs) | ||||
def _doregister(self, func, decl, *args, **kwargs): | ||||
name = self._getname(decl) | ||||
Pierre-Yves David
|
r30608 | if name in self._table: | ||
Augie Fackler
|
r43347 | msg = b'duplicate registration for name: "%s"' % name | ||
Pierre-Yves David
|
r30608 | raise error.ProgrammingError(msg) | ||
Augie Fackler
|
r43347 | if func.__doc__ and not util.safehasattr(func, b'_origdoc'): | ||
Martin von Zweigbergk
|
r42799 | func._origdoc = func.__doc__.strip() | ||
doc = pycompat.sysbytes(func._origdoc) | ||||
Yuya Nishihara
|
r31820 | func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc)) | ||
FUJIWARA Katsunori
|
r28392 | |||
self._table[name] = func | ||||
self._extrasetup(name, func, *args, **kwargs) | ||||
return func | ||||
Matt Harbison
|
r41112 | def _merge(self, registrarbase): | ||
"""Merge the entries of the given registrar object into this one. | ||||
The other registrar object must not contain any entries already in the | ||||
current one, or a ProgrammmingError is raised. Additionally, the types | ||||
of the two registrars must match. | ||||
""" | ||||
Matt Harbison
|
r41128 | if not isinstance(registrarbase, type(self)): | ||
Augie Fackler
|
r43347 | msg = b"cannot merge different types of registrar" | ||
Matt Harbison
|
r41112 | raise error.ProgrammingError(msg) | ||
Matt Harbison
|
r41128 | dups = set(registrarbase._table).intersection(self._table) | ||
Matt Harbison
|
r41112 | |||
if dups: | ||||
Augie Fackler
|
r43347 | msg = b'duplicate registration for names: "%s"' % b'", "'.join(dups) | ||
Matt Harbison
|
r41112 | raise error.ProgrammingError(msg) | ||
self._table.update(registrarbase._table) | ||||
FUJIWARA Katsunori
|
r28392 | def _parsefuncdecl(self, decl): | ||
"""Parse function declaration and return the name of function in it | ||||
""" | ||||
Augie Fackler
|
r43347 | i = decl.find(b'(') | ||
FUJIWARA Katsunori
|
r28392 | if i >= 0: | ||
return decl[:i] | ||||
else: | ||||
return decl | ||||
def _getname(self, decl): | ||||
"""Return the name of the registered function from decl | ||||
Derived class should override this, if it allows more | ||||
descriptive 'decl' string than just a name. | ||||
""" | ||||
return decl | ||||
_docformat = None | ||||
def _formatdoc(self, decl, doc): | ||||
"""Return formatted document of the registered function for help | ||||
'doc' is '__doc__.strip()' of the registered function. | ||||
""" | ||||
return self._docformat % (decl, doc) | ||||
def _extrasetup(self, name, func): | ||||
"""Execute exra setup for registered function, if needed | ||||
""" | ||||
FUJIWARA Katsunori
|
r28393 | |||
Augie Fackler
|
r43346 | |||
Yuya Nishihara
|
r32338 | class command(_funcregistrarbase): | ||
"""Decorator to register a command function to table | ||||
Yuya Nishihara
|
r32337 | |||
Yuya Nishihara
|
r32338 | This class receives a command table as its argument. The table should | ||
Yuya Nishihara
|
r32337 | be a dict. | ||
Yuya Nishihara
|
r32338 | The created object can be used as a decorator for adding commands to | ||
that command table. This accepts multiple arguments to define a command. | ||||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The first argument is the command name (as bytes). | ||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The `options` keyword argument is an iterable of tuples defining command | ||
arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each | ||||
tuple. | ||||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The `synopsis` argument defines a short, one line summary of how to use the | ||
Yuya Nishihara
|
r32337 | command. This shows up in the help output. | ||
rlevasseur@google.com
|
r35106 | There are three arguments that control what repository (if any) is found | ||
and passed to the decorated function: `norepo`, `optionalrepo`, and | ||||
`inferrepo`. | ||||
The `norepo` argument defines whether the command does not require a | ||||
Yuya Nishihara
|
r32337 | local repository. Most commands operate against a repository, thus the | ||
rlevasseur@google.com
|
r35106 | default is False. When True, no repository will be passed. | ||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The `optionalrepo` argument defines whether the command optionally requires | ||
a local repository. If no repository can be found, None will be passed | ||||
to the decorated function. | ||||
Yuya Nishihara
|
r32337 | |||
rlevasseur@google.com
|
r35106 | The `inferrepo` argument defines whether to try to find a repository from | ||
the command line arguments. If True, arguments will be examined for | ||||
potential repository locations. See ``findrepo()``. If a repository is | ||||
found, it will be used and passed to the decorated function. | ||||
Pulkit Goyal
|
r34782 | |||
Gregory Szorc
|
r37734 | The `intents` argument defines a set of intended actions or capabilities | ||
the command is taking. These intents can be used to affect the construction | ||||
of the repository object passed to the command. For example, commands | ||||
declaring that they are read-only could receive a repository that doesn't | ||||
have any methods allowing repository mutation. Other intents could be used | ||||
to prevent the command from running if the requested intent could not be | ||||
fulfilled. | ||||
Pulkit Goyal
|
r34782 | |||
rdamazio@google.com
|
r40327 | If `helpcategory` is set (usually to one of the constants in the help | ||
module), the command will be displayed under that category in the help's | ||||
list of commands. | ||||
Gregory Szorc
|
r37734 | The following intents are defined: | ||
readonly | ||||
The command is read-only | ||||
rlevasseur@google.com
|
r35106 | |||
The signature of the decorated function looks like this: | ||||
def cmd(ui[, repo] [, <args>] [, <options>]) | ||||
`repo` is required if `norepo` is False. | ||||
`<args>` are positional args (or `*args`) arguments, of non-option | ||||
arguments from the command line. | ||||
`<options>` are keyword arguments (or `**options`) of option arguments | ||||
from the command line. | ||||
See the WritingExtensions and MercurialApi documentation for more exhaustive | ||||
descriptions and examples. | ||||
Yuya Nishihara
|
r32337 | """ | ||
Yuya Nishihara
|
r32338 | |||
rdamazio@google.com
|
r40327 | # Command categories for grouping them in help output. | ||
rdamazio@google.com
|
r40450 | # These can also be specified for aliases, like: | ||
# [alias] | ||||
# myalias = something | ||||
# myalias:category = repo | ||||
Augie Fackler
|
r43347 | CATEGORY_REPO_CREATION = b'repo' | ||
CATEGORY_REMOTE_REPO_MANAGEMENT = b'remote' | ||||
CATEGORY_COMMITTING = b'commit' | ||||
CATEGORY_CHANGE_MANAGEMENT = b'management' | ||||
CATEGORY_CHANGE_ORGANIZATION = b'organization' | ||||
CATEGORY_FILE_CONTENTS = b'files' | ||||
CATEGORY_CHANGE_NAVIGATION = b'navigation' | ||||
CATEGORY_WORKING_DIRECTORY = b'wdir' | ||||
CATEGORY_IMPORT_EXPORT = b'import' | ||||
CATEGORY_MAINTENANCE = b'maintenance' | ||||
CATEGORY_HELP = b'help' | ||||
CATEGORY_MISC = b'misc' | ||||
CATEGORY_NONE = b'none' | ||||
rdamazio@google.com
|
r40327 | |||
Augie Fackler
|
r43346 | def _doregister( | ||
self, | ||||
func, | ||||
name, | ||||
options=(), | ||||
synopsis=None, | ||||
norepo=False, | ||||
optionalrepo=False, | ||||
inferrepo=False, | ||||
intents=None, | ||||
helpcategory=None, | ||||
helpbasic=False, | ||||
): | ||||
Yuya Nishihara
|
r32339 | func.norepo = norepo | ||
func.optionalrepo = optionalrepo | ||||
func.inferrepo = inferrepo | ||||
Gregory Szorc
|
r37734 | func.intents = intents or set() | ||
rdamazio@google.com
|
r40327 | func.helpcategory = helpcategory | ||
Rodrigo Damazio
|
r40331 | func.helpbasic = helpbasic | ||
Yuya Nishihara
|
r32339 | if synopsis: | ||
self._table[name] = func, list(options), synopsis | ||||
else: | ||||
self._table[name] = func, list(options) | ||||
return func | ||||
Yuya Nishihara
|
r32337 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r37734 | INTENT_READONLY = b'readonly' | ||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28393 | class revsetpredicate(_funcregistrarbase): | ||
"""Decorator to register revset predicate | ||||
Usage:: | ||||
revsetpredicate = registrar.revsetpredicate() | ||||
@revsetpredicate('mypredicate(arg1, arg2[, arg3])') | ||||
def mypredicatefunc(repo, subset, x): | ||||
'''Explanation of this revset predicate .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Optional argument 'safe' indicates whether a predicate is safe for | ||||
DoS attack (False by default). | ||||
Yuya Nishihara
|
r29933 | Optional argument 'takeorder' indicates whether a predicate function | ||
takes ordering policy as the last argument. | ||||
Jun Wu
|
r34274 | Optional argument 'weight' indicates the estimated run-time cost, useful | ||
for static optimization, default is 1. Higher weight means more expensive. | ||||
Usually, revsets that are fast and return only one revision has a weight of | ||||
0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the | ||||
changelog have weight 10 (ex. author); revsets reading manifest deltas have | ||||
weight 30 (ex. adds); revset reading manifest contents have weight 100 | ||||
(ex. contains). Note: those values are flexible. If the revset has a | ||||
same big-O time complexity as 'contains', but with a smaller constant, it | ||||
might have a weight of 90. | ||||
FUJIWARA Katsunori
|
r28393 | 'revsetpredicate' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'revsetpredicate' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'revset.loadpredicate()' is needed. | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28393 | _getname = _funcregistrarbase._parsefuncdecl | ||
Augie Fackler
|
r43347 | _docformat = b"``%s``\n %s" | ||
FUJIWARA Katsunori
|
r28393 | |||
Jun Wu
|
r34274 | def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1): | ||
FUJIWARA Katsunori
|
r28393 | func._safe = safe | ||
Yuya Nishihara
|
r29933 | func._takeorder = takeorder | ||
Jun Wu
|
r34274 | func._weight = weight | ||
FUJIWARA Katsunori
|
r28447 | |||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28447 | class filesetpredicate(_funcregistrarbase): | ||
"""Decorator to register fileset predicate | ||||
Usage:: | ||||
filesetpredicate = registrar.filesetpredicate() | ||||
@filesetpredicate('mypredicate()') | ||||
def mypredicatefunc(mctx, x): | ||||
'''Explanation of this fileset predicate .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Optional argument 'callstatus' indicates whether a predicate | ||||
implies 'matchctx.status()' at runtime or not (False, by | ||||
default). | ||||
Yuya Nishihara
|
r38865 | Optional argument 'weight' indicates the estimated run-time cost, useful | ||
for static optimization, default is 1. Higher weight means more expensive. | ||||
Yuya Nishihara
|
r38899 | There are predefined weights in the 'filesetlang' module. | ||
Yuya Nishihara
|
r38865 | |||
Yuya Nishihara
|
r38866 | ====== ============================================================= | ||
Weight Description and examples | ||||
====== ============================================================= | ||||
0.5 basic match patterns (e.g. a symbol) | ||||
10 computing status (e.g. added()) or accessing a few files | ||||
30 reading file content for each (e.g. grep()) | ||||
50 scanning working directory (ignored()) | ||||
====== ============================================================= | ||||
FUJIWARA Katsunori
|
r28447 | 'filesetpredicate' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'filesetpredicate' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'fileset.loadpredicate()' is needed. | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28447 | _getname = _funcregistrarbase._parsefuncdecl | ||
Augie Fackler
|
r43347 | _docformat = b"``%s``\n %s" | ||
FUJIWARA Katsunori
|
r28447 | |||
Yuya Nishihara
|
r38865 | def _extrasetup(self, name, func, callstatus=False, weight=1): | ||
FUJIWARA Katsunori
|
r28447 | func._callstatus = callstatus | ||
Yuya Nishihara
|
r38865 | func._weight = weight | ||
FUJIWARA Katsunori
|
r28538 | |||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28538 | class _templateregistrarbase(_funcregistrarbase): | ||
"""Base of decorator to register functions as template specific one | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | _docformat = b":%s: %s" | ||
FUJIWARA Katsunori
|
r28538 | |||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28538 | class templatekeyword(_templateregistrarbase): | ||
"""Decorator to register template keyword | ||||
Usage:: | ||||
Mads Kiilerich
|
r30332 | templatekeyword = registrar.templatekeyword() | ||
FUJIWARA Katsunori
|
r28538 | |||
Yuya Nishihara
|
r36463 | # new API (since Mercurial 4.6) | ||
@templatekeyword('mykeyword', requires={'repo', 'ctx'}) | ||||
def mykeywordfunc(context, mapping): | ||||
'''Explanation of this template keyword .... | ||||
''' | ||||
pass | ||||
FUJIWARA Katsunori
|
r28538 | The first string argument is used also in online help. | ||
Yuya Nishihara
|
r36463 | Optional argument 'requires' should be a collection of resource names | ||
Yuya Nishihara
|
r42535 | which the template keyword depends on. | ||
Yuya Nishihara
|
r36463 | |||
FUJIWARA Katsunori
|
r28538 | 'templatekeyword' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'templatekeyword' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'templatekw.loadkeyword()' is needed. | ||||
""" | ||||
FUJIWARA Katsunori
|
r28692 | |||
Yuya Nishihara
|
r42535 | def _extrasetup(self, name, func, requires=()): | ||
Yuya Nishihara
|
r36463 | func._requires = requires | ||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28692 | class templatefilter(_templateregistrarbase): | ||
"""Decorator to register template filer | ||||
Usage:: | ||||
templatefilter = registrar.templatefilter() | ||||
Yuya Nishihara
|
r37239 | @templatefilter('myfilter', intype=bytes) | ||
FUJIWARA Katsunori
|
r28692 | def myfilterfunc(text): | ||
'''Explanation of this template filter .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Yuya Nishihara
|
r37239 | Optional argument 'intype' defines the type of the input argument, | ||
Yuya Nishihara
|
r37244 | which should be (bytes, int, templateutil.date, or None for any.) | ||
Yuya Nishihara
|
r37239 | |||
FUJIWARA Katsunori
|
r28692 | 'templatefilter' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'templatefilter' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'templatefilters.loadkeyword()' is needed. | ||||
""" | ||||
FUJIWARA Katsunori
|
r28695 | |||
Yuya Nishihara
|
r37239 | def _extrasetup(self, name, func, intype=None): | ||
func._intype = intype | ||||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28695 | class templatefunc(_templateregistrarbase): | ||
"""Decorator to register template function | ||||
Usage:: | ||||
templatefunc = registrar.templatefunc() | ||||
Yuya Nishihara
|
r38447 | @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3', | ||
requires={'ctx'}) | ||||
FUJIWARA Katsunori
|
r28695 | def myfuncfunc(context, mapping, args): | ||
'''Explanation of this template function .... | ||||
''' | ||||
pass | ||||
The first string argument is used also in online help. | ||||
Yuya Nishihara
|
r31886 | If optional 'argspec' is defined, the function will receive 'args' as | ||
a dict of named arguments. Otherwise 'args' is a list of positional | ||||
arguments. | ||||
Yuya Nishihara
|
r38447 | Optional argument 'requires' should be a collection of resource names | ||
which the template function depends on. | ||||
FUJIWARA Katsunori
|
r28695 | 'templatefunc' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'templatefunc' is used for | ||||
decorating in extension. | ||||
Yuya Nishihara
|
r36940 | Otherwise, explicit 'templatefuncs.loadfunction()' is needed. | ||
FUJIWARA Katsunori
|
r28695 | """ | ||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r28695 | _getname = _funcregistrarbase._parsefuncdecl | ||
Yuya Nishihara
|
r31886 | |||
Yuya Nishihara
|
r38447 | def _extrasetup(self, name, func, argspec=None, requires=()): | ||
Yuya Nishihara
|
r31886 | func._argspec = argspec | ||
Yuya Nishihara
|
r38447 | func._requires = requires | ||
FUJIWARA Katsunori
|
r33663 | |||
Augie Fackler
|
r43346 | |||
FUJIWARA Katsunori
|
r33663 | class internalmerge(_funcregistrarbase): | ||
"""Decorator to register in-process merge tool | ||||
Usage:: | ||||
internalmerge = registrar.internalmerge() | ||||
@internalmerge('mymerge', internalmerge.mergeonly, | ||||
FUJIWARA Katsunori
|
r39158 | onfailure=None, precheck=None, | ||
binary=False, symlink=False): | ||||
FUJIWARA Katsunori
|
r33663 | def mymergefunc(repo, mynode, orig, fcd, fco, fca, | ||
toolconf, files, labels=None): | ||||
'''Explanation of this internal merge tool .... | ||||
''' | ||||
return 1, False # means "conflicted", "no deletion needed" | ||||
The first string argument is used to compose actual merge tool name, | ||||
":name" and "internal:name" (the latter is historical one). | ||||
The second argument is one of merge types below: | ||||
========== ======== ======== ========= | ||||
merge type precheck premerge fullmerge | ||||
========== ======== ======== ========= | ||||
nomerge x x x | ||||
mergeonly o x o | ||||
fullmerge o o o | ||||
========== ======== ======== ========= | ||||
Saurabh Singh
|
r34437 | Optional argument 'onfailure' is the format of warning message | ||
FUJIWARA Katsunori
|
r33663 | to be used at failure of merging (target filename is specified | ||
at formatting). Or, None or so, if warning message should be | ||||
suppressed. | ||||
Optional argument 'precheck' is the function to be used | ||||
before actual invocation of internal merge tool itself. | ||||
It takes as same arguments as internal merge tool does, other than | ||||
'files' and 'labels'. If it returns false value, merging is aborted | ||||
immediately (and file is marked as "unresolved"). | ||||
FUJIWARA Katsunori
|
r39158 | Optional argument 'binary' is a binary files capability of internal | ||
merge tool. 'nomerge' merge type implies binary=True. | ||||
Optional argument 'symlink' is a symlinks capability of inetrnal | ||||
merge function. 'nomerge' merge type implies symlink=True. | ||||
FUJIWARA Katsunori
|
r33663 | 'internalmerge' instance in example above can be used to | ||
decorate multiple functions. | ||||
Decorated functions are registered automatically at loading | ||||
extension, if an instance named as 'internalmerge' is used for | ||||
decorating in extension. | ||||
Otherwise, explicit 'filemerge.loadinternalmerge()' is needed. | ||||
""" | ||||
Augie Fackler
|
r43346 | |||
Augie Fackler
|
r43347 | _docformat = b"``:%s``\n %s" | ||
FUJIWARA Katsunori
|
r33663 | |||
# merge type definitions: | ||||
nomerge = None | ||||
Augie Fackler
|
r43347 | mergeonly = b'mergeonly' # just the full merge, no premerge | ||
fullmerge = b'fullmerge' # both premerge and merge | ||||
FUJIWARA Katsunori
|
r33663 | |||
Augie Fackler
|
r43346 | def _extrasetup( | ||
self, | ||||
name, | ||||
func, | ||||
mergetype, | ||||
onfailure=None, | ||||
precheck=None, | ||||
binary=False, | ||||
symlink=False, | ||||
): | ||||
FUJIWARA Katsunori
|
r33663 | func.mergetype = mergetype | ||
func.onfailure = onfailure | ||||
func.precheck = precheck | ||||
FUJIWARA Katsunori
|
r39158 | |||
binarycap = binary or mergetype == self.nomerge | ||||
symlinkcap = symlink or mergetype == self.nomerge | ||||
# actual capabilities, which this internal merge tool has | ||||
Augie Fackler
|
r43347 | func.capabilities = {b"binary": binarycap, b"symlink": symlinkcap} | ||