diff -r 0ae62443e644 -r 67c17a447d99 hgkw/keyword.py --- a/hgkw/keyword.py Sun Sep 26 21:59:47 2010 +0200 +++ b/hgkw/keyword.py Sat Oct 09 11:52:32 2010 +0100 @@ -82,8 +82,8 @@ {desc}" expands to the first line of the changeset description. ''' -from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions -from mercurial import patch, localrepo, templater, templatefilters, util, match +from mercurial import commands, cmdutil, dispatch, filelog, extensions +from mercurial import localrepo, match, patch, templatefilters, templater, util from mercurial.hgweb import webcommands from mercurial.i18n import _ import re, shutil, tempfile @@ -91,15 +91,13 @@ commands.optionalrepo += ' kwdemo' # hg commands that do not act on keywords -nokwcommands = ('add addremove annotate bundle copy export grep incoming init' - ' log outgoing push rename tip verify convert email glog') +nokwcommands = ('add addremove annotate bundle export grep incoming init log' + ' outgoing push tip verify convert email glog') # hg commands that trigger expansion only when writing to working dir, # not when reading filelog, and unexpand when reading from working dir -restricted = 'merge record qrecord resolve transplant' +restricted = 'merge kwexpand kwshrink record qrecord resolve transplant' -# commands using dorecord -recordcommands = 'record qrecord' # names of extensions using dorecord recordextensions = 'record' @@ -138,6 +136,12 @@ templates.update(kwsets[ui.configbool('keywordset', 'svn')]) return templates +def _shrinktext(text, subfunc): + '''Helper for keyword expansion removal in text. + Depending on subfunc also returns number of substitutions.''' + return subfunc(r'$\1$', text) + + class kwtemplater(object): ''' Sets up keyword templates, corresponding keyword regex, and @@ -149,7 +153,7 @@ self.repo = repo self.match = match.match(repo.root, '', [], inc, exc) self.restrict = kwtools['hgcmd'] in restricted.split() - self.record = kwtools['hgcmd'] in recordcommands.split() + self.record = False kwmaps = self.ui.configitems('keywordmaps') if kwmaps: # override default templates @@ -157,9 +161,9 @@ for k, v in kwmaps) else: self.templates = _defaultkwmaps(self.ui) - escaped = map(re.escape, self.templates.keys()) - kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped) - self.re_kw = re.compile(kwpat) + escaped = '|'.join(map(re.escape, self.templates.keys())) + self.re_kw = re.compile(r'\$(%s)\$' % escaped) + self.re_kwexp = re.compile(r'\$(%s): [^$\n\r]*? \$' % escaped) templatefilters.filters.update({'utcdate': utcdate, 'svnisodate': svnisodate, @@ -185,55 +189,52 @@ return self.substitute(data, path, ctx, self.re_kw.sub) return data - def iskwfile(self, path, flagfunc): - '''Returns true if path matches [keyword] pattern - and is not a symbolic link. - Caveat: localrepository._link fails on Windows.''' - return self.match(path) and not 'l' in flagfunc(path) + def iskwfile(self, cand, ctx): + '''Returns subset of candidates which are configured for keyword + expansion are not symbolic links.''' + return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)] - def overwrite(self, ctx, candidates, iswctx, expand, cfiles): + def overwrite(self, ctx, candidates, lookup, expand, recsubn=None): '''Overwrites selected files expanding/shrinking keywords.''' - if cfiles is not None: - candidates = [f for f in candidates if f in cfiles] - candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)] - if candidates: - restrict = self.restrict - self.restrict = True # do not expand when reading - rollback = kwtools['hgcmd'] == 'rollback' + if self.restrict or lookup: # exclude kw_copy + candidates = self.iskwfile(candidates, ctx) + if not candidates: + return + commit = self.restrict and not lookup + if self.restrict or expand and lookup: mf = ctx.manifest() - msg = (expand and _('overwriting %s expanding keywords\n') - or _('overwriting %s shrinking keywords\n')) - for f in candidates: - if not self.record and not rollback: - data = self.repo.file(f).read(mf[f]) - else: - data = self.repo.wread(f) - if util.binary(data): - continue - if expand: - if iswctx: - ctx = self.repo.filectx(f, fileid=mf[f]).changectx() - data, found = self.substitute(data, f, ctx, - self.re_kw.subn) - else: - found = self.re_kw.search(data) - if found: - self.ui.note(msg % f) - self.repo.wwrite(f, data, mf.flags(f)) - if iswctx and not rollback: - self.repo.dirstate.normal(f) - elif self.record: - self.repo.dirstate.normallookup(f) - self.restrict = restrict - - def shrinktext(self, text): - '''Unconditionally removes all keyword substitutions from text.''' - return self.re_kw.sub(r'$\1$', text) + fctx = ctx + subn = (self.restrict and self.re_kw.subn or + recsubn or self.re_kwexp.subn) + msg = (expand and _('overwriting %s expanding keywords\n') + or _('overwriting %s shrinking keywords\n')) + for f in candidates: + if self.restrict: + data = self.repo.file(f).read(mf[f]) + else: + data = self.repo.wread(f) + if util.binary(data): + continue + if expand: + if lookup: + fctx = self.repo.filectx(f, fileid=mf[f]).changectx() + data, found = self.substitute(data, f, fctx, subn) + elif self.restrict: + found = self.re_kw.search(data) + else: + data, found = _shrinktext(data, subn) + if found: + self.ui.note(msg % f) + self.repo.wwrite(f, data, ctx.flags(f)) + if commit: + self.repo.dirstate.normal(f) + elif self.record: + self.repo.dirstate.normallookup(f) def shrink(self, fname, text): '''Returns text with all keyword substitutions removed.''' if self.match(fname) and not util.binary(text): - return self.shrinktext(text) + return _shrinktext(text, self.re_kwexp.sub) return text def shrinklines(self, fname, lines): @@ -241,7 +242,7 @@ if self.match(fname): text = ''.join(lines) if not util.binary(text): - return self.shrinktext(text).splitlines(True) + return _shrinktext(text, self.re_kwexp.sub).splitlines(True) return lines def wread(self, fname, data): @@ -262,6 +263,8 @@ def read(self, node): '''Expands keywords when reading filelog.''' data = super(kwfilelog, self).read(node) + if self.renamed(node): + return data return self.kwt.expand(self.path, node, data) def add(self, text, meta, tr, link, p1=None, p2=None): @@ -272,10 +275,7 @@ def cmp(self, node, text): '''Removes keyword substitutions for comparison.''' 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) + return super(kwfilelog, self).cmp(node, text) def _status(ui, repo, kwt, *pats, **opts): '''Bails out if [keyword] configuration is not active. @@ -299,7 +299,7 @@ modified, added, removed, deleted, unknown, ignored, clean = status if modified or added or removed or deleted: raise util.Abort(_('outstanding uncommitted changes')) - kwt.overwrite(wctx, clean, True, expand, None) + kwt.overwrite(wctx, clean, True, expand) finally: wlock.release() @@ -415,8 +415,8 @@ if not opts.get('unknown') or opts.get('all'): files = sorted(modified + added + clean) wctx = repo[None] - kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)] - kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)] + kwfiles = kwt.iskwfile(files, wctx) + kwunknown = kwt.iskwfile(unknown, wctx) if not opts.get('ignore') or opts.get('all'): showfiles = kwfiles, kwunknown else: @@ -502,21 +502,26 @@ n = super(kwrepo, self).commitctx(ctx, error) # no lock needed, only called from repo.commit() which already locks if not kwt.record: + restrict = kwt.restrict + kwt.restrict = True kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()), - False, True, None) + False, True) + kwt.restrict = restrict return n def rollback(self, dryrun=False): wlock = repo.wlock() try: if not dryrun: - cfiles = self['.'].files() + changed = self['.'].files() ret = super(kwrepo, self).rollback(dryrun) if not dryrun: ctx = self['.'] - modified, added = super(kwrepo, self).status()[:2] - kwt.overwrite(ctx, added, True, False, cfiles) - kwt.overwrite(ctx, modified, True, True, cfiles) + modified, added = self[None].status()[:2] + modified = [f for f in modified if f in changed] + added = [f for f in added if f in changed] + kwt.overwrite(ctx, added, True, False) + kwt.overwrite(ctx, modified, True, True) return ret finally: wlock.release() @@ -541,18 +546,40 @@ kwt.match = util.never return orig(web, req, tmpl) + def kw_copy(orig, ui, repo, pats, opts, rename=False): + '''Wraps cmdutil.copy so that copy/rename destinations do not + contain expanded keywords. + Note that the source may also be a symlink as: + hg cp sym x -> x is symlink + cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords) + ''' + orig(ui, repo, pats, opts, rename) + if opts.get('dry_run'): + return + wctx = repo[None] + candidates = [f for f in repo.dirstate.copies() if + kwt.match(repo.dirstate.copied(f)) and + not 'l' in wctx.flags(f)] + kwt.overwrite(wctx, candidates, False, False) + def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts): '''Wraps record.dorecord expanding keywords after recording.''' wlock = repo.wlock() try: # record returns 0 even when nothing has changed # therefore compare nodes before and after + kwt.record = True ctx = repo['.'] + modified, added = repo[None].status()[:2] ret = orig(ui, repo, commitfunc, *pats, **opts) - recordctx = repo['.'] - if ctx != recordctx: - kwt.overwrite(recordctx, recordctx.files(), - False, True, recordctx) + recctx = repo['.'] + if ctx != recctx: + modified = [f for f in modified if f in recctx] + added = [f for f in added if f in recctx] + kwt.restrict = False + kwt.overwrite(recctx, modified, False, True, kwt.re_kwexp.subn) + kwt.overwrite(recctx, added, False, True, kwt.re_kw.subn) + kwt.restrict = True return ret finally: wlock.release() @@ -561,6 +588,7 @@ extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init) extensions.wrapfunction(patch, 'diff', kw_diff) + extensions.wrapfunction(cmdutil, 'copy', kw_copy) for c in 'annotate changeset rev filediff diff'.split(): extensions.wrapfunction(webcommands, c, kwweb_skip) for name in recordextensions.split():