| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f11fdf1 commit 44c6d0b
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -45,6 +45,7 @@ | |||
| 45 | 45 | ) | |
| 46 | 46 | import io | |
| 47 | 47 | from _io import UnsupportedOperation | |
| 48 | + from git.exc import CommandError | ||
| 48 | 49 | ||
| 49 | 50 | execute_kwargs = set(('istream', 'with_keep_cwd', 'with_extended_output', | |
| 50 | 51 | 'with_exceptions', 'as_process', 'stdout_as_string', | |
@@ -56,9 +57,6 @@ | |||
| 56 | 57 | ||
| 57 | 58 | __all__ = ('Git',) | |
| 58 | 59 | ||
| 59 | - if is_win: | ||
| 60 | - WindowsError = OSError # @ReservedAssignment | ||
| 61 | - | ||
| 62 | 60 | if PY3: | |
| 63 | 61 | _bchr = bchr | |
| 64 | 62 | else: | |
@@ -73,17 +71,23 @@ def _bchr(c): | |||
| 73 | 71 | # Documentation | |
| 74 | 72 | ## @{ | |
| 75 | 73 | ||
| 76 | - def handle_process_output(process, stdout_handler, stderr_handler, finalizer, | ||
| 77 | - decode_stdout=True, decode_stderr=True): | ||
| 74 | + def handle_process_output(process, stdout_handler, stderr_handler, finalizer, decode_streams=True): | ||
| 78 | 75 | """Registers for notifications to lean that process output is ready to read, and dispatches lines to | |
| 79 | 76 | the respective line handlers. We are able to handle carriage returns in case progress is sent by that | |
| 80 | 77 | mean. For performance reasons, we only apply this to stderr. | |
| 81 | 78 | This function returns once the finalizer returns | |
| 79 | + | ||
| 82 | 80 | :return: result of finalizer | |
| 83 | 81 | :param process: subprocess.Popen instance | |
| 84 | 82 | :param stdout_handler: f(stdout_line_string), or None | |
| 85 | 83 | :param stderr_hanlder: f(stderr_line_string), or None | |
| 86 | - :param finalizer: f(proc) - wait for proc to finish""" | ||
| 84 | + :param finalizer: f(proc) - wait for proc to finish | ||
| 85 | + :param decode_streams: | ||
| 86 | + Assume stdout/stderr streams are binary and decode them vefore pushing \ | ||
| 87 | + their contents to handlers. | ||
| 88 | + Set it to False if `universal_newline == True` (then streams are in text-mode) | ||
| 89 | + or if decoding must happen later (i.e. for Diffs). | ||
| 90 | + """ | ||
| 87 | 91 | ||
| 88 | 92 | def _parse_lines_from_buffer(buf): | |
| 89 | 93 | line = b'' | |
@@ -156,18 +160,29 @@ def _deplete_buffer(fno, handler, buf_list, decode): | |||
| 156 | 160 | # Oh ... probably we are on windows. or TC mockap provided for streams. | |
| 157 | 161 | # Anyhow, select.select() can only handle sockets, we have files | |
| 158 | 162 | # The only reliable way to do this now is to use threads and wait for both to finish | |
| 159 | - def _handle_lines(fd, handler, decode): | ||
| 160 | - for line in fd: | ||
| 161 | - if handler: | ||
| 162 | - if decode: | ||
| 163 | - line = line.decode(defenc) | ||
| 164 | - handler(line) | ||
| 165 | - | ||
| 163 | + def pump_stream(cmdline, name, stream, is_decode, handler): | ||
| 164 | + try: | ||
| 165 | + for line in stream: | ||
| 166 | + if handler: | ||
| 167 | + if is_decode: | ||
| 168 | + line = line.decode(defenc) | ||
| 169 | + handler(line) | ||
| 170 | + except Exception as ex: | ||
| 171 | + log.error("Pumping %r of cmd(%s) failed due to: %r", name, cmdline, ex) | ||
| 172 | + raise CommandError(['<%s-pump>' % name] + cmdline, ex) | ||
| 173 | + finally: | ||
| 174 | + stream.close() | ||
| 175 | + | ||
| 176 | + cmdline = getattr(process, 'args', '') # PY3+ only | ||
| 177 | + if not isinstance(cmdline, (tuple, list)): | ||
| 178 | + cmdline = cmdline.split() | ||
| 166 | 179 | threads = [] | |
| 167 | - for fd, handler, decode in zip((process.stdout, process.stderr), | ||
| 168 | - (stdout_handler, stderr_handler), | ||
| 169 | - (decode_stdout, decode_stderr),): | ||
| 170 | - t = threading.Thread(target=_handle_lines, args=(fd, handler, decode)) | ||
| 180 | + for name, stream, handler in ( | ||
| 181 | + ('stdout', process.stdout, stdout_handler), | ||
| 182 | + ('stderr', process.stderr, stderr_handler), | ||
| 183 | + ): | ||
| 184 | + t = threading.Thread(target=pump_stream, | ||
| 185 | + args=(cmdline, name, stream, decode_streams, handler)) | ||
| 171 | 186 | t.setDaemon(True) | |
| 172 | 187 | t.start() | |
| 173 | 188 | threads.append(t) | |
@@ -177,8 +192,8 @@ def _handle_lines(fd, handler, decode): | |||
| 177 | 192 | else: | |
| 178 | 193 | # poll is preferred, as select is limited to file handles up to 1024 ... . This could otherwise be | |
| 179 | 194 | # an issue for us, as it matters how many handles our own process has | |
| 180 | - fdmap = {outfn: (stdout_handler, [b''], decode_stdout), | ||
| 181 | - errfn: (stderr_handler, [b''], decode_stderr)} | ||
| 195 | + fdmap = {outfn: (stdout_handler, [b''], decode_streams), | ||
| 196 | + errfn: (stderr_handler, [b''], decode_streams)} | ||
| 182 | 197 | ||
| 183 | 198 | READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR # @UndefinedVariable | |
| 184 | 199 | CLOSED = select.POLLHUP | select.POLLERR # @UndefinedVariable | |
@@ -334,7 +349,8 @@ def __del__(self): | |||
| 334 | 349 | try: | |
| 335 | 350 | proc.terminate() | |
| 336 | 351 | proc.wait() # ensure process goes away | |
| 337 | - except (OSError, WindowsError): | ||
| 352 | + except OSError as ex: | ||
| 353 | + log.info("Ignored error after process has dies: %r", ex) | ||
| 338 | 354 | pass # ignore error when process already died | |
| 339 | 355 | except AttributeError: | |
| 340 | 356 | # try windows | |
@@ -638,12 +654,12 @@ def execute(self, command, | |||
| 638 | 654 | env.update(self._environment) | |
| 639 | 655 | ||
| 640 | 656 | if is_win: | |
| 641 | - cmd_not_found_exception = WindowsError | ||
| 657 | + cmd_not_found_exception = OSError | ||
| 642 | 658 | if kill_after_timeout: | |
| 643 | - raise GitCommandError('"kill_after_timeout" feature is not supported on Windows.') | ||
| 659 | + raise GitCommandError(command, '"kill_after_timeout" feature is not supported on Windows.') | ||
| 644 | 660 | else: | |
| 645 | 661 | if sys.version_info[0] > 2: | |
| 646 | - cmd_not_found_exception = FileNotFoundError # NOQA # this is defined, but flake8 doesn't know | ||
| 662 | + cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable | ||
| 647 | 663 | else: | |
| 648 | 664 | cmd_not_found_exception = OSError | |
| 649 | 665 | # end handle | |
@@ -663,7 +679,7 @@ def execute(self, command, | |||
| 663 | 679 | **subprocess_kwargs | |
| 664 | 680 | ) | |
| 665 | 681 | except cmd_not_found_exception as err: | |
| 666 | - raise GitCommandNotFound('%s: %s' % (command[0], err)) | ||
| 682 | + raise GitCommandNotFound(command, err) | ||
| 667 | 683 | ||
| 668 | 684 | if as_process: | |
| 669 | 685 | return self.AutoInterrupt(proc, command) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -407,7 +407,7 @@ def _index_from_patch_format(cls, repo, proc): | |||
| 407 | 407 | ||
| 408 | 408 | ## FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise. | |
| 409 | 409 | text = [] | |
| 410 | - handle_process_output(proc, text.append, None, finalize_process, decode_stdout=False) | ||
| 410 | + handle_process_output(proc, text.append, None, finalize_process, decode_streams=False) | ||
| 411 | 411 | ||
| 412 | 412 | # for now, we have to bake the stream | |
| 413 | 413 | text = b''.join(text) | |
@@ -499,6 +499,6 @@ def handle_diff_line(line): | |||
| 499 | 499 | new_file, deleted_file, rename_from, rename_to, '', change_type) | |
| 500 | 500 | index.append(diff) | |
| 501 | 501 | ||
| 502 | - handle_process_output(proc, handle_diff_line, None, finalize_process, decode_stdout=False) | ||
| 502 | + handle_process_output(proc, handle_diff_line, None, finalize_process, decode_streams=False) | ||
| 503 | 503 | ||
| 504 | 504 | return index | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,7 +6,7 @@ | |||
| 6 | 6 | """ Module containing all exceptions thrown througout the git package, """ | |
| 7 | 7 | ||
| 8 | 8 | from gitdb.exc import * # NOQA | |
| 9 | - from git.compat import UnicodeMixin, safe_decode | ||
| 9 | + from git.compat import UnicodeMixin, safe_decode, string_types | ||
| 10 | 10 | ||
| 11 | 11 | ||
| 12 | 12 | class InvalidGitRepositoryError(Exception): | |
@@ -21,25 +21,56 @@ class NoSuchPathError(OSError): | |||
| 21 | 21 | """ Thrown if a path could not be access by the system. """ | |
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | - class GitCommandNotFound(Exception): | ||
| 24 | + class CommandError(UnicodeMixin, Exception): | ||
| 25 | + """Base class for exceptions thrown at every stage of `Popen()` execution. | ||
| 26 | + | ||
| 27 | + :param command: | ||
| 28 | + A non-empty list of argv comprising the command-line. | ||
| 29 | + """ | ||
| 30 | + | ||
| 31 | + #: A unicode print-format with 2 `%s for `<cmdline>` and the rest, | ||
| 32 | + #: e.g. | ||
| 33 | + #: u"'%s' failed%s" | ||
| 34 | + _msg = u"Cmd('%s') failed%s" | ||
| 35 | + | ||
| 36 | + def __init__(self, command, status=None, stderr=None, stdout=None): | ||
| 37 | + assert isinstance(command, (tuple, list)), command | ||
| 38 | + self.command = command | ||
| 39 | + self.status = status | ||
| 40 | + if status: | ||
| 41 | + if isinstance(status, Exception): | ||
| 42 | + status = u"%s('%s')" % (type(status).__name__, safe_decode(str(status))) | ||
| 43 | + else: | ||
| 44 | + try: | ||
| 45 | + status = u'exit code(%s)' % int(status) | ||
| 46 | + except: | ||
| 47 | + s = safe_decode(str(status)) | ||
| 48 | + status = u"'%s'" % s if isinstance(status, string_types) else s | ||
| 49 | + | ||
| 50 | + self._cmd = safe_decode(command[0]) | ||
| 51 | + self._cmdline = u' '.join(safe_decode(i) for i in command) | ||
| 52 | + self._cause = status and u" due to: %s" % status or "!" | ||
| 53 | + self.stdout = stdout and u"\n stdout: '%s'" % safe_decode(stdout) or '' | ||
| 54 | + self.stderr = stderr and u"\n stderr: '%s'" % safe_decode(stderr) or '' | ||
| 55 | + | ||
| 56 | + def __unicode__(self): | ||
| 57 | + return (self._msg + "\n cmdline: %s%s%s") % ( | ||
| 58 | + self._cmd, self._cause, self._cmdline, self.stdout, self.stderr) | ||
| 59 | + | ||
| 60 | + | ||
| 61 | + class GitCommandNotFound(CommandError): | ||
| 25 | 62 | """Thrown if we cannot find the `git` executable in the PATH or at the path given by | |
| 26 | 63 | the GIT_PYTHON_GIT_EXECUTABLE environment variable""" | |
| 27 | - pass | ||
| 64 | + def __init__(self, command, cause): | ||
| 65 | + super(GitCommandNotFound, self).__init__(command, cause) | ||
| 66 | + self._msg = u"Cmd('%s') not found%s" | ||
| 28 | 67 | ||
| 29 | 68 | ||
| 30 | - class GitCommandError(UnicodeMixin, Exception): | ||
| 69 | + class GitCommandError(CommandError): | ||
| 31 | 70 | """ Thrown if execution of the git command fails with non-zero status code. """ | |
| 32 | 71 | ||
| 33 | 72 | def __init__(self, command, status, stderr=None, stdout=None): | |
| 34 | - self.stderr = stderr | ||
| 35 | - self.stdout = stdout | ||
| 36 | - self.status = status | ||
| 37 | - self.command = command | ||
| 38 | - | ||
| 39 | - def __unicode__(self): | ||
| 40 | - cmdline = u' '.join(safe_decode(i) for i in self.command) | ||
| 41 | - return (u"'%s' returned with exit code %s\n stdout: '%s'\n stderr: '%s'" | ||
| 42 | - % (cmdline, self.status, safe_decode(self.stdout), safe_decode(self.stderr))) | ||
| 73 | + super(GitCommandError, self).__init__(command, status, stderr, stdout) | ||
| 43 | 74 | ||
| 44 | 75 | ||
| 45 | 76 | class CheckoutError(Exception): | |
@@ -76,20 +107,13 @@ class UnmergedEntriesError(CacheError): | |||
| 76 | 107 | entries in the cache""" | |
| 77 | 108 | ||
| 78 | 109 | ||
| 79 | - class HookExecutionError(UnicodeMixin, Exception): | ||
| 110 | + class HookExecutionError(CommandError): | ||
| 80 | 111 | """Thrown if a hook exits with a non-zero exit code. It provides access to the exit code and the string returned | |
| 81 | 112 | via standard output""" | |
| 82 | 113 | ||
| 83 | - def __init__(self, command, status, stdout=None, stderr=None): | ||
| 84 | - self.command = command | ||
| 85 | - self.status = status | ||
| 86 | - self.stdout = stdout | ||
| 87 | - self.stderr = stderr | ||
| 88 | - | ||
| 89 | - def __unicode__(self): | ||
| 90 | - cmdline = u' '.join(safe_decode(i) for i in self.command) | ||
| 91 | - return (u"'%s' hook failed with %r\n stdout: '%s'\n stderr: '%s'" | ||
| 92 | - % (cmdline, self.status, safe_decode(self.stdout), safe_decode(self.stderr))) | ||
| 114 | + def __init__(self, command, status, stderr=None, stdout=None): | ||
| 115 | + super(HookExecutionError, self).__init__(command, status, stderr, stdout) | ||
| 116 | + self._msg = u"Hook('%s') failed%s" | ||
| 93 | 117 | ||
| 94 | 118 | ||
| 95 | 119 | class RepositoryDirtyError(Exception): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1091,6 +1091,7 @@ def handle_stderr(proc, iter_checked_out_files): | |||
| 1091 | 1091 | kwargs['as_process'] = True | |
| 1092 | 1092 | kwargs['istream'] = subprocess.PIPE | |
| 1093 | 1093 | proc = self.repo.git.checkout_index(args, **kwargs) | |
| 1094 | + # FIXME: Reading from GIL! | ||
| 1094 | 1095 | make_exc = lambda: GitCommandError(("git-checkout-index",) + tuple(args), 128, proc.stderr.read()) | |
| 1095 | 1096 | checked_out_files = list() | |
| 1096 | 1097 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -681,8 +681,7 @@ def stdout_handler(line): | |||
| 681 | 681 | # END for each line | |
| 682 | 682 | ||
| 683 | 683 | try: | |
| 684 | - handle_process_output(proc, stdout_handler, progress_handler, finalize_process, | ||
| 685 | - decode_stdout=False, decode_stderr=False) | ||
| 684 | + handle_process_output(proc, stdout_handler, progress_handler, finalize_process, decode_streams=False) | ||
| 686 | 685 | except Exception: | |
| 687 | 686 | if len(output) == 0: | |
| 688 | 687 | raise | |
| Back | FazBrowse Home | New Git URL |
0 commit comments