--- a/hgkw/keyword.py Wed Feb 06 11:32:34 2008 +0100
+++ b/hgkw/keyword.py Wed Feb 06 17:25:37 2008 +0100
@@ -80,19 +80,29 @@
"Log = {desc}" expands to the first line of the changeset description.
'''
-from mercurial import commands, cmdutil, context, localrepo
-from mercurial import patch, revlog, templater, templatefilters, util
+from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
+from mercurial import patch, localrepo, templater, templatefilters, util
from mercurial.node import *
-from mercurial.hgweb import webcommands
from mercurial.i18n import _
-import mimetypes, re, shutil, tempfile, time
+import re, shutil, sys, tempfile, time
commands.optionalrepo += ' kwdemo'
+# hg commands that do not act on keywords
+nokwcommands = ('add addremove bundle copy export grep identify incoming init'
+ ' log outgoing push remove rename rollback tip convert')
+
+# hg commands that trigger expansion only when writing to working dir,
+# not when reading filelog, and unexpand when reading from working dir
+restricted = 'diff1 record qfold qimport qnew qpush qrefresh qrecord'
+
def utcdate(date):
'''Returns hgdate in cvs-like UTC format.'''
return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
+
+_kwtemplater = None
+
class kwtemplater(object):
'''
Sets up keyword templates, corresponding keyword regex, and
@@ -108,11 +118,13 @@
'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
}
- def __init__(self, ui, repo, inc, exc):
+ def __init__(self, ui, repo, inc, exc, restricted):
self.ui = ui
self.repo = repo
self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
- self.ctx = None
+ self.restricted = restricted
+ self.commitnode = None
+ self.path = ''
kwmaps = self.ui.configitems('keywordmaps')
if kwmaps: # override default templates
@@ -127,67 +139,102 @@
self.ct = cmdutil.changeset_templater(self.ui, self.repo,
False, '', False)
- def substitute(self, path, data, node, subfunc):
- '''Obtains file's changenode if node not given,
+ def substitute(self, node, data, subfunc):
+ '''Obtains file's changenode if commit node not given,
and calls given substitution function.'''
- if node is None:
- # kwrepo.wwrite except when overwriting on commit
- try:
- fnode = self.ctx.filenode(path)
- fl = self.repo.file(path)
- c = context.filectx(self.repo, path, fileid=fnode, filelog=fl)
- node = c.node()
- except revlog.LookupError:
- # eg: convert
- return subfunc == self.re_kw.sub and data or (data, None)
+ if self.commitnode:
+ fnode = self.commitnode
+ else:
+ c = context.filectx(self.repo, self.path, fileid=node)
+ fnode = c.node()
def kwsub(mobj):
'''Substitutes keyword using corresponding template.'''
kw = mobj.group(1)
self.ct.use_template(self.templates[kw])
self.ui.pushbuffer()
- self.ct.show(changenode=node, root=self.repo.root, file=path)
+ self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
ekw = templatefilters.firstline(self.ui.popbuffer())
return '$%s: %s $' % (kw, ekw)
return subfunc(kwsub, data)
- def expand(self, path, data, ctx):
+ def expand(self, node, data):
'''Returns data with keywords expanded.'''
- if util.binary(data):
+ if self.restricted or util.binary(data):
return data
- if self.ctx is None:
- self.ctx = ctx or self.repo.changectx()
- return self.substitute(path, data, None, self.re_kw.sub)
+ return self.substitute(node, data, self.re_kw.sub)
- def process(self, path, data, expand, ctx, node):
+ def process(self, node, data, expand):
'''Returns a tuple: data, count.
Count is number of keywords/keyword substitutions,
telling caller whether to act on file containing data.'''
if util.binary(data):
return data, None
if expand:
- self.ctx = ctx
- return self.substitute(path, data, node, self.re_kw.subn)
- return self.re_kw.subn(r'$\1$', data)
+ return self.substitute(node, data, self.re_kw.subn)
+ return data, self.re_kw.search(data)
+
+ def shrink(self, text):
+ '''Returns text with all keyword substitutions removed.'''
+ if util.binary(text):
+ return text
+ return self.re_kw.sub(r'$\1$', text)
+
+class kwfilelog(filelog.filelog):
+ '''
+ Subclass of filelog to hook into its read, add, cmp methods.
+ Keywords are "stored" unexpanded, and processed on reading.
+ '''
+ def __init__(self, opener, path):
+ super(kwfilelog, self).__init__(opener, path)
+ _kwtemplater.path = path
- def shrink(self, data):
- '''Returns text with all keyword substitutions removed.'''
- if util.binary(data):
- return data
- return self.re_kw.sub(r'$\1$', data)
+ def kwctread(self, node, expand):
+ '''Reads expanding and counting keywords, called from _overwrite.'''
+ data = super(kwfilelog, self).read(node)
+ return _kwtemplater.process(node, data, expand)
+
+ def read(self, node):
+ '''Expands keywords when reading filelog.'''
+ data = super(kwfilelog, self).read(node)
+ return _kwtemplater.expand(node, data)
+
+ def add(self, text, meta, tr, link, p1=None, p2=None):
+ '''Removes keyword substitutions when adding to filelog.'''
+ text = _kwtemplater.shrink(text)
+ return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
+
+ def cmp(self, node, text):
+ '''Removes keyword substitutions for comparison.'''
+ text = _kwtemplater.shrink(text)
+ if self.renamed(node):
+ t2 = super(kwfilelog, self).read(node)
+ return t2 != text
+ return revlog.revlog.cmp(self, node, text)
+
# store original patch.patchfile.__init__
_patchfile_init = patch.patchfile.__init__
+def _kwpatchfile_init(self, ui, fname, missing=False):
+ '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
+ rejects or conflicts due to expanded keywords in working dir.'''
+ _patchfile_init(self, ui, fname, missing=missing)
-def _iskwfile(f, link, kwt):
- return not link(f) and kwt.matcher(f)
+ if _kwtemplater.matcher(self.fname):
+ # shrink keywords read from working dir
+ kwshrunk = _kwtemplater.shrink(''.join(self.lines))
+ self.lines = kwshrunk.splitlines(True)
+
+
+def _iskwfile(f, link):
+ return not link(f) and _kwtemplater.matcher(f)
def _status(ui, repo, *pats, **opts):
'''Bails out if [keyword] configuration is not active.
Returns status of working directory.'''
- if hasattr(repo, '_kwt'):
+ if _kwtemplater:
files, match, anypats = cmdutil.matchpats(repo, pats, opts)
return repo.status(files=files, match=match, list_clean=True)
if ui.configitems('keyword'):
@@ -198,22 +245,22 @@
'''Overwrites selected files expanding/shrinking keywords.'''
ctx = repo.changectx(node)
mf = ctx.manifest()
- if node is not None:
- # commit
+ if node is not None: # commit
+ _kwtemplater.commitnode = node
files = [f for f in ctx.files() if f in mf]
notify = ui.debug
- else:
- # kwexpand/kwshrink
+ else: # kwexpand/kwshrink
notify = ui.note
- candidates = [f for f in files if _iskwfile(f, mf.linkf, repo._kwt)]
+ candidates = [f for f in files if _iskwfile(f, mf.linkf)]
if candidates:
candidates.sort()
action = expand and 'expanding' or 'shrinking'
for f in candidates:
- data, kwfound = repo._wreadkwct(f, expand, ctx, node)
+ fp = repo.file(f, kwmatch=True)
+ data, kwfound = fp.kwctread(mf[f], expand)
if kwfound:
notify(_('overwriting %s %s keywords\n') % (f, action))
- repo.wwrite(f, data, mf.flags(f), overwrite=True)
+ repo.wwrite(f, data, mf.flags(f))
repo.dirstate.normal(f)
def _kwfwrite(ui, repo, expand, *pats, **opts):
@@ -230,35 +277,6 @@
finally:
del wlock, lock
-def cat(ui, repo, file1, *pats, **opts):
- '''output the current or given revision of files expanding keywords
-
- Print the specified files as they were at the given revision.
- If no revision is given, the parent of the working directory is used,
- or tip if no revision is checked out.
-
- Output may be to a file, in which case the name of the file is
- given using a format string. The formatting rules are the same as
- for the export command, with the following additions:
-
- %s basename of file being printed
- %d dirname of file being printed, or '.' if in repo root
- %p root-relative path name of file being printed
- '''
- ctx = repo.changectx(opts['rev'])
- err = 1
- for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
- ctx.node()):
- fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
- data = ctx.filectx(abs).data()
- try:
- if repo._kwt.matcher(abs):
- data = repo._kwt.expand(abs, data, ctx)
- except AttributeError:
- pass
- fp.write(data)
- err = 0
- return err
def demo(ui, repo, *args, **opts):
'''print [keywordmaps] configuration and an expansion example
@@ -336,7 +354,7 @@
repo.commit(text=msg)
format = ui.verbose and ' in %s' % path or ''
demostatus('%s keywords expanded%s' % (kwstatus, format))
- ui.write(repo.wopener(fn).read())
+ ui.write(repo.wread(fn))
ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
shutil.rmtree(tmpdir, ignore_errors=True)
@@ -363,7 +381,7 @@
if opts.get('untracked'):
files += unknown
files.sort()
- kwfiles = [f for f in files if _iskwfile(f, repo._link, repo._kwt)]
+ kwfiles = [f for f in files if _iskwfile(f, repo._link)]
cwd = pats and repo.getcwd() or ''
kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
if opts.get('all') or opts.get('ignore'):
@@ -386,10 +404,31 @@
def reposetup(ui, repo):
- if not repo.local() or repo.root.endswith('/.hg/patches'):
+ '''Sets up repo as kwrepo for keyword substitution.
+ Overrides file method to return kwfilelog instead of filelog
+ if file matches user configuration.
+ 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():
return
- inc, exc = [], ['.hgtags', '.hg_archival.txt']
+ hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
+ if hgcmd in nokwcommands.split():
+ return
+
+ if hgcmd == 'diff':
+ # only expand if comparing against working dir
+ node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
+ if node2 is not None:
+ return
+ # shrink if rev is not current node
+ if node1 is not None and node1 != repo.changectx().node():
+ hgcmd = 'diff1'
+
+ inc, exc = [], ['.hgtags']
for pat, opt in ui.configitems('keyword'):
if opt != 'ignore':
inc.append(pat)
@@ -398,29 +437,24 @@
if not inc:
return
+ global _kwtemplater
+ _restricted = hgcmd in restricted.split()
+ _kwtemplater = kwtemplater(ui, repo, inc, exc, _restricted)
+
class kwrepo(repo.__class__):
- def _wreadkwct(self, filename, expand, ctx, node):
- '''Reads filename and returns tuple of data with keywords
- expanded/shrunk and count of keywords (for _overwrite).'''
- data = super(kwrepo, self).wread(filename)
- return self._kwt.process(filename, data, expand, ctx, node)
+ def file(self, f, kwmatch=False):
+ if f[0] == '/':
+ f = f[1:]
+ if kwmatch or _kwtemplater.matcher(f):
+ return kwfilelog(self.sopener, f)
+ return filelog.filelog(self.sopener, f)
def wread(self, filename):
data = super(kwrepo, self).wread(filename)
- if self._kwt.matcher(filename):
- return self._kwt.shrink(data)
+ if _restricted and _kwtemplater.matcher(filename):
+ return _kwtemplater.shrink(data)
return data
- def wwrite(self, filename, data, flags, overwrite=False):
- if not overwrite and self._kwt.matcher(filename):
- data = self._kwt.expand(filename, data, None)
- super(kwrepo, self).wwrite(filename, data, flags)
-
- def wwritedata(self, filename, data):
- if self._kwt.matcher(filename):
- data = self._kwt.expand(filename, data, None)
- return super(kwrepo, self).wwritedata(filename, data)
-
def commit(self, files=None, text='', user=None, date=None,
match=util.always, force=False, force_editor=False,
p1=None, p2=None, extra={}, empty_ok=False):
@@ -464,50 +498,11 @@
finally:
del wlock, lock
- kwt = kwrepo._kwt = kwtemplater(ui, repo, inc, exc)
-
- def kwpatchfile_init(self, ui, fname, missing=False):
- '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
- rejects or conflicts due to expanded keywords in working dir.'''
- _patchfile_init(self, ui, fname, missing=missing)
-
- if kwt.matcher(self.fname):
- # shrink keywords read from working dir
- kwshrunk = kwt.shrink(''.join(self.lines))
- self.lines = kwshrunk.splitlines(True)
-
- def kwweb_rawfile(web, req, tmpl):
- '''Monkeypatch webcommands.rawfile so it expands keywords.'''
- path = web.cleanpath(req.form.get('file', [''])[0])
- if not path:
- content = web.manifest(tmpl, web.changectx(req), path)
- req.respond(webcommands.HTTP_OK, web.ctype)
- return content
- try:
- fctx = web.filectx(req)
- except revlog.LookupError:
- content = web.manifest(tmpl, web.changectx(req), path)
- req.respond(webcommands.HTTP_OK, web.ctype)
- return content
- path = fctx.path()
- text = fctx.data()
- if kwt.matcher(path):
- text = kwt.expand(path, text, web.changectx(req))
- mt = mimetypes.guess_type(path)[0]
- if mt is None or util.binary(text):
- mt = mt or 'application/octet-stream'
- req.respond(webcommands.HTTP_OK, mt, path, len(text))
- return [text]
-
repo.__class__ = kwrepo
- patch.patchfile.__init__ = kwpatchfile_init
- webcommands.rawfile = kwweb_rawfile
+ patch.patchfile.__init__ = _kwpatchfile_init
cmdtable = {
- 'kwcat':
- (cat, commands.table['cat'][1],
- _('hg kwcat [OPTION]... FILE...')),
'kwdemo':
(demo,
[('d', 'default', None, _('show default keyword template maps')),