diff -r 5e3707030bce -r b1a7f5eddeba hgkw/keyword.py --- a/hgkw/keyword.py Tue Feb 12 21:38:30 2008 +0100 +++ b/hgkw/keyword.py Thu Feb 14 15:55:21 2008 +0100 @@ -85,8 +85,8 @@ 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, fancyopts, filelog +from mercurial import localrepo, patch, revlog, templater, util from mercurial.node import * from mercurial.i18n import gettext as _ import getopt, os, re, shutil, tempfile, time @@ -100,16 +100,17 @@ # 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' +restricted = '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 = _cmd = _cmdoptions = None +# make keyword tools accessible +kwx = { 'templater': None, 'hgcmd': None } -# backwards compatibility hacks +# monkeypatches and backwards compatibility hacks try: # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78 @@ -123,11 +124,10 @@ _dispatch_parse = commands.parse def _kwdispatch_parse(ui, args): - '''Monkeypatch dispatch._parse to obtain - current command and command options (global _cmd, _cmdoptions).''' - global _cmd, _cmdoptions - _cmd, func, args, options, _cmdoptions = _dispatch_parse(ui, args) - return _cmd, func, args, options, _cmdoptions + '''Monkeypatch dispatch._parse to obtain running hg command.''' + cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args) + kwx['hgcmd'] = cmd + return cmd, func, args, options, cmdoptions try: setattr(dispatch, '_parse', _kwdispatch_parse) @@ -140,8 +140,7 @@ try: # avoid spurious rejects if patchfile is available - from mercurial.patch import patchfile - _patchfile_init = patchfile.__init__ + _patchfile_init = patch.patchfile.__init__ def _kwpatchfile_init(self, ui, fname, missing=False): '''Monkeypatch/wrap patch.patchfile.__init__ to avoid @@ -151,30 +150,34 @@ except TypeError: # "missing" arg added in e90e72c6b4c7 _patchfile_init(self, ui, fname) - if _kwtemplater.matcher(self.fname): - # shrink keywords read from working dir - kwshrunk = _kwtemplater.shrink(''.join(self.lines)) - self.lines = kwshrunk.splitlines(True) -except ImportError: + self.lines = kwx['templater'].shrinklines(self.fname, self.lines) +except AttributeError: pass +_patch_diff = patch.diff +def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always, + fp=None, changes=None, opts=None): + # only expand if comparing against working dir + if node2 is not None: + kwx['templater'].matcher = util.never + elif node1 is not None and node1 != repo.changectx().node(): + kwx['templater'].restrict = True + _patch_diff(repo, node1=node1, node2=node2, files=files, match=match, + fp=fp, changes=changes, opts=opts) + try: from mercurial.hgweb import webcommands def _kwweb_changeset(web, req, tmpl): '''Wraps webcommands.changeset turning off keyword expansion.''' - try: - _kwtemplater.matcher = util.never - except AttributeError: - pass + if kwx['templater']: + kwx['templater'].matcher = util.neve return web.changeset(tmpl, web.changectx(req)) def _kwweb_filediff(web, req, tmpl): '''Wraps webcommands.filediff turning off keyword expansion.''' - try: - _kwtemplater.matcher = util.never - except AttributeError: - pass + if kwx['templater']: + kwx['templater'].matcher = util.neve return web.filediff(tmpl, web.filectx(req)) webcommands.changeset = webcommands.rev = _kwweb_changeset @@ -184,17 +187,13 @@ from mercurial.hgweb.hgweb_mod import hgweb def _kwweb_do_changeset(self, req): - try: - _kwtemplater.matcher = util.never - except AttributeError: - pass + if kwx['templater']: + kwx['templater'].matcher = util.never req.write(self.changeset(self.changectx(req))) def _kwweb_do_filediff(self, req): - try: - _kwtemplater.matcher = util.never - except AttributeError: - pass + if kwx['templater']: + kwx['templater'].matcher = util.never req.write(self.filediff(self.filectx(req))) hgweb.do_changeset = hgweb.do_rev = _kwweb_do_changeset @@ -308,13 +307,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.restrict = hgcmd in restricted.split() - self.commitnode = None - self.path = '' + self.restrict = kwx['hgcmd'] in restricted.split() kwmaps = self.ui.configitems('keywordmaps') if kwmaps: # override default templates @@ -338,46 +335,89 @@ 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, - and calls given substitution function.''' - if self.commitnode: - fnode = self.commitnode - else: - c = context.filectx(self.repo, self.path, fileid=node) - fnode = c.node() + def getnode(self, path, fnode): + '''Derives changenode from file context.''' + c = context.filectx(self.repo, path, fileid=fnode) + return c.node() + def substitute(self, data, path, node, subfunc): + '''Replaces keywords in data with expanded template.''' 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, template_firstline(self.ui.popbuffer())) - return subfunc(kwsub, data) - def expand(self, node, data): + def expand(self, path, node, data): '''Returns data with keywords expanded.''' - if self.restrict or util.binary(data): - return data - return self.substitute(node, data, self.re_kw.sub) + if not self.restrict and self.matcher(path) and not util.binary(data): + changenode = self.getnode(path, node) + return self.substitute(data, path, changenode, self.re_kw.sub) + return data + + def iskwfile(self, path, islink): + '''Returns true if path matches [keyword] pattern + and is not a symbolic link. + Caveat: localrepository._link fails on Windows.''' + return self.matcher(path) and not islink(path) - 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: - return self.substitute(node, data, self.re_kw.subn) - return data, self.re_kw.search(data) + def overwrite(self, node=None, expand=True, files=None): + '''Overwrites selected files expanding/shrinking keywords.''' + ctx = self.repo.changectx(node) + mf = ctx.manifest() + if node is not None: # commit + files = [f for f in ctx.files() if f in mf] + notify = self.ui.debug + else: # kwexpand/kwshrink + notify = self.ui.note + candidates = [f for f in files if self.iskwfile(f, mf.linkf)] + if candidates: + self.restrict = True # do not expand when reading + candidates.sort() + action = expand and 'expanding' or 'shrinking' + overwritten = [] + for f in candidates: + fp = self.repo.file(f) + data = fp.read(mf[f]) + if util.binary(data): + continue + if expand: + changenode = node or self.getnode(f, mf[f]) + data, found = self.substitute(data, f, changenode, + self.re_kw.subn) + else: + found = self.re_kw.search(data) + if found: + notify(_('overwriting %s %s keywords\n') % (f, action)) + self.repo.wwrite(f, data, mf.flags(f)) + overwritten.append(f) + _normal(self.repo, overwritten) + self.restrict = False - def shrink(self, text): + def shrinktext(self, text): + '''Unconditionally removes all keyword substitutions from text.''' + return self.re_kw.sub(r'$\1$', text) + + def shrink(self, fname, text): '''Returns text with all keyword substitutions removed.''' - if util.binary(text): - return text - return self.re_kw.sub(r'$\1$', text) + if self.matcher(fname) and not util.binary(text): + return self.shrinktext(text) + return text + + def shrinklines(self, fname, lines): + '''Returns lines with keyword substitutions removed.''' + if self.matcher(fname): + text = ''.join(lines) + if not util.binary(text): + return self.shrinktext(text).splitlines(True) + return lines + + def wread(self, fname, data): + '''If in restricted mode returns data read from wdir with + keyword substitutions removed.''' + return self.restrict and self.shrink(fname, data) or data class kwfilelog(filelog.filelog): ''' @@ -386,71 +426,41 @@ ''' def __init__(self, opener, path): 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 = kwx['templater'] + self.path = path def read(self, node): '''Expands keywords when reading filelog.''' data = super(kwfilelog, self).read(node) - return _kwtemplater.expand(node, data) + return self.kwt.expand(self.path, node, data) def add(self, text, meta, tr, link, p1=None, p2=None): '''Removes keyword substitutions when adding to filelog.''' - text = _kwtemplater.shrink(text) + text = self.kwt.shrink(self.path, 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) + text = self.kwt.shrink(self.path, 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): +def _status(ui, repo, kwt, *pats, **opts): '''Bails out if [keyword] configuration is not active. Returns status of working directory.''' - if _kwtemplater: + if kwt: files, match, anypats = cmdutil.matchpats(repo, pats, opts) return repo.status(files=files, match=match, list_clean=True) if ui.configitems('keyword'): raise util.Abort(_('[keyword] patterns cannot match')) raise util.Abort(_('no [keyword] patterns configured')) -def _overwrite(ui, repo, node=None, expand=True, files=None): - '''Overwrites selected files expanding/shrinking keywords.''' - ctx = repo.changectx(node) - mf = ctx.manifest() - 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 - notify = ui.note - candidates = [f for f in files if _iskwfile(f, mf.linkf)] - 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) - if kwfound: - notify(_('overwriting %s %s keywords\n') % (f, action)) - _wwrite(repo, f, data, mf) - overwritten.append(f) - _normal(repo, overwritten) - def _kwfwrite(ui, repo, expand, *pats, **opts): - '''Selects files and passes them to _overwrite.''' - status = _status(ui, repo, *pats, **opts) + '''Selects files and passes them to kwtemplater.overwrite.''' + kwt = kwx['templater'] + status = _status(ui, repo, kwt, *pats, **opts) modified, added, removed, deleted, unknown, ignored, clean = status if modified or added or removed or deleted: raise util.Abort(_('outstanding uncommitted changes in given files')) @@ -458,7 +468,7 @@ try: wlock = repo.wlock() lock = repo.lock() - _overwrite(ui, repo, expand=expand, files=clean) + kwt.overwrite(expand=expand, files=clean) finally: del wlock, lock @@ -560,7 +570,8 @@ keyword expansion. That is, files matched by [keyword] config patterns but not symlinks. ''' - status = _status(ui, repo, *pats, **opts) + kwt = kwx['templater'] + status = _status(ui, repo, kwt, *pats, **opts) modified, added, removed, deleted, unknown, ignored, clean = status files = modified + added + clean if opts.get('untracked'): @@ -572,7 +583,7 @@ else: mf = wctx.manifest() islink = mf.linkf - kwfiles = [f for f in files if _iskwfile(f, islink)] + kwfiles = [f for f in files if kwt.iskwfile(f, islink)] cwd = pats and repo.getcwd() or '' kwfstats = not opts.get('ignore') and (('K', kwfiles),) or () if opts.get('all') or opts.get('ignore'): @@ -603,11 +614,8 @@ This is done for local repos only, and only if there are files configured at all for keyword substitution.''' - global _kwtemplater - hgcmd, hgcmdopts = _cmd, _cmdoptions - try: - if (not repo.local() or hgcmd in nokwcommands.split() + if (not repo.local() or kwx['hgcmd'] in nokwcommands.split() or '.hg' in repo.root.split(os.sep) or repo._url.startswith('bundle:')): return @@ -623,30 +631,17 @@ if not inc: return - if hgcmd == 'diff': - # only expand if comparing against working dir - node1, node2 = cmdutil.revpair(repo, hgcmdopts.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' - - _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd) + kwx['templater'] = kwt = kwtemplater(ui, repo, inc, exc) class kwrepo(repo.__class__): - def file(self, f, kwmatch=False): + def file(self, f): if f[0] == '/': f = f[1:] - if kwmatch or _kwtemplater.matcher(f): - return kwfilelog(self.sopener, f) - return filelog.filelog(self.sopener, f) + return kwfilelog(self.sopener, f) def wread(self, filename): data = super(kwrepo, self).wread(filename) - if _kwtemplater.restrict and _kwtemplater.matcher(filename): - return _kwtemplater.shrink(data) - return data + return kwt.wread(filename, data) def _commit(self, files, text, user, date, match, force, lock, wlock, force_editor, p1, p2, extra, empty_ok): @@ -716,17 +711,16 @@ for name, cmd in commithooks.iteritems(): ui.setconfig('hooks', name, cmd) if node is not None: - _overwrite(ui, self, node=node) + kwt.overwrite(node=node) repo.hook('commit', node=node, parent1=_p1, parent2=_p2) return node finally: del _wlock, _lock repo.__class__ = kwrepo - try: - patchfile.__init__ = _kwpatchfile_init - except NameError: - pass + patch.diff = _kw_diff + if hasattr(patch, 'patchfile'): + patch.patchfile.__init__ = _kwpatchfile_init cmdtable = {