changeset 3643:b4ad640a3bcf

templates: move changeset templating bits to cmdutils
author Matt Mackall <mpm@selenic.com>
date Mon, 13 Nov 2006 13:26:57 -0600
parents b2c47652e8e3
children b7547efe78fb
files hgext/bugzilla.py hgext/hbisect.py hgext/notify.py mercurial/cmdutil.py mercurial/commands.py mercurial/templater.py
diffstat 6 files changed, 347 insertions(+), 344 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/bugzilla.py	Mon Nov 13 13:26:57 2006 -0600
+++ b/hgext/bugzilla.py	Mon Nov 13 13:26:57 2006 -0600
@@ -55,7 +55,7 @@
 from mercurial.demandload import *
 from mercurial.i18n import gettext as _
 from mercurial.node import *
-demandload(globals(), 'mercurial:templater,util os re time')
+demandload(globals(), 'mercurial:cmdutil,templater,util os re time')
 
 MySQLdb = None
 
@@ -267,8 +267,8 @@
 
         mapfile = self.ui.config('bugzilla', 'style')
         tmpl = self.ui.config('bugzilla', 'template')
-        sio = templater.stringio()
-        t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
+        sio = cmdutil.stringio()
+        t = cmdutil.changeset_templater(self.ui, self.repo, mapfile, sio)
         if not mapfile and not tmpl:
             tmpl = _('changeset {node|short} in repo {root} refers '
                      'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
--- a/hgext/hbisect.py	Mon Nov 13 13:26:57 2006 -0600
+++ b/hgext/hbisect.py	Mon Nov 13 13:26:57 2006 -0600
@@ -8,7 +8,7 @@
 
 from mercurial.i18n import gettext as _
 from mercurial.demandload import demandload
-demandload(globals(), "os sys sets mercurial:hg,util,commands")
+demandload(globals(), "os sys sets mercurial:hg,util,commands,cmdutil")
 
 versionstr = "0.0.3"
 
@@ -169,7 +169,7 @@
             if ancestors.pop() != self.badrev:
                 raise util.Abort(_("Could not find the first bad revision"))
             self.ui.write(_("The first bad revision is:\n"))
-            displayer = commands.show_changeset(self.ui, self.repo, {})
+            displayer = cmdutil.show_changeset(self.ui, self.repo, {})
             displayer.show(changenode=self.badrev)
             return None
         best_rev = None
--- a/hgext/notify.py	Mon Nov 13 13:26:57 2006 -0600
+++ b/hgext/notify.py	Mon Nov 13 13:26:57 2006 -0600
@@ -68,7 +68,7 @@
 from mercurial.demandload import *
 from mercurial.i18n import gettext as _
 from mercurial.node import *
-demandload(globals(), 'mercurial:commands,patch,templater,util,mail')
+demandload(globals(), 'mercurial:commands,patch,cmdutil,templater,util,mail')
 demandload(globals(), 'email.Parser fnmatch socket time')
 
 # template for single changeset can include email headers.
@@ -107,13 +107,13 @@
         self.stripcount = int(self.ui.config('notify', 'strip', 0))
         self.root = self.strip(self.repo.root)
         self.domain = self.ui.config('notify', 'domain')
-        self.sio = templater.stringio()
+        self.sio = cmdutil.stringio()
         self.subs = self.subscribers()
 
         mapfile = self.ui.config('notify', 'style')
         template = (self.ui.config('notify', hooktype) or
                     self.ui.config('notify', 'template'))
-        self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
+        self.t = cmdutil.changeset_templater(self.ui, self.repo, mapfile,
                                                self.sio)
         if not mapfile and not template:
             template = deftemplates.get(hooktype) or single_template
@@ -237,7 +237,7 @@
         maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
         if maxdiff == 0:
             return
-        fp = templater.stringio()
+        fp = cmdutil.stringio()
         prev = self.repo.changelog.parents(node)[0]
         patch.diff(self.repo, prev, ref, fp=fp)
         difflines = fp.getvalue().splitlines(1)
--- a/mercurial/cmdutil.py	Mon Nov 13 13:26:57 2006 -0600
+++ b/mercurial/cmdutil.py	Mon Nov 13 13:26:57 2006 -0600
@@ -8,8 +8,8 @@
 from demandload import demandload
 from node import *
 from i18n import gettext as _
-demandload(globals(), 'mdiff util')
 demandload(globals(), 'os sys')
+demandload(globals(), 'mdiff util templater cStringIO')
 
 revrangesep = ':'
 
@@ -195,3 +195,330 @@
                                (oldrel, newrel, score * 100))
             if not dry_run:
                 repo.copy(old, new, wlock=wlock)
