diff -r a71e2086fe6e -r 4ac7e51443fe hgkw/keyword.py --- 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')),