Mercurial > hg
changeset 31478:9335dc6b2a9c
pager: avoid shell=True on subprocess.Popen for better errors (issue5491)
man(1) behaves as poorly as Mercurial without this change. This cribs
from git's run-command[0], which has a list of characters that imply a
string that needs to be run using 'sh -c'. If none of those characters
are present in the command string, we can use shell=False mode on
subprocess and get significantly better error messages (see the test)
when the pager process is invalid. With a complicated pager command
(that contains one of the unsafe characters), we behave as we do today
(which is no worse than git manages.)
I briefly tried tapdancing in a thread to catch early pager exits, but
it's just too perilous: you get races between fd duping operations and
a bad pager exiting, and it's too hard to differentiate between a
slow-bad-pager result and a fast-human-quit-pager-early result.
I've observed some weird variation in exit code handling in the "bad
experience" case in test-pager.t: on my Mac hg predictably exits
nonzero, but on Linux hg always exits zero in that case. For now,
we'll work around it with || true. :(
0: https://github.com/git/git/blob/cddbda4bc87b9d2c985b6749b1cf026b15e2d3e7/run-command.c#L201
author | Augie Fackler <augie@google.com> |
---|---|
date | Wed, 15 Mar 2017 20:33:47 -0400 |
parents | 3fb2081ef896 |
children | 96929bd6e58d |
files | mercurial/ui.py tests/test-pager.t |
diffstat | 2 files changed, 43 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/ui.py Fri Mar 17 19:12:22 2017 +0530 +++ b/mercurial/ui.py Wed Mar 15 20:33:47 2017 -0400 @@ -935,9 +935,22 @@ This is separate in part so that extensions (like chg) can override how a pager is invoked. """ - pager = subprocess.Popen(command, shell=True, bufsize=-1, - close_fds=util.closefds, stdin=subprocess.PIPE, - stdout=util.stdout, stderr=util.stderr) + # If the command doesn't contain any of these characters, we + # assume it's a binary and exec it directly. This means for + # simple pager command configurations, we can degrade + # gracefully and tell the user about their broken pager. + shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%") + try: + pager = subprocess.Popen( + command, shell=shell, bufsize=-1, + close_fds=util.closefds, stdin=subprocess.PIPE, + stdout=util.stdout, stderr=util.stderr) + except OSError as e: + if e.errno == errno.ENOENT and not shell: + self.warn(_("missing pager command '%s', skipping pager\n") + % command) + return + raise # back up original file descriptors stdoutfd = os.dup(util.stdout.fileno())
--- a/tests/test-pager.t Fri Mar 17 19:12:22 2017 +0530 +++ b/tests/test-pager.t Wed Mar 15 20:33:47 2017 -0400 @@ -119,6 +119,33 @@ paged! 'summary: modify a 8\n' paged! '\n' +An invalid pager command name is reported sensibly if we don't have to +use shell=True in the subprocess call: + $ hg log --limit 3 --config pager.pager=this-command-better-never-exist + missing pager command 'this-command-better-never-exist', skipping pager + \x1b[0;33mchangeset: 10:46106edeeb38\x1b[0m (esc) + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify a 10 + + \x1b[0;33mchangeset: 9:6dd8ea7dd621\x1b[0m (esc) + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify a 9 + + \x1b[0;33mchangeset: 8:cff05a6312fe\x1b[0m (esc) + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify a 8 + + +A complicated pager command gets worse behavior. Bonus points if you can +improve this. + $ hg log --limit 3 \ + > --config pager.pager='this-command-better-never-exist --seriously' \ + > 2>/dev/null || true + Pager works with shell aliases. $ cat >> $HGRCPATH <<EOF