diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -13,6 +13,7 @@ import match as matchmod 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 @@ class changeset_printer(object): 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 @@ def show_changeset(ui, repo, opts, buffe 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: diff --git a/tests/test-command-template.t b/tests/test-command-template.t --- a/tests/test-command-template.t +++ b/tests/test-command-template.t @@ -468,6 +468,350 @@ Test xml styles: +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 ", + "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 ", + "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 ", + "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 ", + "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 ", + "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 ", + "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