changeset 22427:bd15932846a4

cmdutil: add json style to log-like commands Sadly, this can't be done with the normal templater like we do with XML due to JSON's inter-record comma rules.
author Matt Mackall <mpm@selenic.com>
date Mon, 15 Sep 2014 13:11:13 -0500
parents f6b533e64ed6
children 427e80a18ef8
files mercurial/cmdutil.py tests/test-command-template.t
diffstat 2 files changed, 437 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/cmdutil.py	Mon Sep 15 13:12:49 2014 -0500
+++ b/mercurial/cmdutil.py	Mon Sep 15 13:11:13 2014 -0500
@@ -13,6 +13,7 @@
 import context, repair, graphmod, revset, phases, obsolete, pathutil
 import changelog
 import bookmarks
+import encoding
 import lock as lockmod
 
 def parsealiases(cmd):
@@ -1013,6 +1014,95 @@
                 parents = [parents[0]]
         return parents
 
+class jsonchangeset(changeset_printer):
+    '''format changeset information.'''
+
+    def __init__(self, ui, repo, matchfn, diffopts, buffered):
+        changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
+        self.cache = {}
+        self._first = True
+
+    def close(self):
+        if not self._first:
+            self.ui.write("\n]\n")
+        else:
+            self.ui.write("[]\n")
+
+    def _show(self, ctx, copies, matchfn, props):
+        '''show a single changeset or file revision'''
+        hexnode = hex(ctx.node())
+        rev = ctx.rev()
+        j = encoding.jsonescape
+
+        if self._first:
+            self.ui.write("[\n {")
+            self._first = False
+        else:
+            self.ui.write(",\n {")
+
+        if self.ui.quiet:
+            self.ui.write('\n  "rev": %d' % rev)
+            self.ui.write(',\n  "node": "%s"' % hexnode)
+            self.ui.write('\n }')
+            return
+
+        self.ui.write('\n  "rev": %d' % rev)
+        self.ui.write(',\n  "node": "%s"' % hexnode)
+        self.ui.write(',\n  "branch": "%s"' % j(ctx.branch()))
+        self.ui.write(',\n  "phase": "%s"' % ctx.phasestr())
+        self.ui.write(',\n  "user": "%s"' % j(ctx.user()))
+        self.ui.write(',\n  "date": [%d, %d]' % ctx.date())
+        self.ui.write(',\n  "desc": "%s"' % j(ctx.description()))
+
+        self.ui.write(',\n  "bookmarks": [%s]' %
+                      ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
+        self.ui.write(',\n  "tags": [%s]' %
+                      ", ".join('"%s"' % j(t) for t in ctx.tags()))
+        self.ui.write(',\n  "parents": [%s]' %
+                      ", ".join('"%s"' % c.hex() for c in ctx.parents()))
+
+        if self.ui.debugflag:
+            self.ui.write(',\n  "manifest": "%s"' % hex(ctx.manifestnode()))
+
+            self.ui.write(',\n  "extra": {%s}' %
+                          ", ".join('"%s": "%s"' % (j(k), j(v))
+                                    for k, v in ctx.extra().items()))
+
+            files = ctx.status(ctx.p1())
+            self.ui.write(',\n  "modified": [%s]' %
+                          ", ".join('"%s"' % j(f) for f in files[0]))
+            self.ui.write(',\n  "added": [%s]' %
+                          ", ".join('"%s"' % j(f) for f in files[1]))
+            self.ui.write(',\n  "removed": [%s]' %
+                          ", ".join('"%s"' % j(f) for f in files[2]))
+
+        elif self.ui.verbose:
+            self.ui.write(',\n  "files": [%s]' %
+                          ", ".join('"%s"' % j(f) for f in ctx.files()))
+
+            if copies:
+                self.ui.write(',\n  "copies": {%s}' %
+                              ", ".join('"%s": %s' % (j(k), j(copies[k]))
+                                                      for k in copies))
+
+        matchfn = self.matchfn
+        if matchfn:
+            stat = self.diffopts.get('stat')
+            diff = self.diffopts.get('patch')
+            diffopts = patch.diffopts(self.ui, self.diffopts)
+            node, prev = ctx.node(), ctx.p1().node()
+            if stat:
+                self.ui.pushbuffer()
+                diffordiffstat(self.ui, self.repo, diffopts, prev, node,
+                               match=matchfn, stat=True)
+                self.ui.write(',\n  "diffstat": "%s"' % j(self.ui.popbuffer()))
+            if diff:
+                self.ui.pushbuffer()
+                diffordiffstat(self.ui, self.repo, diffopts, prev, node,
+                               match=matchfn, stat=False)
+                self.ui.write(',\n  "diff": "%s"' % j(self.ui.popbuffer()))
+
+        self.ui.write("\n }")
 
 class changeset_templater(changeset_printer):
     '''format changeset information.'''
@@ -1195,6 +1285,9 @@
     if opts.get('patch') or opts.get('stat'):
         matchfn = scmutil.matchall(repo)
 
+    if opts.get('template') == 'json':
+        return jsonchangeset(ui, repo, matchfn, opts, buffered)
+
     tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
 
     if not tmpl and not mapfile:
--- a/tests/test-command-template.t	Mon Sep 15 13:12:49 2014 -0500
+++ b/tests/test-command-template.t	Mon Sep 15 13:11:13 2014 -0500
@@ -468,6 +468,350 @@
   </log>
 
 
+Test JSON style:
+
+  $ hg log -k nosuch -Tjson
+  []
+
+  $ hg log -qr . -Tjson
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a"
+   }
+  ]
+
+  $ hg log -vpr . -Tjson --stat
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "files": ["fourth", "second", "third"],
+    "diffstat": " fourth |  1 +\n second |  1 -\n third  |  1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
+    "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"
+   }
+  ]
+
+  $ hg log -T json
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"]
+   },
+   {
+    "rev": 7,
+    "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
+    "branch": "default",
+    "phase": "draft",
+    "user": "User Name <user@hostname>",
+    "date": [1000000, 0],
+    "desc": "second",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"]
+   },
+   {
+    "rev": 6,
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"]
+   },
+   {
+    "rev": 5,
+    "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500000, 0],
+    "desc": "new head",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
+   },
+   {
+    "rev": 4,
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "branch": "foo",
+    "phase": "draft",
+    "user": "person",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
+   },
+   {
+    "rev": 3,
+    "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1300000, 0],
+    "desc": "no user, no domain",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["97054abb4ab824450e9164180baf491ae0078465"]
+   },
+   {
+    "rev": 2,
+    "node": "97054abb4ab824450e9164180baf491ae0078465",
+    "branch": "default",
+    "phase": "draft",
+    "user": "other@place",
+    "date": [1200000, 0],
+    "desc": "no person",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"]
+   },
+   {
+    "rev": 1,
+    "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
+    "branch": "default",
+    "phase": "draft",
+    "user": "A. N. Other <other@place>",
+    "date": [1100000, 0],
+    "desc": "other 1\nother 2\n\nother 3",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"]
+   },
+   {
+    "rev": 0,
+    "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
+    "branch": "default",
+    "phase": "draft",
+    "user": "User Name <user@hostname>",
+    "date": [1000000, 0],
+    "desc": "line 1\nline 2",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"]
+   }
+  ]
+
+  $ hg heads -v -Tjson
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "files": ["fourth", "second", "third"]
+   },
+   {
+    "rev": 6,
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
+    "files": []
+   },
+   {
+    "rev": 4,
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "branch": "foo",
+    "phase": "draft",
+    "user": "person",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "files": []
+   }
+  ]
+
+  $ hg log --debug -Tjson
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": ["second"],
+    "removed": ["fourth", "third"]
+   },
+   {
+    "rev": 7,
+    "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
+    "branch": "default",
+    "phase": "draft",
+    "user": "User Name <user@hostname>",
+    "date": [1000000, 0],
+    "desc": "second",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"],
+    "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["second"]
+   },
+   {
+    "rev": 6,
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
+    "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": []
+   },
+   {
+    "rev": 5,
+    "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500000, 0],
+    "desc": "new head",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["d"]
+   },
+   {
+    "rev": 4,
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "branch": "foo",
+    "phase": "draft",
+    "user": "person",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
+    "extra": {"branch": "foo"},
+    "modified": [],
+    "added": [],
+    "removed": []
+   },
+   {
+    "rev": 3,
+    "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1300000, 0],
+    "desc": "no user, no domain",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
+    "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
+    "extra": {"branch": "default"},
+    "modified": ["c"],
+    "added": [],
+    "removed": []
+   },
+   {
+    "rev": 2,
+    "node": "97054abb4ab824450e9164180baf491ae0078465",
+    "branch": "default",
+    "phase": "draft",
+    "user": "other@place",
+    "date": [1200000, 0],
+    "desc": "no person",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
+    "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["c"]
+   },
+   {
+    "rev": 1,
+    "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
+    "branch": "default",
+    "phase": "draft",
+    "user": "A. N. Other <other@place>",
+    "date": [1100000, 0],
+    "desc": "other 1\nother 2\n\nother 3",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
+    "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["b"]
+   },
+   {
+    "rev": 0,
+    "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
+    "branch": "default",
+    "phase": "draft",
+    "user": "User Name <user@hostname>",
+    "date": [1000000, 0],
+    "desc": "line 1\nline 2",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"],
+    "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["a"]
+   }
+  ]
+
 Error if style not readable:
 
 #if unix-permissions no-root