Merge with default stable
authorChristian Ebert <blacktrash@gmx.net>
Sat, 09 Oct 2010 11:52:32 +0100
branchstable
changeset 820 67c17a447d99
parent 810 0ae62443e644 (current diff)
parent 819 8f2c2cc51789 (diff)
child 821 04cb1661adc8
Merge with default
--- 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():
--- a/tests/test-keyword.t	Sun Sep 26 21:59:47 2010 +0200
+++ b/tests/test-keyword.t	Sat Oct 09 11:52:32 2010 +0100
@@ -48,7 +48,11 @@
   > [keyword]
   > ** =
   > b = ignore
+  > i = ignore
   > [hooks]
+  > EOF
+  $ cp $HGRCPATH $HGRCPATH.nohooks
+  > cat <<EOF >> $HGRCPATH
   > commit=
   > commit.test=cp a hooktest
   > EOF
@@ -156,10 +160,7 @@
 
   $ diff a hooktest
 
-Removing commit hook from config
-
-  $ sed -e '/\[hooks\]/,$ d' "$HGRCPATH" > $HGRCPATH.nohook
-  $ mv "$HGRCPATH".nohook "$HGRCPATH"
+  $ cp $HGRCPATH.nohooks $HGRCPATH
   $ rm hooktest
 
 bundle
@@ -246,10 +247,7 @@
   +ignore $Id$
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-Remove notify config
-
-  $ sed -e '/\[hooks\]/,$ d' "$HGRCPATH" > $HGRCPATH.nonotify
-  $ mv "$HGRCPATH".nonotify "$HGRCPATH"
+  $ cp $HGRCPATH.nohooks $HGRCPATH
 
 Touch files and check with status
 
@@ -375,7 +373,6 @@
 
   $ hg status -A a
   C a
-  $ rm msg
 
 rollback and revert expansion
 
@@ -420,6 +417,25 @@
   $ hg update -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
+record added file
+
+  $ echo '$Id$' > r
+  $ hg add r
+  $ hg -v record -l msg -d '1 12' r<<EOF
+  > y
+  > EOF
+  diff --git a/r b/r
+  new file mode 100644
+  examine changes to 'r'? [Ynsfdaq?] 
+  r
+  committed changeset 3:899491280810
+  overwriting r expanding keywords
+  $ hg --verbose rollback
+  rolling back to revision 2 (undo commit)
+  overwriting r shrinking keywords
+  $ hg forget r
+  $ rm msg r
+
 Test patch queue repo
 
   $ hg init --mq
@@ -498,6 +514,38 @@
   $ touch c
   $ hg status
 
+Copy kwfile to keyword ignored file unexpanding keywords
+
+  $ hg --verbose copy a i
+  copying a to i
+  overwriting i shrinking keywords
+  $ head -n 1 i
+  expand $Id$
+  $ hg forget i
+  $ rm i
+
+Copy ignored file to ignored file: no overwriting
+
+  $ hg --verbose copy b i
+  copying b to i
+  $ hg forget i
+  $ rm i
+
+cp symlink (becomes regular file), and hg copy after
+
+  $ cp sym i
+  $ ls -l i
+  -rw-r--r--  * (glob)
+  $ head -1 i
+  expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
+  $ hg copy --after --verbose sym i
+  copying sym to i
+  overwriting i shrinking keywords
+  $ head -1 i
+  expand $Id$
+  $ hg forget i
+  $ rm i
+
 Test different options of hg kwfiles
 
   $ hg kwfiles
@@ -541,6 +589,7 @@
   ** = 
   b = ignore
   demo.txt = 
+  i = ignore
   [keywordmaps]
   Xinfo = {author}: {desc}
   $Xinfo: test: hg keyword configuration and expansion example $
@@ -696,8 +745,8 @@
   
 Imported patch should not be rejected
 
-  $ sed -e 's/Id.*/& rejecttest/' a > a.new
-  $ mv a.new a
+  $ python -c \
+  > 'import re; s=re.sub("(Id.*)","\\1 rejecttest",open("a").read()); open("a","wb").write(s);'
   $ hg --debug commit -m'rejects?' -d '3 0' -u 'User Name <user@example.com>'
   a
   overwriting a expanding keywords