From 74a54dcf3c5d9e7bc99fa2f62ef24e2a6da2fec9 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 27 Sep 2018 09:27:34 -0400 Subject: [PATCH 1/2] RF: do not pretty-print stdout/err within attributes, do in __unicode__ It is better to leave original stdout/stderr values as is (well, after decoding) while storing them, and only add corresponding pretty printing elements while generating a string representation. Otherwise are two conflated and Python code using GitPython need to know about such added formatting which actually was not in the actual outputs --- git/exc.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/git/exc.py b/git/exc.py index 4865da944..4f5ef7b2c 100644 --- a/git/exc.py +++ b/git/exc.py @@ -55,12 +55,16 @@ def __init__(self, command, status=None, stderr=None, stdout=None): self._cmd = safe_decode(command[0]) self._cmdline = u' '.join(safe_decode(i) for i in command) self._cause = status and u" due to: %s" % status or "!" - self.stdout = stdout and u"\n stdout: '%s'" % safe_decode(stdout) or '' - self.stderr = stderr and u"\n stderr: '%s'" % safe_decode(stderr) or '' + self.stdout = stdout and safe_decode(stdout) or '' + self.stderr = stderr and safe_decode(stderr) or '' def __unicode__(self): return (self._msg + "\n cmdline: %s%s%s") % ( - self._cmd, self._cause, self._cmdline, self.stdout, self.stderr) + self._cmd, self._cause, + self._cmdline, + u"\n stdout: '%s'" % self.stdout if self.stdout else '', + u"\n stderr: '%s'" % self.stderr if self.stderr else '' + ) class GitCommandNotFound(CommandError): From 3cc3b7b624b6db48588468efb4fdae7b436cecf5 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 27 Sep 2018 10:14:37 -0400 Subject: [PATCH 2/2] BF: Collect all pumped output to possibly provide it within thrown exception I have decided that it would be best to collect all output, not only not parsed or error lines as is done while parsing it for progress. This would provide more information, happen stderr includes errors spit out during progress output etc. Also a somewhat adhoc tune up of Exception is done to avoid passing those outputs around etc --- git/cmd.py | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index b6305176b..081edf082 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -78,17 +78,19 @@ def handle_process_output(process, stdout_handler, stderr_handler, Set it to False if `universal_newline == True` (then streams are in text-mode) or if decoding must happen later (i.e. for Diffs). """ + pumped_lines = {'stdout': [], 'stderr': []} # Use 2 "pupm" threads and wait for both to finish. def pump_stream(cmdline, name, stream, is_decode, handler): try: for line in stream: + if is_decode: + line = line.decode(defenc) + pumped_lines[name].append(line) if handler: - if is_decode: - line = line.decode(defenc) handler(line) except Exception as ex: log.error("Pumping %r of cmd(%s) failed due to: %r", name, cmdline, ex) - raise CommandError(['<%s-pump>' % name] + cmdline, ex) + raise CommandError(['<%s-pump>' % name] + cmdline, ex, **pumped_lines) finally: stream.close() @@ -104,20 +106,29 @@ def pump_stream(cmdline, name, stream, is_decode, handler): threads = [] - for name, stream, handler in pumps: - t = threading.Thread(target=pump_stream, - args=(cmdline, name, stream, decode_streams, handler)) - t.setDaemon(True) - t.start() - threads.append(t) + try: + for name, stream, handler in pumps: + t = threading.Thread(target=pump_stream, + args=(cmdline, name, stream, decode_streams, handler)) + t.setDaemon(True) + t.start() + threads.append(t) + + ## FIXME: Why Join?? Will block if `stdin` needs feeding... + # + for t in threads: + t.join() + + if finalizer: + return finalizer(process) + except Exception as e: + for outname, out in pumped_lines.items(): + if out and hasattr(e, outname): + eout = getattr(e, outname) + if not eout: + setattr(e, outname, safe_decode(os.linesep.join(out))) + raise - ## FIXME: Why Join?? Will block if `stdin` needs feeding... - # - for t in threads: - t.join() - - if finalizer: - return finalizer(process) def dashify(string):