Patchwork D2081: wireprotoserver: add context manager mechanism for redirecting stdio

mail settings
Submitter phabricator
Date Feb. 8, 2018, 12:27 a.m.
Message ID <>
Download mbox | patch
Permalink /patch/27449/
State Superseded
Headers show


phabricator - Feb. 8, 2018, 12:27 a.m.
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

  Today, proto.redirect() sets up redirecting stdio and proto.restore()
  undoes that. The API is a bit wonky because restore() is only
  implemented on the HTTP protocol. Furthermore, not all calls to
  redirect() are obviously paired with calls to restore(). For
  example, the call to restore() for "unbundle" requests is handled
  by the response handler for the HTTP protocol.
  This commit introduces a new method on the protocol handler interface
  to maybe capture stdio. It emits a file object or None depending on
  whether stdio capture is used by the transport.
  To prove it works, the "pushkey" wire protocol command has been
  updated to use the new API.
  I'm not convinced this is the best mechanism to capture stdio. I may
  need to come up with something better once the new wire protocol
  emerges into existence. But it is strictly better than before and
  gets us closer to a unified interface between the SSH and HTTP

  rHG Mercurial




To: indygreg, #hg-reviewers
Cc: mercurial-devel


diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -8,6 +8,7 @@ 
 import abc
 import cgi
+import contextlib
 import struct
 import sys
@@ -74,6 +75,20 @@ 
+    def mayberedirectstdio(self):
+        """Context manager to possibly redirect stdio.
+        The context manager yields a file-object like object that receives
+        stdout and stderr output when the context manager is active. Or it
+        yields ``None`` if no I/O redirection occurs.
+        The intent of this context manager is to capture stdio output
+        so it may be sent in the response. Some transports support streaming
+        stdio to the client in real time. For these transports, stdio output
+        won't be captured.
+        """
+    @abc.abstractmethod
     def redirect(self):
         """may setup interception for stdout and stderr
@@ -151,6 +166,21 @@ 
         for s in util.filechunkiter(self._req, limit=length):
+    @contextlib.contextmanager
+    def mayberedirectstdio(self):
+        oldout = self._ui.fout
+        olderr = self._ui.ferr
+        out = util.stringio()
+        try:
+            self._ui.fout = out
+            self._ui.ferr = out
+            yield out
+        finally:
+            self._ui.fout = oldout
+            self._ui.ferr = olderr
     def redirect(self):
         self._oldio = self._ui.fout, self._ui.ferr
         self._ui.ferr = self._ui.fout = stringio()
@@ -374,6 +404,10 @@ 
             count = int(self._fin.readline())
+    @contextlib.contextmanager
+    def mayberedirectstdio(self):
+        yield None
     def redirect(self):
diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -978,20 +978,12 @@ 
         new = encoding.tolocal(new) # normal path
-    if util.safehasattr(proto, 'restore'):
-        proto.redirect()
+    with proto.mayberedirectstdio() as output:
         r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
                          encoding.tolocal(old), new) or False
-        output = proto.restore()
-        return '%s\n%s' % (int(r), output)
-    r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
-                     encoding.tolocal(old), new)
-    return '%s\n' % int(r)
+    output = output.getvalue() if output else ''
+    return '%s\n%s' % (int(r), output)
 def stream(repo, proto):