Submitter | Yuya Nishihara |
---|---|
Date | May 30, 2017, 4:07 p.m. |
Message ID | <a20184dbfeadfa5c8da9.1496160434@mimosa> |
Download | mbox | patch |
Permalink | /patch/21073/ |
State | Accepted |
Headers | show |
Comments
On Wed, May 31, 2017 at 01:07:14AM +0900, Yuya Nishihara wrote: > # HG changeset patch > # User Yuya Nishihara <yuya@tcha.org> > # Date 1495716824 -32400 > # Thu May 25 21:53:44 2017 +0900 > # Node ID a20184dbfeadfa5c8da9a67fecafd972d7e2202d > # Parent 88dd1416922eaf34fd90e2e76a3d941f9a950ba3 > cat: add formatter support Queued these, thanks. Should the file contents be armored in json ouptut? IIRC json strings are explicitly unicode, so if we can't unicode-ify the data we're someone hosed, aren't we? (Feel encouraged to do that as a followup - I'd rather we keep moving on formatter-all-the-things.) > > This is an example showing how formatter can handle the --output option. > git subrepo isn't supported for now. > > diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py > --- a/mercurial/cmdutil.py > +++ b/mercurial/cmdutil.py > @@ -2632,21 +2632,21 @@ def remove(ui, repo, m, prefix, after, f > > return ret > > -def cat(ui, repo, ctx, matcher, fntemplate, prefix, **opts): > +def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts): > err = 1 > > def write(path): > + filename = None > if fntemplate: > filename = makefilename(repo, fntemplate, ctx.node(), > pathname=os.path.join(prefix, path)) > - fp = open(filename, 'wb') > - else: > - fp = _unclosablefile(ui.fout) > - with fp: > + with formatter.maybereopen(basefm, filename, opts) as fm: > data = ctx[path].data() > if opts.get('decode'): > data = repo.wwritedata(path, data) > - fp.write(data) > + fm.startitem() > + fm.write('data', '%s', data) > + fm.data(abspath=path, path=matcher.rel(path)) > > # Automation often uses hg cat on single files, so special case it > # for performance to avoid the cost of parsing the manifest. > @@ -2670,7 +2670,7 @@ def cat(ui, repo, ctx, matcher, fntempla > try: > submatch = matchmod.subdirmatcher(subpath, matcher) > > - if not sub.cat(submatch, fntemplate, > + if not sub.cat(submatch, basefm, fntemplate, > os.path.join(prefix, sub._path), **opts): > err = 0 > except error.RepoLookupError: > diff --git a/mercurial/commands.py b/mercurial/commands.py > --- a/mercurial/commands.py > +++ b/mercurial/commands.py > @@ -35,6 +35,7 @@ from . import ( > error, > exchange, > extensions, > + formatter, > graphmod, > hbisect, > help, > @@ -1338,7 +1339,7 @@ def bundle(ui, repo, fname, dest=None, * > _('print output to file with formatted name'), _('FORMAT')), > ('r', 'rev', '', _('print the given revision'), _('REV')), > ('', 'decode', None, _('apply any matching decode filter')), > - ] + walkopts, > + ] + walkopts + formatteropts, > _('[OPTION]... FILE...'), > inferrepo=True) > def cat(ui, repo, file1, *pats, **opts): > @@ -1368,9 +1369,13 @@ def cat(ui, repo, file1, *pats, **opts): > if cmdutil.isstdiofilename(fntemplate): > fntemplate = '' > > - if not fntemplate: > + if fntemplate: > + fm = formatter.nullformatter(ui, 'cat') > + else: > ui.pager('cat') > - return cmdutil.cat(ui, repo, ctx, m, fntemplate, '', **opts) > + fm = ui.formatter('cat', opts) > + with fm: > + return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '', **opts) > > @command('^clone', > [('U', 'noupdate', None, _('the clone will include an empty working ' > diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py > --- a/mercurial/subrepo.py > +++ b/mercurial/subrepo.py > @@ -538,7 +538,7 @@ class abstractsubrepo(object): > self.ui.warn("%s: %s" % (prefix, _("addremove is not supported"))) > return 1 > > - def cat(self, match, fntemplate, prefix, **opts): > + def cat(self, match, fm, fntemplate, prefix, **opts): > return 1 > > def status(self, rev2, **opts): > @@ -767,11 +767,11 @@ class hgsubrepo(abstractsubrepo): > dry_run, similarity) > > @annotatesubrepoerror > - def cat(self, match, fntemplate, prefix, **opts): > + def cat(self, match, fm, fntemplate, prefix, **opts): > rev = self._state[1] > ctx = self._repo[rev] > - return cmdutil.cat(self.ui, self._repo, ctx, match, fntemplate, prefix, > - **opts) > + return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate, > + prefix, **opts) > > @annotatesubrepoerror > def status(self, rev2, **opts): > @@ -1833,7 +1833,7 @@ class gitsubrepo(abstractsubrepo): > > > @annotatesubrepoerror > - def cat(self, match, fntemplate, prefix, **opts): > + def cat(self, match, fm, fntemplate, prefix, **opts): > rev = self._state[1] > if match.anypats(): > return 1 #No support for include/exclude yet > @@ -1841,6 +1841,7 @@ class gitsubrepo(abstractsubrepo): > if not match.files(): > return 1 > > + # TODO: add support for non-plain formatter (see cmdutil.cat()) > for f in match.files(): > output = self._gitcommand(["show", "%s:%s" % (rev, f)]) > fp = cmdutil.makefileobj(self._subparent, fntemplate, > diff --git a/tests/test-cat.t b/tests/test-cat.t > --- a/tests/test-cat.t > +++ b/tests/test-cat.t > @@ -63,6 +63,46 @@ Test fileset > tmp/h_45116003780e > tmp/r_2 > > +Test template output > + > + $ hg --cwd tmp cat ../b ../c -T '== {path} ({abspath}) ==\n{data}' > + == ../b (b) == (glob) > + 1 > + == ../c (c) == (glob) > + 3 > + > + $ hg cat b c -Tjson --output - > + [ > + { > + "abspath": "b", > + "data": "1\n", > + "path": "b" > + }, > + { > + "abspath": "c", > + "data": "3\n", > + "path": "c" > + } > + ] > + > + $ hg cat b c -Tjson --output 'tmp/%p.json' > + $ cat tmp/b.json > + [ > + { > + "abspath": "b", > + "data": "1\n", > + "path": "b" > + } > + ] > + $ cat tmp/c.json > + [ > + { > + "abspath": "c", > + "data": "3\n", > + "path": "c" > + } > + ] > + > Test working directory > > $ echo b-wdir > b > diff --git a/tests/test-completion.t b/tests/test-completion.t > --- a/tests/test-completion.t > +++ b/tests/test-completion.t > @@ -241,7 +241,7 @@ Show all commands + options > branch: force, clean > branches: active, closed, template > bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure > - cat: output, rev, decode, include, exclude > + cat: output, rev, decode, include, exclude, template > config: untrusted, edit, local, global, template > copy: after, force, include, exclude, dry-run > debugancestor: > diff --git a/tests/test-hook.t b/tests/test-hook.t > --- a/tests/test-hook.t > +++ b/tests/test-hook.t > @@ -99,9 +99,9 @@ test generic hooks > abort: pre-identify hook exited with status 1 > [255] > $ hg cat b > - pre-cat hook: HG_ARGS=cat b HG_HOOKNAME=pre-cat HG_HOOKTYPE=pre-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] > + pre-cat hook: HG_ARGS=cat b HG_HOOKNAME=pre-cat HG_HOOKTYPE=pre-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''} HG_PATS=['b'] > b > - post-cat hook: HG_ARGS=cat b HG_HOOKNAME=post-cat HG_HOOKTYPE=post-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0 > + post-cat hook: HG_ARGS=cat b HG_HOOKNAME=post-cat HG_HOOKTYPE=post-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''} HG_PATS=['b'] HG_RESULT=0 > > $ cd ../b > $ hg pull ../a > diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t > --- a/tests/test-subrepo.t > +++ b/tests/test-subrepo.t > @@ -1020,6 +1020,14 @@ Prepare a repo with subrepo > $ hg cat sub/repo/foo > test > test > + $ hg cat sub/repo/foo -Tjson > + [ > + { > + "abspath": "foo", > + "data": "test\ntest\n", > + "path": "sub/repo/foo" (glob) > + } > + ] > $ mkdir -p tmp/sub/repo > $ hg cat -r 0 --output tmp/%p_p sub/repo/foo > $ cat tmp/sub/repo/foo_p > _______________________________________________ > Mercurial-devel mailing list > Mercurial-devel@mercurial-scm.org > https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
On Wed, 31 May 2017 14:25:47 -0400, Augie Fackler wrote: > On Wed, May 31, 2017 at 01:07:14AM +0900, Yuya Nishihara wrote: > > # HG changeset patch > > # User Yuya Nishihara <yuya@tcha.org> > > # Date 1495716824 -32400 > > # Thu May 25 21:53:44 2017 +0900 > > # Node ID a20184dbfeadfa5c8da9a67fecafd972d7e2202d > > # Parent 88dd1416922eaf34fd90e2e76a3d941f9a950ba3 > > cat: add formatter support > > Queued these, thanks. > > Should the file contents be armored in json ouptut? IIRC json strings > are explicitly unicode, so if we can't unicode-ify the data we're > someone hosed, aren't we? It's handled by encoding.toutf8b(). The file name is also arbitrary bytes on Unix.
On Thu, Jun 1, 2017 at 10:16 AM, Yuya Nishihara <yuya@tcha.org> wrote: > On Wed, 31 May 2017 14:25:47 -0400, Augie Fackler wrote: >> On Wed, May 31, 2017 at 01:07:14AM +0900, Yuya Nishihara wrote: >> > # HG changeset patch >> > # User Yuya Nishihara <yuya@tcha.org> >> > # Date 1495716824 -32400 >> > # Thu May 25 21:53:44 2017 +0900 >> > # Node ID a20184dbfeadfa5c8da9a67fecafd972d7e2202d >> > # Parent 88dd1416922eaf34fd90e2e76a3d941f9a950ba3 >> > cat: add formatter support >> >> Queued these, thanks. >> >> Should the file contents be armored in json ouptut? IIRC json strings >> are explicitly unicode, so if we can't unicode-ify the data we're >> someone hosed, aren't we? > > It's handled by encoding.toutf8b(). The file name is also arbitrary bytes > on Unix. Excellent, thanks.
Patch
diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -2632,21 +2632,21 @@ def remove(ui, repo, m, prefix, after, f return ret -def cat(ui, repo, ctx, matcher, fntemplate, prefix, **opts): +def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts): err = 1 def write(path): + filename = None if fntemplate: filename = makefilename(repo, fntemplate, ctx.node(), pathname=os.path.join(prefix, path)) - fp = open(filename, 'wb') - else: - fp = _unclosablefile(ui.fout) - with fp: + with formatter.maybereopen(basefm, filename, opts) as fm: data = ctx[path].data() if opts.get('decode'): data = repo.wwritedata(path, data) - fp.write(data) + fm.startitem() + fm.write('data', '%s', data) + fm.data(abspath=path, path=matcher.rel(path)) # Automation often uses hg cat on single files, so special case it # for performance to avoid the cost of parsing the manifest. @@ -2670,7 +2670,7 @@ def cat(ui, repo, ctx, matcher, fntempla try: submatch = matchmod.subdirmatcher(subpath, matcher) - if not sub.cat(submatch, fntemplate, + if not sub.cat(submatch, basefm, fntemplate, os.path.join(prefix, sub._path), **opts): err = 0 except error.RepoLookupError: diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -35,6 +35,7 @@ from . import ( error, exchange, extensions, + formatter, graphmod, hbisect, help, @@ -1338,7 +1339,7 @@ def bundle(ui, repo, fname, dest=None, * _('print output to file with formatted name'), _('FORMAT')), ('r', 'rev', '', _('print the given revision'), _('REV')), ('', 'decode', None, _('apply any matching decode filter')), - ] + walkopts, + ] + walkopts + formatteropts, _('[OPTION]... FILE...'), inferrepo=True) def cat(ui, repo, file1, *pats, **opts): @@ -1368,9 +1369,13 @@ def cat(ui, repo, file1, *pats, **opts): if cmdutil.isstdiofilename(fntemplate): fntemplate = '' - if not fntemplate: + if fntemplate: + fm = formatter.nullformatter(ui, 'cat') + else: ui.pager('cat') - return cmdutil.cat(ui, repo, ctx, m, fntemplate, '', **opts) + fm = ui.formatter('cat', opts) + with fm: + return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '', **opts) @command('^clone', [('U', 'noupdate', None, _('the clone will include an empty working ' diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py --- a/mercurial/subrepo.py +++ b/mercurial/subrepo.py @@ -538,7 +538,7 @@ class abstractsubrepo(object): self.ui.warn("%s: %s" % (prefix, _("addremove is not supported"))) return 1 - def cat(self, match, fntemplate, prefix, **opts): + def cat(self, match, fm, fntemplate, prefix, **opts): return 1 def status(self, rev2, **opts): @@ -767,11 +767,11 @@ class hgsubrepo(abstractsubrepo): dry_run, similarity) @annotatesubrepoerror - def cat(self, match, fntemplate, prefix, **opts): + def cat(self, match, fm, fntemplate, prefix, **opts): rev = self._state[1] ctx = self._repo[rev] - return cmdutil.cat(self.ui, self._repo, ctx, match, fntemplate, prefix, - **opts) + return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate, + prefix, **opts) @annotatesubrepoerror def status(self, rev2, **opts): @@ -1833,7 +1833,7 @@ class gitsubrepo(abstractsubrepo): @annotatesubrepoerror - def cat(self, match, fntemplate, prefix, **opts): + def cat(self, match, fm, fntemplate, prefix, **opts): rev = self._state[1] if match.anypats(): return 1 #No support for include/exclude yet @@ -1841,6 +1841,7 @@ class gitsubrepo(abstractsubrepo): if not match.files(): return 1 + # TODO: add support for non-plain formatter (see cmdutil.cat()) for f in match.files(): output = self._gitcommand(["show", "%s:%s" % (rev, f)]) fp = cmdutil.makefileobj(self._subparent, fntemplate, diff --git a/tests/test-cat.t b/tests/test-cat.t --- a/tests/test-cat.t +++ b/tests/test-cat.t @@ -63,6 +63,46 @@ Test fileset tmp/h_45116003780e tmp/r_2 +Test template output + + $ hg --cwd tmp cat ../b ../c -T '== {path} ({abspath}) ==\n{data}' + == ../b (b) == (glob) + 1 + == ../c (c) == (glob) + 3 + + $ hg cat b c -Tjson --output - + [ + { + "abspath": "b", + "data": "1\n", + "path": "b" + }, + { + "abspath": "c", + "data": "3\n", + "path": "c" + } + ] + + $ hg cat b c -Tjson --output 'tmp/%p.json' + $ cat tmp/b.json + [ + { + "abspath": "b", + "data": "1\n", + "path": "b" + } + ] + $ cat tmp/c.json + [ + { + "abspath": "c", + "data": "3\n", + "path": "c" + } + ] + Test working directory $ echo b-wdir > b diff --git a/tests/test-completion.t b/tests/test-completion.t --- a/tests/test-completion.t +++ b/tests/test-completion.t @@ -241,7 +241,7 @@ Show all commands + options branch: force, clean branches: active, closed, template bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure - cat: output, rev, decode, include, exclude + cat: output, rev, decode, include, exclude, template config: untrusted, edit, local, global, template copy: after, force, include, exclude, dry-run debugancestor: diff --git a/tests/test-hook.t b/tests/test-hook.t --- a/tests/test-hook.t +++ b/tests/test-hook.t @@ -99,9 +99,9 @@ test generic hooks abort: pre-identify hook exited with status 1 [255] $ hg cat b - pre-cat hook: HG_ARGS=cat b HG_HOOKNAME=pre-cat HG_HOOKTYPE=pre-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] + pre-cat hook: HG_ARGS=cat b HG_HOOKNAME=pre-cat HG_HOOKTYPE=pre-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''} HG_PATS=['b'] b - post-cat hook: HG_ARGS=cat b HG_HOOKNAME=post-cat HG_HOOKTYPE=post-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0 + post-cat hook: HG_ARGS=cat b HG_HOOKNAME=post-cat HG_HOOKTYPE=post-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''} HG_PATS=['b'] HG_RESULT=0 $ cd ../b $ hg pull ../a diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t --- a/tests/test-subrepo.t +++ b/tests/test-subrepo.t @@ -1020,6 +1020,14 @@ Prepare a repo with subrepo $ hg cat sub/repo/foo test test + $ hg cat sub/repo/foo -Tjson + [ + { + "abspath": "foo", + "data": "test\ntest\n", + "path": "sub/repo/foo" (glob) + } + ] $ mkdir -p tmp/sub/repo $ hg cat -r 0 --output tmp/%p_p sub/repo/foo $ cat tmp/sub/repo/foo_p