Mercurial > hgrepos > DevTools > mercurial-extensions
view extensions/kwarchive.py @ 440:72df885a1012 default tip trunk
===== signature for changeset e1ae0c15acfc
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 30 May 2026 13:16:08 +0200 |
| parents | 111aa1d44ffd |
| children |
line wrap: on
line source
# -*- coding: utf-8 -*- # @(#) $HGheader$ # $HGnodeid$ # :- # :Copyright: © 2017-2026 Franz Glasner <fzglas.hg@dom66.de> # :License: This software may be used and distributed according to the # terms of the GNU General Public License version 2 or any # later version. # The license is incorporated herein by reference. # :- # """like :hg:`archive` but with keyword expansion within selected files Configuration is done in a versioned ``.hgkwarchive`` configuration file. This file uses the same syntax as all other Mercurial configuration files. The ``[patterns]`` section specifies which files should have the keywords expanded and possibly the style of expansion. The keys are Mercurial file name patterns as described in :hg:`patterns`. Example:: # # Determine which files are considered for keyword expansion in # which style # [patterns] # # Expand keywords in every python file except those matching "x*" # using the default RCS style expansion format # **.py = x* = NO # This file has reStructuredText style keyword expansion "|VCS<kw>|" only path:VERSION = reST # This file has reStructuredText style and RCS style keyword expansion path:README = reST, RCS The ``[keywords]`` section specifies which keywords are expanded. Aliases can be defined also. By default the pre-defined set of RCC/CVS/SVN-like keywords is expanded. A non-empty ``[keyword]`` section defines an explicit white-list of expanded keywords and/or aliases. Example:: # # Determine which keywords are expanded. # A missing and/or empty section means that all predefined keywords # are expanded. # # Consisting of key-value pairs with of the form # # Alias = PreDefinedKeyword # # An empty `PreDefinedKeyword' means the identity. # [keywords] # `Revision' and possibly the `VCSRevision' keyword is expanded Revision = # # Additionally: `MyKeyword' is expanded with the contents of the # pre-defined `JustDate' (this is in Python's format syntax). # MyKeyword = {JustDate} # # `MyCustomSubstKeyword' is a substitution keyword. # MyCustomSubstKeyword = This is my replacement content A non-existing ``.hgkwarchive`` file deactivates keyword expansion as does an empty ``[patterns]`` section. .. note:: Because the keyword expansion is defined in a *versioned* file no templating is supported here. This could lead to remote code execution secnarios because Mercurial templates can execute a big bunch of Python functions. (rev |VCSRevision|) """ from __future__ import absolute_import __revision__ = "|VCSRevision|" __date__ = "|VCSJustDate|" __author__ = "Franz Glasner" import os import inspect import itertools from mercurial.i18n import _ from mercurial import (archival, config, cmdutil, error, match, subrepo, pycompat, scmutil, templatefilters, templatekw, util, node, demandimport) with demandimport.deactivated(): try: from mercurial import registrar except ImportError: registrar = None # check for new template function API with `(context, mapping)` try: from mercurial import templatefuncs except ImportError: context_mapping_api = False else: # >= 4.6 context_mapping_api = True # some date specific util functions moved to new mercurial.utils.dateutil try: from mercurial.utils import dateutil as _dateutil except ImportError: _dateutil = util # some URL specific util functions moved to new mercurial.utils.urlutil try: from mercurial.utils import urlutil as _urlutil except ImportError: _urlutil = util testedwith = b"4.3.1 4.3.2 4.4.1 4.4.2 4.5.2 4.6.1 4.8.1 4.9 5.0.1 5.1.2 5.2.1 5.5 5.6 5.9.1 6.1.4 6.9.5" SHORTENED_HG_SCHEMES = { b"ssh": b"hg+ssh", b"http": b"hg+http", b"https": b"hg+https", b"file": b"file", } KWARCHIVE_CONFIG = b".hgkwarchive" cmdtable = {} if registrar and hasattr(registrar, "command"): command = registrar.command(cmdtable) else: command = cmdutil.command(cmdtable) def getversion(): """Provide the version information for verbose :hg:`version` output. Read the :file:`VERSION` from the parent of the :file:`extensions` directory. """ import re import os try: fn = __file__ except NameError: return b"<unknown>" else: try: # this is pure Python standard functionality: no util.posixfile verdata = open(os.path.join(os.path.dirname(fn), "../VERSION"), "rb").read() return re.search(b"^(.*)", verdata,).group(1) except OSError: return b"<not found>" @command( b"kwarchive", [ (b'', b"no-decode", None, _(b"do not pass files through decoders")), (b'p', b"prefix", b'', _(b"directory prefix for files in archive"), _(b"PREFIX")), (b'r', b"rev", b'', _(b"revision to distribute"), _(b"REV")), (b't', b"type", b'', _(b"type of distribution to create"), _(b"TYPE")), (b'', b"path", b"default", _(b"the canonical repository to use"), _(b"PATH")), (b'', b"kwconfig", b'', _(b"an alternate pattern configuration configuration file (possibly used for subrepos also)"), _(b"PATTERNCONFIG")), (b'', b"no-shorten-path", None, _(b"don't shorten the path urls")), (b'', b"path-filter", b"short", _(b"determine how the path will be printed")), (b'', b"user-filter", b"user", _(b"the part of the user to be printed"), _(b"USERFILTER")) ] + cmdutil.subrepoopts + cmdutil.walkopts, _(b"[OPTION]... DEST"), inferrepo=True) def kwarchive(ui, repo, dest, **opts): '''create an unversioned archive of a repository revision with some keywords expanded By default, the revision used is the parent of the working directory; use -r/--rev to specify a different revision. The archive type is automatically detected based on file extension (to override, use -t/--type). .. container:: verbose Examples: - create a zip file containing the 1.0 release:: hg archive -r 1.0 project-1.0.zip - create a tarball excluding .hg files:: hg archive project.tar.gz -X ".hg*" Valid types are: :``files``: a directory full of files (default) :``tar``: tar archive, uncompressed :``tbz2``: tar archive, compressed using bzip2 :``tgz``: tar archive, compressed using gzip :``uzip``: zip archive, uncompressed :``zip``: zip archive, compressed using deflate The exact name of the destination archive or directory is given using a format string; see :hg:`help export` for details. Each member added to an archive file has a directory prefix prepended. Use -p/--prefix to specify a format string for the prefix. The default is the basename of the archive, with suffixes removed. Use --path to specify named path information from :hg:`paths` as the canonical repository location. Use ``.`` for the current repository root root. If no path is given then ``default`` is assumed. Full path URLs are printed somewhat shortened by default. To use them as-is use the --no-shorten-path option. Allowed --user-filter values are: :``user``: the short representation of a user name or email address :``person``: the name before an email address as per RFC 5322 :``email``: the email address :``full``: the complete user information w/o filtering :``none``: an alias for ``full`` The --path-filter values detemine how the path will be printed: :``full``: the path will be printed as is (including passwords et al.) :``nopwd``: like ``full`` but without passwords :``nouser``: like ``full`` but without user and password information :``short``: shorten the path somehow: no user/password information and only a short server name :``last``: shorten the path to the last component :``repo``: the repository id will be used as path :``reposhort``: the shortened repository id will be used as path Returns 0 on success. ''' opts = pycompat.byteskwargs(opts) ctx = scmutil.revsingle(repo, opts.get(b"rev")) if not ctx: raise error.Abort(_(b"no Mercurial revision found: please specify a revision")) node = ctx.node() dest = makefilename_compat(ctx, dest) if os.path.realpath(dest) == repo.root: raise error.Abort(_(b"repository root cannot be destination")) kind = opts.get(b"type") or archival.guesskind(dest) or b"files" prefix = opts.get(b"prefix") if dest == b'-': if kind == "files": raise error.Abort(_(b"cannot archive plain files to stdout")) dest = makefileobj_compat(ctx, dest) if not prefix: prefix = os.path.basename(repo.root) + b'-%h' prefix = makefilename_compat(ctx, prefix) matchfn = scmutil.match(ctx, [], opts) # # Monkey patch archival's archivers classes so that an archiver's "addfile()" # expands keywords # for ac in ("fileit", "tarit", "zipit",): patch_archiver_class( ac, make_keyword_filter( ui, ctx, ac, archival.tidyprefix(dest, kind, prefix), hgpath=opts.get(b"path"), path_filter=get_checked_path_filter_option(opts), user_filter=get_checked_user_filter_option(opts), subrepos=opts.get(b"subrepos"), kwconfig=opts.get(b"kwconfig"))) archival.archive(repo, dest, node, kind, not opts.get(b"no_decode"), matchfn, prefix, subrepos=opts.get(b"subrepos")) # XXX FIXME: Should the original methods be restored here? # XXX FIXME: Should we automatically amend ".hg_archival.txt" with a # path item? But this means shat we should know the complete # configuration ("ui.archivemeta") and it's name and some # output match filters (see archival.archive()). @command( b"kwprint", [ (b'r', b"rev", b'', _(b"revision to distribute"), _(b"REV")), (b'', b"path", b"default", _(b"the canonical repository to use"), _(b"PATH")), (b'', b"no-shorten-path", None, _(b"don't shorten the path urls")), (b'', b"path-filter", b"short", _(b"determine how the path will be printed")), (b'', b"user-filter", b"user", _(b"the part of the user to be printed"), _(b"USERFILTER")), (b'', b"reST", None, _(b"output in reST substitution definition syntax")), (b'', b"no-file", None, _(b"don't show file-dependent keywords")), ] + cmdutil.subrepoopts + cmdutil.walkopts, _(b"[OPTION]..."), inferrepo=True) def kwprint(ui, repo, **opts): '''print the file-independent keywords and for an example file-dependent keywords By default, the revision used is the parent of the working directory; use -r/--rev to specify a different revision. Use --path to specify named path information from :hg:`paths` as the canonical repository location. Use ``.`` for the current repository root root. If no path is given then ``default`` is assumed. Full path URLs are printed somewhat shortened by default. To use them as-is use the --no-shorten-path option. Allowed --user-filter values are: :``user``: the short representation of a user name or email address :``person``: the name before an email address as per RFC 5322 :``email``: the email address :``full``: the complete user information w/o filtering :``none``: an alias for ``full`` The --path-filter values detemine how the path will be printed: :``full``: the path will be printed as is (including passwords et al.) :``nopwd``: like ``full`` but without passwords :``nouser``: like ``full`` but without user and password information :``short``: shorten the path somehow: no user/password information and only a short server name :``last``: shorten the path to the last component :``repo``: the repository id will be used as path :``reposhort``: the shortened repository id will be used as path Use --reST to output the keyword in a format suitable for including in reStructuredText (reST) documents by using it's substitution feature. All revision keyword names have a ``VCS`` prefix. Use --no-file to suppress the output of file-dependent keywords with an example file. Returns 0 on success. ''' opts = pycompat.byteskwargs(opts) ctx = scmutil.revsingle(repo, opts.get(b"rev")) if not ctx: raise error.Abort(_(b"no Mercurial revision found: please specify a revision")) node = ctx.node() prefix = makefilename_compat(ctx, b'') keywords = make_node_keywords( ui, ctx, hgpath=opts.get(b"path"), path_filter=get_checked_path_filter_option(opts), user_filter=get_checked_user_filter_option(opts)) # make file-dependent keywords for an example file if not opts.get(b"no_file"): file_keywords = make_file_keywords( keywords, b"dir1/dir2/test.file", b"a4dd6f4b22e11fec41158eec187630c24a43120a") else: file_keywords = None _kwprint_keywords(ui, keywords, file_keywords, opts.get(b"reST"), opts.get(b"no_file")) if opts.get(b"subrepos"): _kwprint_subrepos(ctx, ui, opts.get(b"reST"), opts.get(b"no_file"), path_filter=get_checked_path_filter_option(opts), user_filter=get_checked_user_filter_option(opts)) def _kwprint_keywords(ui, keywords, file_keywords, rest, no_file): """Print all the prepared keywords into the output""" for key in sorted(keywords.keys()): if rest: if keywords[key]: ui.write(b".. |VCS%s| replace:: %s\n" % (key, keywords[key])) else: # # empty replacements are not allowed: # write a non-breaking space instead # ui.write(b".. |VCS%s| unicode:: 0xA0\n" % key) else: ui.write(b"$%s: %s $\n" % (key, keywords[key])) if not no_file: ui.write(b"\n") for key in sorted(file_keywords.keys()): if rest: ui.write(b".. |VCS%s| replace:: %s\n" % (key, file_keywords[key])) else: ui.write(b"$%s: %s $\n" % (key, file_keywords[key])) def _kwprint_subrepos(ctx, ui, rest, no_file, path_filter, user_filter): """For all subrepos in `ctx` do keyword expansion resursively""" for subpath in sorted(ctx.substate): substate = ctx.substate[subpath] # skip on non-Mercurial subrepos if substate[2] != b"hg": continue subrep = ctx.workingsub(subpath) subctx = subrep._getctx() assert subctx.repo() == subrep._repo keywords = make_node_keywords(ui, subctx, hgpath=None, path_filter=path_filter, user_filter=user_filter, hglocation=substate[0]) if not no_file: file_keywords = make_file_keywords( keywords, b"dir3/dir4/test-s.file", b"ffffffff22e11fec41158eec187630c24a43120a") else: file_keywords = None ui.write(b"\n\n") _kwprint_keywords(ui, keywords, file_keywords, rest, no_file) # Recursively check for other subrepos _kwprint_subrepos(subctx, ui, rest, no_file, path_filter, user_filter) def _test_subrepos(ctx, ui): ui.write("STATE: " + repr(subrepo.state(ctx, ui)) + "\n") for subpath in sorted(ctx.substate): ui.write("SUBPATH: " + subpath + "\n"); subrep = ctx.workingsub(subpath) ui.write(repr(subrep) + ": subrelpath=" + subrepo.subrelpath(subrep) + " reporelpath=" + subrepo.reporelpath(subrep._repo) + '\n') # Yes(!) assert subrepo.subrelpath(subrep) == subrepo.reporelpath(subrep._repo) assert subrepo.subrelpath(subrep).endswith(subpath) ui.write(" " + repr(subrep._getctx()) + '\n') _test_subrepos(subrep._getctx(), ui) def patch_archiver_class(archivername, filter): """Patch an archiver class and return the original unbound method""" archiver_class = getattr(archival, archivername) orig_addfile = getattr(archiver_class, "addfile") def new_addfile(self, name, mode, islink, data): return orig_addfile(self, name, mode, islink, filter(name, data)) setattr(archiver_class, "addfile", new_addfile) return orig_addfile def make_keyword_filter(ui, ctx, archive_class, prefix, hgpath=b"default", path_filter=b"short", user_filter=b"user", subrepos=False, kwconfig=b""): assert isinstance(archive_class, str), "must be a native string" filterdata_by_subrepos = { b'': _make_repo_filterdata(ui, ctx, hgpath, None, kwconfig, path_filter, user_filter), } if subrepos: _amend_filterdata_by_subrepos(filterdata_by_subrepos, ui, ctx, kwconfig, path_filter, user_filter) subrepo_paths = list(filterdata_by_subrepos.keys()) subrepo_paths.sort(key=len, reverse=True) def _filter(name, data): real_name = rel_name_in_subrepo = name if archive_class != "fileit": if prefix: assert name.startswith(prefix) real_name = rel_name_in_subrepo = name[len(prefix):] # find the filterdata configuration corresponding to current subrepo for s in subrepo_paths: if real_name.startswith(s): filterdata = filterdata_by_subrepos[s] rel_name_in_subrepo = real_name[len(s):] break else: raise ValueError("invalid subrepo filter data") if filterdata is None: return data matcher, matcher_rcs, matcher_rst, manifest, \ keyword_substitutions, keywords = filterdata if not matcher(rel_name_in_subrepo): #ui.write("NOT MATCHER for " + real_name + " (name: " + name + ") \n") return data try: nodeid = node.hex(manifest[rel_name_in_subrepo]) except LookupError: nodeid = None # file specific keywords file_keywords = make_file_keywords( keywords, rel_name_in_subrepo, nodeid) _predef_keywords = keywords.copy() _predef_keywords.update(file_keywords) # This prevents unwanted keyword expansion here _MARKER_RCS = b'$' _MARKER_RST = b'|' for kw, value in itertools.chain(keyword_substitutions.items(), keywords.items(), file_keywords.items()): # # Non-empty keyword_substitutions are an implicit whitelist and # the value are Python format templates when expanding. # if keyword_substitutions: if kw in keyword_substitutions: kwds = [kw] if value: if not pycompat.ispy3: value = value.format(**_predef_keywords) else: # Assume UTF-8 strings in config files _predef_keywords_u = dict((k.decode("utf-8"), v.decode("utf-8")) for k, v in _predef_keywords.items()) value = value.decode("utf-8").format(**_predef_keywords_u).encode("utf-8") else: value = _predef_keywords[kw] else: # not whitelisted -> ignore kwds = [] else: # Empty keyword_substitutions mean: expand built-in keywords kwds = [kw] for kw in kwds: if matcher_rcs(rel_name_in_subrepo): filekw = b"%s%s%s" % (_MARKER_RCS, kw, _MARKER_RCS) filevalue = b"%s%s: %s %s" \ % (_MARKER_RCS, kw, value, _MARKER_RCS) data = data.replace(filekw, filevalue) if matcher_rst(rel_name_in_subrepo): filekw = b"%sVCS%s%s" % (_MARKER_RST, kw, _MARKER_RST) filevalue = b"%s" % value # always convert to a string data = data.replace(filekw, filevalue) return data return _filter def _make_repo_filterdata(ui, ctx, hgpath, hglocation, kwconfig, path_filter, user_filter): keywords = make_node_keywords(ui, ctx, hgpath=hgpath, path_filter=path_filter, user_filter=user_filter, hglocation=hglocation) kwconfigdata = kwconfigname = None if kwconfig: kwconfigdata = util.posixfile(kwconfig, "rb").read() kwconfigname = kwconfig try: if kwconfigdata is None: # use versioned .hgkwarchive kwconfigdata = ctx[KWARCHIVE_CONFIG].data() kwconfigname = KWARCHIVE_CONFIG except (IOError, LookupError): # sigil for no keyword expansion configured -> filter is pass-through return None else: # # Parse the data in ".hgkwarchive" and generate a # Mercurial matcher # cfg = config.config() cfg.parse(kwconfigname, kwconfigdata) include = [] exclude = [] patterns = [] patterns_rcs = [] patterns_rst = [] if cfg.items(b"patterns"): for pattern, styles in cfg.items(b"patterns"): styles = [s.strip() for s in styles.upper().split(b',')] if b"YES" in styles or b"INCLUDE" in styles: include.append(pattern) elif b"NO" in styles or b"EXCLUDE" in styles: exclude.append(pattern) else: patterns.append(pattern) if b"REST" in styles or b"RST" in styles \ or b"RCS" in styles: if b"RCS" in styles: patterns_rcs.append(pattern) if b"REST" in styles or b"RST" in styles: patterns_rst.append(pattern) else: # default to RCS if no keyword style is given patterns_rcs.append(pattern) matcher = match.match(ctx.repo().root, b'', patterns=patterns, include=include, exclude=exclude) else: matcher = _matchmod_never(ctx.repo().root, b'') # # An empty patterns_rcs does not mean that match_rcs is always # true. # if not patterns_rcs: matcher_rcs = _matchmod_never(ctx.repo().root, b'') else: matcher_rcs = match.match(ctx.repo().root, b'', patterns=patterns_rcs, include=[], exclude=[]) # # An empty patterns_rst does not mean that match_rst is always # true. # if not patterns_rst: matcher_rst = _matchmod_never(ctx.repo().root, b'') else: matcher_rst = match.match(ctx.repo().root, b'', patterns=patterns_rst, include=[], exclude=[]) # # This are the settings of the [keywords] section in .hgkwarchive. # An empty section means: all default keywords are enabled. # Otherwise only the given keywords are enabled with their expanded # values on the right side. An empty right side means: use the default # expansion. # keyword_substitutions = {} for alias, value in cfg.items(b"keywords"): keyword_substitutions[alias] = value # # Get the manifest to be able to determine a file's NodeId # manifest = ctx.manifest() return (matcher, matcher_rcs, matcher_rst, manifest, keyword_substitutions, keywords,) def _amend_filterdata_by_subrepos(filterdata_by_subrepos, ui, ctx, kwconfig, path_filter, user_filter): for subpath in sorted(ctx.substate): substate = ctx.substate[subpath] # skip on non-Mercurial subrepos if substate[2] != b"hg": continue subrep = ctx.workingsub(subpath) subctx = subrep._getctx() # Really amend filterdata_by_subrepos[subrepo.subrelpath(subrep) + b'/'] = \ _make_repo_filterdata(ui, subctx, None, substate[0], kwconfig, path_filter, user_filter) # Recursively check for other subrepos _amend_filterdata_by_subrepos(filterdata_by_subrepos, ui, subctx, kwconfig, path_filter, user_filter) def make_node_keywords(ui, ctx, hgpath=b"default", path_filter=b"short", user_filter=b"user", hglocation=None): """Make all the node-specific (i.e. file-path independent) keywords """ if path_filter == b"repo": path_uri = b"repo:" + ctx.repo()[ctx.repo().lookup(b'0')].hex() elif path_filter == b"reposhort": path_uri = b"repo:" + templatefilters.short( ctx.repo()[ctx.repo().lookup(b'0')].hex()) else: if (hglocation is not None) and hgpath: raise ValueError("either `hgpath' or `hglocation` can be set") if (hglocation is not None) or (hgpath and hgpath != b"."): if hglocation is None: # # Since Mercurial 5.8 ui.paths[n] yields a list of # locations # locations = ui.paths[hgpath] if isinstance(locations, list): # for now a quick check of assumptions assert len(locations) == 1 hglocation = locations[0].loc else: hglocation = locations.loc try: if path_filter == b"full": path_uri = bytes(_urlutil.url(hglocation)) elif path_filter == b"nopwd": path_uri = _urlutil.hidepassword(hglocation) elif path_filter == b"nouser": path_url = _urlutil.url(hglocation) path_url.user = None path_url.passwd = None path_uri = bytes(path_url) elif path_filter == b"short": path_url = _urlutil.url(hglocation) path_url.scheme = SHORTENED_HG_SCHEMES.get(path_url.scheme, b"hg") path_url.user = None path_url.passwd = None path_url.host = stripped_hostname(path_url.host) path_url.port = None path_uri = bytes(path_url) elif path_filter == b"last": path_url = str(_urlutil.url(hglocation)).split('/') path_uri = pycompat.sysbytes(".../"+path_url[-1]) else: raise error.Abort(b"path-filter `%s' not implemented" % path_filter) except LookupError: raise error.Abort( _(b"remote repository named `%s' not found") % hgpath) else: path_uri = ctx.repo().root if path_filter == b"last": m = max(path_uri.rfind(b"/"), path_uri.rfind(b"\\")) if m >= 0: path_uri = b".../" + path_uri[m+1:] if path_uri.startswith(b"\\\\"): # Make an URL from a Windows UNC path path_uri = b"file:///" + path_uri.replace(b'\\', b'/') elif len(path_uri) >= 2 \ and ((pycompat.ispy3 and ((b'A' <= pycompat.bytechr(path_uri[0]) <= b'Z') or (b'a' <= pycompat.bytechr(path_uri[0]) <= b'z'))) or ((not pycompat.ispy3 and (b'A' <= path_uri[0].upper() <= b'Z')))) \ and path_uri[1] == b':': # make an URL from a Windows path with drive letter path_uri = b"file:///" + path_uri.replace(b'\\', b'/') elif path_uri.startswith(b'/'): # an absolute POSIX path path_uri = b"file://" + path_uri if context_mapping_api: mapping = { b'repo': ctx.repo(), b'ctx': ctx, b'cache': {} } latesttags = templatekw.getlatesttags(_FakeRenderContext(), mapping) else: latesttags = templatekw.getlatesttags(ctx.repo(), ctx, {}) keywords = { b"HGrepoid": ctx.repo()[ctx.repo().lookup(b'0')].hex(), # repo id b"HGshortrepoid": templatefilters.short(ctx.repo()[ctx.repo().lookup(b'0')].hex()), # short form of the repo id b"HGpath": path_uri, # XXX FIXME: Should Archive an alias of this b"HGbranch": ctx.branch(), b"HGtags": b' '.join([tag for tag in ctx.tags() if tag != b"tip"]), b"HGlatesttags": b" ".join(latesttags[2]), b"HGlatesttagdistance": pycompat.sysbytes(str(latesttags[1])), b"HGlatesttagdate": templatefilters.isodatesec(_dateutil.makedate(latesttags[0])), b"HGlatesttagjustdate": templatefilters.shortdate(_dateutil.makedate(latesttags[0])), b"HGbookmarks": b' '.join([bm if not bm.startswith(b'*') else bm[1:] for bm in ctx.bookmarks() if bm != b"@"]), b"State": ctx.phasestr(), b"HGrevision": ctx.hex(), b"Revision": templatefilters.short(ctx.hex()), b"Date": templatefilters.isodatesec(ctx.date()), b"JustDate": templatefilters.shortdate(ctx.date()), # compatibility alias for `JustDate' b"HGshortdate": templatefilters.shortdate(ctx.date()), } keywords[b"Date2"] = keywords[b"Date"].replace(b"-", b"/", 2) keywords[b"JustDate2"] = keywords[b"JustDate"].replace(b"-", b"/", 2) if user_filter == b"user": keywords[b"Author"] = templatefilters.emailuser(ctx.user()) elif user_filter == b"person": keywords[b"Author"] = templatefilters.utf8( templatefilters.person(ctx.user()).replace(b' ', b'+')) elif user_filter == b"email": keywords[b"Author"] = templatefilters.email(ctx.user()) elif user_filter in (b"full", b"none",): # # "none" is retained for compatibility reasons and now an # alias for "full". # But make it **one** word because that is meant in the RCS spec. # keywords[b"Author"] = templatefilters.utf8(ctx.user().replace(b' ', b'+')) else: raise error.Abort(_(b"unknown user filter")) return keywords def make_file_keywords(keywords, rel_name, nodeid): return { b"HGsource": keywords[b"HGpath"] + b'/' + rel_name, b"Source": rel_name, b"File": templatefilters.basename(rel_name), b"Header": b"%s %s %s %s %s" % (rel_name, keywords[b"Revision"], keywords[b"Date"], keywords[b"Author"], keywords[b"State"]), b"Header2": b"%s %s %s %s %s" % (rel_name, keywords[b"Revision"], keywords[b"Date2"], keywords[b"Author"], keywords[b"State"]), b"HGid": b"%s %s %s %s %s" % (keywords[b"HGpath"] + b'/' + rel_name, keywords[b"Revision"], keywords[b"Date"], keywords[b"Author"], keywords[b"State"]), b"HGid2": b"%s %s %s %s %s" % (keywords[b"HGpath"] + b'/' + rel_name, keywords[b"Revision"], keywords[b"Date2"], keywords[b"Author"], keywords[b"State"]), b"HGheader": b"%s %s %s %s %s" % (keywords[b"HGpath"] + b'/' + rel_name, keywords[b"HGrevision"], keywords[b"Date"], keywords[b"Author"], keywords[b"State"]), b"HGheader2": b"%s %s %s %s %s" % (keywords[b"HGpath"] + b'/' + rel_name, keywords[b"HGrevision"], keywords[b"Date2"], keywords[b"Author"], keywords[b"State"]), b"Id": b"%s %s %s %s %s" % (templatefilters.basename(rel_name), keywords[b"Revision"], keywords[b"Date"], keywords[b"Author"], keywords[b"State"]), b"Id2": b"%s %s %s %s %s" % (templatefilters.basename(rel_name), keywords[b"Revision"], keywords[b"Date2"], keywords[b"Author"], keywords[b"State"]), b"HGnodeid": nodeid or b'', } def stripped_hostname(hostname): """Return `hostname` without any domain port information""" if not hostname: return hostname idx = hostname.find(b'.') if idx < 0: return hostname return hostname[:idx] def get_checked_user_filter_option(opts): uf = opts.get(b"user_filter") # "none" is retained for compatibility reasons and now an alias for "full" if uf not in (b"person", b"user", b"email", b"full", b"none"): raise error.Abort( _(b"user filter must be any of `user', `person', `email' or `none'")) return uf def get_checked_path_filter_option(opts): pf = opts.get(b"path_filter") if pf not in (b"full", b"nopwd", b"nouser", b"short", b"last", b"repo", b"reposhort"): raise error.Abort( _(b"path filter must be any of `full', `nopwd', `nouser`, `short', `last', `repo' or `reposhort'")) return pf if hasattr(inspect, "getfullargspec"): # PY3 _has_makefilename_ctx = "ctx" in inspect.getfullargspec(cmdutil.makefilename).args _has_matchmod_with_root = "root" in inspect.getfullargspec(match.exact).args else: _has_makefilename_ctx = "ctx" in inspect.getargspec(cmdutil.makefilename).args _has_matchmod_with_root = "root" in inspect.getargspec(match.exact).args if _has_makefilename_ctx: # Mercurial >= 4.6 makefilename_compat = cmdutil.makefilename makefileobj_compat = cmdutil.makefileobj else: # Mercurial < 4.6 def makefilename_compat(ctx, pat, **props): return cmdutil.makefilename(ctx.repo(), pat, ctx.node(), **props) def makefileobj_compat(ctx, pat, **props): return cmdutil.makefileobj(ctx.repo(), pat, ctx.node(), **props) if _has_matchmod_with_root: # Mercurial < 5 _matchmod_never = match.never else: def _matchmod_never(root, cwd, badfn=None): return match.never(badfn=badfn) if context_mapping_api: class _FakeRenderContext(object): @staticmethod def resource(mapping, name): return mapping[name]
