pager: preserve Hg's exit code (and fix Windows support) (issue3225) stable
authorBrodie Rao <brodie@sf.io>
Fri, 11 May 2012 15:45:37 +0200
branchstable
changeset 16631 369741ef7253
parent 16630 f30226b1a46a
child 16632 0c0c1101e46d
pager: preserve Hg's exit code (and fix Windows support) (issue3225) This changes how the pager extension invokes the pager. Prior to this change, the extension would fork Hg and exec the pager in the parent process. This loses Hg exit code, and it doesn't work on Windows. Now the pager is invoked using the subprocess library, and an atexit handler is registered that makes Hg wait for the pager to exit before it exits itself. Note that if you exit the pager before Hg is done running, you'll get an exit code of 255, which is caused by Python blowing up due to a broken pipe. If you set pager.quiet=True, you'll get the OS-level return code of 141.
hgext/pager.py
--- a/hgext/pager.py	Fri May 11 18:33:45 2012 +0300
+++ b/hgext/pager.py	Fri May 11 15:45:37 2012 +0200
@@ -53,37 +53,27 @@
 normal behavior.
 '''
 
-import sys, os, signal, shlex, errno
+import atexit, sys, os, signal, subprocess
 from mercurial import commands, dispatch, util, extensions
 from mercurial.i18n import _
 
 def _runpager(p):
-    if not util.safehasattr(os, 'fork'):
-        sys.stdout = util.popen(p, 'wb')
-        if util.isatty(sys.stderr):
-            sys.stderr = sys.stdout
-        return
-    fdin, fdout = os.pipe()
-    pid = os.fork()
-    if pid == 0:
-        os.close(fdin)
-        os.dup2(fdout, sys.stdout.fileno())
-        if util.isatty(sys.stderr):
-            os.dup2(fdout, sys.stderr.fileno())
-        os.close(fdout)
-        return
-    os.dup2(fdin, sys.stdin.fileno())
-    os.close(fdin)
-    os.close(fdout)
-    try:
-        os.execvp('/bin/sh', ['/bin/sh', '-c', p])
-    except OSError, e:
-        if e.errno == errno.ENOENT:
-            # no /bin/sh, try executing the pager directly
-            args = shlex.split(p)
-            os.execvp(args[0], args)
-        else:
-            raise
+    pager = subprocess.Popen(p, shell=True, bufsize=-1,
+                             close_fds=util.closefds, stdin=subprocess.PIPE,
+                             stdout=sys.stdout, stderr=sys.stderr)
+
+    stdout = os.dup(sys.stdout.fileno())
+    stderr = os.dup(sys.stderr.fileno())
+    os.dup2(pager.stdin.fileno(), sys.stdout.fileno())
+    if util.isatty(sys.stderr):
+        os.dup2(pager.stdin.fileno(), sys.stderr.fileno())
+
+    @atexit.register
+    def killpager():
+        pager.stdin.close()
+        os.dup2(stdout, sys.stdout.fileno())
+        os.dup2(stderr, sys.stderr.fileno())
+        pager.wait()
 
 def uisetup(ui):
     if ui.plain() or '--debugger' in sys.argv or not util.isatty(sys.stdout):