Skip to content

Commit da86442

Browse files
committed
Merge branch 'pr-cmd-raise-with-stderr-on-error' of https://github.com/barry-scott/GitPython into barry-scott-pr-cmd-raise-with-stderr-on-error
2 parents ec830a2 + 6891caf commit da86442

File tree

3 files changed

+61
-9
lines changed

3 files changed

+61
-9
lines changed

git/cmd.py

+29-4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ def _bchr(c):
6969
# Documentation
7070
## @{
7171

72+
def _drop_output_handler(line):
73+
pass
74+
75+
7276
def handle_process_output(process, stdout_handler, stderr_handler, finalizer):
7377
"""Registers for notifications to lean that process output is ready to read, and dispatches lines to
7478
the respective line handlers. We are able to handle carriage returns in case progress is sent by that
@@ -79,6 +83,13 @@ def handle_process_output(process, stdout_handler, stderr_handler, finalizer):
7983
:param stdout_handler: f(stdout_line_string), or None
8084
:param stderr_hanlder: f(stderr_line_string), or None
8185
:param finalizer: f(proc) - wait for proc to finish"""
86+
87+
log.debug('handle_process_output( process=%r, stdout_handler=%r, stderr_handler=%r, finalizer=%r'
88+
% (process, stdout_handler, stderr_handler, finalizer))
89+
90+
if stdout_handler is None:
91+
stdout_handler = _drop_output_handler
92+
8293
fdmap = {process.stdout.fileno(): (stdout_handler, [b'']),
8394
process.stderr.fileno(): (stderr_handler, [b''])}
8495

@@ -119,6 +130,7 @@ def _dispatch_single_line(line, handler):
119130
# end single line helper
120131

121132
def _dispatch_lines(fno, handler, buf_list):
133+
log.debug('fno=%d, handler=%r, buf_list=%r' % (fno, handler, buf_list))
122134
lc = 0
123135
for line in _read_lines_from_fno(fno, buf_list):
124136
_dispatch_single_line(line, handler)
@@ -307,22 +319,35 @@ def __del__(self):
307319
def __getattr__(self, attr):
308320
return getattr(self.proc, attr)
309321

310-
def wait(self, stderr=None):
322+
def wait(self, stderr=b''):
311323
"""Wait for the process and return its status code.
312324
313325
:param stderr: Previously read value of stderr, in case stderr is already closed.
314326
:warn: may deadlock if output or error pipes are used and not handled separately.
315327
:raise GitCommandError: if the return status is not 0"""
328+
329+
# stderr must be a bytes object as it will
330+
# combined with more data from the process and
331+
# decoded by the caller
332+
if stderr is None:
333+
stderr = b''
334+
elif type(stderr) == unicode:
335+
stderr = stderr.encode(defenc)
336+
316337
status = self.proc.wait()
317338

318339
def read_all_from_possibly_closed_stream(stream):
319340
try:
320-
return stream.read()
341+
last_stderr = stream.read()
342+
if type(last_stderr) == unicode:
343+
last_stderr = last_stderr.encode(defenc)
344+
return stderr + last_stderr
321345
except ValueError:
322-
return stderr or ''
346+
return stderr or b''
323347

324348
if status != 0:
325349
errstr = read_all_from_possibly_closed_stream(self.proc.stderr)
350+
log.debug('AutoInterrupt wait stderr: %r' % (errstr,))
326351
raise GitCommandError(self.args, status, errstr)
327352
# END status handling
328353
return status
@@ -609,7 +634,7 @@ def execute(self, command,
609634
bufsize=-1,
610635
stdin=istream,
611636
stderr=PIPE,
612-
stdout=PIPE if with_stdout else open(os.devnull, 'wb'),
637+
stdout=PIPE,
613638
shell=self.USE_SHELL,
614639
close_fds=(os.name == 'posix'), # unsupported on windows
615640
universal_newlines=universal_newlines,

git/remote.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -570,21 +570,36 @@ def _get_fetch_info_from_stderr(self, proc, progress):
570570

571571
progress_handler = progress.new_message_handler()
572572

573+
error_message = None
574+
stderr_text = None
575+
573576
for line in proc.stderr:
574577
line = force_text(line)
575578
for pline in progress_handler(line):
576579
if line.startswith('fatal:') or line.startswith('error:'):
577-
raise GitCommandError(("Error when fetching: %s" % line,), 2)
580+
error_message = "Error when fetching: %s" % (line,)
581+
break
582+
578583
# END handle special messages
579584
for cmd in cmds:
580585
if len(line) > 1 and line[0] == ' ' and line[1] == cmd:
581586
fetch_info_lines.append(line)
582587
continue
583588
# end find command code
584589
# end for each comand code we know
590+
591+
if error_message is not None:
592+
break
585593
# end for each line progress didn't handle
594+
595+
if error_message is not None:
596+
stderr_text = proc.stderr.read()
597+
586598
# end
587-
finalize_process(proc)
599+
finalize_process(proc, stderr=stderr_text)
600+
601+
if error_message is not None:
602+
raise GitCommandError(error_message, 2, stderr=stderr_text)
588603

589604
# read head information
590605
fp = open(join(self.repo.git_dir, 'FETCH_HEAD'), 'rb')
@@ -631,6 +646,10 @@ def stdout_handler(line):
631646

632647
try:
633648
handle_process_output(proc, stdout_handler, progress_handler, finalize_process)
649+
except GitCommandError as err:
650+
# convert any error from wait() into the same error with stdout lines
651+
raise GitCommandError(err.command, err.status, progress.get_stderr())
652+
634653
except Exception:
635654
if len(output) == 0:
636655
raise

git/util.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,17 @@ class RemoteProgress(object):
173173
DONE_TOKEN = 'done.'
174174
TOKEN_SEPARATOR = ', '
175175

176-
__slots__ = ("_cur_line", "_seen_ops")
176+
__slots__ = ("_cur_line", "_seen_ops", "_error_lines")
177177
re_op_absolute = re.compile(r"(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
178178
re_op_relative = re.compile(r"(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
179179

180180
def __init__(self):
181181
self._seen_ops = list()
182182
self._cur_line = None
183+
self._error_lines = []
184+
185+
def get_stderr(self):
186+
return '\n'.join(self._error_lines)
183187

184188
def _parse_progress_line(self, line):
185189
"""Parse progress information from the given line as retrieved by git-push
@@ -190,6 +194,10 @@ def _parse_progress_line(self, line):
190194
# Counting objects: 4, done.
191195
# Compressing objects: 50% (1/2) \rCompressing objects: 100% (2/2) \rCompressing objects: 100% (2/2), done.
192196
self._cur_line = line
197+
if len(self._error_lines) > 0 or self._cur_line.startswith(('error:', 'fatal:')):
198+
self._error_lines.append(self._cur_line)
199+
return []
200+
193201
sub_lines = line.split('\r')
194202
failed_lines = list()
195203
for sline in sub_lines:
@@ -764,10 +772,10 @@ def done(self):
764772
self.cv.notify_all()
765773
self.cv.release()
766774

767-
def wait(self):
775+
def wait(self, stderr=b''):
768776
self.cv.acquire()
769777
while self.count > 0:
770-
self.cv.wait()
778+
self.cv.wait(strerr=stderr)
771779
self.cv.release()
772780

773781

0 commit comments

Comments
 (0)