+
+class changeset_printer(object):
+    '''show changeset information when templating not requested.'''
+
+    def __init__(self, ui, repo):
+        self.ui = ui
+        self.repo = repo
+
+    def show(self, rev=0, changenode=None, brinfo=None, copies=None):
+        '''show a single changeset or file revision'''
+        log = self.repo.changelog
+        if changenode is None:
+            changenode = log.node(rev)
+        elif not rev:
+            rev = log.rev(changenode)
+
+        if self.ui.quiet:
+            self.ui.write("%d:%s\n" % (rev, short(changenode)))
+            return
+
+        changes = log.read(changenode)
+        date = util.datestr(changes[2])
+        extra = changes[5]
+        branch = extra.get("branch")
+
+        hexfunc = self.ui.debugflag and hex or short
+
+        parents = log.parentrevs(rev)
+        if not self.ui.debugflag:
+            if parents[1] == nullrev:
+                if parents[0] >= rev - 1:
+                    parents = []
+                else:
+                    parents = [parents[0]]
+        parents = [(p, hexfunc(log.node(p))) for p in parents]
+
+        self.ui.write(_("changeset:   %d:%s\n") % (rev, hexfunc(changenode)))
+
+        if branch:
+            self.ui.write(_("branch:      %s\n") % branch)
+        for tag in self.repo.nodetags(changenode):
+            self.ui.write(_("tag:         %s\n") % tag)
+        for parent in parents:
+            self.ui.write(_("parent:      %d:%s\n") % parent)
+
+        if brinfo and changenode in brinfo:
+            br = brinfo[changenode]
+            self.ui.write(_("branch:      %s\n") % " ".join(br))
+
+        if self.ui.debugflag:
+            self.ui.write(_("manifest:    %d:%s\n") %
+                          (self.repo.manifest.rev(changes[0]), hex(changes[0])))
+        self.ui.write(_("user:        %s\n") % changes[1])
+        self.ui.write(_("date:        %s\n") % date)
+
+        if self.ui.debugflag:
+            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
+            for key, value in zip([_("files:"), _("files+:"), _("files-:")],
+                                  files):
+                if value:
+                    self.ui.write("%-12s %s\n" % (key, " ".join(value)))
+        elif changes[3] and self.ui.verbose:
+            self.ui.write(_("files:       %s\n") % " ".join(changes[3]))
+        if copies and self.ui.verbose:
+            copies = ['%s (%s)' % c for c in copies]
+            self.ui.write(_("copies:      %s\n") % ' '.join(copies))
+
+        if extra and self.ui.debugflag:
+            extraitems = extra.items()
+            extraitems.sort()
+            for key, value in extraitems:
+                self.ui.write(_("extra:       %s=%s\n")
+                              % (key, value.encode('string_escape')))
+
+        description = changes[4].strip()
+        if description:
+            if self.ui.verbose:
+                self.ui.write(_("description:\n"))
+                self.ui.write(description)
+                self.ui.write("\n\n")
+            else:
+                self.ui.write(_("summary:     %s\n") %
+                              description.splitlines()[0])
+        self.ui.write("\n")
+
+class changeset_templater(object):
+    '''format changeset information.'''
+
+    def __init__(self, ui, repo, mapfile, dest=None):
+        self.t = templater.templater(mapfile, templater.common_filters,
+                                     cache={'parent': '{rev}:{node|short} ',
+                                            'manifest': '{rev}:{node|short}',
+                                            'filecopy': '{name} ({source})'})
+        self.ui = ui
+        self.dest = dest
+        self.repo = repo
+
+    def use_template(self, t):
+        '''set template string to use'''
+        self.t.cache['changeset'] = t
+
+    def show(self, rev=0, changenode=None, brinfo=None, copies=[], **props):
+        '''show a single changeset or file revision'''
+        log = self.repo.changelog
+        if changenode is None:
+            changenode = log.node(rev)
+        elif not rev:
+            rev = log.rev(changenode)
+
+        changes = log.read(changenode)
+
+        def showlist(name, values, plural=None, **args):
+            '''expand set of values.
+            name is name of key in template map.
+            values is list of strings or dicts.
+            plural is plural of name, if not simply name + 's'.
+
+            expansion works like this, given name 'foo'.
+
+            if values is empty, expand 'no_foos'.
+
+            if 'foo' not in template map, return values as a string,
+            joined by space.
+
+            expand 'start_foos'.
+
+            for each value, expand 'foo'. if 'last_foo' in template
+            map, expand it instead of 'foo' for last key.
+
+            expand 'end_foos'.
+            '''
+            if plural: names = plural
+            else: names = name + 's'
+            if not values:
+                noname = 'no_' + names
+                if noname in self.t:
+                    yield self.t(noname, **args)
+                return
+            if name not in self.t:
+                if isinstance(values[0], str):
+                    yield ' '.join(values)
+                else:
+                    for v in values:
+                        yield dict(v, **args)
+                return
+            startname = 'start_' + names
+            if startname in self.t:
+                yield self.t(startname, **args)
+            vargs = args.copy()
+            def one(v, tag=name):
+                try:
+                    vargs.update(v)
+                except (AttributeError, ValueError):
+                    try:
+                        for a, b in v:
+                            vargs[a] = b
+                    except ValueError:
+                        vargs[name] = v
+                return self.t(tag, **vargs)
+            lastname = 'last_' + name
+            if lastname in self.t:
+                last = values.pop()
+            else:
+                last = None
+            for v in values:
+                yield one(v)
+            if last is not None:
+                yield one(last, tag=lastname)
+            endname = 'end_' + names
+            if endname in self.t:
+                yield self.t(endname, **args)
+
+        def showbranches(**args):
+            branch = changes[5].get("branch")
+            if branch:
+                yield showlist('branch', [branch], plural='branches', **args)
+            # add old style branches if requested
+            if brinfo and changenode in brinfo:
+                yield showlist('branch', brinfo[changenode],
+                               plural='branches', **args)
+
+        def showparents(**args):
+            parents = [[('rev', log.rev(p)), ('node', hex(p))]
+                       for p in log.parents(changenode)
+                       if self.ui.debugflag or p != nullid]
+            if (not self.ui.debugflag and len(parents) == 1 and
+                parents[0][0][1] == rev - 1):
+                return
+            return showlist('parent', parents, **args)
+
+        def showtags(**args):
+            return showlist('tag', self.repo.nodetags(changenode), **args)
+
+        def showextras(**args):
+            extras = changes[5].items()
+            extras.sort()
+            for key, value in extras:
+                args = args.copy()
+                args.update(dict(key=key, value=value))
+                yield self.t('extra', **args)
+
+        def showcopies(**args):
+            c = [{'name': x[0], 'source': x[1]} for x in copies]
+            return showlist('file_copy', c, plural='file_copies', **args)
+
+        if self.ui.debugflag:
+            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
+            def showfiles(**args):
+                return showlist('file', files[0], **args)
+            def showadds(**args):
+                return showlist('file_add', files[1], **args)
+            def showdels(**args):
+                return showlist('file_del', files[2], **args)
+            def showmanifest(**args):
+                args = args.copy()
+                args.update(dict(rev=self.repo.manifest.rev(changes[0]),
+                                 node=hex(changes[0])))
+                return self.t('manifest', **args)
+        else:
+            def showfiles(**args):
+                yield showlist('file', changes[3], **args)
+            showadds = ''
+            showdels = ''
+            showmanifest = ''
+
+        defprops = {
+            'author': changes[1],
+            'branches': showbranches,
+            'date': changes[2],
+            'desc': changes[4],
+            'file_adds': showadds,
+            'file_dels': showdels,
+            'files': showfiles,
+            'file_copies': showcopies,
+            'manifest': showmanifest,
+            'node': hex(changenode),
+            'parents': showparents,
+            'rev': rev,
+            'tags': showtags,
+            'extras': showextras,
+            }
+        props = props.copy()
+        props.update(defprops)
+
+        try:
+            dest = self.dest or self.ui
+            if self.ui.debugflag and 'header_debug' in self.t:
+                key = 'header_debug'
+            elif self.ui.quiet and 'header_quiet' in self.t:
+                key = 'header_quiet'
+            elif self.ui.verbose and 'header_verbose' in self.t:
+                key = 'header_verbose'
+            elif 'header' in self.t:
+                key = 'header'
+            else:
+                key = ''
+            if key:
+                dest.write_header(templater.stringify(self.t(key, **props)))
+            if self.ui.debugflag and 'changeset_debug' in self.t:
+                key = 'changeset_debug'
+            elif self.ui.quiet and 'changeset_quiet' in self.t:
+                key = 'changeset_quiet'
+            elif self.ui.verbose and 'changeset_verbose' in self.t:
+                key = 'changeset_verbose'
+            else:
+                key = 'changeset'
+            dest.write(templater.stringify(self.t(key, **props)))
+        except KeyError, inst:
+            raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
+                                                           inst.args[0]))
+        except SyntaxError, inst:
+            raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
+
+class stringio(object):
+    '''wrap cStringIO for use by changeset_templater.'''
+    def __init__(self):
+        self.fp = cStringIO.StringIO()
+
+    def write(self, *args):
+        for a in args:
+            self.fp.write(a)
+
+    write_header = write
+
+    def __getattr__(self, key):
+        return getattr(self.fp, key)
+
+def show_changeset(ui, repo, opts):
+    """show one changeset using template or regular display.
+
+    Display format will be the first non-empty hit of:
+    1. option 'template'
+    2. option 'style'
+    3. [ui] setting 'logtemplate'
+    4. [ui] setting 'style'
+    If all of these values are either the unset or the empty string,
+    regular display via changeset_printer() is done.
+    """
+    # options
+    tmpl = opts.get('template')
+    mapfile = None
+    if tmpl:
+        tmpl = templater.parsestring(tmpl, quoted=False)
+    else:
+        mapfile = opts.get('style')
+        # ui settings
+        if not mapfile:
+            tmpl = ui.config('ui', 'logtemplate')
+            if tmpl:
+                tmpl = templater.parsestring(tmpl)
+            else:
+                mapfile = ui.config('ui', 'style')
+
+    if tmpl or mapfile:
+        if mapfile:
+            if not os.path.split(mapfile)[0]:
+                mapname = (templater.templatepath('map-cmdline.' + mapfile)
+                           or templater.templatepath(mapfile))
+                if mapname: mapfile = mapname
+        try:
+            t = changeset_templater(ui, repo, mapfile)
+        except SyntaxError, inst:
+            raise util.Abort(inst.args[0])
+        if tmpl: t.use_template(tmpl)
+        return t
+    return changeset_printer(ui, repo)
+
--- a/mercurial/commands.py	Mon Nov 13 13:26:57 2006 -0600
+++ b/mercurial/commands.py	Mon Nov 13 13:26:57 2006 -0600
@@ -9,7 +9,7 @@
 from node import *
 from i18n import gettext as _
 demandload(globals(), "os re sys signal imp urllib pdb shlex")
-demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
+demandload(globals(), "fancyopts ui hg util lock revlog bundlerepo")
 demandload(globals(), "difflib patch tempfile time")
 demandload(globals(), "traceback errno version atexit bz2")
 demandload(globals(), "archival changegroup cmdutil hgweb.server sshserver")
@@ -295,130 +295,6 @@
         if cleanup is not None:
             os.unlink(cleanup)
 
-class changeset_printer(object):
-    '''show changeset information when templating not requested.'''
-
-    def __init__(self, ui, repo):
-        self.ui = ui
-        self.repo = repo
-
-    def show(self, rev=0, changenode=None, brinfo=None, copies=None):
-        '''show a single changeset or file revision'''
-        log = self.repo.changelog
-        if changenode is None:
-            changenode = log.node(rev)
-        elif not rev:
-            rev = log.rev(changenode)
-
-        if self.ui.quiet:
-            self.ui.write("%d:%s\n" % (rev, short(changenode)))
-            return
-
-        changes = log.read(changenode)
-        date = util.datestr(changes[2])
-        extra = changes[5]
-        branch = extra.get("branch")
-
-        hexfunc = self.ui.debugflag and hex or short
-
-        parents = log.parentrevs(rev)
-        if not self.ui.debugflag:
-            if parents[1] == nullrev:
-                if parents[0] >= rev - 1:
-                    parents = []
-                else:
-                    parents = [parents[0]]
-        parents = [(p, hexfunc(log.node(p))) for p in parents]
-
-        self.ui.write(_("changeset:   %d:%s\n") % (rev, hexfunc(changenode)))
-
-        if branch:
-            self.ui.write(_("branch:      %s\n") % branch)
-        for tag in self.repo.nodetags(changenode):
-            self.ui.write(_("tag:         %s\n") % tag)
-        for parent in parents:
-            self.ui.write(_("parent:      %d:%s\n") % parent)
-
-        if brinfo and changenode in brinfo:
-            br = brinfo[changenode]
-            self.ui.write(_("branch:      %s\n") % " ".join(br))
-
-        if self.ui.debugflag:
-            self.ui.write(_("manifest:    %d:%s\n") %
-                          (self.repo.manifest.rev(changes[0]), hex(changes[0])))
-        self.ui.write(_("user:        %s\n") % changes[1])
-        self.ui.write(_("date:        %s\n") % date)
-
-        if self.ui.debugflag:
-            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
-            for key, value in zip([_("files:"), _("files+:"), _("files-:")],
-                                  files):
-                if value:
-                    self.ui.write("%-12s %s\n" % (key, " ".join(value)))
-        elif changes[3] and self.ui.verbose:
-            self.ui.write(_("files:       %s\n") % " ".join(changes[3]))
-        if copies and self.ui.verbose:
-            copies = ['%s (%s)' % c for c in copies]
-            self.ui.write(_("copies:      %s\n") % ' '.join(copies))
-
-        if extra and self.ui.debugflag:
-            extraitems = extra.items()
-            extraitems.sort()
-            for key, value in extraitems:
-                self.ui.write(_("extra:       %s=%s\n")
-                              % (key, value.encode('string_escape')))
-
-        description = changes[4].strip()
-        if description:
-            if self.ui.verbose:
-                self.ui.write(_("description:\n"))
-                self.ui.write(description)
-                self.ui.write("\n\n")
-            else:
-                self.ui.write(_("summary:     %s\n") %
-                              description.splitlines()[0])
-        self.ui.write("\n")
-
-def show_changeset(ui, repo, opts):
-    """show one changeset using template or regular display.
-
-    Display format will be the first non-empty hit of:
-    1. option 'template'
-    2. option 'style'
-    3. [ui] setting 'logtemplate'
-    4. [ui] setting 'style'
-    If all of these values are either the unset or the empty string,
-    regular display via changeset_printer() is done.
-    """
-    # options
-    tmpl = opts.get('template')
-    mapfile = None
-    if tmpl:
-        tmpl = templater.parsestring(tmpl, quoted=False)
-    else:
-        mapfile = opts.get('style')
-        # ui settings
-        if not mapfile:
-            tmpl = ui.config('ui', 'logtemplate')
-            if tmpl:
-                tmpl = templater.parsestring(tmpl)
-            else:
-                mapfile = ui.config('ui', 'style')
-
-    if tmpl or mapfile:
-        if mapfile:
-            if not os.path.split(mapfile)[0]:
-                mapname = (templater.templatepath('map-cmdline.' + mapfile)
-                           or templater.templatepath(mapfile))
-                if mapname: mapfile = mapname
-        try:
-            t = templater.changeset_templater(ui, repo, mapfile)
-        except SyntaxError, inst:
-            raise util.Abort(inst.args[0])
-        if tmpl: t.use_template(tmpl)
-        return t
-    return changeset_printer(ui, repo)
-
 def setremoteconfig(ui, opts):
     "copy remote options to ui tree"
     if opts.get('ssh'):
@@ -1563,7 +1439,7 @@
         ui.warn(_("the --branches option is deprecated, "
                   "please use 'hg branches' instead\n"))
         br = repo.branchlookup(heads)
-    displayer = show_changeset(ui, repo, opts)
+    displayer = cmdutil.show_changeset(ui, repo, opts)
     for n in heads:
         displayer.show(changenode=n, brinfo=br)
 
@@ -1710,7 +1586,7 @@
         o = other.changelog.nodesbetween(incoming, revs)[0]
         if opts['newest_first']:
             o.reverse()
-        displayer = show_changeset(ui, other, opts)
+        displayer = cmdutil.show_changeset(ui, other, opts)
         for n in o:
             parents = [p for p in other.changelog.parents(n) if p != nullid]
             if opts['no_merges'] and len(parents) == 2:
@@ -1879,7 +1755,7 @@
             return ncache[fn].get(dcache[1][fn])
         return None
 
-    displayer = show_changeset(ui, repo, opts)
+    displayer = cmdutil.show_changeset(ui, repo, opts)
     for st, rev, fns in changeiter:
         if st == 'window':
             du = dui(ui)
@@ -2015,7 +1891,7 @@
     o = repo.changelog.nodesbetween(o, revs)[0]
     if opts['newest_first']:
         o.reverse()
-    displayer = show_changeset(ui, repo, opts)
+    displayer = cmdutil.show_changeset(ui, repo, opts)
     for n in o:
         parents = [p for p in repo.changelog.parents(n) if p != nullid]
         if opts['no_merges'] and len(parents) == 2:
@@ -2056,7 +1932,7 @@
         ui.warn(_("the --branches option is deprecated, "
                   "please use 'hg branches' instead\n"))
         br = repo.branchlookup(p)
-    displayer = show_changeset(ui, repo, opts)
+    displayer = cmdutil.show_changeset(ui, repo, opts)
     for n in p:
         if n != nullid:
             displayer.show(changenode=n, brinfo=br)
@@ -2683,7 +2559,7 @@
         ui.warn(_("the --branches option is deprecated, "
                   "please use 'hg branches' instead\n"))
         br = repo.branchlookup([n])
-    show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
+    cmdutil.show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
     if opts['patch']:
         patch.diff(repo, repo.changelog.parents(n)[0], n)
 
@@ -2752,7 +2628,8 @@
         if len(found) > 1:
             repo.ui.warn(_("Found multiple heads for %s\n") % branch)
             for x in found:
-                show_changeset(ui, repo, {}).show(changenode=x, brinfo=br)
+                cmdutil.show_changeset(ui, repo, {}).show(
+                    changenode=x, brinfo=br)
             raise util.Abort("")
         if len(found) == 1:
             node = found[0]
--- a/mercurial/templater.py	Mon Nov 13 13:26:57 2006 -0600
+++ b/mercurial/templater.py	Mon Nov 13 13:26:57 2006 -0600
@@ -8,7 +8,7 @@
 from demandload import demandload
 from i18n import gettext as _
 from node import *
-demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
+demandload(globals(), "cgi re sys os time urllib util textwrap")
 
 def parsestring(s, quoted=True):
     '''parse a string using simple c-like syntax.
@@ -290,204 +290,3 @@
         if (name and os.path.exists(p)) or os.path.isdir(p):
             return os.path.normpath(p)
 
-class changeset_templater(object):
-    '''format changeset information.'''
-
-    def __init__(self, ui, repo, mapfile, dest=None):
-        self.t = templater(mapfile, common_filters,
-                           cache={'parent': '{rev}:{node|short} ',
-                                  'manifest': '{rev}:{node|short}',
-                                  'filecopy': '{name} ({source})'})
-        self.ui = ui
-        self.dest = dest
-        self.repo = repo
-
-    def use_template(self, t):
-        '''set template string to use'''
-        self.t.cache['changeset'] = t
-
-    def show(self, rev=0, changenode=None, brinfo=None, copies=[], **props):
-        '''show a single changeset or file revision'''
-        log = self.repo.changelog
-        if changenode is None:
-            changenode = log.node(rev)
-        elif not rev:
-            rev = log.rev(changenode)
-
-        changes = log.read(changenode)
-
-        def showlist(name, values, plural=None, **args):
-            '''expand set of values.
-            name is name of key in template map.
-            values is list of strings or dicts.
-            plural is plural of name, if not simply name + 's'.
-
-            expansion works like this, given name 'foo'.
-
-            if values is empty, expand 'no_foos'.
-
-            if 'foo' not in template map, return values as a string,
-            joined by space.
-
-            expand 'start_foos'.
-
-            for each value, expand 'foo'. if 'last_foo' in template
-            map, expand it instead of 'foo' for last key.
-
-            expand 'end_foos'.
-            '''
-            if plural: names = plural
-            else: names = name + 's'
-            if not values:
-                noname = 'no_' + names
-                if noname in self.t:
-                    yield self.t(noname, **args)
-                return
-            if name not in self.t:
-                if isinstance(values[0], str):
-                    yield ' '.join(values)
-                else:
-                    for v in values:
-                        yield dict(v, **args)
-                return
-            startname = 'start_' + names
-            if startname in self.t:
-                yield self.t(startname, **args)
-            vargs = args.copy()
-            def one(v, tag=name):
-                try:
-                    vargs.update(v)
-                except (AttributeError, ValueError):
-                    try:
-                        for a, b in v:
-                            vargs[a] = b
-                    except ValueError:
-                        vargs[name] = v
-                return self.t(tag, **vargs)
-            lastname = 'last_' + name
-            if lastname in self.t:
-                last = values.pop()
-            else:
-                last = None
-            for v in values:
-                yield one(v)
-            if last is not None:
-                yield one(last, tag=lastname)
-            endname = 'end_' + names
-            if endname in self.t:
-                yield self.t(endname, **args)
-
-        def showbranches(**args):
-            branch = changes[5].get("branch")
-            if branch:
-                yield showlist('branch', [branch], plural='branches', **args)
-            # add old style branches if requested
-            if brinfo and changenode in brinfo:
-                yield showlist('branch', brinfo[changenode],
-                               plural='branches', **args)
-
-        def showparents(**args):
-            parents = [[('rev', log.rev(p)), ('node', hex(p))]
-                       for p in log.parents(changenode)
-                       if self.ui.debugflag or p != nullid]
-            if (not self.ui.debugflag and len(parents) == 1 and
-                parents[0][0][1] == rev - 1):
-                return
-            return showlist('parent', parents, **args)
-
-        def showtags(**args):
-            return showlist('tag', self.repo.nodetags(changenode), **args)
-
-        def showextras(**args):
-            extras = changes[5].items()
-            extras.sort()
-            for key, value in extras:
-                args = args.copy()
-                args.update(dict(key=key, value=value))
-                yield self.t('extra', **args)
-
-        def showcopies(**args):
-            c = [{'name': x[0], 'source': x[1]} for x in copies]
-            return showlist('file_copy', c, plural='file_copies', **args)
-
-        if self.ui.debugflag:
-            files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
-            def showfiles(**args):
-                return showlist('file', files[0], **args)
-            def showadds(**args):
-                return showlist('file_add', files[1], **args)
-            def showdels(**args):
-                return showlist('file_del', files[2], **args)
-            def showmanifest(**args):
-                args = args.copy()
-                args.update(dict(rev=self.repo.manifest.rev(changes[0]),
-                                 node=hex(changes[0])))
-                return self.t('manifest', **args)
-        else:
-            def showfiles(**args):
-                yield showlist('file', changes[3], **args)
-            showadds = ''
-            showdels = ''
-            showmanifest = ''
-
-        defprops = {
-            'author': changes[1],
-            'branches': showbranches,
-            'date': changes[2],
-            'desc': changes[4],
-            'file_adds': showadds,
-            'file_dels': showdels,
-            'files': showfiles,
-            'file_copies': showcopies,
-            'manifest': showmanifest,
-            'node': hex(changenode),
-            'parents': showparents,
-            'rev': rev,
-            'tags': showtags,
-            'extras': showextras,
-            }
-        props = props.copy()
-        props.update(defprops)
-
-        try:
-            dest = self.dest or self.ui
-            if self.ui.debugflag and 'header_debug' in self.t:
-                key = 'header_debug'
-            elif self.ui.quiet and 'header_quiet' in self.t:
-                key = 'header_quiet'
-            elif self.ui.verbose and 'header_verbose' in self.t:
-                key = 'header_verbose'
-            elif 'header' in self.t:
-                key = 'header'
-            else:
-                key = ''
-            if key:
-                dest.write_header(stringify(self.t(key, **props)))
-            if self.ui.debugflag and 'changeset_debug' in self.t:
-                key = 'changeset_debug'
-            elif self.ui.quiet and 'changeset_quiet' in self.t:
-                key = 'changeset_quiet'
-            elif self.ui.verbose and 'changeset_verbose' in self.t:
-                key = 'changeset_verbose'
-            else:
-                key = 'changeset'
-            dest.write(stringify(self.t(key, **props)))
-        except KeyError, inst:
-            raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
-                                                           inst.args[0]))
-        except SyntaxError, inst:
-            raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
-
-class stringio(object):
-    '''wrap cStringIO for use by changeset_templater.'''
-    def __init__(self):
-        self.fp = cStringIO.StringIO()
-
-    def write(self, *args):
-        for a in args:
-            self.fp.write(a)
-
-    write_header = write
-
-    def __getattr__(self, key):
-        return getattr(self.fp, key)