# HG changeset patch # User Christian Ebert # Date 1184790804 -7200 # Node ID 0d2a6c9f8343798c23eef264df660351c5e0c183 # Parent 4c3775e904b6bf5dab49fb1bac97e346a147e02a# Parent 64dce6787d829d963e343b6465786fe2384fcc44 Discard self_initializing_hook branch diff -r 64dce6787d82 -r 0d2a6c9f8343 .hgignore --- a/.hgignore Fri Mar 30 09:08:48 2007 +0200 +++ b/.hgignore Wed Jul 18 22:33:24 2007 +0200 @@ -1,7 +1,10 @@ syntax: glob *.pyc +*.pyo *~ *.swp *.orig *.rej + +hgkw/__version__.py diff -r 64dce6787d82 -r 0d2a6c9f8343 .hgtags --- a/.hgtags Fri Mar 30 09:08:48 2007 +0200 +++ b/.hgtags Wed Jul 18 22:33:24 2007 +0200 @@ -2,3 +2,6 @@ ba000e29ecf3b8df09e0fd363a78cabbe3c2a78f cvs_scheme 1fe48bf82d056f1ece05baccab888357c10c5ab8 r0.1 2e930f84224222ad6514a3c5dc6e00350e199e92 very_cvs +99dc49c5bcfba9d5b412c5fa6d0bf3ba20d68df1 hgkw_standalone_setup +15e8cd7f5295728b089fc8ba236c0f079572fb1d nohook +0c8b7e5c25a6b9a0d2782eaa3748eb546f5a254f archive diff -r 64dce6787d82 -r 0d2a6c9f8343 MANIFEST.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Wed Jul 18 22:33:24 2007 +0200 @@ -0,0 +1,5 @@ +# $Id$ + +exclude hgkw/__version__.py + +include tests/test-keyword tests/test-keyword.out diff -r 64dce6787d82 -r 0d2a6c9f8343 README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.txt Wed Jul 18 22:33:24 2007 +0200 @@ -0,0 +1,45 @@ +$Id$ + +keyword extension for Mercurial SCM +=================================== + +install +------- + +Either copy hgkw/keyword.py into the hgext directory of your +Mercurial installation. +Then add the lines: + +[extensions] +hgext.keyword = + +to your hgrc file. + +Or run "python setup.py install". +See also "pyton setup.py --help". +Then add the line: + +[extensions] +keyword = /path/to/hgkw/keyword.py + +to your hgrc, where /path/to/ is somewhere in your $PYTHONPATH. + + +first steps and online help +--------------------------- + +$ hg keyword help +$ hg kwdemo + + +testing +------- + +Copy hgkw/keyword.py into the hgext directory of your Mercurial +source tree. Copy tests/test-keyword, tests/test-keyword.out into +the tests directory of your Mercurial source tree. Change to that +directory and run: + +$ python run-tests.py test-keyword + +and then keep your fingers crossed ... diff -r 64dce6787d82 -r 0d2a6c9f8343 hgkw/keyword.py --- a/hgkw/keyword.py Fri Mar 30 09:08:48 2007 +0200 +++ b/hgkw/keyword.py Wed Jul 18 22:33:24 2007 +0200 @@ -20,9 +20,6 @@ # Keyword expansion is based on Mercurial's changeset template mappings. # The extension provides an additional UTC-date filter ({date|utcdate}). # -# The user has the choice either to create his own keywords and their -# expansions or to use the CVS-like default ones. -# # Expansions spanning more than one line are truncated to their first line. # Incremental expansion (like CVS' $Log$) is not supported. # @@ -37,76 +34,72 @@ '''keyword expansion in local repositories -This extension expands RCS/CVS-like or self-customized keywords in -the text files selected by your configuration. +This extension expands RCS/CVS-like or self-customized $Keywords$ +in the text files selected by your configuration. Keywords are only expanded in local repositories and not logged by Mercurial internally. The mechanism can be regarded as a convenience -for the current user and may be turned off anytime. - -The exansion works in 2 modes: - 1) working mode: substitution takes place on every commit and - update of the working repository. - 2) archive mode: substitution is only triggered by "hg archive". - -Caveat: "hg import" might fail if the patches were exported from a -repo with a different/no keyword setup, whereas "hg unbundle" is -safe. +for the current user or archive distribution. Configuration is done in the [keyword] and [keywordmaps] sections of hgrc files. Example: - [keyword] - # filename patterns for expansion are configured in this section - **.py = ## expand keywords in all python files - x* = ignore ## but ignore files matching "x*" - ** = archive ## keywords in all textfiles are expanded - ## when creating a distribution - y* = noarchive ## keywords in files matching "y*" are not expanded - ## on archive creation - ... - [keywordmaps] - # custom hg template maps _replace_ the CVS-like default ones - HGdate = {date|rfc822date} - lastlog = {desc} ## same as {desc|firstline} in this context - checked in by = {author} - ... + [extensions] + hgext.keyword = + + [keyword] + # expand keywords in every python file except those matching "x*" + **.py = + x* = ignore + +Note: the more specific you are in your [keyword] filename patterns + the less you lose speed in huge repos. + +For a [keywordmaps] template mapping and expansion demonstration +run "hg kwdemo". -If no [keywordmaps] are configured the extension falls back on the -following defaults: +An additional date template filter {date|utcdate} is provided. + +You can replace the default template mappings with customized keywords +and templates of your choice. +Again, run "hg kwdemo" to control the results of your config changes. - Revision: changeset id - Author: username - Date: %Y/%m/%d %H:%M:%S ## [UTC] - RCSFile: basename,v - Source: /path/to/basename,v - Id: basename,v csetid %Y/%m/%d %H:%M:%S username - Header: /path/to/basename,v csetid %Y/%m/%d %H:%M:%S username +When you change keyword configuration, especially the active keywords, +and do not want to store expanded keywords in change history, run +"hg kwshrink", and then change configuration. + +Caveat: "hg import" fails if the patch context contains an active + keyword. In that case run "hg kwshrink", reimport, and then + "hg kwexpand". + Or, better, use bundle/unbundle to share changes. ''' -from mercurial.node import * -try: - from mercurial.demandload import * # stable - from mercurial.i18n import gettext as _ - demandload(globals(), 'mercurial:commands,fancyopts,templater,util') - demandload(globals(), 'mercurial:cmdutil,context,filelog') - demandload(globals(), 'os re sys time') -except ImportError: # demandimport - from mercurial.i18n import _ - from mercurial import commands, fancyopts, templater, util - from mercurial import cmdutil, context, filelog - import os, re, sys, time +from mercurial import commands, cmdutil, context, fancyopts +from mercurial import filelog, localrepo, templater, util, hg +from mercurial.i18n import gettext as _ +# findcmd might be in cmdutil or commands +# depending on mercurial version +if hasattr(cmdutil, 'findcmd'): + findcmd = cmdutil.findcmd +else: + findcmd = commands.findcmd +import os, re, shutil, sys, tempfile, time + +commands.optionalrepo += ' kwdemo' deftemplates = { - 'Revision': '{node|short}', - 'Author': '{author|user}', - 'Date': '{date|utcdate}', - 'RCSFile': '{file|basename},v', - 'Source': '{root}/{file},v', - 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', - 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', - } + 'Revision': '{node|short}', + 'Author': '{author|user}', + 'Date': '{date|utcdate}', + 'RCSFile': '{file|basename},v', + 'Source': '{root}/{file},v', + 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}', + 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', +} + +nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy', 'export', + 'incoming', 'outgoing', 'push', 'remove', 'rename', 'rollback') def utcdate(date): '''Returns hgdate in cvs-like UTC format.''' @@ -121,51 +114,85 @@ raise commands.ParseError(None, inst) if args: cmd = args[0] - aliases, i = commands.findcmd(ui, cmd) + aliases, i = findcmd(ui, cmd) return aliases[0] +def keywordmatcher(ui, repo): + '''Collects include/exclude filename patterns for expansion + candidates of current configuration. Returns filename matching + function if include patterns exist, None otherwise.''' + inc, exc = [], ['.hg*'] + for pat, opt in ui.configitems('keyword'): + if opt != 'ignore': + inc.append(pat) + else: + exc.append(pat) + if not inc: + return None + return util.matcher(repo.root, inc=inc, exc=exc)[1] + class kwtemplater(object): ''' Sets up keyword templates, corresponding keyword regex, and provides keyword substitution functions. ''' - def __init__(self, ui, repo): + def __init__(self, ui, repo, path='', node=None, expand=True): self.ui = ui self.repo = repo + self.path = path + self.node = node templates = dict(ui.configitems('keywordmaps')) if templates: - # parse templates here for less overhead in kwsub matchfunc for k in templates.keys(): templates[k] = templater.parsestring(templates[k], - quoted=False) + quoted=False) self.templates = templates or deftemplates - self.re_kw = re.compile(r'\$(%s)[^$]*?\$' % - '|'.join([re.escape(k) for k in self.templates.keys()])) - templater.common_filters['utcdate'] = utcdate - try: - self.t = cmdutil.changeset_templater(ui, repo, - False, '', False) - except TypeError: - # depending on hg rev changeset_templater has extra "brinfo" arg - self.t = cmdutil.changeset_templater(ui, repo, - False, None, '', False) + escaped = [re.escape(k) for k in self.templates.keys()] + self.re_kw = re.compile(r'\$(%s)[^$]*?\$' % '|'.join(escaped)) + if expand: + templater.common_filters['utcdate'] = utcdate + try: + self.t = cmdutil.changeset_templater(ui, repo, + False, '', False) + except TypeError: + # depending on hg rev changeset_templater has extra "brinfo" arg + self.t = cmdutil.changeset_templater(ui, repo, + False, None, '', False) + else: + self.t = None - def kwsub(self, mobj, path, node): + def ctxnode(self, node): + '''Obtains missing node from file context.''' + if not self.node: + c = context.filectx(self.repo, self.path, fileid=node) + self.node = c.node() + + def kwsub(self, mobj): '''Substitutes keyword using corresponding template.''' kw = mobj.group(1) self.t.use_template(self.templates[kw]) self.ui.pushbuffer() - self.t.show(changenode=node, root=self.repo.root, file=path) + self.t.show(changenode=self.node, root=self.repo.root, file=self.path) keywordsub = templater.firstline(self.ui.popbuffer()) return '$%s: %s $' % (kw, keywordsub) - def expand(self, path, node, filelog, data): - '''Returns data with expanded keywords.''' + def expand(self, node, data): + '''Returns data with keywords expanded.''' if util.binary(data): return data - c = context.filectx(self.repo, path, fileid=node, filelog=filelog) - cnode = c.node() - return self.re_kw.sub(lambda m: self.kwsub(m, path, cnode), data) + self.ctxnode(node) + return self.re_kw.sub(self.kwsub, data) + + def process(self, node, data): + '''Returns a tuple: data, count. + Count is number of keywords/keyword substitutions. + Keywords in data are expanded, if templater was initialized.''' + if util.binary(data): + return data, None + if self.t: + self.ctxnode(node) + return self.re_kw.subn(self.kwsub, data) + return data, self.re_kw.search(data) def shrink(self, text): '''Returns text with all keyword substitutions removed.''' @@ -173,117 +200,228 @@ return text return self.re_kw.sub(r'$\1$', text) - def overwrite(self, candidates, node): - '''Overwrites candidates in working dir expanding keywords.''' + def overwrite(self, candidates, man, commit=True): + '''Overwrites files in working directory if keywords are detected. + Keywords are expanded if keyword templater is initialized, + otherwise their substitution is removed.''' + expand = self.t is not None + action = ('shrinking', 'expanding')[expand] + notify = (self.ui.note, self.ui.debug)[commit] + files = [] for f in candidates: - data = self.repo.wfile(f).read() - if not util.binary(data): - data, kwct = self.re_kw.subn(lambda m: - self.kwsub(m, f, node), data) - if kwct: - self.ui.debug(_('overwriting %s expanding keywords\n') % f) - self.repo.wfile(f, 'w').write(data) + fp = self.repo.file(f, kwcnt=True, kwexp=expand) + data, cnt = fp.read(man[f]) + if cnt: + notify(_('overwriting %s %s keywords\n') % (f, action)) + try: + self.repo.wwrite(f, data, man.flags(f)) + except AttributeError: + # older versions want file descriptor as 3. optional arg + self.repo.wwrite(f, data) + files.append(f) + if files: + self.repo.dirstate.update(files, 'n') class kwfilelog(filelog.filelog): ''' - Superclass over filelog to customize its read, add, cmp methods. - Keywords are "stored" unexpanded, and expanded on reading. + Subclass of filelog to hook into its read, add, cmp methods. + Keywords are "stored" unexpanded, and processed on reading. ''' - def __init__(self, opener, path, kwtemplater): + def __init__(self, opener, path, kwtemplater, kwcnt): super(kwfilelog, self).__init__(opener, path) - self.path = path self.kwtemplater = kwtemplater + self.kwcnt = kwcnt def read(self, node): - '''Substitutes keywords when reading filelog.''' + '''Passes data through kwemplater methods for + either unconditional keyword expansion + or counting of keywords and substitution method + set by the calling overwrite function.''' data = super(kwfilelog, self).read(node) - return self.kwtemplater.expand(self.path, node, self, data) + if not self.kwcnt: + return self.kwtemplater.expand(node, data) + return self.kwtemplater.process(node, data) def add(self, text, meta, tr, link, p1=None, p2=None): '''Removes keyword substitutions when adding to filelog.''' text = self.kwtemplater.shrink(text) - return super(kwfilelog, self).add(text, - meta, tr, link, p1=p1, p2=p2) + return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2) def cmp(self, node, text): '''Removes keyword substitutions for comparison.''' text = self.kwtemplater.shrink(text) + if self.renamed(node): + t2 = super(kwfilelog, self).read(node) + return t2 != text return super(kwfilelog, self).cmp(node, text) +def overwrite(ui, repo, files=None, expand=True): + '''Expands/shrinks keywords in working directory.''' + wlock = repo.wlock() + try: + ctx = repo.changectx() + if not ctx: + raise hg.RepoError(_('no changeset found')) + for changed in repo.status()[:4]: + if changed: + raise util.Abort(_('local changes detected')) + kwfmatcher = keywordmatcher(ui, repo) + if kwfmatcher is None: + ui.warn(_('no files configured for keyword expansion\n')) + return + m = ctx.manifest() + if files: + files = [f for f in files if f in m.keys()] + else: + files = m.keys() + files = [f for f in files if kwfmatcher(f) and not os.path.islink(f)] + if not files: + ui.warn(_('given files not tracked or ' + 'not configured for expansion\n')) + return + kwt = kwtemplater(ui, repo, node=ctx.node(), expand=expand) + kwt.overwrite(files, m, commit=False) + finally: + wlock.release() + + +def shrink(ui, repo, *args): + '''revert expanded keywords in working directory + + run before: + disabling keyword expansion + changing keyword expansion configuration + or if you experience problems with "hg import" + ''' + overwrite(ui, repo, files=args, expand=False) + +def expand(ui, repo, *args): + '''expand keywords in working directory + + run after (re)enabling keyword expansion + ''' + overwrite(ui, repo, files=args) + +def demo(ui, repo, *args, **opts): + '''print [keywordmaps] configuration and an expansion example + + show current, custom, or default keyword template maps and their expansion + ''' + msg = 'hg keyword config and expansion example' + kwstatus = 'current' + fn = 'demo.txt' + tmpdir = tempfile.mkdtemp('', 'kwdemo.') + ui.note(_('creating temporary repo at %s\n') % tmpdir) + _repo = localrepo.localrepository(ui, path=tmpdir, create=True) + # for backwards compatibility + ui = _repo.ui + ui.setconfig('keyword', fn, '') + if opts['default']: + kwstatus = 'default' + kwmaps = deftemplates + else: + if args or opts['rcfile']: + kwstatus = 'custom' + for tmap in args: + k, v = tmap.split('=', 1) + ui.setconfig('keywordmaps', k.strip(), v.strip()) + if opts['rcfile']: + ui.readconfig(opts['rcfile']) + kwmaps = dict(ui.configitems('keywordmaps')) or deftemplates + if ui.configitems('keywordmaps'): + for k, v in kwmaps.items(): + ui.setconfig('keywordmaps', k, v) + reposetup(ui, _repo) + ui.status(_('config with %s keyword template maps:\n') % kwstatus) + ui.write('[keyword]\n%s =\n[keywordmaps]\n' % fn) + for k, v in kwmaps.items(): + ui.write('%s = %s\n' % (k, v)) + path = _repo.wjoin(fn) + keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n' + _repo.wopener(fn, 'w').write(keywords) + _repo.add([fn]) + ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path)) + ui.note(keywords) + ui.note(_("\nhg --repository '%s' commit\n") % tmpdir) + _repo.commit(text=msg) + pathinfo = ('', ' in %s' % path)[ui.verbose] + ui.status(_('\n%s keywords expanded%s:\n') % (kwstatus, pathinfo)) + ui.write(_repo.wread(fn)) + ui.debug(_('\nremoving temporary repo %s\n') % tmpdir) + shutil.rmtree(tmpdir) + def reposetup(ui, repo): '''Sets up repo as kwrepo for keyword substitution. Overrides file method to return kwfilelog instead of filelog if file matches user configuration. - Uses self-initializing pretxncommit-hook to overwrite configured files with - updated keyword substitutions. + Wraps commit to overwrite configured files with updated + keyword substitutions. This is done for local repos only, and only if there are files configured at all for keyword substitution.''' - if not repo.local(): + # for backwards compatibility + ui = repo.ui + + if not repo.local() or getcmd(ui) in nokwcommands: return - archivemode = (getcmd(repo.ui) == 'archive') - - inc, exc, archive, noarchive = [], ['.hg*'], [], ['.hg*'] - for pat, opt in repo.ui.configitems('keyword'): - if opt == 'archive': - archive.append(pat) - elif opt == 'noarchive': - noarchive.append(pat) - elif opt == 'ignore': - exc.append(pat) - else: - inc.append(pat) - if archivemode: - inc, exc = archive, noarchive - if not inc: + kwfmatcher = keywordmatcher(ui, repo) + if kwfmatcher is None: return - repo.kwfmatcher = util.matcher(repo.root, inc=inc, exc=exc)[1] - class kwrepo(repo.__class__): - def file(self, f): + def file(self, f, kwcnt=False, kwexp=True): if f[0] == '/': f = f[1:] - # only use kwfilelog when needed - if self.kwfmatcher(f): - kwt = kwtemplater(self.ui, self) - return kwfilelog(self.sopener, f, kwt) + if kwfmatcher(f): + kwt = kwtemplater(ui, self, path=f, expand=kwexp) + return kwfilelog(self.sopener, f, kwt, kwcnt) else: return filelog.filelog(self.sopener, f) + def commit(self, files=None, text='', user=None, date=None, + match=util.always, force=False, lock=None, wlock=None, + force_editor=False, p1=None, p2=None, extra={}): + wrelease = False + if not wlock: + wlock = self.wlock() + wrelease = True + try: + removed = self.status(node1=p1, node2=p2, files=files, + match=match, wlock=wlock)[2] + + node = super(kwrepo, + self).commit(files=files, text=text, user=user, + date=date, match=match, force=force, + lock=lock, wlock=wlock, + force_editor=force_editor, + p1=p1, p2=p2, extra=extra) + if node is None: + return node + + cl = self.changelog.read(node) + candidates = [f for f in cl[3] if kwfmatcher(f) + and f not in removed + and not os.path.islink(self.wjoin(f))] + if candidates: + m = self.manifest.read(cl[0]) + kwt = kwtemplater(ui, self, node=node) + kwt.overwrite(candidates, m) + return node + finally: + if wrelease: + wlock.release() + repo.__class__ = kwrepo - # make pretxncommit hook import kwmodule regardless of where it's located - for k, v in sys.modules.iteritems(): - if v is None: - continue - if not hasattr(v, '__file__'): - continue - if v.__file__.startswith(__file__): - mod = k - break - else: - sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) - mod = os.path.splitext(os.path.basename(__file__))[0] - ui.setconfig('hooks', 'pretxncommit.keyword', 'python:%s.pretxnkw' % mod) - del mod - -def pretxnkw(ui, repo, hooktype, **args): - '''pretxncommit hook that collects candidates for keyword expansion - on commit and expands keywords in working dir.''' - - cmd, sysargs, globalopts, cmdopts = commands.parse(ui, sys.argv[1:])[1:] - if repr(cmd).split()[1] in ('tag', 'import_'): - return - - files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts) - modified, added = repo.status(files=files, match=match)[:2] - candidates = [f for f in modified + added if repo.kwfmatcher(f) - and not os.path.islink(repo.wjoin(f))] - - if candidates: - kwt = kwtemplater(ui, repo) - kwt.overwrite(candidates, bin(args['node'])) +cmdtable = { + 'kwdemo': + (demo, + [('d', 'default', None, _('show default keyword template maps')), + ('f', 'rcfile', [], _('read maps from RCFILE'))], + _('hg kwdemo [-d || [-f RCFILE] TEMPLATEMAP ...]')), + 'kwshrink': (shrink, [], _('hg kwshrink [NAME] ...')), + 'kwexpand': (expand, [], _('hg kwexpand [NAME] ...')), +} diff -r 64dce6787d82 -r 0d2a6c9f8343 hgkw/version.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgkw/version.py Wed Jul 18 22:33:24 2007 +0200 @@ -0,0 +1,46 @@ +# $Id$ + +'''version.py - hgkw version. +Code stolen from Mercurial, and simplified for my needs. +''' + +import os, time + +unknown_version = 'unknown' + +def getversion(doreload=False): + try: + import hgkw.__version__ + if doreload: + reload(hgkw.__version__) + version = hgkw.__version__.version + except ImportError: + version = unknown_version + return version + +def rememberversion(version=None): + if not version and os.path.isdir('.hg'): + # get version from Mercurial + p = os.popen('hg --quiet identify 2> %s' % os.devnull) + ident = p.read()[:-1] + if not p.close() and ident: + if ident[-1] != '+': + version = ident + else: + version = ident[:-1] + version += time.strftime('+%Y%m%d') + if version and version != getversion(): # write version + directory = os.path.dirname(__file__) + for suff in ['py', 'pyc', 'pyo']: + try: + os.unlink(os.path.join(directory, '__version__.%s' % suff)) + except OSError: + pass + f = open(os.path.join(directory, '__version__.py'), 'w') + try: + f.write('# this file is auto-generated\n') + f.write('version = %r\n' % version) + finally: + f.close() + # reload file + getversion(doreload=True) diff -r 64dce6787d82 -r 0d2a6c9f8343 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Wed Jul 18 22:33:24 2007 +0200 @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# $Id$ + +from distutils.core import setup +import hgkw.version + +# specify version, Mercurial version otherwise +version = '' + +hgkw.version.rememberversion(version) + +setup(name='hgkw', + version=hgkw.version.getversion(), + description='Mercurial keyword extension (standalone)', + author='Christian Ebert', + author_email='blacktrash@gmx.net', + license='GNU GPL', + packages=['hgkw'], + ) diff -r 64dce6787d82 -r 0d2a6c9f8343 tests/test-keyword --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-keyword Wed Jul 18 22:33:24 2007 +0200 @@ -0,0 +1,133 @@ +#!/bin/sh + +cat <> $HGRCPATH +[extensions] +hgext.keyword = +[keyword] +* = +b = ignore +EOF + +echo % help +hg help keyword + +echo % hg kwdemo +hg --quiet kwdemo --default \ +| sed -e 's![^ ][^ ]*demo.txt,v!/TMP/demo.txt,v!' \ + -e 's/,v [a-z0-9][a-z0-9]* /,v xxxxxxxxxxxx /' \ + -e '/[$]Revision/ s/: [a-z0-9][a-z0-9]* /: xxxxxxxxxxxx /' \ + -e 's! 20[0-9][0-9]/[01][0-9]/[0-3][0-9] [0-2][0-9]:[0-6][0-9]:[0-6][0-9]! 2000/00/00 00:00:00!' + +hg init Test +cd Test + +echo % kwshrink should abort in empty/invalid repo +hg kwshrink + +echo 'expand $Id$' > a +echo 'ignore $Id$' > b +echo % cat +cat a b + +echo % default keyword expansion +echo % commit +hg --debug commit -A -mab -d '0 0' -u 'User Name ' +echo % status +hg status +echo % identify +hg --quiet identify +echo % cat +cat a b +echo % hg cat +hg cat a b + +echo % touch +touch a b +echo % status +hg status + +rm a b +echo % update +hg update +echo % cat +cat a b + +echo % copy +hg cp a c +echo % commit +hg --debug commit -ma2c -d '1 0' -u 'User Name ' +echo % cat a c +cat a c +echo % touch copied c +touch c +echo % status + +echo % rollback +hg rollback +echo % status +hg status +echo % update -C +hg update --clean + +echo % custom keyword expansion +echo % try with kwdemo +hg --quiet kwdemo "Xinfo = {author}: {desc}" + +cat <>$HGRCPATH +[keywordmaps] +Id = {file} {node|short} {date|rfc822date} {author|user} +Xinfo = {author}: {desc} +EOF + +echo % cat +cat a b +echo % hg cat +hg cat a b + +echo '$Xinfo$' >> a +cat <> log +firstline +secondline +EOF + +echo % interrupted commit +HGEDITOR=false hg commit +echo % status +hg status + +echo % commit +hg --debug commit -l log -d '2 0' -u 'User Name ' +rm log +echo % status +hg status + +echo % cat +cat a b +echo % hg cat +hg cat a b + +cd .. +hg clone -r0 Test Test-a +cd Test-a +cat <> .hg/hgrc +[paths] +default = ../Test +EOF +echo % incoming +# remove path to temp dir +hg incoming | sed -e 's/^\(comparing with \).*\(test-keyword.*\)/\1\2/' + +echo % switch off expansion +cd ../Test +echo % kwshrink +hg --debug kwshrink +echo % cat +cat a b +echo % hg cat +hg cat a b + +rm $HGRCPATH +echo % cat +cat a b +echo % hg cat +hg cat a b diff -r 64dce6787d82 -r 0d2a6c9f8343 tests/test-keyword.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-keyword.out Wed Jul 18 22:33:24 2007 +0200 @@ -0,0 +1,175 @@ +% help +keyword extension - keyword expansion in local repositories + +This extension expands RCS/CVS-like or self-customized $Keywords$ +in the text files selected by your configuration. + +Keywords are only expanded in local repositories and not logged by +Mercurial internally. The mechanism can be regarded as a convenience +for the current user or archive distribution. + +Configuration is done in the [keyword] and [keywordmaps] sections of +hgrc files. + +Example: + [extensions] + hgext.keyword = + + [keyword] + # expand keywords in every python file except those matching "x*" + **.py = + x* = ignore + +Note: the more specific you are in your [keyword] filename patterns + the less you lose speed in huge repos. + +For a [keywordmaps] template mapping and expansion demonstration +run "hg kwdemo". + +An additional date template filter {date|utcdate} is provided. + +You can replace the default template mappings with customized keywords +and templates of your choice. +Again, run "hg kwdemo" to control the results of your config changes. + +When you change keyword configuration, especially the active keywords, +and do not want to store expanded keywords in change history, run +"hg kwshrink", and then change configuration. + +Caveat: "hg import" fails if the patch context contains an active + keyword. In that case run "hg kwshrink", reimport, and then + "hg kwexpand". + Or, better, use bundle/unbundle to share changes. + +list of commands (use "hg help -v keyword" to show aliases and global options): + + kwdemo print [keywordmaps] configuration and an expansion example + kwexpand expand keywords in working directory + kwshrink revert expanded keywords in working directory +% hg kwdemo +[keyword] +demo.txt = +[keywordmaps] +RCSFile = {file|basename},v +Author = {author|user} +Header = {root}/{file},v {node|short} {date|utcdate} {author|user} +Source = {root}/{file},v +Date = {date|utcdate} +Id = {file|basename},v {node|short} {date|utcdate} {author|user} +Revision = {node|short} +$RCSFile: demo.txt,v $ +$Author: test $ +$Header: /TMP/demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $ +$Source: /TMP/demo.txt,v $ +$Date: 2000/00/00 00:00:00 $ +$Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $ +$Revision: xxxxxxxxxxxx $ +% kwshrink should abort in empty/invalid repo +abort: no changeset found! +% cat +expand $Id$ +ignore $Id$ +% default keyword expansion +% commit +adding a +adding b +a +b +overwriting a expanding keywords +% status +% identify +65cbcc9534b0 +% cat +expand $Id: a,v 65cbcc9534b0 1970/01/01 00:00:00 user $ +ignore $Id$ +% hg cat +expand $Id: a,v 65cbcc9534b0 1970/01/01 00:00:00 user $ +ignore $Id$ +% touch +% status +% update +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +% cat +expand $Id: a,v 65cbcc9534b0 1970/01/01 00:00:00 user $ +ignore $Id$ +% copy +% commit +c + c: copy a:e6cc15c9eb5fd3c09ec691b667cf6ccd6dfb936e +overwriting c expanding keywords +% cat a c +expand $Id: a,v 65cbcc9534b0 1970/01/01 00:00:00 user $ +expand $Id: c,v 9460ba56f8d0 1970/01/01 00:00:01 user $ +% touch copied c +% status +% rollback +rolling back last transaction +% status +A c +% update -C +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +% custom keyword expansion +% try with kwdemo +[keyword] +demo.txt = +[keywordmaps] +Xinfo = {author}: {desc} +$Xinfo: test: hg keyword config and expansion example $ +% cat +expand $Id: a,v 65cbcc9534b0 1970/01/01 00:00:00 user $ +ignore $Id$ +% hg cat +expand $Id: a 65cbcc9534b0 Thu, 01 Jan 1970 00:00:00 +0000 user $ +ignore $Id$ +% interrupted commit +abort: edit failed: false exited with status 1 +transaction abort! +rollback completed +% status +M a +? log +% commit +a +overwriting a expanding keywords +% status +% cat +expand $Id: a 6ade9dd7b017 Thu, 01 Jan 1970 00:00:02 +0000 user $ +$Xinfo: User Name : firstline $ +ignore $Id$ +% hg cat +expand $Id: a 6ade9dd7b017 Thu, 01 Jan 1970 00:00:02 +0000 user $ +$Xinfo: User Name : firstline $ +ignore $Id$ +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 2 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +% incoming +searching for changes +changeset: 1:6ade9dd7b017 +tag: tip +user: User Name +date: Thu Jan 01 00:00:02 1970 +0000 +summary: firstline + +% switch off expansion +% kwshrink +overwriting a shrinking keywords +% cat +expand $Id$ +$Xinfo$ +ignore $Id$ +% hg cat +expand $Id: a 6ade9dd7b017 Thu, 01 Jan 1970 00:00:02 +0000 user $ +$Xinfo: User Name : firstline $ +ignore $Id$ +% cat +expand $Id$ +$Xinfo$ +ignore $Id$ +% hg cat +expand $Id$ +$Xinfo$ +ignore $Id$