# HG changeset patch # User Christian Ebert # Date 1201624330 -3600 # Node ID 159bf80a43018652a250f68abe3686a5fb170be4 # Parent 19d5f328a979485e7c00369699b8c9739090446c Refactor overriding wread, wwrite, wwritedata repo methods - restrict consistently to reading/writing in working dir - no cludgy sys.argv parsing anymore Eases collaboration with other extensions, or external tools like TortoiseHg. Changes in behaviour: - hg cat does not expand; will be implemented with new kwcat command - no expansion in web /display/, only in downloaded archives Thanks to Jesse Glick for inciting this approach. diff -r 19d5f328a979 -r 159bf80a4301 hgkw/keyword.py --- a/hgkw/keyword.py Fri Jan 25 17:31:55 2008 +0000 +++ b/hgkw/keyword.py Tue Jan 29 17:32:10 2008 +0100 @@ -80,40 +80,18 @@ "Log = {desc}" expands to the first line of the changeset description. ''' -from mercurial import commands, cmdutil, context, dispatch, filelog -from mercurial import patch, localrepo, revlog, templater, util +from mercurial import commands, cmdutil, context, localrepo +from mercurial import patch, revlog, templater, util from mercurial.node import * from mercurial.i18n import _ -import re, shutil, sys, tempfile, time +import re, shutil, tempfile, time 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): @@ -131,13 +109,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 @@ -152,78 +128,50 @@ self.ct = cmdutil.changeset_templater(self.ui, self.repo, False, '', 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() + fnode = self.ctx.filenode(path) + fl = self.repo.file(path) + c = context.filectx(self.repo, path, fileid=fnode, filelog=fl) + 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): '''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, None, 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) - - 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 + self.ctx = ctx + return self.substitute(path, data, node, self.re_kw.subn) + return self.re_kw.subn(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) + 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) # store original patch.patchfile.__init__ @@ -257,22 +205,22 @@ '''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)] if candidates: 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)) - repo.wwrite(f, data, mf.flags(f)) + repo.wwrite(f, data, mf.flags(f), overwrite=True) repo.dirstate.normal(f) def _kwfwrite(ui, repo, expand, *pats, **opts): @@ -366,7 +314,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) @@ -416,44 +364,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) @@ -463,22 +377,31 @@ return global _kwtemplater - _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd) + _kwtemplater = kwtemplater(ui, repo, inc, exc) class kwrepo(repo.__class__): - 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 _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 _kwtemplater.process(filename, data, expand, ctx, node) def wread(self, filename): data = super(kwrepo, self).wread(filename) - if hgcmd in restricted and _kwtemplater.matcher(filename): + if _kwtemplater.matcher(filename): return _kwtemplater.shrink(data) return data + def wwrite(self, filename, data, flags, overwrite=False): + if not overwrite and _kwtemplater.matcher(filename): + data = _kwtemplater.expand(filename, data) + super(kwrepo, self).wwrite(filename, data, flags) + + def wwritedata(self, filename, data): + if _kwtemplater.matcher(filename): + data = _kwtemplater.expand(filename, data) + 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={}):