Refactor overriding wread, wwrite, wwritedata repo methods
authorChristian Ebert <blacktrash@gmx.net>
Tue, 29 Jan 2008 17:32:10 +0100
changeset 353 159bf80a4301
parent 351 19d5f328a979
child 354 8e3364294d0c
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.
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={}):