(0.9.2compat) implement context based changes from default branch 0.9.2compat
authorChristian Ebert <blacktrash@gmx.net>
Tue, 04 Dec 2007 09:56:12 +0100
branch0.9.2compat
changeset 311 6160401f94f2
parent 303 46ccec2f325f
child 312 b92767fb8fb5
(0.9.2compat) implement context based changes from default branch NOTE: Interrupted commit is broken with versions < 0.9.5! Leave tests outcommented.
hgkw/keyword.py
tests/test-keyword
tests/test-keyword.out
--- a/hgkw/keyword.py	Mon Nov 26 12:52:54 2007 +0100
+++ b/hgkw/keyword.py	Tue Dec 04 09:56:12 2007 +0100
@@ -82,11 +82,30 @@
 
 from mercurial import commands, cmdutil, context
 from mercurial import filelog, localrepo, revlog, templater, util
+from mercurial.node import *
 from mercurial.i18n import gettext as _
 import getopt, os.path, re, shutil, sys, tempfile, time
 
 # backwards compatibility hacks
 
+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()
+    try:
+        for f in files:
+            repo.dirstate.normal(f)
+    except AttributeError:
+        repo.dirstate.update(files, 'n')
+
 def _pathto(repo, f, cwd=None):
     '''kwfiles behaves similar to status, using pathto since 78b6add1f966.'''
     try:
@@ -147,6 +166,8 @@
     '''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
@@ -162,12 +183,12 @@
         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
     }
 
-    def __init__(self, ui, repo, expand, path='', node=None):
+    def __init__(self, ui, repo, inc, exc):
         self.ui = ui
         self.repo = repo
-        self.t = expand or None
-        self.path = path
-        self.node = node
+        self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
+        self.node = None
+        self.path = ''
 
         kwmaps = self.ui.configitems('keywordmaps')
         if kwmaps: # override default templates
@@ -177,9 +198,9 @@
         escaped = map(re.escape, self.templates.keys())
         kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
         self.re_kw = re.compile(kwpat)
-        if self.t:
-            templater.common_filters['utcdate'] = utcdate
-            self.t = self._changeset_templater()
+
+        templater.common_filters['utcdate'] = utcdate
+        self.ct = self._changeset_templater()
 
     def _changeset_templater(self):
         '''Backwards compatible cmdutil.changeset_templater.'''
@@ -191,24 +212,6 @@
             return cmdutil.changeset_templater(self.ui, self.repo,
                                                False, None, '', False)
 
-    def _wwrite(self, f, data, man):
-        '''Makes repo.wwrite backwards compatible.'''
-        # 656e06eebda7 removed file descriptor argument
-        # 67982d3ee76c added flags argument
-        try:
-            self.repo.wwrite(f, data, man.flags(f))
-        except (AttributeError, TypeError):
-            self.repo.wwrite(f, data)
-
-    def _normal(self, files):
-        '''Backwards compatible repo.dirstate.normal/update.'''
-        # 6fd953d5faea introduced dirstate.normal()
-        try:
-            for f in files:
-                self.repo.dirstate.normal(f)
-        except AttributeError:
-            self.repo.dirstate.update(files, 'n')
-
     def substitute(self, node, data, subfunc):
         '''Obtains node if missing, and calls given substitution function.'''
         if not self.node:
@@ -218,10 +221,10 @@
         def kwsub(mobj):
             '''Substitutes keyword using corresponding template.'''
             kw = mobj.group(1)
-            self.t.use_template(self.templates[kw])
+            self.ct.use_template(self.templates[kw])
             self.ui.pushbuffer()
-            self.t.show(changenode=self.node,
-                        root=self.repo.root, file=self.path)
+            self.ct.show(changenode=self.node,
+                         root=self.repo.root, file=self.path)
             return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer()))
 
         return subfunc(kwsub, data)
@@ -232,14 +235,14 @@
             return data
         return self.substitute(node, data, self.re_kw.sub)
 
-    def process(self, node, data):
+    def process(self, node, data, expand):
         '''Returns a tuple: data, count.
         Count is number of keywords/keyword substitutions, indicates
         to caller whether to act on file containing data.
         Keywords in data are expanded, if templater was initialized.'''
         if util.binary(data):
             return data, None
-        if self.t:
+        if expand:
             return self.substitute(node, data, self.re_kw.subn)
         return data, self.re_kw.search(data)
 
@@ -249,23 +252,6 @@
             return text
         return self.re_kw.sub(r'$\1$', text)
 
-    def overwrite(self, candidates, man, commit):
-        '''Overwrites files in working directory if keywords are detected.
-        Keywords are expanded if keyword templater is initialized,
-        otherwise their substitution is removed.'''
-        expand = self.t is not None
-        action = ('shrinking', 'expanding')[expand]
-        notify = (self.ui.note, self.ui.debug)[commit]
-        overwritten = []
-        for f in candidates:
-            fp = self.repo.file(f, kwexp=expand, kwmatch=True)
-            data, kwfound = fp.kwctread(man[f])
-            if kwfound:
-                notify(_('overwriting %s %s keywords\n') % (f, action))
-                self._wwrite(f, data, man)
-                overwritten.append(f)
-        self._normal(overwritten)
-
 class kwfilelog(filelog.filelog):
     '''
     Subclass of filelog to hook into its read, add, cmp methods.
@@ -274,12 +260,13 @@
     def __init__(self, opener, path, kwtemplater):
         super(kwfilelog, self).__init__(opener, path)
         self.kwtemplater = kwtemplater
+        self.kwtemplater.path = path
 
-    def kwctread(self, node):
+    def kwctread(self, node, expand):
         '''Reads expanding and counting keywords
         (only called from kwtemplater.overwrite).'''
         data = super(kwfilelog, self).read(node)
-        return self.kwtemplater.process(node, data)
+        return self.kwtemplater.process(node, data, expand)
 
     def read(self, node):
         '''Expands keywords when reading filelog.'''
@@ -299,28 +286,47 @@
             return t2 != text
         return revlog.revlog.cmp(self, node, text)
 
-def _status(ui, repo, *pats, **opts):
+def _iskwfile(f, kwtemplater, link):
+    return not link(f) and kwtemplater.matcher(f)
+
+def _status(ui, repo, kwtemplater, *pats, **opts):
     '''Bails out if [keyword] configuration is not active.
     Returns status of working directory.'''
-    if hasattr(ui, 'kwfmatcher'):
+    if kwtemplater:
         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 _iskwfile(ui, man, f):
-    return not man.linkf(f) and ui.kwfmatcher(f)
-
-def _overwrite(ui, repo, files, node, man, expand, commit):
-    '''Passes given files to kwtemplater for overwriting.'''
-    files.sort()
-    kwt = kwtemplater(ui, repo, expand, node=node)
-    kwt.overwrite(files, man, commit)
+def _overwrite(ui, repo, kwtemplater, node=None, expand=True, files=None):
+    '''Overwrites selected files expanding/shrinking keywords.'''
+    ctx = repo.changectx(node)
+    mf = ctx.manifest()
+    if files is None:
+        notify = ui.debug # commit
+        files = [f for f in ctx.files() if mf.has_key(f)]
+    else:
+        notify = ui.note  # kwexpand/kwshrink
+    candidates = [f for f in files if _iskwfile(f, kwtemplater, mf.linkf)]
+    if candidates:
+        overwritten = []
+        candidates.sort()
+        action = expand and 'expanding' or 'shrinking'
+        kwtemplater.node = node or ctx.node()
+        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)
+    global _kwtemplater
+    status = _status(ui, repo, _kwtemplater, *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'))
@@ -328,12 +334,7 @@
     try:
         wlock = repo.wlock()
         lock = repo.lock()
-        ctx = repo.changectx()
-        man = ctx.manifest()
-        candidates = [f for f in clean if _iskwfile(ui, man, f)]
-        if candidates:
-            # 7th argument sets commit to False
-            _overwrite(ui, repo, candidates, ctx.node(), man, expand, False)
+        _overwrite(ui, repo, _kwtemplater, expand=expand, files=clean)
     finally:
         del wlock, lock
 
@@ -407,10 +408,14 @@
     ui.quiet = not verbose
     commands.branch(ui, repo, branchname)
     ui.quiet = quiet
+    for name, cmd in ui.configitems('hooks'):
+        if name.split('.', 1)[0].find('commit') > -1:
+            repo.ui.setconfig('hooks', name, '')
+    ui.note(_('unhooked all commit hooks\n'))
     ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
     repo.commit(text=msg)
-    pathinfo = ('', ' in %s' % path)[ui.verbose]
-    demostatus('%s keywords expanded%s' % (kwstatus, pathinfo))
+    format = ui.verbose and ' in %s' % path or ''
+    demostatus('%s keywords expanded%s' % (kwstatus, format))
     ui.write(repo.wread(fn))
     ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
     shutil.rmtree(tmpdir, ignore_errors=True)
@@ -432,7 +437,8 @@
     keyword expansion.
     That is, files matched by [keyword] config patterns but not symlinks.
     '''
-    status = _status(ui, repo, *pats, **opts)
+    global _kwtemplater
+    status = _status(ui, repo, _kwtemplater, *pats, **opts)
     modified, added, removed, deleted, unknown, ignored, clean = status
     if opts['untracked']:
         files = modified + added + unknown + clean
@@ -440,7 +446,7 @@
         files = modified + added + clean
     files.sort()
     # use the full definition of repo._link for backwards compatibility
-    kwfiles = [f for f in files if ui.kwfmatcher(f)
+    kwfiles = [f for f in files if _kwtemplater.matcher(f)
                and not os.path.islink(repo.wjoin(f))]
     cwd = pats and repo.getcwd() or ''
     allf = opts['all']
@@ -452,12 +458,9 @@
     if allf or ignore:
         kwfstats += (('I', [f for f in files if f not in kwfiles]),)
     for char, filenames in kwfstats:
-        if allf or ui.verbose:
-            format = '%s %%s\n' % char
-        else:
-            format = '%s\n'
+        format = (allf or ui.verbose) and '%s %%s\n' % char or '%s\n'
         for f in filenames:
-            ui.write(format % repo.pathto(f, cwd))
+            ui.write(format % _pathto(repo, f, cwd))
 
 def shrink(ui, repo, *pats, **opts):
     '''revert expanded keywords in working directory
@@ -503,15 +506,15 @@
     if not inc:
         return
 
-    ui.kwfmatcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
+    global _kwtemplater
+    _kwtemplater = kwtemplater(ui, repo, inc, exc)
 
     class kwrepo(repo.__class__):
-        def file(self, f, kwexp=True, kwmatch=False):
+        def file(self, f, kwmatch=False):
             if f[0] == '/':
                 f = f[1:]
-            if kwmatch or ui.kwfmatcher(f):
-                kwt = kwtemplater(ui, self, kwexp, path=f)
-                return kwfilelog(self.sopener, f, kwt)
+            if kwmatch or _kwtemplater.matcher(f):
+                return kwfilelog(self.sopener, f, _kwtemplater)
             return filelog.filelog(self.sopener, f)
 
         def _commit(self, files, text, user, date, match, force, lock, wlock,
@@ -540,21 +543,39 @@
             _lock = lock
             _wlock = wlock
             del wlock, lock
+            _p1 = _p2 = None
             try:
                 if not _wlock:
                     _wlock = self.wlock()
                 if not _lock:
                     _lock = self.lock()
+                # store and postpone commit hooks
+                commithooks = []
+                for name, cmd in ui.configitems('hooks'):
+                    if name.split('.', 1)[0] == 'commit':
+                        commithooks.append((name, cmd))
+                        ui.setconfig('hooks', name, '')
+                if commithooks:
+                    # store parents for commit hook environment
+                    if p1 is None:
+                        _p1, _p2 = repo.dirstate.parents()
+                    else:
+                        _p1, _p2 = p1, p2 or nullid
+                    _p1 = hex(_p1)
+                    if _p2 == nullid:
+                        _p2 = ''
+                    else:
+                        _p2 = hex(_p2)
+
                 node = self._commit(files, text, user, date, match, force,
                                     _lock, _wlock, force_editor, p1, p2, extra)
+
+                # restore commit hooks
+                for name, cmd in commithooks:
+                    ui.setconfig('hooks', name, cmd)
                 if node is not None:
-                    cl = self.changelog.read(node)
-                    mn = self.manifest.read(cl[0])
-                    candidates = [f for f in cl[3] if mn.has_key(f)
-                                  and _iskwfile(ui, mn, f)]
-                    if candidates:
-                        # 6th, 7th arguments set expansion, commit to True
-                        _overwrite(ui, self, candidates, node, mn, True, True)
+                    _overwrite(ui, self, _kwtemplater, node=node)
+                    repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
                 return node
             finally:
                 del _wlock, _lock
--- a/tests/test-keyword	Mon Nov 26 12:52:54 2007 +0100
+++ b/tests/test-keyword	Tue Dec 04 09:56:12 2007 +0100
@@ -6,6 +6,9 @@
 [keyword]
 * =
 b = ignore
+[hooks]
+commit=
+commit.test=cp a hooktest
 EOF
 
 echo % help
@@ -35,9 +38,19 @@
 echo % cat
 cat a b
 
-echo % default keyword expansion
+echo % addremove
+hg addremove
+echo % status
+hg status
+
+echo % default keyword expansion including commit hook
+#echo % interrupted commit should not change state or run commit hook
+#HGEDITOR=false hg --debug commit
+#echo % status
+#hg status
+
 echo % commit
-hg --debug commit -A -mab -d '0 0' -u 'User Name <user@example.com>'
+hg --debug commit -mab -d '0 0' -u 'User Name <user@example.com>'
 echo % status
 hg status
 echo % identify
@@ -47,6 +60,15 @@
 echo % hg cat
 hg cat a b
 
+echo
+echo % diff a hooktest
+diff a hooktest
+
+echo % removing commit hook from config
+sed -e '/\[hooks\]/,$ d' $HGRCPATH > $HGRCPATH.nohook
+mv $HGRCPATH.nohook $HGRCPATH
+rm hooktest
+
 echo % touch
 touch a b
 echo % status
@@ -108,11 +130,11 @@
 secondline
 EOF
 
-echo % interrupted commit
-# redirection and grep for backwards compatibility
-HGEDITOR=false hg commit 2>&1 | grep -v 'edit failed:'
-echo % status
-hg status
+#echo % interrupted commit
+## redirection and grep for backwards compatibility
+#HGEDITOR=false hg commit 2>&1 | grep -v 'edit failed:'
+#echo % status
+#hg status
 
 echo % commit
 hg --debug commit -l log -d '2 0' -u 'User Name <user@example.com>'
--- a/tests/test-keyword.out	Mon Nov 26 12:52:54 2007 +0100
+++ b/tests/test-keyword.out	Tue Dec 04 09:56:12 2007 +0100
@@ -71,14 +71,20 @@
 do not process $Id:
 xxx $
 ignore $Id$
-% default keyword expansion
-% commit
+% addremove
 adding a
 adding b
+% status
+A a
+A b
+% default keyword expansion including commit hook
+% commit
 a
 b
 overwriting a expanding keywords
+running hook commit.test: cp a hooktest
 % status
+? hooktest
 % identify
 7f0665a496fd
 % cat
@@ -91,6 +97,9 @@
 do not process $Id:
 xxx $
 ignore $Id$
+
+% diff a hooktest
+% removing commit hook from config
 % touch
 % status
 % update
@@ -154,12 +163,6 @@
 do not process $Id:
 xxx $
 ignore $Id$
-% interrupted commit
-transaction abort!
-rollback completed
-% status
-M a
-? log
 % commit
 a
 overwriting a expanding keywords