--- a/hgkw/keyword.py Fri Jan 25 21:42:52 2008 +0000
+++ b/hgkw/keyword.py Wed Jan 30 10:38:10 2008 +0100
@@ -84,34 +84,14 @@
Or, better, use bundle/unbundle to share changes.
'''
-from mercurial import commands, cmdutil, context, fancyopts
-from mercurial import filelog, localrepo, revlog, templater, util
+from mercurial import commands, cmdutil, context, filelog, localrepo
+from mercurial import patch, revlog, templater, util
from mercurial.node import *
from mercurial.i18n import gettext as _
-import getopt, os.path, re, shutil, sys, tempfile, time
+import os.path, re, shutil, tempfile, time
# backwards compatibility hacks
-try:
- # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78
- # also avoid name conflict with other dispatch package(s)
- from mercurial.dispatch import _parse
-except ImportError:
- try:
- # commands.parse moves to cmdutil.parse in 0c61124ad877
- _parse = cmdutil.parse
- except AttributeError:
- _parse = commands.parse
-
-def _wwrite(repo, f, data, mf):
- '''Makes repo.wwrite backwards compatible.'''
- # 656e06eebda7 removed file descriptor argument
- # 67982d3ee76c added flags argument
- try:
- repo.wwrite(f, data, mf.flags(f))
- except (AttributeError, TypeError):
- repo.wwrite(f, data)
-
def _normal(repo, files):
'''Backwards compatible repo.dirstate.normal/update.'''
# 6fd953d5faea introduced dirstate.normal()
@@ -121,6 +101,12 @@
except AttributeError:
repo.dirstate.update(files, 'n')
+def _link(repo, f):
+ try:
+ return repo._link(f)
+ except AttributeError:
+ return os.path.islink(repo.wjoin(f))
+
def _pathto(repo, f, cwd=None):
'''kwfiles behaves similar to status, using pathto since 78b6add1f966.'''
try:
@@ -128,95 +114,13 @@
except AttributeError:
return f
-# commands.parse/cmdutil.parse returned nothing for
-# "hg diff --rev" before 88803a69b24a due to bug in fancyopts
-def _fancyopts(args, options, state):
- '''Fixed fancyopts from a9b7e425674f.'''
- namelist = []
- shortlist = ''
- argmap = {}
- defmap = {}
-
- for short, name, default, comment in options:
- # convert opts to getopt format
- oname = name
- name = name.replace('-', '_')
-
- argmap['-' + short] = argmap['--' + oname] = name
- defmap[name] = default
-
- # copy defaults to state
- if isinstance(default, list):
- state[name] = default[:]
- elif callable(default):
- print "whoa", name, default
- state[name] = None
- else:
- state[name] = default
-
- # does it take a parameter?
- if not (default is None or default is True or default is False):
- if short: short += ':'
- if oname: oname += '='
- if short:
- shortlist += short
- if name:
- namelist.append(oname)
-
- # parse arguments
- opts, args = getopt.getopt(args, shortlist, namelist)
-
- # transfer result to state
- for opt, val in opts:
- name = argmap[opt]
- t = type(defmap[name])
- if t is type(fancyopts):
- state[name] = defmap[name](val)
- elif t is type(1):
- state[name] = int(val)
- elif t is type(''):
- state[name] = val
- elif t is type([]):
- state[name].append(val)
- elif t is type(None) or t is type(False):
- state[name] = True
-
- # return unparsed args
- return args
-
-fancyopts.fancyopts = _fancyopts
-
commands.optionalrepo += ' kwdemo'
-# handle for external callers
-externalcall = None, None, {}
-
-def externalcmdhook(hgcmd, *args, **opts):
- '''Hook for external callers to pass hg commands to keyword.
-
- Caveat: hgcmd, args, opts are not checked for validity.
- This is the responsibility of the caller.
-
- hgmcd can be either the hg function object, eg diff or patch,
- or its string represenation, eg 'diff' or 'patch'.'''
- global externalcall
- if not isinstance(hgcmd, str):
- hgcmd = hgcmd.__name__.split('.')[-1]
- externalcall = hgcmd, args, opts
-
-# 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
@@ -232,13 +136,11 @@
'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
}
- def __init__(self, ui, repo, inc, exc, hgcmd):
+ def __init__(self, ui, repo, inc, exc):
self.ui = ui
self.repo = repo
self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
- self.hgcmd = hgcmd
- self.commitnode = None
- self.path = ''
+ self.ctx = None
kwmaps = self.ui.configitems('keywordmaps')
if kwmaps: # override default templates
@@ -262,86 +164,78 @@
return cmdutil.changeset_templater(self.ui, self.repo,
False, None, '', False)
- def substitute(self, node, data, subfunc):
- '''Obtains file's changenode if commit node not given,
+ def substitute(self, path, data, node, subfunc):
+ '''Obtains file's changenode if node not given,
and calls given substitution function.'''
- if self.commitnode:
- fnode = self.commitnode
- else:
- c = context.filectx(self.repo, self.path, fileid=node)
- fnode = c.node()
+ if node is None:
+ # kwrepo.wwrite except when overwriting on commit
+ if self.ctx is None:
+ self.ctx = self.repo.changectx()
+ 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)
+ elif subfunc == self.re_kw.sub:
+ # hg kwcat using kwfilelog.read
+ c = context.filectx(self.repo, path, fileid=node)
+ node = 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=fnode, root=self.repo.root, file=self.path)
+ self.ct.show(changenode=node, root=self.repo.root, file=path)
return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer()))
return subfunc(kwsub, data)
- def expand(self, node, data):
+ def expand(self, path, data, node):
'''Returns data with keywords expanded.'''
- if util.binary(data) or self.hgcmd in restricted:
+ if util.binary(data):
return data
- return self.substitute(node, data, self.re_kw.sub)
+ return self.substitute(path, data, node, self.re_kw.sub)
- def process(self, node, data, expand):
+ def process(self, path, data, expand, ctx, node):
'''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:
- return self.substitute(node, data, self.re_kw.subn)
- return data, self.re_kw.search(data)
+ self.ctx = ctx
+ return self.substitute(path, data, node, self.re_kw.subn)
+ return self.re_kw.subn(r'$\1$', data)
- def shrink(self, text):
+ def shrink(self, data):
'''Returns text with all keyword substitutions removed.'''
- if util.binary(text):
- return text
- return self.re_kw.sub(r'$\1$', text)
+ if util.binary(data):
+ return data
+ return self.re_kw.sub(r'$\1$', data)
class kwfilelog(filelog.filelog):
'''
- Subclass of filelog to hook into its read, add, cmp methods.
- Keywords are "stored" unexpanded, and processed on reading.
+ Subclass of filelog to hook into its read method for kwcat.
'''
- def __init__(self, opener, path):
+ def __init__(self, opener, path, kwt):
super(kwfilelog, self).__init__(opener, path)
- _kwtemplater.path = path
-
- 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)
+ self._kwt = kwt
+ self._path = path
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)
+ return self._kwt.expand(self._path, data, node)
- 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)
-
-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 _kwtemplater:
+ if hasattr(repo, '_kwt'):
files, match, anypats = cmdutil.matchpats(repo, pats, opts)
return repo.status(files=files, match=match, list_clean=True)
if ui.configitems('keyword'):
@@ -352,23 +246,23 @@
'''Overwrites selected files expanding/shrinking keywords.'''
ctx = repo.changectx(node)
mf = ctx.manifest()
- if node is not None: # commit
- _kwtemplater.commitnode = node
+ if node is not None:
+ # commit
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)]
+ candidates = [f for f in files if not mf.linkf(f) and repo._kwt.matcher(f)]
if candidates:
overwritten = []
candidates.sort()
action = expand and 'expanding' or 'shrinking'
for f in candidates:
- fp = repo.file(f, kwmatch=True)
- data, kwfound = fp.kwctread(mf[f], expand)
+ data, kwfound = repo._wreadkwct(f, expand, ctx, node)
if kwfound:
notify(_('overwriting %s %s keywords\n') % (f, action))
- _wwrite(repo, f, data, mf)
+ repo.wwrite(f, data, mf.flags(f), overwrite=True)
overwritten.append(f)
_normal(repo, overwritten)
@@ -386,6 +280,26 @@
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
+ '''
+ try:
+ repo.file = repo._kwfile
+ except AttributeError:
+ pass
+ commands.cat(ui, repo, file1, *pats, **opts)
def demo(ui, repo, *args, **opts):
'''print [keywordmaps] configuration and an expansion example
@@ -463,7 +377,7 @@
repo.commit(text=msg)
format = ui.verbose and ' in %s' % path or ''
demostatus('%s keywords expanded%s' % (kwstatus, format))
- ui.write(repo.wread(fn))
+ ui.write(repo.wopener(fn).read())
ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
shutil.rmtree(tmpdir, ignore_errors=True)
@@ -490,9 +404,7 @@
if opts.get('untracked'):
files += unknown
files.sort()
- # use the full definition of repo._link for backwards compatibility
- kwfiles = [f for f in files if _kwtemplater.matcher(f)
- and not os.path.islink(repo.wjoin(f))]
+ kwfiles = [f for f in files if not _link(repo, f) and repo._kwt.matcher(f)]
cwd = pats and repo.getcwd() or ''
kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
if opts.get('all') or opts.get('ignore'):
@@ -515,44 +427,10 @@
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.
- 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
- nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
- 'export', 'grep', 'identify', 'incoming', 'init',
- 'log', 'outgoing', 'push', 'remove', 'rename',
- 'rollback', 'tip',
- 'convert')
- try:
- hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
- except (cmdutil.UnknownCommand, dispatch.ParseError):
- # must be an external caller, otherwise Exception would have been
- # raised at core command line parsing
- hgcmd, args, cmdopts = externalcall
- if hgcmd is None:
- # not an "official" hg command as from command line
- return
- if hgcmd in nokwcommands:
- 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']
+ inc, exc = [], ['.hgtags', '.hg_archival.txt']
for pat, opt in ui.configitems('keyword'):
if opt != 'ignore':
inc.append(pat)
@@ -561,25 +439,44 @@
if not inc:
return
- global _kwtemplater
- _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
-
class kwrepo(repo.__class__):
- def file(self, f, kwmatch=False):
+ def _kwfile(self, f):
+ '''Returns filelog expanding keywords on read (for kwcat).'''
if f[0] == '/':
f = f[1:]
- if kwmatch or _kwtemplater.matcher(f):
- return kwfilelog(self.sopener, f)
+ if self._kwt.matcher(f):
+ return kwfilelog(self.sopener, f, self._kwt)
return filelog.filelog(self.sopener, f)
+ 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 wread(self, filename):
data = super(kwrepo, self).wread(filename)
- if hgcmd in restricted and _kwtemplater.matcher(filename):
- return _kwtemplater.shrink(data)
+ if self._kwt.matcher(filename):
+ return self._kwt.shrink(data)
return data
+ def wwrite(self, filename, data, flags=None, overwrite=False):
+ if not overwrite and self._kwt.matcher(filename):
+ data = self._kwt.expand(filename, data, None)
+ try:
+ super(kwrepo, self).wwrite(filename, data, flags)
+ except (AttributeError, TypeError):
+ # 656e06eebda7 removed file descriptor argument
+ # 67982d3ee76c added flags argument
+ super(kwrepo, self).wwrite(filename, data)
+
+ 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, text, user, date, match, force, lock, wlock,
- force_editor, p1, p2, extra):
+ force_editor, p1, p2, extra, empty_ok):
'''Private commit wrapper for backwards compatibility.'''
try:
return super(kwrepo, self).commit(files=files, text=text,
@@ -589,15 +486,25 @@
force_editor=force_editor,
p1=p1, p2=p2, extra=extra)
except TypeError:
- return super(kwrepo, self).commit(files=files, text=text,
- user=user, date=date,
- match=match, force=force,
- force_editor=force_editor,
- p1=p1, p2=p2, extra=extra)
+ try:
+ return super(kwrepo, self).commit(files=files, text=text,
+ user=user, date=date,
+ match=match, force=force,
+ force_editor=force_editor,
+ p1=p1, p2=p2,
+ extra=extra,
+ empty_ok=empty_ok)
+ except TypeError:
+ return super(kwrepo, self).commit(files=files, text=text,
+ user=user, date=date,
+ match=match, force=force,
+ force_editor=force_editor,
+ p1=p1, p2=p2, extra=extra)
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={}):
+ force_editor=False, p1=None, p2=None, extra={},
+ empty_ok=False):
# (w)lock arguments removed in 126f527b3ba3
# so they are None or what was passed to commit
# use private _(w)lock for deletion
@@ -629,7 +536,8 @@
_p2 = hex(_p2)
node = self._commit(files, text, user, date, match, force,
- _lock, _wlock, force_editor, p1, p2, extra)
+ _lock, _wlock, force_editor, p1, p2, extra,
+ empty_ok)
# restore commit hooks
for name, cmd in commithooks.iteritems():
@@ -641,10 +549,14 @@
finally:
del _wlock, _lock
+ kwrepo._kwt = kwtemplater(ui, repo, inc, exc)
repo.__class__ = kwrepo
cmdtable = {
+ 'kwcat':
+ (cat, commands.table['cat'][1],
+ _('hg kwcat [OPTION]... FILE...')),
'kwdemo':
(demo,
[('d', 'default', None, _('show default keyword template maps')),