| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -83,10 +83,6 @@ In short, I want to make a new release of 0.3 with all contributions and fixes i | |||
| 83 | 83 | ||
| 84 | 84 | The goals I have set for myself, in order, are as follows, all on branch 0.3. | |
| 85 | 85 | ||
| 86 | - * bring the test suite back online to work with the most commonly used git version | ||
| 87 | - * merge all open pull requests, may there be a test-case or not, back. If something breaks, fix it if possible or let the contributor know | ||
| 88 | - * conform git-python's structure and toolchain to the one used in my [other OSS projects](https://github.com/Byron/bcore) | ||
| 89 | - * evaluate all open issues and close them if possible | ||
| 90 | 86 | * evaluate python 3.3 compatibility and establish it if possible | |
| 91 | 87 | ||
| 92 | 88 | While that is happening, I will try hard to foster community around the project. This means being more responsive on the mailing list and in issues, as well as setting up clear guide lines about the [contribution](http://rfc.zeromq.org/spec:22) and maintenance workflow. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1 +1 @@ | |||
| 1 | - 0.3.3 | ||
| 1 | + 0.3.4 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,10 +2,18 @@ | |||
| 2 | 2 | Changelog | |
| 3 | 3 | ========= | |
| 4 | 4 | ||
| 5 | + 0.3.4 - Python 3 Support | ||
| 6 | + ======================== | ||
| 7 | + * Internally, hexadecimal SHA1 are treated as ascii encoded strings. Binary SHA1 are treated as bytes. | ||
| 8 | + * Id attribute of Commit objects is now `hexsha`, instead of `binsha`. The latter makes no sense in python 3 and I see no application of it anyway besides its artificial usage in test cases. | ||
| 9 | + * **IMPORTANT**: If you were using the config_writer(), you implicitly relied on __del__ to work as expected to flush changes. To be sure changes are flushed under PY3, you will have to call the new `release()` method to trigger a flush. For some reason, __del__ is not called necessarily anymore when a symbol goes out of scope. | ||
| 10 | + * The `Tree` now has a `.join('name')` method which is equivalent to `tree / 'name'` | ||
| 11 | + | ||
| 5 | 12 | 0.3.3 | |
| 6 | 13 | ===== | |
| 7 | 14 | * When fetching, pulling or pushing, and an error occours, it will not be reported on stdout anymore. However, if there is a fatal error, it will still result in a GitCommandError to be thrown. This goes hand in hand with improved fetch result parsing. | |
| 8 | 15 | * Code Cleanup (in preparation for python 3 support) | |
| 16 | + | ||
| 9 | 17 | * Applied autopep8 and cleaned up code | |
| 10 | 18 | * Using python logging module instead of print statments to signal certain kinds of errors | |
| 11 | 19 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,18 +7,24 @@ | |||
| 7 | 7 | import os | |
| 8 | 8 | import sys | |
| 9 | 9 | import logging | |
| 10 | - from util import ( | ||
| 11 | - LazyMixin, | ||
| 12 | - stream_copy | ||
| 13 | - ) | ||
| 14 | - from exc import GitCommandError | ||
| 15 | - | ||
| 16 | 10 | from subprocess import ( | |
| 17 | 11 | call, | |
| 18 | 12 | Popen, | |
| 19 | 13 | PIPE | |
| 20 | 14 | ) | |
| 21 | 15 | ||
| 16 | + | ||
| 17 | + from .util import ( | ||
| 18 | + LazyMixin, | ||
| 19 | + stream_copy | ||
| 20 | + ) | ||
| 21 | + from .exc import GitCommandError | ||
| 22 | + from git.compat import ( | ||
| 23 | + string_types, | ||
| 24 | + defenc, | ||
| 25 | + PY3 | ||
| 26 | + ) | ||
| 27 | + | ||
| 22 | 28 | execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', | |
| 23 | 29 | 'with_exceptions', 'as_process', | |
| 24 | 30 | 'output_stream') | |
@@ -114,7 +120,7 @@ def wait(self): | |||
| 114 | 120 | :raise GitCommandError: if the return status is not 0""" | |
| 115 | 121 | status = self.proc.wait() | |
| 116 | 122 | if status != 0: | |
| 117 | - raise GitCommandError(self.args, status, self.proc.stderr.read()) | ||
| 123 | + raise GitCommandError(self.args, status, self.proc.stderr.read().decode(defenc)) | ||
| 118 | 124 | # END status handling | |
| 119 | 125 | return status | |
| 120 | 126 | # END auto interrupt | |
@@ -314,6 +320,7 @@ def execute(self, command, | |||
| 314 | 320 | always be created with a pipe due to issues with subprocess. | |
| 315 | 321 | This merely is a workaround as data will be copied from the | |
| 316 | 322 | output pipe to the given output stream directly. | |
| 323 | + Judging from the implementation, you shouldn't use this flag ! | ||
| 317 | 324 | ||
| 318 | 325 | :param subprocess_kwargs: | |
| 319 | 326 | Keyword arguments to be passed to subprocess.Popen. Please note that | |
@@ -365,23 +372,23 @@ def execute(self, command, | |||
| 365 | 372 | ||
| 366 | 373 | # Wait for the process to return | |
| 367 | 374 | status = 0 | |
| 368 | - stdout_value = '' | ||
| 369 | - stderr_value = '' | ||
| 375 | + stdout_value = b'' | ||
| 376 | + stderr_value = b'' | ||
| 370 | 377 | try: | |
| 371 | 378 | if output_stream is None: | |
| 372 | 379 | stdout_value, stderr_value = proc.communicate() | |
| 373 | 380 | # strip trailing "\n" | |
| 374 | - if stdout_value.endswith("\n"): | ||
| 381 | + if stdout_value.endswith(b"\n"): | ||
| 375 | 382 | stdout_value = stdout_value[:-1] | |
| 376 | - if stderr_value.endswith("\n"): | ||
| 383 | + if stderr_value.endswith(b"\n"): | ||
| 377 | 384 | stderr_value = stderr_value[:-1] | |
| 378 | 385 | status = proc.returncode | |
| 379 | 386 | else: | |
| 380 | 387 | stream_copy(proc.stdout, output_stream, self.max_chunk_size) | |
| 381 | 388 | stdout_value = output_stream | |
| 382 | 389 | stderr_value = proc.stderr.read() | |
| 383 | 390 | # strip trailing "\n" | |
| 384 | - if stderr_value.endswith("\n"): | ||
| 391 | + if stderr_value.endswith(b"\n"): | ||
| 385 | 392 | stderr_value = stderr_value[:-1] | |
| 386 | 393 | status = proc.wait() | |
| 387 | 394 | # END stdout handling | |
@@ -392,9 +399,10 @@ def execute(self, command, | |||
| 392 | 399 | if self.GIT_PYTHON_TRACE == 'full': | |
| 393 | 400 | cmdstr = " ".join(command) | |
| 394 | 401 | if stderr_value: | |
| 395 | - log.info("%s -> %d; stdout: '%s'; stderr: '%s'", cmdstr, status, stdout_value, stderr_value) | ||
| 402 | + log.info("%s -> %d; stdout: '%s'; stderr: '%s'", | ||
| 403 | + cmdstr, status, stdout_value.decode(defenc), stderr_value.decode(defenc)) | ||
| 396 | 404 | elif stdout_value: | |
| 397 | - log.info("%s -> %d; stdout: '%s'", cmdstr, status, stdout_value) | ||
| 405 | + log.info("%s -> %d; stdout: '%s'", cmdstr, status, stdout_value.decode(defenc)) | ||
| 398 | 406 | else: | |
| 399 | 407 | log.info("%s -> %d", cmdstr, status) | |
| 400 | 408 | # END handle debug printing | |
@@ -405,9 +413,12 @@ def execute(self, command, | |||
| 405 | 413 | else: | |
| 406 | 414 | raise GitCommandError(command, status, stderr_value) | |
| 407 | 415 | ||
| 416 | + if isinstance(stdout_value, bytes): # could also be output_stream | ||
| 417 | + stdout_value = stdout_value.decode(defenc) | ||
| 418 | + | ||
| 408 | 419 | # Allow access to the command's status code | |
| 409 | 420 | if with_extended_output: | |
| 410 | - return (status, stdout_value, stderr_value) | ||
| 421 | + return (status, stdout_value, stderr_value.decode(defenc)) | ||
| 411 | 422 | else: | |
| 412 | 423 | return stdout_value | |
| 413 | 424 | ||
@@ -433,16 +444,18 @@ def transform_kwargs(self, split_single_char_options=False, **kwargs): | |||
| 433 | 444 | @classmethod | |
| 434 | 445 | def __unpack_args(cls, arg_list): | |
| 435 | 446 | if not isinstance(arg_list, (list, tuple)): | |
| 436 | - if isinstance(arg_list, unicode): | ||
| 437 | - return [arg_list.encode('utf-8')] | ||
| 447 | + # This is just required for unicode conversion, as subprocess can't handle it | ||
| 448 | + # However, in any other case, passing strings (usually utf-8 encoded) is totally fine | ||
| 449 | + if not PY3 and isinstance(arg_list, unicode): | ||
| 450 | + return [arg_list.encode(defenc)] | ||
| 438 | 451 | return [str(arg_list)] | |
| 439 | 452 | ||
| 440 | 453 | outlist = list() | |
| 441 | 454 | for arg in arg_list: | |
| 442 | 455 | if isinstance(arg_list, (list, tuple)): | |
| 443 | 456 | outlist.extend(cls.__unpack_args(arg)) | |
| 444 | - elif isinstance(arg_list, unicode): | ||
| 445 | - outlist.append(arg_list.encode('utf-8')) | ||
| 457 | + elif not PY3 and isinstance(arg_list, unicode): | ||
| 458 | + outlist.append(arg_list.encode(defenc)) | ||
| 446 | 459 | # END recursion | |
| 447 | 460 | else: | |
| 448 | 461 | outlist.append(str(arg)) | |
@@ -498,8 +511,8 @@ def _call_process(self, method, *args, **kwargs): | |||
| 498 | 511 | ||
| 499 | 512 | # Prepare the argument list | |
| 500 | 513 | opt_args = self.transform_kwargs(**kwargs) | |
| 501 | - | ||
| 502 | 514 | ext_args = self.__unpack_args([a for a in args if a is not None]) | |
| 515 | + | ||
| 503 | 516 | args = opt_args + ext_args | |
| 504 | 517 | ||
| 505 | 518 | def make_call(): | |
@@ -567,14 +580,20 @@ def _parse_object_header(self, header_line): | |||
| 567 | 580 | raise ValueError("Failed to parse header: %r" % header_line) | |
| 568 | 581 | return (tokens[0], tokens[1], int(tokens[2])) | |
| 569 | 582 | ||
| 570 | - def __prepare_ref(self, ref): | ||
| 571 | - # required for command to separate refs on stdin | ||
| 572 | - refstr = str(ref) # could be ref-object | ||
| 573 | - if refstr.endswith("\n"): | ||
| 574 | - return refstr | ||
| 575 | - return refstr + "\n" | ||
| 583 | + def _prepare_ref(self, ref): | ||
| 584 | + # required for command to separate refs on stdin, as bytes | ||
| 585 | + refstr = ref | ||
| 586 | + if isinstance(ref, bytes): | ||
| 587 | + # Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text | ||
| 588 | + refstr = ref.decode('ascii') | ||
| 589 | + elif not isinstance(ref, string_types): | ||
| 590 | + refstr = str(ref) # could be ref-object | ||
| 591 | + | ||
| 592 | + if not refstr.endswith("\n"): | ||
| 593 | + refstr += "\n" | ||
| 594 | + return refstr.encode(defenc) | ||
| 576 | 595 | ||
| 577 | - def __get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs): | ||
| 596 | + def _get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs): | ||
| 578 | 597 | cur_val = getattr(self, attr_name) | |
| 579 | 598 | if cur_val is not None: | |
| 580 | 599 | return cur_val | |
@@ -587,7 +606,7 @@ def __get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs): | |||
| 587 | 606 | return cmd | |
| 588 | 607 | ||
| 589 | 608 | def __get_object_header(self, cmd, ref): | |
| 590 | - cmd.stdin.write(self.__prepare_ref(ref)) | ||
| 609 | + cmd.stdin.write(self._prepare_ref(ref)) | ||
| 591 | 610 | cmd.stdin.flush() | |
| 592 | 611 | return self._parse_object_header(cmd.stdout.readline()) | |
| 593 | 612 | ||
@@ -599,7 +618,7 @@ def get_object_header(self, ref): | |||
| 599 | 618 | once and reuses the command in subsequent calls. | |
| 600 | 619 | ||
| 601 | 620 | :return: (hexsha, type_string, size_as_int)""" | |
| 602 | - cmd = self.__get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) | ||
| 621 | + cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) | ||
| 603 | 622 | return self.__get_object_header(cmd, ref) | |
| 604 | 623 | ||
| 605 | 624 | def get_object_data(self, ref): | |
@@ -616,7 +635,7 @@ def stream_object_data(self, ref): | |||
| 616 | 635 | :return: (hexsha, type_string, size_as_int, stream) | |
| 617 | 636 | :note: This method is not threadsafe, you need one independent Command instance | |
| 618 | 637 | per thread to be safe !""" | |
| 619 | - cmd = self.__get_persistent_cmd("cat_file_all", "cat_file", batch=True) | ||
| 638 | + cmd = self._get_persistent_cmd("cat_file_all", "cat_file", batch=True) | ||
| 620 | 639 | hexsha, typename, size = self.__get_object_header(cmd, ref) | |
| 621 | 640 | return (hexsha, typename, size, self.CatFileContentStream(size, cmd.stdout)) | |
| 622 | 641 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,66 @@ | |||
| 1 | + #-*-coding:utf-8-*- | ||
| 2 | + # config.py | ||
| 3 | + # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors | ||
| 4 | + # | ||
| 5 | + # This module is part of GitPython and is released under | ||
| 6 | + # the BSD License: http://www.opensource.org/licenses/bsd-license.php | ||
| 7 | + """utilities to help provide compatibility with python 3""" | ||
| 8 | + # flake8: noqa | ||
| 9 | + | ||
| 10 | + import sys | ||
| 11 | + | ||
| 12 | + from gitdb.utils.compat import ( | ||
| 13 | + PY3, | ||
| 14 | + xrange, | ||
| 15 | + MAXSIZE, | ||
| 16 | + izip, | ||
| 17 | + ) | ||
| 18 | + | ||
| 19 | + from gitdb.utils.encoding import ( | ||
| 20 | + string_types, | ||
| 21 | + text_type, | ||
| 22 | + force_bytes, | ||
| 23 | + force_text | ||
| 24 | + ) | ||
| 25 | + | ||
| 26 | + defenc = sys.getdefaultencoding() | ||
| 27 | + if PY3: | ||
| 28 | + import io | ||
| 29 | + FileType = io.IOBase | ||
| 30 | + def byte_ord(b): | ||
| 31 | + return b | ||
| 32 | + def bchr(n): | ||
| 33 | + return bytes([n]) | ||
| 34 | + def mviter(d): | ||
| 35 | + return d.values() | ||
| 36 | + else: | ||
| 37 | + FileType = file | ||
| 38 | + # usually, this is just ascii, which might not enough for our encoding needs | ||
| 39 | + # Unless it's set specifically, we override it to be utf-8 | ||
| 40 | + if defenc == 'ascii': | ||
| 41 | + defenc = 'utf-8' | ||
| 42 | + byte_ord = ord | ||
| 43 | + bchr = chr | ||
| 44 | + def mviter(d): | ||
| 45 | + return d.itervalues() | ||
| 46 | + | ||
| 47 | + | ||
| 48 | + def with_metaclass(meta, *bases): | ||
| 49 | + """copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15""" | ||
| 50 | + class metaclass(meta): | ||
| 51 | + __call__ = type.__call__ | ||
| 52 | + __init__ = type.__init__ | ||
| 53 | + | ||
| 54 | + def __new__(cls, name, nbases, d): | ||
| 55 | + if nbases is None: | ||
| 56 | + return type.__new__(cls, name, (), d) | ||
| 57 | + # There may be clients who rely on this attribute to be set to a reasonable value, which is why | ||
| 58 | + # we set the __metaclass__ attribute explicitly | ||
| 59 | + if not PY3 and '___metaclass__' not in d: | ||
| 60 | + d['__metaclass__'] = meta | ||
| 61 | + # end | ||
| 62 | + return meta(name, bases, d) | ||
| 63 | + # end | ||
| 64 | + # end metaclass | ||
| 65 | + return metaclass(meta.__name__ + 'Helper', None, {}) | ||
| 66 | + # end handle py2 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments