mercurial/commands.py
changeset 1907 7718885070b1
parent 1800 414e81ae971f
child 1908 be71c04d62c0
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -9,7 +9,7 @@ from demandload import demandload
 from node import *
 from i18n import gettext as _
 demandload(globals(), "os re sys signal shutil imp urllib pdb")
-demandload(globals(), "fancyopts ui hg util lock revlog")
+demandload(globals(), "fancyopts ui hg util lock revlog templater")
 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
 demandload(globals(), "errno socket version struct atexit sets bz2")
 
@@ -337,63 +337,219 @@ def trimuser(ui, name, rev, revcache):
         user = revcache[rev] = ui.shortuser(name)
     return user
 
-def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
-    """show a single changeset or file revision"""
-    log = repo.changelog
-    if changenode is None:
-        changenode = log.node(rev)
-    elif not rev:
-        rev = log.rev(changenode)
-
-    if ui.quiet:
-        ui.write("%d:%s\n" % (rev, short(changenode)))
-        return
-
-    changes = log.read(changenode)
-    date = util.datestr(changes[2])
-
-    parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
-               for p in log.parents(changenode)
-               if ui.debugflag or p != nullid]
-    if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
-        parents = []
-
-    if ui.verbose:
-        ui.write(_("changeset:   %d:%s\n") % (rev, hex(changenode)))
+class changeset_templater(object):
+    def __init__(self, ui, repo, mapfile):
+        self.t = templater.templater(mapfile, templater.common_filters)
+        self.ui = ui
+        self.repo = repo
+
+    def use_template(self, t):
+        self.t.cache['template'] = t
+
+    def write(self, thing):
+        for t in thing:
+            if hasattr(t, '__iter__'):
+                self.write(t)
+            else:
+                self.ui.write(t)
+
+    def show(self, rev=0, changenode=None, brinfo=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)
+
+        changes = log.read(changenode)
+
+        def showlist(name, values, plural=None, **args):
+            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
+            vargs = args.copy()
+            if name not in self.t:
+                yield ' '.join(values)
+                return
+            startname = 'start_' + names
+            if startname in self.t:
+                yield self.t(startname, **args)
+            def one(v):
+                try:
+                    vargs.update(v)
+                except ValueError:
+                    vargs.update([(name, v)])
+                return self.t(name, **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:
+                name = lastname
+                yield one(last)
+            endname = 'end_' + names
+            if endname in self.t:
+                yield self.t(endname, **args)
+
+        if brinfo:
+            def showbranches(**args):
+                if changenode in brinfo:
+                    for x in showlist('branch', brinfo[changenode],
+                                      plural='branches', **args):
+                        yield x
+        else:
+            showbranches = ''
+
+        def showmanifest(**args):
+            args = args.copy()
+            args.update(rev=self.repo.manifest.rev(changes[0]),
+                        node=hex(changes[0]))
+            yield self.t('manifest', **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
+            for x in showlist('parent', parents, **args):
+                yield x
+
+        def showtags(**args):
+            for x in showlist('tag', self.repo.nodetags(changenode), **args):
+                yield x
+
+        if self.ui.debugflag:
+            files = self.repo.changes(log.parents(changenode)[0], changenode)
+            def showfiles(**args):
+                for x in showlist('file', files[0], **args): yield x
+            def showadds(**args):
+                for x in showlist('file_add', files[1], **args): yield x
+            def showdels(**args):
+                for x in showlist('file_del', files[2], **args): yield x
+        else:
+            def showfiles(**args):
+                for x in showlist('file', changes[3], **args): yield x
+            showadds = ''
+            showdels = ''
+
+        props = {
+            'author': changes[1],
+            'branches': showbranches,
+            'date': changes[2],
+            'desc': changes[4],
+            'file_adds': showadds,
+            'file_dels': showdels,
+            'files': showfiles,
+            'manifest': showmanifest,
+            'node': hex(changenode),
+            'parents': showparents,
+            'rev': rev,
+            'tags': showtags,
+            }
+
+        try:
+            self.write(self.t('template', **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 changeset_printer(object):
+    def __init__(self, ui, repo):
+        self.ui = ui
+        self.repo = repo
+
+    def show(self, rev=0, changenode=None, brinfo=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])
+
+        parents = [(log.rev(p), self.ui.verbose and hex(p) or short(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] == rev-1):
+            parents = []
+
+        if self.ui.verbose:
+            self.ui.write(_("changeset:   %d:%s\n") % (rev, hex(changenode)))
+        else:
+            self.ui.write(_("changeset:   %d:%s\n") % (rev, short(changenode)))
+
+        for tag in self.repo.nodetags(changenode):
+            self.ui.status(_("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))
+
+        self.ui.debug(_("manifest:    %d:%s\n") %
+                      (self.repo.manifest.rev(changes[0]), hex(changes[0])))
+        self.ui.status(_("user:        %s\n") % changes[1])
+        self.ui.status(_("date:        %s\n") % date)
+
+        if self.ui.debugflag:
+            files = self.repo.changes(log.parents(changenode)[0], changenode)
+            for key, value in zip([_("files:"), _("files+:"), _("files-:")],
+                                  files):
+                if value:
+                    self.ui.note("%-12s %s\n" % (key, " ".join(value)))
+        else:
+            self.ui.note(_("files:       %s\n") % " ".join(changes[3]))
+
+        description = changes[4].strip()
+        if description:
+            if self.ui.verbose:
+                self.ui.status(_("description:\n"))
+                self.ui.status(description)
+                self.ui.status("\n\n")
+            else:
+                self.ui.status(_("summary:     %s\n") %
+                               description.splitlines()[0])
+        self.ui.status("\n")
+
+def show_changeset(ui, repo, opts):
+    tmpl = opts.get('template')
+    if tmpl:
+        tmpl = templater.parsestring(tmpl, quoted=False)
     else:
-        ui.write(_("changeset:   %d:%s\n") % (rev, short(changenode)))
-
-    for tag in repo.nodetags(changenode):
-        ui.status(_("tag:         %s\n") % tag)
-    for parent in parents:
-        ui.write(_("parent:      %d:%s\n") % parent)
-
-    if brinfo and changenode in brinfo:
-        br = brinfo[changenode]
-        ui.write(_("branch:      %s\n") % " ".join(br))
-
-    ui.debug(_("manifest:    %d:%s\n") % (repo.manifest.rev(changes[0]),
-                                      hex(changes[0])))
-    ui.status(_("user:        %s\n") % changes[1])
-    ui.status(_("date:        %s\n") % date)
-
-    if ui.debugflag:
-        files = repo.changes(log.parents(changenode)[0], changenode)
-        for key, value in zip([_("files:"), _("files+:"), _("files-:")], files):
-            if value:
-                ui.note("%-12s %s\n" % (key, " ".join(value)))
-    else:
-        ui.note(_("files:       %s\n") % " ".join(changes[3]))
-
-    description = changes[4].strip()
-    if description:
-        if ui.verbose:
-            ui.status(_("description:\n"))
-            ui.status(description)
-            ui.status("\n\n")
-        else:
-            ui.status(_("summary:     %s\n") % description.splitlines()[0])
-    ui.status("\n")
+        tmpl = ui.config('ui', 'logtemplate')
+        if tmpl: tmpl = templater.parsestring(tmpl)
+    mapfile = opts.get('map_file') or ui.config('ui', 'logmap')
+    if tmpl or mapfile:
+        if mapfile:
+            if not os.path.isfile(mapfile):
+                mapname = 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)
 
 def show_version(ui):
     """output version and copyright information"""
@@ -1409,8 +1565,9 @@ def heads(ui, repo, **opts):
     br = None
     if opts['branches']:
         br = repo.branchlookup(heads)
+    displayer = show_changeset(ui, repo, opts)
     for n in heads:
-        show_changeset(ui, repo, changenode=n, brinfo=br)
+        displayer.show(changenode=n, brinfo=br)
 
 def identify(ui, repo):
     """print information about the working copy
@@ -1537,11 +1694,12 @@ def incoming(ui, repo, source="default",
     o = other.changelog.nodesbetween(o)[0]
     if opts['newest_first']:
         o.reverse()
+    displayer = 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:
             continue
-        show_changeset(ui, other, changenode=n)
+        displayer.show(changenode=n)
         if opts['patch']:
             prev = (parents and parents[0]) or nullid
             dodiff(ui, ui, other, prev, n)
@@ -1638,9 +1796,11 @@ def log(ui, repo, *pats, **opts):
         limit = sys.maxint
     count = 0
 
+    displayer = show_changeset(ui, repo, opts)
     for st, rev, fns in changeiter:
         if st == 'window':
             du = dui(ui)
+            displayer.ui = du
         elif st == 'add':
             du.bump(rev)
             changenode = repo.changelog.node(rev)
@@ -1667,7 +1827,7 @@ def log(ui, repo, *pats, **opts):
             if opts['branches']:
                 br = repo.branchlookup([repo.changelog.node(rev)])
 
-            show_changeset(du, repo, rev, brinfo=br)
+            displayer.show(rev, brinfo=br)
             if opts['patch']:
                 prev = (parents and parents[0]) or nullid
                 dodiff(du, du, repo, prev, changenode, match=matchfn)
@@ -1718,17 +1878,18 @@ def outgoing(ui, repo, dest="default-pus
     o = repo.changelog.nodesbetween(o)[0]
     if opts['newest_first']:
         o.reverse()
+    displayer = 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:
             continue
-        show_changeset(ui, repo, changenode=n)
+        displayer.show(changenode=n)
         if opts['patch']:
             prev = (parents and parents[0]) or nullid
             dodiff(ui, ui, repo, prev, n)
             ui.write("\n")
 
-def parents(ui, repo, rev=None, branches=None):
+def parents(ui, repo, rev=None, branches=None, **opts):
     """show the parents of the working dir or revision
 
     Print the working directory's parent revisions.
@@ -1741,9 +1902,10 @@ def parents(ui, repo, rev=None, branches
     br = None
     if branches is not None:
         br = repo.branchlookup(p)
+    displayer = show_changeset(ui, repo, opts)
     for n in p:
         if n != nullid:
-            show_changeset(ui, repo, changenode=n, brinfo=br)
+            displayer.show(changenode=n, brinfo=br)
 
 def paths(ui, search=None):
     """show definition of symbolic path names
@@ -2246,7 +2408,7 @@ def tip(ui, repo, **opts):
     br = None
     if opts['branches']:
         br = repo.branchlookup([n])
-    show_changeset(ui, repo, changenode=n, brinfo=br)
+    show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
     if opts['patch']:
         dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
 
@@ -2291,7 +2453,7 @@ def undo(ui, repo):
     repo.undo()
 
 def update(ui, repo, node=None, merge=False, clean=False, force=None,
-           branch=None):
+           branch=None, **opts):
     """update or merge working directory
 
     Update the working directory to the specified revision.
@@ -2318,7 +2480,7 @@ def update(ui, repo, node=None, merge=Fa
         if len(found) > 1:
             ui.warn(_("Found multiple heads for %s\n") % branch)
             for x in found:
-                show_changeset(ui, repo, changenode=x, brinfo=br)
+                show_changeset(ui, repo, opts).show(changenode=x, brinfo=br)
             return 1
         if len(found) == 1:
             node = found[0]
@@ -2462,7 +2624,9 @@ table = {
     "heads":
         (heads,
          [('b', 'branches', None, _('show branches')),
-          ('r', 'rev', '', _('show only heads which are descendants of rev'))],
+          ('', 'map-file', '', _('display using template map file')),
+          ('r', 'rev', '', _('show only heads which are descendants of rev')),
+          ('t', 'template', '', _('display with template'))],
          _('hg heads [-b] [-r <rev>]')),
     "help": (help_, [], _('hg help [COMMAND]')),
     "identify|id": (identify, [], _('hg identify')),
@@ -2477,8 +2641,10 @@ table = {
          _('hg import [-f] [-p NUM] [-b BASE] PATCH...')),
     "incoming|in": (incoming,
          [('M', 'no-merges', None, _('do not show merges')),
+          ('', 'map-file', '', _('display using template map file')),
+          ('n', 'newest-first', None, _('show newest record first')),
           ('p', 'patch', None, _('show patch')),
-          ('n', 'newest-first', None, _('show newest record first'))],
+          ('t', 'template', '', _('display with template'))],
          _('hg incoming [-p] [-n] [-M] [SOURCE]')),
     "^init": (init, [], _('hg init [DEST]')),
     "locate":
@@ -2500,18 +2666,24 @@ table = {
           ('l', 'limit', '', _('limit number of changes displayed')),
           ('r', 'rev', [], _('show the specified revision or range')),
           ('M', 'no-merges', None, _('do not show merges')),
+          ('', 'map-file', '', _('display using template map file')),
           ('m', 'only-merges', None, _('show only merges')),
-          ('p', 'patch', None, _('show patch'))],
+          ('p', 'patch', None, _('show patch')),
+          ('t', 'template', '', _('display with template'))],
          _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
     "manifest": (manifest, [], _('hg manifest [REV]')),
     "outgoing|out": (outgoing,
          [('M', 'no-merges', None, _('do not show merges')),
           ('p', 'patch', None, _('show patch')),
-          ('n', 'newest-first', None, _('show newest record first'))],
+          ('', 'map-file', '', _('display using template map file')),
+          ('n', 'newest-first', None, _('show newest record first')),
+          ('t', 'template', '', _('display with template'))],
          _('hg outgoing [-p] [-n] [-M] [DEST]')),
     "^parents":
         (parents,
-         [('b', 'branches', None, _('show branches'))],
+         [('b', 'branches', None, _('show branches')),
+          ('', 'map-file', '', _('display using template map file')),
+          ('t', 'template', '', _('display with template'))],
          _('hg parents [-b] [REV]')),
     "paths": (paths, [], _('hg paths [NAME]')),
     "^pull":
@@ -2602,7 +2774,9 @@ table = {
     "tip":
         (tip,
          [('b', 'branches', None, _('show branches')),
-          ('p', 'patch', None, _('show patch'))],
+          ('', 'map-file', '', _('display using template map file')),
+          ('p', 'patch', None, _('show patch')),
+          ('t', 'template', '', _('display with template'))],
          _('hg [-b] [-p] tip')),
     "unbundle":
         (unbundle,
@@ -2613,9 +2787,11 @@ table = {
     "^update|up|checkout|co":
         (update,
          [('b', 'branch', '', _('checkout the head of a specific branch')),
+          ('', 'map-file', '', _('display using template map file')),
           ('m', 'merge', None, _('allow merging of branches')),
           ('C', 'clean', None, _('overwrite locally modified files')),
-          ('f', 'force', None, _('force a merge with outstanding changes'))],
+          ('f', 'force', None, _('force a merge with outstanding changes')),
+          ('t', 'template', '', _('display with template'))],
          _('hg update [-b TAG] [-m] [-C] [-f] [REV]')),
     "verify": (verify, [], _('hg verify')),
     "version": (show_version, [], _('hg version')),