From f6aa8d116eb33293c0a9d6d600eb7c32832758b9 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 4 Jan 2015 19:14:33 +0100 Subject: initial set of adjustments to make (most) imports work. More to come, especially when it's about strings --- README.md | 4 ---- doc/source/changes.rst | 1 + git/cmd.py | 14 ++++++++------ git/compat.py | 19 +++++++++++++++++++ git/config.py | 6 +++++- git/db.py | 11 +++++------ git/diff.py | 6 +++--- git/index/base.py | 11 +++++------ git/index/fun.py | 8 ++++---- git/index/typ.py | 9 ++++----- git/objects/__init__.py | 7 ++++--- git/objects/base.py | 2 +- git/objects/blob.py | 3 +-- git/objects/commit.py | 15 +++++++-------- git/objects/submodule/base.py | 6 +++--- git/objects/submodule/root.py | 7 +++++-- git/objects/submodule/util.py | 2 +- git/objects/tag.py | 6 +++--- git/objects/tree.py | 16 +++++++--------- git/refs/head.py | 8 +++----- git/refs/reference.py | 3 ++- git/refs/remote.py | 3 ++- git/refs/symbolic.py | 3 ++- git/refs/tag.py | 2 +- git/remote.py | 29 +++++++++++++++-------------- git/repo/base.py | 2 +- git/repo/fun.py | 4 +++- git/test/lib/asserts.py | 18 ++++++++---------- git/test/lib/helper.py | 6 +++--- git/test/performance/test_commit.py | 2 +- git/test/test_commit.py | 2 +- git/test/test_fun.py | 2 +- git/test/test_index.py | 2 +- git/test/test_repo.py | 2 +- git/test/test_tree.py | 2 +- git/util.py | 5 +++-- tox.ini | 2 +- 37 files changed, 136 insertions(+), 114 deletions(-) create mode 100644 git/compat.py diff --git a/README.md b/README.md index 4b678184..1bc6430e 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,6 @@ In short, I want to make a new release of 0.3 with all contributions and fixes i The goals I have set for myself, in order, are as follows, all on branch 0.3. -* bring the test suite back online to work with the most commonly used git version -* 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 -* conform git-python's structure and toolchain to the one used in my [other OSS projects](https://github.com/Byron/bcore) -* evaluate all open issues and close them if possible * evaluate python 3.3 compatibility and establish it if possible 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. diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 924743bd..84437884 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -6,6 +6,7 @@ Changelog ===== * 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. * Code Cleanup (in preparation for python 3 support) + * Applied autopep8 and cleaned up code * Using python logging module instead of print statments to signal certain kinds of errors diff --git a/git/cmd.py b/git/cmd.py index c355eacf..b9e4bc09 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -7,18 +7,20 @@ import os import sys import logging -from util import ( - LazyMixin, - stream_copy -) -from exc import GitCommandError - from subprocess import ( call, Popen, PIPE ) + +from .util import ( + LazyMixin, + stream_copy +) +from .exc import GitCommandError + + execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', 'with_exceptions', 'as_process', 'output_stream') diff --git a/git/compat.py b/git/compat.py new file mode 100644 index 00000000..52fc599c --- /dev/null +++ b/git/compat.py @@ -0,0 +1,19 @@ +#-*-coding:utf-8-*- +# config.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php +"""utilities to help provide compatibility with python 3""" + +from gitdb.utils.compat import ( # noqa + PY3, + xrange, + MAXSIZE, + izip, +) + +from gitdb.utils.encoding import ( # noqa + string_types, + text_type +) diff --git a/git/config.py b/git/config.py index 6a85760c..685dbed8 100644 --- a/git/config.py +++ b/git/config.py @@ -7,7 +7,11 @@ configuration files""" import re -import ConfigParser as cp +try: + import ConfigParser as cp +except ImportError: + # PY3 + import configparser as cp import inspect import logging diff --git a/git/db.py b/git/db.py index ab39f6c5..c4e19858 100644 --- a/git/db.py +++ b/git/db.py @@ -1,14 +1,8 @@ """Module with our own gitdb implementation - it uses the git command""" -from exc import ( - GitCommandError, - BadObject -) - from gitdb.base import ( OInfo, OStream ) - from gitdb.util import ( bin_to_hex, hex_to_bin @@ -16,6 +10,11 @@ from gitdb.util import ( from gitdb.db import GitDB from gitdb.db import LooseObjectDB +from .exc import ( + GitCommandError, + BadObject +) + __all__ = ('GitCmdObjectDB', 'GitDB') diff --git a/git/diff.py b/git/diff.py index 5325ad6b..b3e7245b 100644 --- a/git/diff.py +++ b/git/diff.py @@ -3,13 +3,13 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php - import re -from objects.blob import Blob -from objects.util import mode_str_to_int from gitdb.util import hex_to_bin +from .objects.blob import Blob +from .objects.util import mode_str_to_int + __all__ = ('Diffable', 'DiffIndex', 'Diff') diff --git a/git/index/base.py b/git/index/base.py index fdcfcd12..91dcea42 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -8,16 +8,16 @@ import os import sys import subprocess import glob -from cStringIO import StringIO +from io import StringIO from stat import S_ISLNK -from typ import ( +from .typ import ( BaseIndexEntry, IndexEntry, ) -from util import ( +from .util import ( TemporaryFileSwap, post_clear_cache, default_index, @@ -25,7 +25,6 @@ from util import ( ) import git.diff as diff - from git.exc import ( GitCommandError, CheckoutError @@ -40,6 +39,7 @@ from git.objects import ( ) from git.objects.util import Serializable +from git.compat import izip from git.util import ( LazyMixin, @@ -49,7 +49,7 @@ from git.util import ( to_native_path_linux, ) -from fun import ( +from .fun import ( entry_key, write_cache, read_cache, @@ -62,7 +62,6 @@ from fun import ( from gitdb.base import IStream from gitdb.db import MemoryDB from gitdb.util import to_bin_sha -from itertools import izip __all__ = ('IndexFile', 'CheckoutError') diff --git a/git/index/fun.py b/git/index/fun.py index eec90519..004f992e 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -12,7 +12,7 @@ from stat import ( S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule -from cStringIO import StringIO +from io import StringIO from git.util import IndexFileSHA1Writer from git.exc import UnmergedEntriesError @@ -22,7 +22,7 @@ from git.objects.fun import ( traverse_trees_recursive ) -from typ import ( +from .typ import ( BaseIndexEntry, IndexEntry, CE_NAMEMASK, @@ -30,7 +30,7 @@ from typ import ( ) CE_NAMEMASK_INV = ~CE_NAMEMASK -from util import ( +from .util import ( pack, unpack ) @@ -49,7 +49,7 @@ def stat_mode_to_index_mode(mode): return S_IFLNK if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules return S_IFGITLINK - return S_IFREG | 0644 | (mode & 0100) # blobs with or without executable bit + return S_IFREG | 0o644 | (mode & 0o100) # blobs with or without executable bit def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1Writer): diff --git a/git/index/typ.py b/git/index/typ.py index 222252c5..692e1e18 100644 --- a/git/index/typ.py +++ b/git/index/typ.py @@ -1,15 +1,14 @@ """Module with additional types used by the index""" -from util import ( +from binascii import b2a_hex + +from .util import ( pack, unpack ) +from git.objects import Blob -from binascii import ( - b2a_hex, -) -from git.objects import Blob __all__ = ('BlobFilter', 'BaseIndexEntry', 'IndexEntry') #{ Invariants diff --git a/git/objects/__init__.py b/git/objects/__init__.py index 70fc52cb..ee642876 100644 --- a/git/objects/__init__.py +++ b/git/objects/__init__.py @@ -7,9 +7,10 @@ import inspect from .base import * # Fix import dependency - add IndexObject to the util module, so that it can be # imported by the submodule.base -from .submodule import util -util.IndexObject = IndexObject -util.Object = Object +from .submodule import util as smutil +smutil.IndexObject = IndexObject +smutil.Object = Object +del(smutil) from .submodule.base import * from .submodule.root import * diff --git a/git/objects/base.py b/git/objects/base.py index 20147e57..1f0d5752 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -3,8 +3,8 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php +from .util import get_object_type_by_name from git.util import LazyMixin, join_path_native, stream_copy -from util import get_object_type_by_name from gitdb.util import ( bin_to_hex, basename diff --git a/git/objects/blob.py b/git/objects/blob.py index b05e5b84..322f6992 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -3,9 +3,8 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php - from mimetypes import guess_type -import base +from . import base __all__ = ('Blob', ) diff --git a/git/objects/commit.py b/git/objects/commit.py index 9c733695..5b6b9a33 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -4,6 +4,8 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php +from gitdb import IStream +from gitdb.util import hex_to_bin from git.util import ( Actor, Iterable, @@ -11,26 +13,23 @@ from git.util import ( finalize_process ) from git.diff import Diffable -from tree import Tree -from gitdb import IStream -from cStringIO import StringIO -import base -from gitdb.util import ( - hex_to_bin -) -from util import ( +from .tree import Tree +from . import base +from .util import ( Traversable, Serializable, parse_date, altz_to_utctz_str, parse_actor_and_date ) + from time import ( time, altzone ) import os +from io import StringIO import logging log = logging.getLogger('git.objects.commit') diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index d6f8982b..5ccebd4c 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -1,5 +1,5 @@ -import util -from util import ( +from . import util +from .util import ( mkhead, sm_name, sm_section, @@ -8,7 +8,7 @@ from util import ( find_first_remote_branch ) from git.objects.util import Traversable -from StringIO import StringIO # need a dict to set bloody .name field +from io import StringIO # need a dict to set bloody .name field from git.util import ( Iterable, join_path_native, diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py index 708749c7..8c9afff1 100644 --- a/git/objects/submodule/root.py +++ b/git/objects/submodule/root.py @@ -1,5 +1,8 @@ -from base import Submodule, UpdateProgress -from util import ( +from .base import ( + Submodule, + UpdateProgress +) +from .util import ( find_first_remote_branch ) from git.exc import InvalidGitRepositoryError diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index 01bd03b3..cb84ccb1 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -1,7 +1,7 @@ import git from git.exc import InvalidGitRepositoryError from git.config import GitConfigParser -from StringIO import StringIO +from io import StringIO import weakref __all__ = ('sm_section', 'sm_name', 'mkhead', 'unbare_repo', 'find_first_remote_branch', diff --git a/git/objects/tag.py b/git/objects/tag.py index 3c379579..5e76e230 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -4,12 +4,12 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php """ Module containing all object based types. """ -import base -from gitdb.util import hex_to_bin -from util import ( +from . import base +from .util import ( get_object_type_by_name, parse_actor_and_date ) +from gitdb.util import hex_to_bin __all__ = ("TagObject", ) diff --git a/git/objects/tree.py b/git/objects/tree.py index c77e6056..a216322b 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -3,22 +3,20 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -import util -from base import IndexObject from git.util import join_path -from blob import Blob -from submodule.base import Submodule import git.diff as diff +from gitdb.util import to_bin_sha -from fun import ( +from . import util +from .base import IndexObject +from .blob import Blob +from .submodule.base import Submodule + +from .fun import ( tree_entries_from_data, tree_to_stream ) -from gitdb.util import ( - to_bin_sha, -) - __all__ = ("TreeModifier", "Tree") diff --git a/git/refs/head.py b/git/refs/head.py index 25c994a3..0a14158c 100644 --- a/git/refs/head.py +++ b/git/refs/head.py @@ -1,12 +1,10 @@ -from symbolic import SymbolicReference -from reference import Reference - from git.config import SectionConstraint - from git.util import join_path - from git.exc import GitCommandError +from .symbolic import SymbolicReference +from .reference import Reference + __all__ = ["HEAD", "Head"] diff --git a/git/refs/reference.py b/git/refs/reference.py index b07ac0cd..8741ebb9 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -1,8 +1,9 @@ -from symbolic import SymbolicReference from git.util import ( LazyMixin, Iterable, ) +from .symbolic import SymbolicReference + __all__ = ["Reference"] diff --git a/git/refs/remote.py b/git/refs/remote.py index e3827ad9..b692e6df 100644 --- a/git/refs/remote.py +++ b/git/refs/remote.py @@ -1,7 +1,8 @@ -from head import Head from git.util import join_path from gitdb.util import join +from .head import Head + import os diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index e0f5531a..0cd04e07 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -1,4 +1,5 @@ import os + from git.objects import Object, Commit from git.util import ( join_path, @@ -19,7 +20,7 @@ from gitdb.util import ( LockedFD ) -from log import RefLog +from .log import RefLog __all__ = ["SymbolicReference"] diff --git a/git/refs/tag.py b/git/refs/tag.py index 6509c891..3334e53c 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -1,4 +1,4 @@ -from reference import Reference +from .reference import Reference __all__ = ["TagReference", "Tag"] diff --git a/git/remote.py b/git/remote.py index 44b7ffaa..9ebc52fe 100644 --- a/git/remote.py +++ b/git/remote.py @@ -5,33 +5,34 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php # Module implementing a remote object allowing easy access to git remotes +import re +import os -from exc import GitCommandError -from ConfigParser import NoOptionError -from config import SectionConstraint - -from git.util import ( - LazyMixin, - Iterable, - IterableList, - RemoteProgress +from .exc import GitCommandError +from .config import ( + SectionConstraint, + cp, ) - -from refs import ( +from .refs import ( Reference, RemoteReference, SymbolicReference, TagReference ) + +from git.util import ( + LazyMixin, + Iterable, + IterableList, + RemoteProgress +) from git.util import ( join_path, finalize_process ) from gitdb.util import join -import re -import os __all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote') @@ -390,7 +391,7 @@ class Remote(LazyMixin, Iterable): # even though a slot of the same name exists try: return self._config_reader.get(attr) - except NoOptionError: + except cp.NoOptionError: return super(Remote, self).__getattr__(attr) # END handle exception diff --git a/git/repo/base.py b/git/repo/base.py index dcf98152..e5ae7623 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -40,7 +40,7 @@ from gitdb.util import ( hex_to_bin ) -from fun import ( +from .fun import ( rev_parse, is_git_dir, find_git_dir, diff --git a/git/repo/fun.py b/git/repo/fun.py index b8905517..d08e5fed 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -1,5 +1,7 @@ """Package with general repository related functions""" import os +from string import digits + from gitdb.exc import BadObject from git.refs import SymbolicReference from git.objects import Object @@ -11,7 +13,7 @@ from gitdb.util import ( hex_to_bin, bin_to_hex ) -from string import digits + __all__ = ('rev_parse', 'is_git_dir', 'touch', 'read_gitfile', 'find_git_dir', 'name_to_object', 'short_to_long', 'deref_tag', 'to_commit') diff --git a/git/test/lib/asserts.py b/git/test/lib/asserts.py index 0f2fd99a..60a888b3 100644 --- a/git/test/lib/asserts.py +++ b/git/test/lib/asserts.py @@ -7,13 +7,6 @@ import re import stat -__all__ = ['assert_instance_of', 'assert_not_instance_of', - 'assert_none', 'assert_not_none', - 'assert_match', 'assert_not_match', 'assert_mode_644', - 'assert_mode_755', - 'assert_equal', 'assert_not_equal', 'assert_raises', 'patch', 'raises', - 'assert_true', 'assert_false'] - from nose.tools import ( assert_equal, assert_not_equal, @@ -23,9 +16,14 @@ from nose.tools import ( assert_false ) -from mock import ( - patch -) +from mock import patch + +__all__ = ['assert_instance_of', 'assert_not_instance_of', + 'assert_none', 'assert_not_none', + 'assert_match', 'assert_not_match', 'assert_mode_644', + 'assert_mode_755', + 'assert_equal', 'assert_not_equal', 'assert_raises', 'patch', 'raises', + 'assert_true', 'assert_false'] def assert_instance_of(expected, actual, msg=None): diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index 9c935ce0..0ea4fc7e 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -11,7 +11,7 @@ from unittest import TestCase import time import tempfile import shutil -import cStringIO +import io GIT_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) @@ -46,8 +46,8 @@ class StringProcessAdapter(object): Its tailored to work with the test system only""" def __init__(self, input_string): - self.stdout = cStringIO.StringIO(input_string) - self.stderr = cStringIO.StringIO() + self.stdout = io.StringIO(input_string) + self.stderr = io.StringIO() def wait(self): return 0 diff --git a/git/test/performance/test_commit.py b/git/test/performance/test_commit.py index a890c833..fed6ef18 100644 --- a/git/test/performance/test_commit.py +++ b/git/test/performance/test_commit.py @@ -8,7 +8,7 @@ from .lib import TestBigRepoRW from git import Commit from gitdb import IStream from git.test.test_commit import assert_commit_serialization -from cStringIO import StringIO +from io import StringIO from time import time import sys diff --git a/git/test/test_commit.py b/git/test/test_commit.py index bfad6fd6..84f81f21 100644 --- a/git/test/test_commit.py +++ b/git/test/test_commit.py @@ -21,7 +21,7 @@ from git import ( from gitdb import IStream from gitdb.util import hex_to_bin -from cStringIO import StringIO +from io import StringIO import time import sys import re diff --git a/git/test/test_fun.py b/git/test/test_fun.py index bf178aaa..4093065d 100644 --- a/git/test/test_fun.py +++ b/git/test/test_fun.py @@ -24,7 +24,7 @@ from stat import ( ) from git.index import IndexFile -from cStringIO import StringIO +from io import StringIO class TestFun(TestBase): diff --git a/git/test/test_index.py b/git/test/test_index.py index 15fff8d4..74bdac53 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -30,7 +30,7 @@ from stat import ( ST_MODE ) -from StringIO import StringIO +from io import StringIO from gitdb.base import IStream from git.objects import Blob from git.index.typ import ( diff --git a/git/test/test_repo.py b/git/test/test_repo.py index f6b46a6e..41c1c8f1 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -35,7 +35,7 @@ import os import sys import tempfile import shutil -from cStringIO import StringIO +from io import StringIO class TestRepo(TestBase): diff --git a/git/test/test_tree.py b/git/test/test_tree.py index d2e3606b..3b89abee 100644 --- a/git/test/test_tree.py +++ b/git/test/test_tree.py @@ -11,7 +11,7 @@ from git import ( Blob ) -from cStringIO import StringIO +from io import StringIO class TestTree(TestBase): diff --git a/git/util.py b/git/util.py index fecd9fa2..b3a22883 100644 --- a/git/util.py +++ b/git/util.py @@ -15,7 +15,8 @@ import getpass # NOTE: Some of the unused imports might be used/imported by others. # Handle once test-cases are back up and running. -from exc import GitCommandError +from .exc import GitCommandError +from .compat import MAXSIZE # Most of these are unused here, but are for use by git-python modules so these # don't see gitdb all the time. Flake of course doesn't like it. @@ -548,7 +549,7 @@ class BlockingLockFile(LockFile): can never be obtained.""" __slots__ = ("_check_interval", "_max_block_time") - def __init__(self, file_path, check_interval_s=0.3, max_block_time_s=sys.maxint): + def __init__(self, file_path, check_interval_s=0.3, max_block_time_s=MAXSIZE): """Configure the instance :parm check_interval_s: diff --git a/tox.ini b/tox.ini index 6c562a1d..a3509756 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,flake8 +envlist = py26,py27,py33,py34,flake8 [testenv] commands = nosetests {posargs} -- cgit v1.2.3 From ae2ff0f9d704dc776a1934f72a339da206a9fff4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 4 Jan 2015 19:50:28 +0100 Subject: Dum brute force conversion of all types. However, StringIO really is ByteIO in most cases, and py2.7 should run but doesn't. This should be made work first. --- git/cmd.py | 6 +++--- git/compat.py | 11 +++++++++-- git/config.py | 12 ++++++++---- git/index/base.py | 14 +++++++++----- git/objects/commit.py | 7 ++++--- git/objects/fun.py | 6 +++++- git/objects/submodule/base.py | 3 ++- git/objects/tree.py | 3 ++- git/refs/log.py | 6 +++++- git/refs/symbolic.py | 3 ++- git/repo/base.py | 9 +++++---- git/repo/fun.py | 1 + git/test/lib/helper.py | 8 +++++--- git/test/performance/test_commit.py | 9 ++++++--- git/test/test_commit.py | 12 ++++++++---- git/test/test_config.py | 3 ++- git/test/test_index.py | 3 ++- git/test/test_remote.py | 5 +++-- git/test/test_repo.py | 3 ++- git/test/test_submodule.py | 3 ++- git/test/test_util.py | 3 ++- 21 files changed, 87 insertions(+), 43 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index b9e4bc09..aa5e3a25 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -19,7 +19,7 @@ from .util import ( stream_copy ) from .exc import GitCommandError - +from git.compat import text_type execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', 'with_exceptions', 'as_process', @@ -435,7 +435,7 @@ class Git(LazyMixin): @classmethod def __unpack_args(cls, arg_list): if not isinstance(arg_list, (list, tuple)): - if isinstance(arg_list, unicode): + if isinstance(arg_list, text_type): return [arg_list.encode('utf-8')] return [str(arg_list)] @@ -443,7 +443,7 @@ class Git(LazyMixin): for arg in arg_list: if isinstance(arg_list, (list, tuple)): outlist.extend(cls.__unpack_args(arg)) - elif isinstance(arg_list, unicode): + elif isinstance(arg_list, text_type): outlist.append(arg_list.encode('utf-8')) # END recursion else: diff --git a/git/compat.py b/git/compat.py index 52fc599c..611005a1 100644 --- a/git/compat.py +++ b/git/compat.py @@ -5,15 +5,22 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php """utilities to help provide compatibility with python 3""" +# flake8: noqa -from gitdb.utils.compat import ( # noqa +from gitdb.utils.compat import ( PY3, xrange, MAXSIZE, izip, ) -from gitdb.utils.encoding import ( # noqa +from gitdb.utils.encoding import ( string_types, text_type ) + +if PY3: + import io + FileType = io.IOBase +else: + FileType = file diff --git a/git/config.py b/git/config.py index 685dbed8..34fe290b 100644 --- a/git/config.py +++ b/git/config.py @@ -17,6 +17,10 @@ import logging from git.odict import OrderedDict from git.util import LockFile +from git.compat import ( + string_types, + FileType +) __all__ = ('GitConfigParser', 'SectionConstraint') @@ -175,7 +179,7 @@ class GitConfigParser(cp.RawConfigParser, object): "Write-ConfigParsers can operate on a single file only, multiple files have been passed") # END single file check - if not isinstance(file_or_files, basestring): + if not isinstance(file_or_files, string_types): file_or_files = file_or_files.name # END get filename from handle/stream # initialize lock base - we want to write @@ -333,7 +337,7 @@ class GitConfigParser(cp.RawConfigParser, object): close_fp = False # we have a physical file on disk, so get a lock - if isinstance(fp, (basestring, file)): + if isinstance(fp, string_types + (FileType, )): self._lock._obtain_lock() # END get lock for physical files @@ -391,7 +395,7 @@ class GitConfigParser(cp.RawConfigParser, object): return default raise - types = (long, float) + types = (int, float) for numtype in types: try: val = numtype(valuestr) @@ -412,7 +416,7 @@ class GitConfigParser(cp.RawConfigParser, object): if vl == 'true': return True - if not isinstance(valuestr, basestring): + if not isinstance(valuestr, string_types): raise TypeError("Invalid value type: only int, long, float and str are allowed", valuestr) return valuestr diff --git a/git/index/base.py b/git/index/base.py index 91dcea42..25ac121c 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -39,7 +39,11 @@ from git.objects import ( ) from git.objects.util import Serializable -from git.compat import izip +from git.compat import ( + izip, + xrange, + string_types, +) from git.util import ( LazyMixin, @@ -541,7 +545,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): entries = list() for item in items: - if isinstance(item, basestring): + if isinstance(item, string_types): paths.append(self._to_relative_path(item)) elif isinstance(item, (Blob, Submodule)): entries.append(BaseIndexEntry.from_blob(item)) @@ -752,7 +756,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): for item in items: if isinstance(item, (BaseIndexEntry, (Blob, Submodule))): paths.append(self._to_relative_path(item.path)) - elif isinstance(item, basestring): + elif isinstance(item, string_types): paths.append(self._to_relative_path(item)) else: raise TypeError("Invalid item type: %r" % item) @@ -1004,7 +1008,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): handle_stderr(proc, rval_iter) return rval_iter else: - if isinstance(paths, basestring): + if isinstance(paths, string_types): paths = [paths] # make sure we have our entries loaded before we start checkout_index @@ -1140,7 +1144,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # index against anything but None is a reverse diff with the respective # item. Handle existing -R flags properly. Transform strings to the object # so that we can call diff on it - if isinstance(other, basestring): + if isinstance(other, string_types): other = self.repo.rev_parse(other) # END object conversion diff --git a/git/objects/commit.py b/git/objects/commit.py index 5b6b9a33..c9d7ddc8 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -23,6 +23,7 @@ from .util import ( altz_to_utctz_str, parse_actor_and_date ) +from git.compat import text_type from time import ( time, @@ -378,7 +379,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): a = self.author aname = a.name - if isinstance(aname, unicode): + if isinstance(aname, text_type): aname = aname.encode(self.encoding) # END handle unicode in name @@ -390,7 +391,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): # encode committer aname = c.name - if isinstance(aname, unicode): + if isinstance(aname, text_type): aname = aname.encode(self.encoding) # END handle unicode in name write(fmt % ("committer", aname, c.email, @@ -408,7 +409,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): write("\n") # write plain bytes, be sure its encoded according to our encoding - if isinstance(self.message, unicode): + if isinstance(self.message, text_type): write(self.message.encode(self.encoding)) else: write(self.message) diff --git a/git/objects/fun.py b/git/objects/fun.py index 416a52e6..db2ec7c2 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -1,5 +1,9 @@ """Module with functions which are supposed to be as fast as possible""" from stat import S_ISDIR +from git.compat import ( + xrange, + text_type +) __all__ = ('tree_to_stream', 'tree_entries_from_data', 'traverse_trees_recursive', 'traverse_tree_recursive') @@ -28,7 +32,7 @@ def tree_to_stream(entries, write): # hence we must convert to an utf8 string for it to work properly. # According to my tests, this is exactly what git does, that is it just # takes the input literally, which appears to be utf8 on linux. - if isinstance(name, unicode): + if isinstance(name, text_type): name = name.encode("utf8") write("%s %s\0%s" % (mode_str, name, binsha)) # END for each item diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 5ccebd4c..69bf748a 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -22,6 +22,7 @@ from git.exc import ( InvalidGitRepositoryError, NoSuchPathError ) +from git.compat import string_types import stat import git @@ -93,7 +94,7 @@ class Submodule(util.IndexObject, Iterable, Traversable): if url is not None: self._url = url if branch_path is not None: - assert isinstance(branch_path, basestring) + assert isinstance(branch_path, string_types) self._branch_path = branch_path if name is not None: self._name = name diff --git a/git/objects/tree.py b/git/objects/tree.py index a216322b..6776a15e 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -11,6 +11,7 @@ from . import util from .base import IndexObject from .blob import Blob from .submodule.base import Submodule +from git.compat import string_types from .fun import ( tree_entries_from_data, @@ -232,7 +233,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): info = self._cache[item] return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2])) - if isinstance(item, basestring): + if isinstance(item, string_types): # compatability return self.__div__(item) # END index is basestring diff --git a/git/refs/log.py b/git/refs/log.py index e3f3363c..94e07104 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -17,6 +17,10 @@ from git.objects.util import ( Serializable, altz_to_utctz_str, ) +from git.compat import ( + xrange, + string_types +) import time import re @@ -170,7 +174,7 @@ class RefLog(list, Serializable): :param stream: file-like object containing the revlog in its native format or basestring instance pointing to a file to read""" new_entry = RefLogEntry.from_line - if isinstance(stream, basestring): + if isinstance(stream, string_types): stream = file_contents_ro_filepath(stream) # END handle stream type while True: diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 0cd04e07..624b1a09 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -19,6 +19,7 @@ from gitdb.util import ( hex_to_bin, LockedFD ) +from git.compat import string_types from .log import RefLog @@ -274,7 +275,7 @@ class SymbolicReference(object): elif isinstance(ref, Object): obj = ref write_value = ref.hexsha - elif isinstance(ref, basestring): + elif isinstance(ref, string_types): try: obj = self.repo.rev_parse(ref + "^{}") # optionally deref tags write_value = obj.hexsha diff --git a/git/repo/base.py b/git/repo/base.py index e5ae7623..f92a85ce 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -47,6 +47,7 @@ from .fun import ( read_gitfile, touch, ) +from git.compat import text_type import os import sys @@ -176,11 +177,11 @@ class Repo(object): # Description property def _get_description(self): filename = join(self.git_dir, 'description') - return file(filename).read().rstrip() + return open(filename).read().rstrip() def _set_description(self, descr): filename = join(self.git_dir, 'description') - file(filename, 'w').write(descr + '\n') + open(filename, 'w').write(descr + '\n') description = property(_get_description, _set_description, doc="the project's description") @@ -389,7 +390,7 @@ class Repo(object): if rev is None: return self.head.commit else: - return self.rev_parse(unicode(rev) + "^0") + return self.rev_parse(text_type(rev) + "^0") def iter_trees(self, *args, **kwargs): """:return: Iterator yielding Tree objects @@ -412,7 +413,7 @@ class Repo(object): if rev is None: return self.head.commit.tree else: - return self.rev_parse(unicode(rev) + "^{tree}") + return self.rev_parse(text_type(rev) + "^{tree}") def iter_commits(self, rev=None, paths='', **kwargs): """A list of Commit objects representing the history of a given ref/commit diff --git a/git/repo/fun.py b/git/repo/fun.py index d08e5fed..64b9b4a9 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -13,6 +13,7 @@ from gitdb.util import ( hex_to_bin, bin_to_hex ) +from git.compat import xrange __all__ = ('rev_parse', 'is_git_dir', 'touch', 'read_gitfile', 'find_git_dir', 'name_to_object', diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index 0ea4fc7e..43079dbe 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -6,13 +6,15 @@ from __future__ import print_function import os import sys -from git import Repo, Remote, GitCommandError, Git from unittest import TestCase import time import tempfile import shutil import io +from git import Repo, Remote, GitCommandError, Git +from git.compat import string_types + GIT_REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) __all__ = ( @@ -89,7 +91,7 @@ def with_rw_repo(working_tree_ref, bare=False): To make working with relative paths easier, the cwd will be set to the working dir of the repository. """ - assert isinstance(working_tree_ref, basestring), "Decorator requires ref name for working tree checkout" + assert isinstance(working_tree_ref, string_types), "Decorator requires ref name for working tree checkout" def argument_passer(func): def repo_creator(self): @@ -152,7 +154,7 @@ def with_rw_and_rw_remote_repo(working_tree_ref): See working dir info in with_rw_repo :note: We attempt to launch our own invocation of git-daemon, which will be shutdown at the end of the test. """ - assert isinstance(working_tree_ref, basestring), "Decorator requires ref name for working tree checkout" + assert isinstance(working_tree_ref, string_types), "Decorator requires ref name for working tree checkout" def argument_passer(func): def remote_repo_creator(self): diff --git a/git/test/performance/test_commit.py b/git/test/performance/test_commit.py index fed6ef18..a55b6d43 100644 --- a/git/test/performance/test_commit.py +++ b/git/test/performance/test_commit.py @@ -4,13 +4,16 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php from __future__ import print_function +from io import StringIO +from time import time +import sys + from .lib import TestBigRepoRW from git import Commit from gitdb import IStream +from git.compat import xrange from git.test.test_commit import assert_commit_serialization -from io import StringIO -from time import time -import sys + class TestPerformance(TestBigRepoRW): diff --git a/git/test/test_commit.py b/git/test/test_commit.py index 84f81f21..adf1cb10 100644 --- a/git/test/test_commit.py +++ b/git/test/test_commit.py @@ -20,6 +20,10 @@ from git import ( ) from gitdb import IStream from gitdb.util import hex_to_bin +from git.compat import ( + string_types, + text_type +) from io import StringIO import time @@ -129,7 +133,7 @@ class TestCommit(TestBase): assert len(name) == 9 special = Actor._from_string(u"%s " % name) assert special.name == name - assert isinstance(special.name, unicode) + assert isinstance(special.name, text_type) def test_traversal(self): start = self.rorepo.commit("a4d06724202afccd2b5c54f81bcf2bf26dea7fff") @@ -250,7 +254,7 @@ class TestCommit(TestBase): def test_base(self): name_rev = self.rorepo.head.commit.name_rev - assert isinstance(name_rev, basestring) + assert isinstance(name_rev, string_types) @with_rw_repo('HEAD', bare=True) def test_serialization(self, rwrepo): @@ -263,8 +267,8 @@ class TestCommit(TestBase): # create a commit with unicode in the message, and the author's name # Verify its serialization and deserialization cmt = self.rorepo.commit('0.1.6') - assert isinstance(cmt.message, unicode) # it automatically decodes it as such - assert isinstance(cmt.author.name, unicode) # same here + assert isinstance(cmt.message, text_type) # it automatically decodes it as such + assert isinstance(cmt.author.name, text_type) # same here cmt.message = "üäêèß".decode("utf-8") assert len(cmt.message) == 5 diff --git a/git/test/test_config.py b/git/test/test_config.py index d1c8e72f..ef0707e9 100644 --- a/git/test/test_config.py +++ b/git/test/test_config.py @@ -11,6 +11,7 @@ from git.test.lib import ( from git import ( GitConfigParser ) +from git.compat import string_types import StringIO from copy import copy from ConfigParser import NoSectionError @@ -85,7 +86,7 @@ class TestBase(TestCase): num_options += 1 val = r_config.get(section, option) val_typed = r_config.get_value(section, option) - assert isinstance(val_typed, (bool, long, float, basestring)) + assert isinstance(val_typed, (bool, int, float, ) + string_types) assert val assert "\n" not in option assert "\n" not in val diff --git a/git/test/test_index.py b/git/test/test_index.py index 74bdac53..70d70a05 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -20,6 +20,7 @@ from git import ( GitCommandError, CheckoutError, ) +from git.compat import string_types from gitdb.util import hex_to_bin import os import sys @@ -343,7 +344,7 @@ class TestIndex(TestBase): index.checkout(test_file) except CheckoutError as e: assert len(e.failed_files) == 1 and e.failed_files[0] == os.path.basename(test_file) - assert (len(e.failed_files) == len(e.failed_reasons)) and isinstance(e.failed_reasons[0], basestring) + assert (len(e.failed_files) == len(e.failed_reasons)) and isinstance(e.failed_reasons[0], string_types) assert len(e.valid_files) == 0 assert open(test_file).read().endswith(append_data) else: diff --git a/git/test/test_remote.py b/git/test/test_remote.py index a8d5179a..75dc19c5 100644 --- a/git/test/test_remote.py +++ b/git/test/test_remote.py @@ -23,6 +23,7 @@ from git import ( GitCommandError ) from git.util import IterableList +from git.compat import string_types import tempfile import shutil import os @@ -97,7 +98,7 @@ class TestRemote(TestBase): # self._print_fetchhead(remote.repo) assert len(results) > 0 and isinstance(results[0], FetchInfo) for info in results: - assert isinstance(info.note, basestring) + assert isinstance(info.note, string_types) if isinstance(info.ref, Reference): assert info.flags != 0 # END reference type flags handling @@ -113,7 +114,7 @@ class TestRemote(TestBase): assert len(results) > 0 and isinstance(results[0], PushInfo) for info in results: assert info.flags - assert isinstance(info.summary, basestring) + assert isinstance(info.summary, string_types) if info.old_commit is not None: assert isinstance(info.old_commit, Commit) if info.flags & info.ERROR: diff --git a/git/test/test_repo.py b/git/test/test_repo.py index 41c1c8f1..f33fe467 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -30,6 +30,7 @@ from git import ( from git.util import join_path_native from git.exc import BadObject from gitdb.util import bin_to_hex +from git.compat import string_types import os import sys @@ -286,7 +287,7 @@ class TestRepo(TestBase): # test the 'lines per commit' entries tlist = b[0][1] assert_true(tlist) - assert_true(isinstance(tlist[0], basestring)) + assert_true(isinstance(tlist[0], string_types)) assert_true(len(tlist) < sum(len(t) for t in tlist)) # test for single-char bug def test_blame_real(self): diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py index ec3459e4..8c1580fe 100644 --- a/git/test/test_submodule.py +++ b/git/test/test_submodule.py @@ -9,6 +9,7 @@ from git.exc import InvalidGitRepositoryError from git.objects.submodule.base import Submodule from git.objects.submodule.root import RootModule, RootUpdateProgress from git.util import to_native_path_linux, join_path_native +from git.compat import string_types import shutil import git import sys @@ -76,7 +77,7 @@ class TestSubmodule(TestBase): self.failUnlessRaises(InvalidGitRepositoryError, getattr, sm, 'branch') # branch_path works, as its just a string - assert isinstance(sm.branch_path, basestring) + assert isinstance(sm.branch_path, string_types) # some commits earlier we still have a submodule, but its at a different commit smold = Submodule.iter_items(rwrepo, self.k_subm_changed).next() diff --git a/git/test/test_util.py b/git/test/test_util.py index 888eb4ee..c6ca6920 100644 --- a/git/test/test_util.py +++ b/git/test/test_util.py @@ -24,6 +24,7 @@ from git.objects.util import ( parse_date, ) from git.cmd import dashify +from git.compat import string_types import time @@ -104,7 +105,7 @@ class TestUtils(TestBase): # now that we are here, test our conversion functions as well utctz = altz_to_utctz_str(offset) - assert isinstance(utctz, basestring) + assert isinstance(utctz, string_types) assert utctz_to_altz(verify_utctz(utctz)) == offset # END assert rval utility -- cgit v1.2.3 From bc8c91200a7fb2140aadd283c66b5ab82f9ad61e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 5 Jan 2015 10:09:51 +0100 Subject: Fixed io types to make tests work on PY2 once again. Now it's about going through PY3 issues --- git/compat.py | 3 ++- git/index/base.py | 6 ++++-- git/index/fun.py | 4 ++-- git/objects/commit.py | 6 +++--- git/objects/submodule/base.py | 6 +++--- git/objects/submodule/util.py | 4 ++-- git/refs/log.py | 2 +- git/test/fixtures/git_config_global | 1 + git/test/lib/helper.py | 4 ++-- git/test/performance/test_commit.py | 4 ++-- git/test/test_commit.py | 12 ++++++------ git/test/test_config.py | 4 ++-- git/test/test_fun.py | 4 ++-- git/test/test_index.py | 6 +++--- git/test/test_repo.py | 14 +++++++------- git/test/test_tree.py | 6 +++--- 16 files changed, 45 insertions(+), 41 deletions(-) diff --git a/git/compat.py b/git/compat.py index 611005a1..a95c5667 100644 --- a/git/compat.py +++ b/git/compat.py @@ -16,7 +16,8 @@ from gitdb.utils.compat import ( from gitdb.utils.encoding import ( string_types, - text_type + text_type, + force_bytes ) if PY3: diff --git a/git/index/base.py b/git/index/base.py index 25ac121c..a994e7b6 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -8,7 +8,7 @@ import os import sys import subprocess import glob -from io import StringIO +from io import BytesIO from stat import S_ISLNK @@ -43,6 +43,7 @@ from git.compat import ( izip, xrange, string_types, + force_bytes ) from git.util import ( @@ -562,7 +563,8 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): st = os.lstat(filepath) # handles non-symlinks as well stream = None if S_ISLNK(st.st_mode): - stream = StringIO(os.readlink(filepath)) + # in PY3, readlink is string, but we need bytes. In PY2, it's just OS encoded bytes, we assume UTF-8 + stream = BytesIO(force_bytes(os.readlink(filepath), encoding='utf-8')) else: stream = open(filepath, 'rb') # END handle stream diff --git a/git/index/fun.py b/git/index/fun.py index 004f992e..0e49ae8d 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -12,7 +12,7 @@ from stat import ( S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule -from io import StringIO +from io import BytesIO from git.util import IndexFileSHA1Writer from git.exc import UnmergedEntriesError @@ -218,7 +218,7 @@ def write_tree_from_cache(entries, odb, sl, si=0): # END for each entry # finally create the tree - sio = StringIO() + sio = BytesIO() tree_to_stream(tree_items, sio.write) sio.seek(0) diff --git a/git/objects/commit.py b/git/objects/commit.py index c9d7ddc8..79d460ad 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -30,7 +30,7 @@ from time import ( altzone ) import os -from io import StringIO +from io import BytesIO import logging log = logging.getLogger('git.objects.commit') @@ -133,7 +133,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): if attr in Commit.__slots__: # read the data in a chunk, its faster - then provide a file wrapper binsha, typename, self.size, stream = self.repo.odb.stream(self.binsha) - self._deserialize(StringIO(stream.read())) + self._deserialize(BytesIO(stream.read())) else: super(Commit, self)._set_cache_(attr) # END handle attrs @@ -345,7 +345,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): committer, committer_time, committer_offset, message, parent_commits, conf_encoding) - stream = StringIO() + stream = BytesIO() new_commit._serialize(stream) streamlen = stream.tell() stream.seek(0) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 69bf748a..0fb3f35f 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -8,7 +8,7 @@ from .util import ( find_first_remote_branch ) from git.objects.util import Traversable -from io import StringIO # need a dict to set bloody .name field +from io import BytesIO # need a dict to set bloody .name field from git.util import ( Iterable, join_path_native, @@ -187,8 +187,8 @@ class Submodule(util.IndexObject, Iterable, Traversable): @classmethod def _sio_modules(cls, parent_commit): - """:return: Configuration file as StringIO - we only access it through the respective blob's data""" - sio = StringIO(parent_commit.tree[cls.k_modules_file].data_stream.read()) + """:return: Configuration file as BytesIO - we only access it through the respective blob's data""" + sio = BytesIO(parent_commit.tree[cls.k_modules_file].data_stream.read()) sio.name = cls.k_modules_file return sio diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index cb84ccb1..5604dec7 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -1,7 +1,7 @@ import git from git.exc import InvalidGitRepositoryError from git.config import GitConfigParser -from io import StringIO +from io import BytesIO import weakref __all__ = ('sm_section', 'sm_name', 'mkhead', 'unbare_repo', 'find_first_remote_branch', @@ -83,7 +83,7 @@ class SubmoduleConfigParser(GitConfigParser): """Flush changes in our configuration file to the index""" assert self._smref is not None # should always have a file here - assert not isinstance(self._file_or_files, StringIO) + assert not isinstance(self._file_or_files, BytesIO) sm = self._smref() if sm is not None: diff --git a/git/refs/log.py b/git/refs/log.py index 94e07104..f397548e 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -85,7 +85,7 @@ class RefLogEntry(tuple): :param line: line without trailing newline :raise ValueError: If line could not be parsed""" try: - info, msg = line.split('\t', 2) + info, msg = line.split('\t', 1) except ValueError: raise ValueError("line is missing tab separator") # END handle first plit diff --git a/git/test/fixtures/git_config_global b/git/test/fixtures/git_config_global index 1a55397f..56fbd3b3 100644 --- a/git/test/fixtures/git_config_global +++ b/git/test/fixtures/git_config_global @@ -1,3 +1,4 @@ +# just a comment [alias] st = status ci = commit diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index 43079dbe..bc9c351a 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -48,8 +48,8 @@ class StringProcessAdapter(object): Its tailored to work with the test system only""" def __init__(self, input_string): - self.stdout = io.StringIO(input_string) - self.stderr = io.StringIO() + self.stdout = io.BytesIO(input_string) + self.stderr = io.BytesIO() def wait(self): return 0 diff --git a/git/test/performance/test_commit.py b/git/test/performance/test_commit.py index a55b6d43..9e8c1325 100644 --- a/git/test/performance/test_commit.py +++ b/git/test/performance/test_commit.py @@ -4,7 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php from __future__ import print_function -from io import StringIO +from io import BytesIO from time import time import sys @@ -93,7 +93,7 @@ class TestPerformance(TestBigRepoRW): hc.committer, hc.committed_date, hc.committer_tz_offset, str(i), parents=hc.parents, encoding=hc.encoding) - stream = StringIO() + stream = BytesIO() cm._serialize(stream) slen = stream.tell() stream.seek(0) diff --git a/git/test/test_commit.py b/git/test/test_commit.py index adf1cb10..5f45e59d 100644 --- a/git/test/test_commit.py +++ b/git/test/test_commit.py @@ -25,7 +25,7 @@ from git.compat import ( text_type ) -from io import StringIO +from io import BytesIO import time import sys import re @@ -44,7 +44,7 @@ def assert_commit_serialization(rwrepo, commit_id, print_performance_info=False) # assert that we deserialize commits correctly, hence we get the same # sha on serialization - stream = StringIO() + stream = BytesIO() cm._serialize(stream) ns += 1 streamlen = stream.tell() @@ -59,7 +59,7 @@ def assert_commit_serialization(rwrepo, commit_id, print_performance_info=False) cm.message, cm.parents, cm.encoding) assert nc.parents == cm.parents - stream = StringIO() + stream = BytesIO() nc._serialize(stream) ns += 1 streamlen = stream.tell() @@ -276,7 +276,7 @@ class TestCommit(TestBase): cmt.author.name = "äüß".decode("utf-8") assert len(cmt.author.name) == 3 - cstream = StringIO() + cstream = BytesIO() cmt._serialize(cstream) cstream.seek(0) assert len(cstream.getvalue()) @@ -316,7 +316,7 @@ JzJMZDRLQLFvnzqZuCjE cmt.gpgsig = "" assert cmt.gpgsig != fixture_sig - cstream = StringIO() + cstream = BytesIO() cmt._serialize(cstream) assert re.search(r"^gpgsig $", cstream.getvalue(), re.MULTILINE) @@ -326,6 +326,6 @@ JzJMZDRLQLFvnzqZuCjE assert cmt.gpgsig == "" cmt.gpgsig = None - cstream = StringIO() + cstream = BytesIO() cmt._serialize(cstream) assert not re.search(r"^gpgsig ", cstream.getvalue(), re.MULTILINE) diff --git a/git/test/test_config.py b/git/test/test_config.py index ef0707e9..0301c54f 100644 --- a/git/test/test_config.py +++ b/git/test/test_config.py @@ -12,7 +12,7 @@ from git import ( GitConfigParser ) from git.compat import string_types -import StringIO +import io from copy import copy from ConfigParser import NoSectionError @@ -21,7 +21,7 @@ class TestBase(TestCase): def _to_memcache(self, file_path): fp = open(file_path, "r") - sio = StringIO.StringIO(fp.read()) + sio = io.BytesIO(fp.read()) sio.name = file_path return sio diff --git a/git/test/test_fun.py b/git/test/test_fun.py index 4093065d..9d3e749e 100644 --- a/git/test/test_fun.py +++ b/git/test/test_fun.py @@ -24,7 +24,7 @@ from stat import ( ) from git.index import IndexFile -from io import StringIO +from io import BytesIO class TestFun(TestBase): @@ -72,7 +72,7 @@ class TestFun(TestBase): def mktree(self, odb, entries): """create a tree from the given tree entries and safe it to the database""" - sio = StringIO() + sio = BytesIO() tree_to_stream(entries, sio.write) sio.seek(0) istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio)) diff --git a/git/test/test_index.py b/git/test/test_index.py index 70d70a05..38cc3563 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -31,7 +31,7 @@ from stat import ( ST_MODE ) -from io import StringIO +from io import BytesIO from gitdb.base import IStream from git.objects import Blob from git.index.typ import ( @@ -698,9 +698,9 @@ class TestIndex(TestBase): # instead of throwing the Exception we are expecting. This is # a quick hack to make this test fail when expected. rw_bare_repo._working_tree_dir = None - contents = 'This is a StringIO file' + contents = b'This is a BytesIO file' filesize = len(contents) - fileobj = StringIO(contents) + fileobj = BytesIO(contents) filename = 'my-imaginary-file' istream = rw_bare_repo.odb.store( IStream(Blob.type, filesize, fileobj)) diff --git a/git/test/test_repo.py b/git/test/test_repo.py index f33fe467..ae824086 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -36,7 +36,7 @@ import os import sys import tempfile import shutil -from io import StringIO +from io import BytesIO class TestRepo(TestBase): @@ -362,22 +362,22 @@ class TestRepo(TestBase): def test_git_cmd(self): # test CatFileContentStream, just to be very sure we have no fencepost errors # last \n is the terminating newline that it expects - l1 = "0123456789\n" - l2 = "abcdefghijklmnopqrstxy\n" - l3 = "z\n" - d = "%s%s%s\n" % (l1, l2, l3) + l1 = b"0123456789\n" + l2 = b"abcdefghijklmnopqrstxy\n" + l3 = b"z\n" + d = b"%s%s%s\n" % (l1, l2, l3) l1p = l1[:5] # full size # size is without terminating newline def mkfull(): - return Git.CatFileContentStream(len(d) - 1, StringIO(d)) + return Git.CatFileContentStream(len(d) - 1, BytesIO(d)) ts = 5 def mktiny(): - return Git.CatFileContentStream(ts, StringIO(d)) + return Git.CatFileContentStream(ts, BytesIO(d)) # readlines no limit s = mkfull() diff --git a/git/test/test_tree.py b/git/test/test_tree.py index 3b89abee..72bda485 100644 --- a/git/test/test_tree.py +++ b/git/test/test_tree.py @@ -11,7 +11,7 @@ from git import ( Blob ) -from io import StringIO +from io import BytesIO class TestTree(TestBase): @@ -30,7 +30,7 @@ class TestTree(TestBase): orig_data = tree.data_stream.read() orig_cache = tree._cache - stream = StringIO() + stream = BytesIO() tree._serialize(stream) assert stream.getvalue() == orig_data @@ -82,7 +82,7 @@ class TestTree(TestBase): mod.set_done() # multiple times are okay # serialize, its different now - stream = StringIO() + stream = BytesIO() testtree._serialize(stream) stream.seek(0) assert stream.getvalue() != orig_data -- cgit v1.2.3 From 04357d0d46fee938a618b64daed1716606e05ca5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 5 Jan 2015 15:53:46 +0100 Subject: Intermediate commit: test_config and test_actor works Kind of tackling the tasks step by step, picking low-hanging fruit first, or the ones that everyone depends on --- doc/source/changes.rst | 4 ++++ git/cmd.py | 44 +++++++++++++++++++++++++++----------------- git/compat.py | 32 +++++++++++++++++++++++++++++++- git/config.py | 42 ++++++++++++++++++++++-------------------- git/objects/util.py | 16 ++++++++-------- git/refs/symbolic.py | 15 +++++++++------ git/remote.py | 11 ++++++----- git/test/test_config.py | 13 +++++++------ 8 files changed, 114 insertions(+), 63 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 84437884..cf528b28 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,6 +2,10 @@ Changelog ========= +0.3.4 - python 3 support +======================== +* Internally, hexadecimal SHA1 are treated as ascii encoded strings. Binary SHA1 are treated as bytes. + 0.3.3 ===== * 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. diff --git a/git/cmd.py b/git/cmd.py index aa5e3a25..c536b43c 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -19,7 +19,11 @@ from .util import ( stream_copy ) from .exc import GitCommandError -from git.compat import text_type +from git.compat import ( + text_type, + string_types, + defenc +) execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', 'with_exceptions', 'as_process', @@ -373,9 +377,9 @@ class Git(LazyMixin): if output_stream is None: stdout_value, stderr_value = proc.communicate() # strip trailing "\n" - if stdout_value.endswith("\n"): + if stdout_value.endswith(b"\n"): stdout_value = stdout_value[:-1] - if stderr_value.endswith("\n"): + if stderr_value.endswith(b"\n"): stderr_value = stderr_value[:-1] status = proc.returncode else: @@ -394,9 +398,9 @@ class Git(LazyMixin): if self.GIT_PYTHON_TRACE == 'full': cmdstr = " ".join(command) if stderr_value: - log.info("%s -> %d; stdout: '%s'; stderr: '%s'", cmdstr, status, stdout_value, stderr_value) + log.info("%s -> %d; stdout: '%s'; stderr: '%s'", cmdstr, status, stdout_value.decode(defenc), stderr_value.decode(defenc)) elif stdout_value: - log.info("%s -> %d; stdout: '%s'", cmdstr, status, stdout_value) + log.info("%s -> %d; stdout: '%s'", cmdstr, status, stdout_value.decode(defenc)) else: log.info("%s -> %d", cmdstr, status) # END handle debug printing @@ -436,7 +440,7 @@ class Git(LazyMixin): def __unpack_args(cls, arg_list): if not isinstance(arg_list, (list, tuple)): if isinstance(arg_list, text_type): - return [arg_list.encode('utf-8')] + return [arg_list.encode(defenc)] return [str(arg_list)] outlist = list() @@ -444,7 +448,7 @@ class Git(LazyMixin): if isinstance(arg_list, (list, tuple)): outlist.extend(cls.__unpack_args(arg)) elif isinstance(arg_list, text_type): - outlist.append(arg_list.encode('utf-8')) + outlist.append(arg_list.encode(defenc)) # END recursion else: outlist.append(str(arg)) @@ -569,14 +573,20 @@ class Git(LazyMixin): raise ValueError("Failed to parse header: %r" % header_line) return (tokens[0], tokens[1], int(tokens[2])) - def __prepare_ref(self, ref): - # required for command to separate refs on stdin - refstr = str(ref) # could be ref-object - if refstr.endswith("\n"): - return refstr - return refstr + "\n" + def _prepare_ref(self, ref): + # required for command to separate refs on stdin, as bytes + refstr = ref + if isinstance(ref, bytes): + # Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text + refstr = ref.decode('ascii') + elif not isinstance(ref, string_types): + refstr = str(ref) # could be ref-object + + if not refstr.endswith("\n"): + refstr += "\n" + return refstr.encode(defenc) - def __get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs): + def _get_persistent_cmd(self, attr_name, cmd_name, *args, **kwargs): cur_val = getattr(self, attr_name) if cur_val is not None: return cur_val @@ -589,7 +599,7 @@ class Git(LazyMixin): return cmd def __get_object_header(self, cmd, ref): - cmd.stdin.write(self.__prepare_ref(ref)) + cmd.stdin.write(self._prepare_ref(ref)) cmd.stdin.flush() return self._parse_object_header(cmd.stdout.readline()) @@ -601,7 +611,7 @@ class Git(LazyMixin): once and reuses the command in subsequent calls. :return: (hexsha, type_string, size_as_int)""" - cmd = self.__get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) + cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) return self.__get_object_header(cmd, ref) def get_object_data(self, ref): @@ -618,7 +628,7 @@ class Git(LazyMixin): :return: (hexsha, type_string, size_as_int, stream) :note: This method is not threadsafe, you need one independent Command instance per thread to be safe !""" - cmd = self.__get_persistent_cmd("cat_file_all", "cat_file", batch=True) + cmd = self._get_persistent_cmd("cat_file_all", "cat_file", batch=True) hexsha, typename, size = self.__get_object_header(cmd, ref) return (hexsha, typename, size, self.CatFileContentStream(size, cmd.stdout)) diff --git a/git/compat.py b/git/compat.py index a95c5667..4a892ad2 100644 --- a/git/compat.py +++ b/git/compat.py @@ -7,6 +7,8 @@ """utilities to help provide compatibility with python 3""" # flake8: noqa +import sys + from gitdb.utils.compat import ( PY3, xrange, @@ -17,11 +19,39 @@ from gitdb.utils.compat import ( from gitdb.utils.encoding import ( string_types, text_type, - force_bytes + force_bytes, + force_text ) +defenc = sys.getdefaultencoding() if PY3: import io FileType = io.IOBase else: FileType = file + # usually, this is just ascii, which might not enough for our encoding needs + # Unless it's set specifically, we override it to be utf-8 + if defenc == 'ascii': + defenc = 'utf-8' + + +def with_metaclass(meta, *bases): + """copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15""" + class metaclass(meta): + __call__ = type.__call__ + __init__ = type.__init__ + + def __new__(cls, name, nbases, d): + if nbases is None: + return type.__new__(cls, name, (), d) + # There may be clients who rely on this attribute to be set to a reasonable value, which is why + # we set the __metaclass__ attribute explicitly + if not PY3 and '___metaclass__' not in d: + d['__metaclass__'] = meta + # end + return meta(name, bases, d) + # end + # end metaclass + return metaclass(meta.__name__ + 'Helper', None, {}) + # end handle py2 + diff --git a/git/config.py b/git/config.py index 34fe290b..988547a0 100644 --- a/git/config.py +++ b/git/config.py @@ -14,12 +14,15 @@ except ImportError: import configparser as cp import inspect import logging +import abc from git.odict import OrderedDict from git.util import LockFile from git.compat import ( string_types, - FileType + FileType, + defenc, + with_metaclass ) __all__ = ('GitConfigParser', 'SectionConstraint') @@ -28,7 +31,7 @@ __all__ = ('GitConfigParser', 'SectionConstraint') log = logging.getLogger('git.config') -class MetaParserBuilder(type): +class MetaParserBuilder(abc.ABCMeta): """Utlity class wrapping base-class methods into decorators that assure read-only properties""" def __new__(metacls, name, bases, clsdict): @@ -39,7 +42,7 @@ class MetaParserBuilder(type): if kmm in clsdict: mutating_methods = clsdict[kmm] for base in bases: - methods = (t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_")) + methods = (t for t in inspect.getmembers(base, inspect.isroutine) if not t[0].startswith("_")) for name, method in methods: if name in clsdict: continue @@ -112,7 +115,7 @@ class SectionConstraint(object): return self._config -class GitConfigParser(cp.RawConfigParser, object): +class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)): """Implements specifics required to read git style configuration files. @@ -128,7 +131,6 @@ class GitConfigParser(cp.RawConfigParser, object): :note: The config is case-sensitive even when queried, hence section and option names must match perfectly.""" - __metaclass__ = MetaParserBuilder #{ Configuration # The lock type determines the type of lock to use in new configuration readers. @@ -150,7 +152,6 @@ class GitConfigParser(cp.RawConfigParser, object): # list of RawConfigParser methods able to change the instance _mutating_methods_ = ("add_section", "remove_section", "remove_option", "set") - __slots__ = ("_sections", "_defaults", "_file_or_files", "_read_only", "_is_initialized", '_lock') def __init__(self, file_or_files, read_only=True): """Initialize a configuration reader to read the given file_or_files and to @@ -162,12 +163,12 @@ class GitConfigParser(cp.RawConfigParser, object): :param read_only: If True, the ConfigParser may only read the data , but not change it. If False, only a single file path or file object may be given.""" - super(GitConfigParser, self).__init__() - # initialize base with ordered dictionaries to be sure we write the same - # file back - self._sections = OrderedDict() - self._defaults = OrderedDict() + cp.RawConfigParser.__init__(self, dict_type=OrderedDict) + # Used in python 3, needs to stay in sync with sections for underlying implementation to work + if not hasattr(self, '_proxies'): + self._proxies = self._dict() + self._file_or_files = file_or_files self._read_only = read_only self._is_initialized = False @@ -222,7 +223,8 @@ class GitConfigParser(cp.RawConfigParser, object): lineno = 0 e = None # None, or an exception while True: - line = fp.readline() + # we assume to read binary ! + line = fp.readline().decode(defenc) if not line: break lineno = lineno + 1 @@ -242,9 +244,9 @@ class GitConfigParser(cp.RawConfigParser, object): elif sectname == cp.DEFAULTSECT: cursect = self._defaults else: - # THE ONLY LINE WE CHANGED ! - cursect = OrderedDict((('__name__', sectname),)) + cursect = self._dict((('__name__', sectname),)) self._sections[sectname] = cursect + self._proxies[sectname] = None # So sections can't start with a continuation line optname = None # no section header in the file? @@ -295,7 +297,7 @@ class GitConfigParser(cp.RawConfigParser, object): # assume a path if it is not a file-object if not hasattr(file_object, "seek"): try: - fp = open(file_object) + fp = open(file_object, 'rb') close_fp = True except IOError: continue @@ -314,16 +316,17 @@ class GitConfigParser(cp.RawConfigParser, object): """Write an .ini-format representation of the configuration state in git compatible format""" def write_section(name, section_dict): - fp.write("[%s]\n" % name) + fp.write(("[%s]\n" % name).encode(defenc)) for (key, value) in section_dict.items(): if key != "__name__": - fp.write("\t%s = %s\n" % (key, str(value).replace('\n', '\n\t'))) + fp.write(("\t%s = %s\n" % (key, str(value).replace('\n', '\n\t'))).encode(defenc)) # END if key is not __name__ # END section writing if self._defaults: write_section(cp.DEFAULTSECT, self._defaults) - map(lambda t: write_section(t[0], t[1]), self._sections.items()) + for name, value in self._sections.items(): + write_section(name, value) @needs_values def write(self): @@ -371,8 +374,7 @@ class GitConfigParser(cp.RawConfigParser, object): @set_dirty_and_flush_changes def add_section(self, section): """Assures added options will stay in order""" - super(GitConfigParser, self).add_section(section) - self._sections[section] = OrderedDict() + return super(GitConfigParser, self).add_section(section) @property def read_only(self): diff --git a/git/objects/util.py b/git/objects/util.py index fdf9622b..cefef862 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -46,17 +46,17 @@ def get_object_type_by_name(object_type_name): :param object_type_name: Member of TYPES :raise ValueError: In case object_type_name is unknown""" - if object_type_name == "commit": - import commit + if object_type_name == b"commit": + from . import commit return commit.Commit - elif object_type_name == "tag": - import tag + elif object_type_name == b"tag": + from . import tag return tag.TagObject - elif object_type_name == "blob": - import blob + elif object_type_name == b"blob": + from . import blob return blob.Blob - elif object_type_name == "tree": - import tree + elif object_type_name == b"tree": + from . import tree return tree.Tree else: raise ValueError("Cannot handle unknown object type: %s" % object_type_name) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 624b1a09..1ac9ac65 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -19,7 +19,9 @@ from gitdb.util import ( hex_to_bin, LockedFD ) -from git.compat import string_types +from git.compat import ( + string_types, +) from .log import RefLog @@ -79,10 +81,10 @@ class SymbolicReference(object): @classmethod def _iter_packed_refs(cls, repo): - """Returns an iterator yielding pairs of sha1/path pairs for the corresponding refs. + """Returns an iterator yielding pairs of sha1/path pairs (as bytes) for the corresponding refs. :note: The packed refs file will be kept open as long as we iterate""" try: - fp = open(cls._get_packed_refs_path(repo), 'rb') + fp = open(cls._get_packed_refs_path(repo), 'rt') for line in fp: line = line.strip() if not line: @@ -123,12 +125,12 @@ class SymbolicReference(object): @classmethod def _get_ref_info(cls, repo, ref_path): - """Return: (sha, target_ref_path) if available, the sha the file at + """Return: (str(sha), str(target_ref_path)) if available, the sha the file at rela_path points to, or None. target_ref_path is the reference we point to, or None""" tokens = None try: - fp = open(join(repo.git_dir, ref_path), 'r') + fp = open(join(repo.git_dir, ref_path), 'rt') value = fp.read().rstrip() fp.close() # Don't only split on spaces, but on whitespace, which allows to parse lines like @@ -141,7 +143,8 @@ class SymbolicReference(object): for sha, path in cls._iter_packed_refs(repo): if path != ref_path: continue - tokens = (sha, path) + # sha will be used as + tokens = sha, path break # END for each packed ref # END handle packed refs diff --git a/git/remote.py b/git/remote.py index 9ebc52fe..63f21c4e 100644 --- a/git/remote.py +++ b/git/remote.py @@ -32,6 +32,7 @@ from git.util import ( finalize_process ) from gitdb.util import join +from git.compat import defenc __all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote') @@ -46,16 +47,16 @@ def digest_process_messages(fh, progress): :param fh: File handle to read from :return: list(line, ...) list of lines without linebreaks that did not contain progress information""" - line_so_far = '' + line_so_far = b'' dropped_lines = list() while True: - char = fh.read(1) + char = fh.read(1) # reads individual single byte strings if not char: break - if char in ('\r', '\n') and line_so_far: - dropped_lines.extend(progress._parse_progress_line(line_so_far)) - line_so_far = '' + if char in (b'\r', b'\n') and line_so_far: + dropped_lines.extend(progress._parse_progress_line(line_so_far.decode(defenc))) + line_so_far = b'' else: line_so_far += char # END process parsed line diff --git a/git/test/test_config.py b/git/test/test_config.py index 0301c54f..f02754d5 100644 --- a/git/test/test_config.py +++ b/git/test/test_config.py @@ -11,16 +11,17 @@ from git.test.lib import ( from git import ( GitConfigParser ) -from git.compat import string_types +from git.compat import ( + string_types, +) import io from copy import copy -from ConfigParser import NoSectionError - +from git.config import cp class TestBase(TestCase): def _to_memcache(self, file_path): - fp = open(file_path, "r") + fp = open(file_path, "rb") sio = io.BytesIO(fp.read()) sio.name = file_path return sio @@ -39,7 +40,7 @@ class TestBase(TestCase): w_config.write() # enforce writing # we stripped lines when reading, so the results differ - assert file_obj.getvalue() != file_obj_orig.getvalue() + assert file_obj.getvalue() and file_obj.getvalue() != file_obj_orig.getvalue() # creating an additional config writer must fail due to exclusive access self.failUnlessRaises(IOError, GitConfigParser, file_obj, read_only=False) @@ -105,4 +106,4 @@ class TestBase(TestCase): assert r_config.get_value("doesnt", "exist", default) == default # it raises if there is no default though - self.failUnlessRaises(NoSectionError, r_config.get_value, "doesnt", "exist") + self.failUnlessRaises(cp.NoSectionError, r_config.get_value, "doesnt", "exist") -- cgit v1.2.3 From 3d0556a31916a709e9da3eafb92fc6b8bf69896c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 5 Jan 2015 16:10:50 +0100 Subject: Added test of #147 to verify it works. Applied a few more fixes to commit implementation, possibly not the last --- git/objects/commit.py | 25 +++++++++---------------- git/test/test_base.py | 11 +++++++++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index 79d460ad..5ad7902b 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -373,40 +373,33 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): def _serialize(self, stream): write = stream.write - write("tree %s\n" % self.tree) + write(("tree %s\n" % self.tree).encode('ascii')) for p in self.parents: - write("parent %s\n" % p) + write(("parent %s\n" % p).encode('ascii')) a = self.author aname = a.name - if isinstance(aname, text_type): - aname = aname.encode(self.encoding) - # END handle unicode in name - c = self.committer fmt = "%s %s <%s> %s %s\n" - write(fmt % ("author", aname, a.email, + write((fmt % ("author", aname, a.email, self.authored_date, - altz_to_utctz_str(self.author_tz_offset))) + altz_to_utctz_str(self.author_tz_offset))).encode(self.encoding)) # encode committer aname = c.name - if isinstance(aname, text_type): - aname = aname.encode(self.encoding) - # END handle unicode in name - write(fmt % ("committer", aname, c.email, + write((fmt % ("committer", aname, c.email, self.committed_date, - altz_to_utctz_str(self.committer_tz_offset))) + altz_to_utctz_str(self.committer_tz_offset))).encode(self.encoding)) if self.encoding != self.default_encoding: - write("encoding %s\n" % self.encoding) + write(("encoding %s\n" % self.encoding).encode('ascii')) if self.gpgsig: write("gpgsig") for sigline in self.gpgsig.rstrip("\n").split("\n"): - write(" " + sigline + "\n") + write((" " + sigline + "\n").encode('ascii')) - write("\n") + write(b"\n") # write plain bytes, be sure its encoded according to our encoding if isinstance(self.message, text_type): diff --git a/git/test/test_base.py b/git/test/test_base.py index a14d4680..edacbd80 100644 --- a/git/test/test_base.py +++ b/git/test/test_base.py @@ -1,3 +1,4 @@ +#-*-coding:utf-8-*- # test_base.py # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors # @@ -106,3 +107,13 @@ class TestBase(TestBase): assert not rw_repo.config_reader("repository").getboolean("core", "bare") assert rw_remote_repo.config_reader("repository").getboolean("core", "bare") assert os.path.isdir(os.path.join(rw_repo.working_tree_dir, 'lib')) + + @with_rw_repo('0.1.6') + def test_add_unicode(self, rw_repo): + filename = u"שלום.txt" + + file_path = os.path.join(rw_repo.working_dir, filename) + open(file_path, "wb").write('something') + + rw_repo.git.add(rw_repo.working_dir) + rw_repo.index.commit('message') -- cgit v1.2.3 From 8a308613467a1510f8dac514624abae4e10c0779 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 5 Jan 2015 16:44:54 +0100 Subject: Fixes test_blob and improved commit writing/reading --- git/compat.py | 4 +++- git/objects/commit.py | 44 ++++++++++++++++++++++++-------------------- git/objects/fun.py | 26 +++++++++++++------------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/git/compat.py b/git/compat.py index 4a892ad2..f11d1423 100644 --- a/git/compat.py +++ b/git/compat.py @@ -27,12 +27,15 @@ defenc = sys.getdefaultencoding() if PY3: import io FileType = io.IOBase + def byte_ord(b): + return b else: FileType = file # usually, this is just ascii, which might not enough for our encoding needs # Unless it's set specifically, we override it to be utf-8 if defenc == 'ascii': defenc = 'utf-8' + byte_ord = ord def with_metaclass(meta, *bases): @@ -54,4 +57,3 @@ def with_metaclass(meta, *bases): # end metaclass return metaclass(meta.__name__ + 'Helper', None, {}) # end handle py2 - diff --git a/git/objects/commit.py b/git/objects/commit.py index 5ad7902b..f8b5c969 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -419,23 +419,25 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): next_line = None while True: parent_line = readline() - if not parent_line.startswith('parent'): + if not parent_line.startswith(b'parent'): next_line = parent_line break # END abort reading parents - self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1]))) + self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode('ascii')))) # END for each parent line self.parents = tuple(self.parents) - self.author, self.authored_date, self.author_tz_offset = parse_actor_and_date(next_line) - self.committer, self.committed_date, self.committer_tz_offset = parse_actor_and_date(readline()) + # we don't know actual author encoding before we have parsed it, so keep the lines around + author_line = next_line + committer_line = readline() # we might run into one or more mergetag blocks, skip those for now next_line = readline() - while next_line.startswith('mergetag '): + while next_line.startswith(b'mergetag '): next_line = readline() while next_line.startswith(' '): next_line = readline() + # end skip mergetags # now we can have the encoding line, or an empty line followed by the optional # message. @@ -444,39 +446,40 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): # read headers enc = next_line buf = enc.strip() - while buf != "": - if buf[0:10] == "encoding ": - self.encoding = buf[buf.find(' ') + 1:] - elif buf[0:7] == "gpgsig ": - sig = buf[buf.find(' ') + 1:] + "\n" + while buf: + if buf[0:10] == b"encoding ": + self.encoding = buf[buf.find(' ') + 1:].decode('ascii') + elif buf[0:7] == b"gpgsig ": + sig = buf[buf.find(b' ') + 1:] + b"\n" is_next_header = False while True: sigbuf = readline() - if sigbuf == "": + if not sigbuf: break - if sigbuf[0:1] != " ": + if sigbuf[0:1] != b" ": buf = sigbuf.strip() is_next_header = True break sig += sigbuf[1:] - self.gpgsig = sig.rstrip("\n") + # end read all signature + self.gpgsig = sig.rstrip(b"\n").decode('ascii') if is_next_header: continue buf = readline().strip() - # decode the authors name + try: - self.author.name = self.author.name.decode(self.encoding) + self.author, self.authored_date, self.author_tz_offset = \ + parse_actor_and_date(author_line.decode(self.encoding)) except UnicodeDecodeError: - log.error("Failed to decode author name '%s' using encoding %s", self.author.name, self.encoding, + log.error("Failed to decode author line '%s' using encoding %s", author_line, self.encoding, exc_info=True) - # END handle author's encoding - # decode committer name try: - self.committer.name = self.committer.name.decode(self.encoding) + self.committer, self.committed_date, self.committer_tz_offset = \ + parse_actor_and_date(committer_line.decode(self.encoding)) except UnicodeDecodeError: - log.error("Failed to decode committer name '%s' using encoding %s", self.committer.name, self.encoding, + log.error("Failed to decode committer line '%s' using encoding %s", committer_line, self.encoding, exc_info=True) # END handle author's encoding @@ -488,6 +491,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): except UnicodeDecodeError: log.error("Failed to decode message '%s' using encoding %s", self.message, self.encoding, exc_info=True) # END exception handling + return self #} END serializable implementation diff --git a/git/objects/fun.py b/git/objects/fun.py index db2ec7c2..f92a4c06 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -1,6 +1,9 @@ """Module with functions which are supposed to be as fast as possible""" from stat import S_ISDIR from git.compat import ( + byte_ord, + force_bytes, + defenc, xrange, text_type ) @@ -17,13 +20,13 @@ def tree_to_stream(entries, write): bit_mask = 7 # 3 bits set for binsha, mode, name in entries: - mode_str = '' + mode_str = b'' for i in xrange(6): mode_str = chr(((mode >> (i * 3)) & bit_mask) + ord_zero) + mode_str # END for each 8 octal value # git slices away the first octal if its zero - if mode_str[0] == '0': + if byte_ord(mode_str[0]) == ord_zero: mode_str = mode_str[1:] # END save a byte @@ -33,16 +36,16 @@ def tree_to_stream(entries, write): # According to my tests, this is exactly what git does, that is it just # takes the input literally, which appears to be utf8 on linux. if isinstance(name, text_type): - name = name.encode("utf8") - write("%s %s\0%s" % (mode_str, name, binsha)) + name = name.encode(defenc) + write(b''.join(mode_str, b' ', name, b'\0', binsha)) # END for each item - def tree_entries_from_data(data): """Reads the binary representation of a tree and returns tuples of Tree items - :param data: data block with tree data + :param data: data block with tree data (as bytes) :return: list(tuple(binsha, mode, tree_relative_path), ...)""" ord_zero = ord('0') + space_ord = ord(' ') len_data = len(data) i = 0 out = list() @@ -52,10 +55,10 @@ def tree_entries_from_data(data): # read mode # Some git versions truncate the leading 0, some don't # The type will be extracted from the mode later - while data[i] != ' ': + while byte_ord(data[i]) != space_ord: # move existing mode integer up one level being 3 bits # and add the actual ordinal value of the character - mode = (mode << 3) + (ord(data[i]) - ord_zero) + mode = (mode << 3) + (byte_ord(data[i]) - ord_zero) i += 1 # END while reading mode @@ -65,7 +68,7 @@ def tree_entries_from_data(data): # parse name, it is NULL separated ns = i - while data[i] != '\0': + while byte_ord(data[i]) != 0: i += 1 # END while not reached NULL @@ -73,12 +76,9 @@ def tree_entries_from_data(data): # Only use the respective unicode object if the byte stream was encoded name = data[ns:i] try: - name_enc = name.decode("utf-8") + name = name.decode(defenc) except UnicodeDecodeError: pass - else: - if len(name) > len(name_enc): - name = name_enc # END handle encoding # byte is NULL, get next 20 -- cgit v1.2.3 From e1060a2a8c90c0730c3541811df8f906dac510a7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 5 Jan 2015 17:59:22 +0100 Subject: test_commit works once again --- doc/source/changes.rst | 1 + git/cmd.py | 7 ++++++- git/config.py | 7 ++++++- git/diff.py | 2 +- git/objects/base.py | 7 ++++--- git/objects/commit.py | 4 ++-- git/objects/fun.py | 3 +-- git/refs/symbolic.py | 2 +- git/repo/base.py | 17 ++++++++++------- git/test/test_commit.py | 44 +++++++++++++++++++++++--------------------- 10 files changed, 55 insertions(+), 39 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index cf528b28..06a73f41 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -5,6 +5,7 @@ Changelog 0.3.4 - python 3 support ======================== * Internally, hexadecimal SHA1 are treated as ascii encoded strings. Binary SHA1 are treated as bytes. +* 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. 0.3.3 ===== diff --git a/git/cmd.py b/git/cmd.py index c536b43c..2bff3310 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -320,6 +320,7 @@ class Git(LazyMixin): always be created with a pipe due to issues with subprocess. This merely is a workaround as data will be copied from the output pipe to the given output stream directly. + Judging from the implementation, you shouldn't use this flag ! :param subprocess_kwargs: Keyword arguments to be passed to subprocess.Popen. Please note that @@ -411,9 +412,13 @@ class Git(LazyMixin): else: raise GitCommandError(command, status, stderr_value) + + if isinstance(stdout_value, bytes): # could also be output_stream + stdout_value = stdout_value.decode(defenc) + # Allow access to the command's status code if with_extended_output: - return (status, stdout_value, stderr_value) + return (status, stdout_value, stderr_value.decode(defenc)) else: return stdout_value diff --git a/git/config.py b/git/config.py index 988547a0..7917bc5a 100644 --- a/git/config.py +++ b/git/config.py @@ -201,6 +201,11 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje self.write() except IOError: log.error("Exception during destruction of GitConfigParser", exc_info=True) + except ReferenceError: + # This happens in PY3 ... and usually means that some state cannot be written + # as the sections dict cannot be iterated + # Usually when shutting down the interpreter, don'y know how to fix this + pass finally: self._lock._release_lock() @@ -345,7 +350,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje # END get lock for physical files if not hasattr(fp, "seek"): - fp = open(self._file_or_files, "w") + fp = open(self._file_or_files, "wb") close_fp = True else: fp.seek(0) diff --git a/git/diff.py b/git/diff.py index b3e7245b..1692d83e 100644 --- a/git/diff.py +++ b/git/diff.py @@ -195,7 +195,7 @@ class Diff(object): """, re.VERBOSE | re.MULTILINE) # can be used for comparisons NULL_HEX_SHA = "0" * 40 - NULL_BIN_SHA = "\0" * 20 + NULL_BIN_SHA = b"\0" * 20 __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "new_file", "deleted_file", "rename_from", "rename_to", "diff") diff --git a/git/objects/base.py b/git/objects/base.py index 1f0d5752..004e3981 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -21,7 +21,7 @@ class Object(LazyMixin): """Implements an Object which may be Blobs, Trees, Commits and Tags""" NULL_HEX_SHA = '0' * 40 - NULL_BIN_SHA = '\0' * 20 + NULL_BIN_SHA = b'\0' * 20 TYPES = (dbtyp.str_blob_type, dbtyp.str_tree_type, dbtyp.str_commit_type, dbtyp.str_tag_type) __slots__ = ("repo", "binsha", "size") @@ -94,7 +94,7 @@ class Object(LazyMixin): def __str__(self): """:return: string of our SHA1 as understood by all git commands""" - return bin_to_hex(self.binsha) + return self.hexsha def __repr__(self): """:return: string with pythonic representation of our object""" @@ -103,7 +103,8 @@ class Object(LazyMixin): @property def hexsha(self): """:return: 40 byte hex version of our 20 byte binary sha""" - return bin_to_hex(self.binsha) + # b2a_hex produces bytes + return bin_to_hex(self.binsha).decode('ascii') @property def data_stream(self): diff --git a/git/objects/commit.py b/git/objects/commit.py index f8b5c969..53af22cd 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -62,7 +62,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): "author", "authored_date", "author_tz_offset", "committer", "committed_date", "committer_tz_offset", "message", "parents", "encoding", "gpgsig") - _id_attribute_ = "binsha" + _id_attribute_ = "hexsha" def __init__(self, repo, binsha, tree=None, author=None, authored_date=None, author_tz_offset=None, committer=None, committed_date=None, committer_tz_offset=None, @@ -395,7 +395,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): write(("encoding %s\n" % self.encoding).encode('ascii')) if self.gpgsig: - write("gpgsig") + write(b"gpgsig") for sigline in self.gpgsig.rstrip("\n").split("\n"): write((" " + sigline + "\n").encode('ascii')) diff --git a/git/objects/fun.py b/git/objects/fun.py index f92a4c06..610bdb5c 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -2,7 +2,6 @@ from stat import S_ISDIR from git.compat import ( byte_ord, - force_bytes, defenc, xrange, text_type @@ -37,7 +36,7 @@ def tree_to_stream(entries, write): # takes the input literally, which appears to be utf8 on linux. if isinstance(name, text_type): name = name.encode(defenc) - write(b''.join(mode_str, b' ', name, b'\0', binsha)) + write(b''.join((mode_str, b' ', name, b'\0', binsha))) # END for each item def tree_entries_from_data(data): diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 1ac9ac65..252462a9 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -308,7 +308,7 @@ class SymbolicReference(object): lfd = LockedFD(fpath) fd = lfd.open(write=True, stream=True) - fd.write(write_value) + fd.write(write_value.encode('ascii')) lfd.commit() # Adjust the reflog diff --git a/git/repo/base.py b/git/repo/base.py index f92a85ce..27c640ff 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -47,7 +47,10 @@ from .fun import ( read_gitfile, touch, ) -from git.compat import text_type +from git.compat import ( + text_type, + defenc +) import os import sys @@ -177,11 +180,11 @@ class Repo(object): # Description property def _get_description(self): filename = join(self.git_dir, 'description') - return open(filename).read().rstrip() + return open(filename, 'rb').read().rstrip().decode(defenc) def _set_description(self, descr): filename = join(self.git_dir, 'description') - open(filename, 'w').write(descr + '\n') + open(filename, 'wb').write((descr + '\n').encode(defenc)) description = property(_get_description, _set_description, doc="the project's description") @@ -464,8 +467,8 @@ class Repo(object): if os.path.exists(alternates_path): try: - f = open(alternates_path) - alts = f.read() + f = open(alternates_path, 'rb') + alts = f.read().decode(defenc) finally: f.close() return alts.strip().splitlines() @@ -489,8 +492,8 @@ class Repo(object): os.remove(alternates_path) else: try: - f = open(alternates_path, 'w') - f.write("\n".join(alts)) + f = open(alternates_path, 'wb') + f.write("\n".join(alts).encode(defenc)) finally: f.close() # END file handling diff --git a/git/test/test_commit.py b/git/test/test_commit.py index 5f45e59d..37a54092 100644 --- a/git/test/test_commit.py +++ b/git/test/test_commit.py @@ -51,7 +51,7 @@ def assert_commit_serialization(rwrepo, commit_id, print_performance_info=False) stream.seek(0) istream = rwrepo.odb.store(IStream(Commit.type, streamlen, stream)) - assert istream.hexsha == cm.hexsha + assert istream.hexsha == cm.hexsha.encode('ascii') nc = Commit(rwrepo, Commit.NULL_BIN_SHA, cm.tree, cm.author, cm.authored_date, cm.author_tz_offset, @@ -129,7 +129,7 @@ class TestCommit(TestBase): def test_unicode_actor(self): # assure we can parse unicode actors correctly - name = "Üäöß ÄußÉ".decode("utf-8") + name = u"Üäöß ÄußÉ" assert len(name) == 9 special = Actor._from_string(u"%s " % name) assert special.name == name @@ -146,13 +146,13 @@ class TestCommit(TestBase): # basic branch first, depth first dfirst = start.traverse(branch_first=False) bfirst = start.traverse(branch_first=True) - assert dfirst.next() == p0 - assert dfirst.next() == p00 + assert next(dfirst) == p0 + assert next(dfirst) == p00 - assert bfirst.next() == p0 - assert bfirst.next() == p1 - assert bfirst.next() == p00 - assert bfirst.next() == p10 + assert next(bfirst) == p0 + assert next(bfirst) == p1 + assert next(bfirst) == p00 + assert next(bfirst) == p10 # at some point, both iterations should stop assert list(bfirst)[-1] == first @@ -161,19 +161,19 @@ class TestCommit(TestBase): assert len(l[0]) == 2 # ignore self - assert start.traverse(ignore_self=False).next() == start + assert next(start.traverse(ignore_self=False)) == start # depth assert len(list(start.traverse(ignore_self=False, depth=0))) == 1 # prune - assert start.traverse(branch_first=1, prune=lambda i, d: i == p0).next() == p1 + assert next(start.traverse(branch_first=1, prune=lambda i, d: i == p0)) == p1 # predicate - assert start.traverse(branch_first=1, predicate=lambda i, d: i == p1).next() == p1 + assert next(start.traverse(branch_first=1, predicate=lambda i, d: i == p1)) == p1 # traversal should stop when the beginning is reached - self.failUnlessRaises(StopIteration, first.traverse().next) + self.failUnlessRaises(StopIteration, next, first.traverse()) # parents of the first commit should be empty ( as the only parent has a null # sha ) @@ -210,7 +210,7 @@ class TestCommit(TestBase): first_parent=True, bisect_all=True) - commits = Commit._iter_from_process_or_stream(self.rorepo, StringProcessAdapter(revs)) + commits = Commit._iter_from_process_or_stream(self.rorepo, StringProcessAdapter(revs.encode('ascii'))) expected_ids = ( '7156cece3c49544abb6bf7a0c218eb36646fad6d', '1f66cfbbce58b4b552b041707a12d437cc5f400a', @@ -224,8 +224,10 @@ class TestCommit(TestBase): assert self.rorepo.tag('refs/tags/0.1.5').commit.count() == 143 def test_list(self): + # This doesn't work anymore, as we will either attempt getattr with bytes, or compare 20 byte string + # with actual 20 byte bytes. This usage makes no sense anyway assert isinstance(Commit.list_items(self.rorepo, '0.1.5', max_count=5)[ - hex_to_bin('5117c9c8a4d3af19a9958677e45cda9269de1541')], Commit) + '5117c9c8a4d3af19a9958677e45cda9269de1541'], Commit) def test_str(self): commit = Commit(self.rorepo, Commit.NULL_BIN_SHA) @@ -247,12 +249,12 @@ class TestCommit(TestBase): c = self.rorepo.commit('0.1.5') for skip in (0, 1): piter = c.iter_parents(skip=skip) - first_parent = piter.next() + first_parent = next(piter) assert first_parent != c assert first_parent == c.parents[0] # END for each - def test_base(self): + def test_name_rev(self): name_rev = self.rorepo.head.commit.name_rev assert isinstance(name_rev, string_types) @@ -270,10 +272,10 @@ class TestCommit(TestBase): assert isinstance(cmt.message, text_type) # it automatically decodes it as such assert isinstance(cmt.author.name, text_type) # same here - cmt.message = "üäêèß".decode("utf-8") + cmt.message = u"üäêèß" assert len(cmt.message) == 5 - cmt.author.name = "äüß".decode("utf-8") + cmt.author.name = u"äüß" assert len(cmt.author.name) == 3 cstream = BytesIO() @@ -292,7 +294,7 @@ class TestCommit(TestBase): def test_gpgsig(self): cmt = self.rorepo.commit() - cmt._deserialize(open(fixture_path('commit_with_gpgsig'))) + cmt._deserialize(open(fixture_path('commit_with_gpgsig'), 'rb')) fixture_sig = """-----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) @@ -318,7 +320,7 @@ JzJMZDRLQLFvnzqZuCjE cstream = BytesIO() cmt._serialize(cstream) - assert re.search(r"^gpgsig $", cstream.getvalue(), re.MULTILINE) + assert re.search(r"^gpgsig $", cstream.getvalue().decode('ascii'), re.MULTILINE) cstream.seek(0) cmt.gpgsig = None @@ -328,4 +330,4 @@ JzJMZDRLQLFvnzqZuCjE cmt.gpgsig = None cstream = BytesIO() cmt._serialize(cstream) - assert not re.search(r"^gpgsig ", cstream.getvalue(), re.MULTILINE) + assert not re.search(r"^gpgsig ", cstream.getvalue().decode('ascii'), re.MULTILINE) -- cgit v1.2.3 From 31b3673bdb9d8fb7feea8ae887be455c4a880f76 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 5 Jan 2015 18:03:08 +0100 Subject: test_diff works --- git/diff.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git/diff.py b/git/diff.py index 1692d83e..3c4e8529 100644 --- a/git/diff.py +++ b/git/diff.py @@ -10,6 +10,8 @@ from gitdb.util import hex_to_bin from .objects.blob import Blob from .objects.util import mode_str_to_int +from git.compat import defenc + __all__ = ('Diffable', 'DiffIndex', 'Diff') @@ -294,7 +296,7 @@ class Diff(object): :param stream: result of 'git diff' as a stream (supporting file protocol) :return: git.DiffIndex """ # for now, we have to bake the stream - text = stream.read() + text = stream.read().decode(defenc) index = DiffIndex() diff_header = cls.re_header.match @@ -323,6 +325,7 @@ class Diff(object): # :100644 100644 687099101... 37c5e30c8... M .gitignore index = DiffIndex() for line in stream: + line = line.decode(defenc) if not line.startswith(":"): continue # END its not a valid diff line -- cgit v1.2.3 From 4a67e4e49c4e7b82e416067df69c72656213e886 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 5 Jan 2015 18:21:49 +0100 Subject: test_fun works --- git/compat.py | 3 +++ git/index/fun.py | 9 +++++---- git/objects/fun.py | 5 +++-- git/test/test_fun.py | 12 ++++++------ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/git/compat.py b/git/compat.py index f11d1423..b9205418 100644 --- a/git/compat.py +++ b/git/compat.py @@ -29,6 +29,8 @@ if PY3: FileType = io.IOBase def byte_ord(b): return b + def bchr(n): + return bytes([n]) else: FileType = file # usually, this is just ascii, which might not enough for our encoding needs @@ -36,6 +38,7 @@ else: if defenc == 'ascii': defenc = 'utf-8' byte_ord = ord + bchr = chr def with_metaclass(meta, *bases): diff --git a/git/index/fun.py b/git/index/fun.py index 0e49ae8d..3e66a7ba 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -37,6 +37,7 @@ from .util import ( from gitdb.base import IStream from gitdb.typ import str_tree_type +from git.compat import defenc __all__ = ('write_cache', 'read_cache', 'write_tree_from_cache', 'entry_key', 'stat_mode_to_index_mode', 'S_IFGITLINK') @@ -86,9 +87,9 @@ def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1 flags = plen | (entry[2] & CE_NAMEMASK_INV) # clear possible previous values write(pack(">LLLLLL20sH", entry[6], entry[7], entry[0], entry[8], entry[9], entry[10], entry[1], flags)) - write(path) + write(path.encode(defenc)) real_size = ((tell() - beginoffset + 8) & ~7) - write("\0" * ((beginoffset + real_size) - tell())) + write(b"\0" * ((beginoffset + real_size) - tell())) # END for each entry # write previously cached extensions data @@ -102,7 +103,7 @@ def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1 def read_header(stream): """Return tuple(version_long, num_entries) from the given stream""" type_id = stream.read(4) - if type_id != "DIRC": + if type_id != b"DIRC": raise AssertionError("Invalid index file header: %r" % type_id) version, num_entries = unpack(">LL", stream.read(4 * 2)) @@ -142,7 +143,7 @@ def read_cache(stream): (dev, ino, mode, uid, gid, size, sha, flags) = \ unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2)) path_size = flags & CE_NAMEMASK - path = read(path_size) + path = read(path_size).decode(defenc) real_size = ((tell() - beginoffset + 8) & ~7) read((beginoffset + real_size) - tell()) diff --git a/git/objects/fun.py b/git/objects/fun.py index 610bdb5c..ba8dbcf4 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -4,7 +4,8 @@ from git.compat import ( byte_ord, defenc, xrange, - text_type + text_type, + bchr ) __all__ = ('tree_to_stream', 'tree_entries_from_data', 'traverse_trees_recursive', @@ -21,7 +22,7 @@ def tree_to_stream(entries, write): for binsha, mode, name in entries: mode_str = b'' for i in xrange(6): - mode_str = chr(((mode >> (i * 3)) & bit_mask) + ord_zero) + mode_str + mode_str = bchr(((mode >> (i * 3)) & bit_mask) + ord_zero) + mode_str # END for each 8 octal value # git slices away the first octal if its zero diff --git a/git/test/test_fun.py b/git/test/test_fun.py index 9d3e749e..40d040b9 100644 --- a/git/test/test_fun.py +++ b/git/test/test_fun.py @@ -30,7 +30,7 @@ from io import BytesIO class TestFun(TestBase): def _assert_index_entries(self, entries, trees): - index = IndexFile.from_tree(self.rorepo, *[self.rorepo.tree(bin_to_hex(t)) for t in trees]) + index = IndexFile.from_tree(self.rorepo, *[self.rorepo.tree(bin_to_hex(t).decode('ascii')) for t in trees]) assert entries assert len(index.entries) == len(entries) for entry in entries: @@ -91,9 +91,9 @@ class TestFun(TestBase): assert has_conflict == (len([e for e in entries if e.stage != 0]) > 0) mktree = self.mktree - shaa = "\1" * 20 - shab = "\2" * 20 - shac = "\3" * 20 + shaa = b"\1" * 20 + shab = b"\2" * 20 + shac = b"\3" * 20 odb = rwrepo.odb @@ -256,6 +256,6 @@ class TestFun(TestBase): assert entries # END for each commit - def test_tree_entries_from_data(self): + def test_tree_entries_from_data_with_failing_name_decode(self): r = tree_entries_from_data(b'100644 \x9f\0aaa') - assert r == [('aaa', 33188, '\x9f')], r + assert r == [(b'aaa', 33188, b'\x9f')], r -- cgit v1.2.3 From 7297ff651c3cc6abf648b3fe730c2b5b1f3edbd3 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 5 Jan 2015 18:30:30 +0100 Subject: test_git works --- git/test/test_git.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/git/test/test_git.py b/git/test/test_git.py index 553f8d1b..8087d6f8 100644 --- a/git/test/test_git.py +++ b/git/test/test_git.py @@ -1,3 +1,4 @@ +#-*-coding:utf-8-*- # test_git.py # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors # @@ -32,12 +33,12 @@ class TestGit(TestBase): assert_equal(git.call_args, ((['git', 'version'],), {})) def test_call_unpack_args_unicode(self): - args = Git._Git__unpack_args(u'Unicode' + unichr(40960)) - assert_equal(args, ['Unicode\xea\x80\x80']) + args = Git._Git__unpack_args(u'Unicode€™') + assert_equal(args, [b'Unicode\xe2\x82\xac\xe2\x84\xa2']) def test_call_unpack_args(self): - args = Git._Git__unpack_args(['git', 'log', '--', u'Unicode' + unichr(40960)]) - assert_equal(args, ['git', 'log', '--', 'Unicode\xea\x80\x80']) + args = Git._Git__unpack_args(['git', 'log', '--', u'Unicode€™']) + assert_equal(args, [b'git', b'log', b'--', b'Unicode\xe2\x82\xac\xe2\x84\xa2']) @raises(GitCommandError) def test_it_raises_errors(self): @@ -75,13 +76,13 @@ class TestGit(TestBase): import subprocess as sp hexsha = "b2339455342180c7cc1e9bba3e9f181f7baa5167" g = self.git.cat_file(batch_check=True, istream=sp.PIPE, as_process=True) - g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n") + g.stdin.write(b"b2339455342180c7cc1e9bba3e9f181f7baa5167\n") g.stdin.flush() obj_info = g.stdout.readline() # read header + data g = self.git.cat_file(batch=True, istream=sp.PIPE, as_process=True) - g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n") + g.stdin.write(b"b2339455342180c7cc1e9bba3e9f181f7baa5167\n") g.stdin.flush() obj_info_two = g.stdout.readline() assert obj_info == obj_info_two @@ -92,7 +93,7 @@ class TestGit(TestBase): g.stdout.read(1) # now we should be able to read a new object - g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n") + g.stdin.write(b"b2339455342180c7cc1e9bba3e9f181f7baa5167\n") g.stdin.flush() assert g.stdout.readline() == obj_info -- cgit v1.2.3 From 45c1c99a22e95d730d3096c339d97181d314d6ca Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 10:43:25 +0100 Subject: test_index works --- git/compat.py | 4 ++++ git/config.py | 9 ++++++++- git/index/base.py | 23 ++++++++++++----------- git/index/fun.py | 2 +- git/index/typ.py | 2 +- git/repo/base.py | 5 ++++- git/test/test_index.py | 37 +++++++++++++++++++------------------ git/util.py | 9 +++------ 8 files changed, 52 insertions(+), 39 deletions(-) diff --git a/git/compat.py b/git/compat.py index b9205418..5c330e5b 100644 --- a/git/compat.py +++ b/git/compat.py @@ -31,6 +31,8 @@ if PY3: return b def bchr(n): return bytes([n]) + def mviter(d): + return d.values() else: FileType = file # usually, this is just ascii, which might not enough for our encoding needs @@ -39,6 +41,8 @@ else: defenc = 'utf-8' byte_ord = ord bchr = chr + def mviter(d): + return d.itervalues() def with_metaclass(meta, *bases): diff --git a/git/config.py b/git/config.py index 7917bc5a..96991b84 100644 --- a/git/config.py +++ b/git/config.py @@ -99,6 +99,13 @@ class SectionConstraint(object): self._config = config self._section_name = section + def __del__(self): + # Yes, for some reason, we have to call it explicitly for it to work in PY3 ! + # Apparently __del__ doesn't get call anymore if refcount becomes 0 + # Ridiculous ... . + self._config.__del__() + # del self._config + def __getattr__(self, attr): if attr in self._valid_attrs_: return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs) @@ -193,7 +200,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje """Write pending changes if required and release locks""" # checking for the lock here makes sure we do not raise during write() # in case an invalid parser was created who could not get a lock - if self.read_only or not self._lock._has_lock(): + if self.read_only or (self._lock and not self._lock._has_lock()): return try: diff --git a/git/index/base.py b/git/index/base.py index a994e7b6..cc883469 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -43,7 +43,9 @@ from git.compat import ( izip, xrange, string_types, - force_bytes + force_bytes, + defenc, + mviter ) from git.util import ( @@ -105,7 +107,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): repository's index on demand.""" self.repo = repo self.version = self._VERSION - self._extension_data = '' + self._extension_data = b'' self._file_path = file_path or self._index_path() def _set_cache_(self, attr): @@ -165,9 +167,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): def _entries_sorted(self): """:return: list of entries, in a sorted fashion, first by path, then by stage""" - entries_sorted = self.entries.values() - entries_sorted.sort(key=lambda e: (e.path, e.stage)) # use path/stage as sort key - return entries_sorted + return sorted(self.entries.values(), key=lambda e: (e.path, e.stage)) def _serialize(self, stream, ignore_tree_extension_data=False): entries = self._entries_sorted() @@ -399,7 +399,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): fprogress(filepath, False, item) rval = None try: - proc.stdin.write("%s\n" % filepath) + proc.stdin.write(("%s\n" % filepath).encode(defenc)) except IOError: # pipe broke, usually because some error happend raise fmakeexc() @@ -418,7 +418,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): Function(t) returning True if tuple(stage, Blob) should be yielded by the iterator. A default filter, the BlobFilter, allows you to yield blobs only if they match a given list of paths. """ - for entry in self.entries.itervalues(): + for entry in mviter(self.entries): blob = entry.to_blob(self.repo) blob.size = entry.size output = (entry.stage, blob) @@ -443,7 +443,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): for stage, blob in self.iter_blobs(is_unmerged_blob): path_map.setdefault(blob.path, list()).append((stage, blob)) # END for each unmerged blob - for l in path_map.itervalues(): + for l in mviter(path_map): l.sort() return path_map @@ -860,7 +860,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): # parse result - first 0:n/2 lines are 'checking ', the remaining ones # are the 'renaming' ones which we parse - for ln in xrange(len(mvlines) / 2, len(mvlines)): + for ln in xrange(int(len(mvlines) / 2), len(mvlines)): tokens = mvlines[ln].split(' to ') assert len(tokens) == 2, "Too many tokens in %s" % mvlines[ln] @@ -958,6 +958,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): if not stderr: return # line contents: + stderr = stderr.decode(defenc) # git-checkout-index: this already exists failed_files = list() failed_reasons = list() @@ -1006,7 +1007,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): proc = self.repo.git.checkout_index(*args, **kwargs) proc.wait() fprogress(None, True, None) - rval_iter = (e.path for e in self.entries.itervalues()) + rval_iter = (e.path for e in mviter(self.entries)) handle_stderr(proc, rval_iter) return rval_iter else: @@ -1036,7 +1037,7 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): dir = co_path if not dir.endswith('/'): dir += '/' - for entry in self.entries.itervalues(): + for entry in mviter(self.entries): if entry.path.startswith(dir): p = entry.path self._write_path_to_stdin(proc, p, p, make_exc, diff --git a/git/index/fun.py b/git/index/fun.py index 3e66a7ba..f0dee961 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -73,7 +73,7 @@ def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1 # header version = 2 - write("DIRC") + write(b"DIRC") write(pack(">LL", version, len(entries))) # body diff --git a/git/index/typ.py b/git/index/typ.py index 692e1e18..0998ecb0 100644 --- a/git/index/typ.py +++ b/git/index/typ.py @@ -75,7 +75,7 @@ class BaseIndexEntry(tuple): @property def hexsha(self): """hex version of our sha""" - return b2a_hex(self[1]) + return b2a_hex(self[1]).decode('ascii') @property def stage(self): diff --git a/git/repo/base.py b/git/repo/base.py index 27c640ff..3e0e51cc 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -732,7 +732,10 @@ class Repo(object): # sure repo = cls(os.path.abspath(path), odbt=odbt) if repo.remotes: - repo.remotes[0].config_writer.set_value('url', repo.remotes[0].url.replace("\\\\", "\\").replace("\\", "/")) + writer = repo.remotes[0].config_writer + writer.set_value('url', repo.remotes[0].url.replace("\\\\", "\\").replace("\\", "/")) + # PY3: be sure cleanup is performed and lock is released + del writer # END handle remote repo return repo diff --git a/git/test/test_index.py b/git/test/test_index.py index 38cc3563..d81d08ef 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -48,7 +48,7 @@ class TestIndex(TestBase): def _assert_fprogress(self, entries): assert len(entries) == len(self._fprogress_map) - for path, call_count in self._fprogress_map.iteritems(): + for path, call_count in self._fprogress_map.items(): assert call_count == 2 # END for each item in progress map self._reset_progress() @@ -86,7 +86,7 @@ class TestIndex(TestBase): assert index.version > 0 # test entry - entry = index.entries.itervalues().next() + entry = next(iter(index.entries.values())) for attr in ("path", "ctime", "mtime", "dev", "inode", "mode", "uid", "gid", "size", "binsha", "hexsha", "stage"): getattr(entry, attr) @@ -100,7 +100,7 @@ class TestIndex(TestBase): # test stage index_merge = IndexFile(self.rorepo, fixture_path("index_merge")) assert len(index_merge.entries) == 106 - assert len(list(e for e in index_merge.entries.itervalues() if e.stage != 0)) + assert len(list(e for e in index_merge.entries.values() if e.stage != 0)) # write the data - it must match the original tmpfile = tempfile.mktemp() @@ -167,7 +167,7 @@ class TestIndex(TestBase): assert unmerged_blob_map # pick the first blob at the first stage we find and use it as resolved version - three_way_index.resolve_blobs(l[0][1] for l in unmerged_blob_map.itervalues()) + three_way_index.resolve_blobs(l[0][1] for l in unmerged_blob_map.values()) tree = three_way_index.write_tree() assert isinstance(tree, Tree) num_blobs = 0 @@ -201,7 +201,7 @@ class TestIndex(TestBase): # Add a change with a NULL sha that should conflict with next_commit. We # pretend there was a change, but we do not even bother adding a proper # sha for it ( which makes things faster of course ) - manifest_fake_entry = BaseIndexEntry((manifest_entry[0], "\0" * 20, 0, manifest_entry[3])) + manifest_fake_entry = BaseIndexEntry((manifest_entry[0], b"\0" * 20, 0, manifest_entry[3])) # try write flag self._assert_entries(rw_repo.index.add([manifest_fake_entry], write=False)) # add actually resolves the null-hex-sha for us as a feature, but we can @@ -236,7 +236,7 @@ class TestIndex(TestBase): # now make a proper three way merge with unmerged entries unmerged_tree = IndexFile.from_tree(rw_repo, parent_commit, tree, next_commit) unmerged_blobs = unmerged_tree.unmerged_blobs() - assert len(unmerged_blobs) == 1 and unmerged_blobs.keys()[0] == manifest_key[0] + assert len(unmerged_blobs) == 1 and list(unmerged_blobs.keys())[0] == manifest_key[0] @with_rw_repo('0.1.6') def test_index_file_diffing(self, rw_repo): @@ -295,7 +295,7 @@ class TestIndex(TestBase): assert index.diff(None) # reset the working copy as well to current head,to pull 'back' as well - new_data = "will be reverted" + new_data = b"will be reverted" file_path = os.path.join(rw_repo.working_tree_dir, "CHANGES") fp = open(file_path, "wb") fp.write(new_data) @@ -312,7 +312,7 @@ class TestIndex(TestBase): # test full checkout test_file = os.path.join(rw_repo.working_tree_dir, "CHANGES") - open(test_file, 'ab').write("some data") + open(test_file, 'ab').write(b"some data") rval = index.checkout(None, force=True, fprogress=self._fprogress) assert 'CHANGES' in list(rval) self._assert_fprogress([None]) @@ -336,7 +336,7 @@ class TestIndex(TestBase): self.failUnlessRaises(CheckoutError, index.checkout, paths=["doesnt/exist"]) # checkout file with modifications - append_data = "hello" + append_data = b"hello" fp = open(test_file, "ab") fp.write(append_data) fp.close() @@ -346,13 +346,13 @@ class TestIndex(TestBase): assert len(e.failed_files) == 1 and e.failed_files[0] == os.path.basename(test_file) assert (len(e.failed_files) == len(e.failed_reasons)) and isinstance(e.failed_reasons[0], string_types) assert len(e.valid_files) == 0 - assert open(test_file).read().endswith(append_data) + assert open(test_file, 'rb').read().endswith(append_data) else: raise AssertionError("Exception CheckoutError not thrown") # if we force it it should work index.checkout(test_file, force=True) - assert not open(test_file).read().endswith(append_data) + assert not open(test_file, 'rb').read().endswith(append_data) # checkout directory shutil.rmtree(os.path.join(rw_repo.working_tree_dir, "lib")) @@ -379,14 +379,15 @@ class TestIndex(TestBase): uname = "Some Developer" umail = "sd@company.com" - rw_repo.config_writer().set_value("user", "name", uname) - rw_repo.config_writer().set_value("user", "email", umail) + writer = rw_repo.config_writer() + writer.set_value("user", "name", uname) + writer.set_value("user", "email", umail) # remove all of the files, provide a wild mix of paths, BaseIndexEntries, # IndexEntries def mixed_iterator(): count = 0 - for entry in index.entries.itervalues(): + for entry in index.entries.values(): type_id = count % 4 if type_id == 0: # path yield entry.path @@ -500,7 +501,7 @@ class TestIndex(TestBase): # mode 0 not allowed null_hex_sha = Diff.NULL_HEX_SHA - null_bin_sha = "\0" * 20 + null_bin_sha = b"\0" * 20 self.failUnlessRaises(ValueError, index.reset( new_commit).add, [BaseIndexEntry((0, null_bin_sha, 0, "doesntmatter"))]) @@ -526,7 +527,7 @@ class TestIndex(TestBase): assert S_ISLNK(index.entries[index.entry_key("my_real_symlink", 0)].mode) # we expect only the target to be written - assert index.repo.odb.stream(entries[0].binsha).read() == target + assert index.repo.odb.stream(entries[0].binsha).read().decode('ascii') == target # END real symlink test # add fake symlink and assure it checks-our as symlink @@ -618,7 +619,7 @@ class TestIndex(TestBase): for fid in range(3): fname = 'newfile%i' % fid - open(fname, 'wb').write("abcd") + open(fname, 'wb').write(b"abcd") yield Blob(rw_repo, Blob.NULL_BIN_SHA, 0o100644, fname) # END for each new file # END path producer @@ -716,5 +717,5 @@ class TestIndex(TestBase): try: rw_bare_repo.index.add([path]) except Exception as e: - asserted = "does not have a working tree" in e.message + asserted = "does not have a working tree" in str(e) assert asserted, "Adding using a filename is not correctly asserted." diff --git a/git/util.py b/git/util.py index b3a22883..4de736d3 100644 --- a/git/util.py +++ b/git/util.py @@ -446,7 +446,7 @@ class IndexFileSHA1Writer(object): def __init__(self, f): self.f = f - self.sha1 = make_sha("") + self.sha1 = make_sha(b"") def write(self, data): self.sha1.update(data) @@ -490,10 +490,7 @@ class LockFile(object): def _has_lock(self): """:return: True if we have a lock and if the lockfile still exists :raise AssertionError: if our lock-file does not exist""" - if not self._owns_lock: - return False - - return True + return self._owns_lock def _obtain_lock_or_raise(self): """Create a lock file as flag for other instances, mark our instance as lock-holder @@ -531,7 +528,7 @@ class LockFile(object): # on bloody windows, the file needs write permissions to be removable. # Why ... if os.name == 'nt': - os.chmod(lfp, int("0777", 8)) + os.chmod(lfp, 0o777) # END handle win32 os.remove(lfp) except OSError: -- cgit v1.2.3 From b5a37564b6eec05b98c2efa5edcd1460a2df02aa Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 11:02:31 +0100 Subject: test_reflog works --- git/refs/log.py | 22 +++++++++++++--------- git/test/test_reflog.py | 5 +++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/git/refs/log.py b/git/refs/log.py index f397548e..c3019d39 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -19,7 +19,8 @@ from git.objects.util import ( ) from git.compat import ( xrange, - string_types + string_types, + defenc ) import time @@ -38,9 +39,8 @@ class RefLogEntry(tuple): def __repr__(self): """Representation of ourselves in git reflog format""" act = self.actor - name = act.name.encode('utf-8') time = self.time - return self._fmt % (self.oldhexsha, self.newhexsha, name, act.email, + return self._fmt % (self.oldhexsha, self.newhexsha, act.name, act.email, time[0], altz_to_utctz_str(time[1]), self.message) @property @@ -82,8 +82,9 @@ class RefLogEntry(tuple): @classmethod def from_line(cls, line): """:return: New RefLogEntry instance from the given revlog line. - :param line: line without trailing newline + :param line: line bytes without trailing newline :raise ValueError: If line could not be parsed""" + line = line.decode(defenc) try: info, msg = line.split('\t', 1) except ValueError: @@ -253,15 +254,18 @@ class RefLog(list, Serializable): # END handle sha type assure_directory_exists(filepath, is_file=True) committer = isinstance(config_reader, Actor) and config_reader or Actor.committer(config_reader) - entry = RefLogEntry( - (bin_to_hex(oldbinsha), bin_to_hex(newbinsha), committer, (int(time.time()), time.altzone), message)) + entry = RefLogEntry(( + bin_to_hex(oldbinsha).decode('ascii'), + bin_to_hex(newbinsha).decode('ascii'), + committer, (int(time.time()), time.altzone), message + )) lf = LockFile(filepath) lf._obtain_lock_or_raise() - fd = open(filepath, 'a') + fd = open(filepath, 'ab') try: - fd.write(repr(entry)) + fd.write(repr(entry).encode(defenc)) finally: fd.close() lf._release_lock() @@ -286,7 +290,7 @@ class RefLog(list, Serializable): # write all entries for e in self: - write(repr(e)) + write(repr(e).encode(defenc)) # END for each entry def _deserialize(self, stream): diff --git a/git/test/test_reflog.py b/git/test/test_reflog.py index 4efb8025..3571e083 100644 --- a/git/test/test_reflog.py +++ b/git/test/test_reflog.py @@ -8,6 +8,7 @@ from git.refs import ( RefLog ) from git.util import Actor +from gitdb.util import hex_to_bin import tempfile import shutil @@ -51,7 +52,7 @@ class TestRefLog(TestBase): assert len(reflog) # iter_entries works with path and with stream - assert len(list(RefLog.iter_entries(open(rlp_master)))) + assert len(list(RefLog.iter_entries(open(rlp_master, 'rb')))) assert len(list(RefLog.iter_entries(rlp_master))) # raise on invalid revlog @@ -65,7 +66,7 @@ class TestRefLog(TestBase): self.failUnlessRaises(ValueError, RefLog().write) # test serialize and deserialize - results must match exactly - binsha = chr(255) * 20 + binsha = hex_to_bin(('f' * 40).encode('ascii')) msg = "my reflog message" cr = self.rorepo.config_reader() for rlp in (rlp_head, rlp_master): -- cgit v1.2.3 From a83eee5bcee64eeb000dd413ab55aa98dcc8c7f2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 11:14:19 +0100 Subject: test_refs works --- git/objects/tag.py | 6 ++++-- git/refs/symbolic.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/git/objects/tag.py b/git/objects/tag.py index 5e76e230..c8684447 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -10,6 +10,7 @@ from .util import ( parse_actor_and_date ) from gitdb.util import hex_to_bin +from git.compat import defenc __all__ = ("TagObject", ) @@ -52,11 +53,12 @@ class TagObject(base.Object): """Cache all our attributes at once""" if attr in TagObject.__slots__: ostream = self.repo.odb.stream(self.binsha) - lines = ostream.read().splitlines() + lines = ostream.read().decode(defenc).splitlines() obj, hexsha = lines[0].split(" ") # object type_token, type_name = lines[1].split(" ") # type - self.object = get_object_type_by_name(type_name)(self.repo, hex_to_bin(hexsha)) + self.object = \ + get_object_type_by_name(type_name.encode('ascii'))(self.repo, hex_to_bin(hexsha)) self.tag = lines[2][4:] # tag diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 252462a9..45a12385 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -21,6 +21,7 @@ from gitdb.util import ( ) from git.compat import ( string_types, + defenc ) from .log import RefLog @@ -429,6 +430,7 @@ class SymbolicReference(object): # in the line # If we deleted the last line and this one is a tag-reference object, # we drop it as well + line = line.decode(defenc) if (line.startswith('#') or full_ref_path not in line) and \ (not dropped_last_line or dropped_last_line and not line.startswith('^')): new_lines.append(line) @@ -446,7 +448,7 @@ class SymbolicReference(object): if made_change: # write-binary is required, otherwise windows will # open the file in text mode and change LF to CRLF ! - open(pack_file_path, 'wb').writelines(new_lines) + open(pack_file_path, 'wb').writelines(l.encode(defenc) for l in new_lines) # END write out file # END open exception handling # END handle deletion @@ -478,7 +480,7 @@ class SymbolicReference(object): target_data = target.path if not resolve: target_data = "ref: " + target_data - existing_data = open(abs_ref_path, 'rb').read().strip() + existing_data = open(abs_ref_path, 'rb').read().decode(defenc).strip() if existing_data != target_data: raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" % (full_ref_path, existing_data, target_data)) -- cgit v1.2.3 From 60e54133aa1105a1270f0a42e74813f75cd2dc46 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 12:22:16 +0100 Subject: test_remote works And I have to wonder why git-daemon serves under py2.7, but really wants receive-pack to be allowed under 3.4. Maybe it's a repository override which for some reason doesn't work in py3.4 ? Maybe because the change is not flushed ? --- git/cmd.py | 2 +- git/exc.py | 10 ++++++---- git/remote.py | 10 ++++++---- git/repo/fun.py | 2 +- git/test/lib/helper.py | 11 +++++++---- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 2bff3310..aac7ffb7 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -120,7 +120,7 @@ class Git(LazyMixin): :raise GitCommandError: if the return status is not 0""" status = self.proc.wait() if status != 0: - raise GitCommandError(self.args, status, self.proc.stderr.read()) + raise GitCommandError(self.args, status, self.proc.stderr.read().decode(defenc)) # END status handling return status # END auto interrupt diff --git a/git/exc.py b/git/exc.py index ba57c624..67fb9894 100644 --- a/git/exc.py +++ b/git/exc.py @@ -29,10 +29,12 @@ class GitCommandError(Exception): self.command = command def __str__(self): - ret = "'%s' returned exit status %i: %s" % \ - (' '.join(str(i) for i in self.command), self.status, self.stderr) - if self.stdout is not None: - ret += "\nstdout: %s" % self.stdout + ret = "'%s' returned with exit code %i" % \ + (' '.join(str(i) for i in self.command), self.status) + if self.stderr: + ret += "\nstderr: '%s'" % self.stderr + if self.stdout: + ret += "\nstdout: '%s'" % self.stdout return ret diff --git a/git/remote.py b/git/remote.py index 63f21c4e..484bc031 100644 --- a/git/remote.py +++ b/git/remote.py @@ -138,7 +138,7 @@ class PushInfo(object): @classmethod def _from_line(cls, remote, line): """Create a new PushInfo instance as parsed from line which is expected to be like - refs/heads/master:refs/heads/master 05d2687..1d0568e""" + refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes""" control_character, from_to, summary = line.split('\t', 3) flags = 0 @@ -522,6 +522,7 @@ class Remote(LazyMixin, Iterable): def _get_fetch_info_from_stderr(self, proc, progress): # skip first line as it is some remote info we are not interested in + # TODO: Use poll() to process stdout and stderr at same time output = IterableList('name') # lines which are no progress are fetch info lines @@ -544,8 +545,8 @@ class Remote(LazyMixin, Iterable): # END for each line # read head information - fp = open(join(self.repo.git_dir, 'FETCH_HEAD'), 'r') - fetch_head_info = fp.readlines() + fp = open(join(self.repo.git_dir, 'FETCH_HEAD'), 'rb') + fetch_head_info = [l.decode(defenc) for l in fp.readlines()] fp.close() # NOTE: We assume to fetch at least enough progress lines to allow matching each fetch head line with it. @@ -562,10 +563,12 @@ class Remote(LazyMixin, Iterable): # we hope stdout can hold all the data, it should ... # read the lines manually as it will use carriage returns between the messages # to override the previous one. This is why we read the bytes manually + # TODO: poll() on file descriptors to know what to read next, process streams concurrently digest_process_messages(proc.stderr, progress) output = IterableList('name') for line in proc.stdout.readlines(): + line = line.decode(defenc) try: output.append(PushInfo._from_line(self, line)) except ValueError: @@ -573,7 +576,6 @@ class Remote(LazyMixin, Iterable): pass # END exception handling # END for each line - finalize_process(proc) return output diff --git a/git/repo/fun.py b/git/repo/fun.py index 64b9b4a9..ac0fa6f1 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -21,7 +21,7 @@ __all__ = ('rev_parse', 'is_git_dir', 'touch', 'read_gitfile', 'find_git_dir', ' def touch(filename): - fp = open(filename, "a") + fp = open(filename, "ab") fp.close() diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index bc9c351a..eea594e7 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -193,7 +193,7 @@ def with_rw_and_rw_remote_repo(working_tree_ref): temp_dir = os.path.dirname(_mktemp()) # On windows, this will fail ... we deal with failures anyway and default to telling the user to do it try: - gd = Git().daemon(temp_dir, as_process=True) + gd = Git().daemon(temp_dir, enable='receive-pack', as_process=True) # yes, I know ... fortunately, this is always going to work if sleep time is just large enough time.sleep(0.5) except Exception: @@ -215,7 +215,8 @@ def with_rw_and_rw_remote_repo(working_tree_ref): msg += 'Otherwise, run: git-daemon "%s"' % temp_dir raise AssertionError(msg) else: - msg = 'Please start a git-daemon to run this test, execute: git-daemon "%s"' % temp_dir + msg = 'Please start a git-daemon to run this test, execute: git daemon --enable=receive-pack "%s"' + msg %= temp_dir raise AssertionError(msg) # END make assertion # END catch ls remote error @@ -227,7 +228,8 @@ def with_rw_and_rw_remote_repo(working_tree_ref): return func(self, rw_repo, rw_remote_repo) finally: # gd.proc.kill() ... no idea why that doesn't work - os.kill(gd.proc.pid, 15) + if gd is not None: + os.kill(gd.proc.pid, 15) os.chdir(prev_cwd) rw_repo.git.clear_cache() @@ -235,7 +237,8 @@ def with_rw_and_rw_remote_repo(working_tree_ref): shutil.rmtree(repo_dir, onerror=_rmtree_onerror) shutil.rmtree(remote_repo_dir, onerror=_rmtree_onerror) - gd.proc.wait() + if gd is not None: + gd.proc.wait() # END cleanup # END bare repo creator remote_repo_creator.__name__ = func.__name__ -- cgit v1.2.3 From a05e49d2419d65c59c65adf5cd8c05f276550e1d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 14:05:30 +0100 Subject: test_repo works --- doc/source/changes.rst | 1 + git/cmd.py | 17 ++++++++++------- git/config.py | 14 ++++++++++++-- git/exc.py | 6 ++++-- git/objects/submodule/base.py | 31 +++++++++++++++++++++++-------- git/refs/head.py | 1 + git/repo/base.py | 5 +++-- git/repo/fun.py | 2 +- git/test/lib/helper.py | 1 + git/test/test_index.py | 1 + git/test/test_refs.py | 6 ++++-- git/test/test_repo.py | 28 ++++++++++++++++++---------- git/test/test_submodule.py | 38 +++++++++++++++++++++++++++----------- 13 files changed, 106 insertions(+), 45 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 06a73f41..3b62ddc8 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -6,6 +6,7 @@ Changelog ======================== * Internally, hexadecimal SHA1 are treated as ascii encoded strings. Binary SHA1 are treated as bytes. * 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. +* **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. 0.3.3 ===== diff --git a/git/cmd.py b/git/cmd.py index aac7ffb7..9b85b14a 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -22,7 +22,8 @@ from .exc import GitCommandError from git.compat import ( text_type, string_types, - defenc + defenc, + PY3 ) execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', @@ -372,8 +373,8 @@ class Git(LazyMixin): # Wait for the process to return status = 0 - stdout_value = '' - stderr_value = '' + stdout_value = b'' + stderr_value = b'' try: if output_stream is None: stdout_value, stderr_value = proc.communicate() @@ -388,7 +389,7 @@ class Git(LazyMixin): stdout_value = output_stream stderr_value = proc.stderr.read() # strip trailing "\n" - if stderr_value.endswith("\n"): + if stderr_value.endswith(b"\n"): stderr_value = stderr_value[:-1] status = proc.wait() # END stdout handling @@ -444,7 +445,9 @@ class Git(LazyMixin): @classmethod def __unpack_args(cls, arg_list): if not isinstance(arg_list, (list, tuple)): - if isinstance(arg_list, text_type): + # This is just required for unicode conversion, as subprocess can't handle it + # However, in any other case, passing strings (usually utf-8 encoded) is totally fine + if not PY3 and isinstance(arg_list, unicode): return [arg_list.encode(defenc)] return [str(arg_list)] @@ -452,7 +455,7 @@ class Git(LazyMixin): for arg in arg_list: if isinstance(arg_list, (list, tuple)): outlist.extend(cls.__unpack_args(arg)) - elif isinstance(arg_list, text_type): + elif not PY3 and isinstance(arg_list, unicode): outlist.append(arg_list.encode(defenc)) # END recursion else: @@ -509,8 +512,8 @@ class Git(LazyMixin): # Prepare the argument list opt_args = self.transform_kwargs(**kwargs) - ext_args = self.__unpack_args([a for a in args if a is not None]) + args = opt_args + ext_args def make_call(): diff --git a/git/config.py b/git/config.py index 96991b84..c7e8b7ac 100644 --- a/git/config.py +++ b/git/config.py @@ -103,8 +103,7 @@ class SectionConstraint(object): # Yes, for some reason, we have to call it explicitly for it to work in PY3 ! # Apparently __del__ doesn't get call anymore if refcount becomes 0 # Ridiculous ... . - self._config.__del__() - # del self._config + self._config.release() def __getattr__(self, attr): if attr in self._valid_attrs_: @@ -121,6 +120,10 @@ class SectionConstraint(object): """return: Configparser instance we constrain""" return self._config + def release(self): + """Equivalent to GitConfigParser.release(), which is called on our underlying parser instance""" + return self._config.release() + class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)): @@ -198,6 +201,13 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje def __del__(self): """Write pending changes if required and release locks""" + # NOTE: only consistent in PY2 + self.release() + + def release(self): + """Flush changes and release the configuration write lock. This instance must not be used anymore afterwards. + In Python 3, it's required to explicitly release locks and flush changes, as __del__ is not called + deterministically anymore.""" # checking for the lock here makes sure we do not raise during write() # in case an invalid parser was created who could not get a lock if self.read_only or (self._lock and not self._lock._has_lock()): diff --git a/git/exc.py b/git/exc.py index 67fb9894..42191c62 100644 --- a/git/exc.py +++ b/git/exc.py @@ -7,6 +7,8 @@ from gitdb.exc import * # NOQA +from git.compat import defenc + class InvalidGitRepositoryError(Exception): @@ -32,9 +34,9 @@ class GitCommandError(Exception): ret = "'%s' returned with exit code %i" % \ (' '.join(str(i) for i in self.command), self.status) if self.stderr: - ret += "\nstderr: '%s'" % self.stderr + ret += "\nstderr: '%s'" % self.stderr.decode(defenc) if self.stdout: - ret += "\nstdout: '%s'" % self.stdout + ret += "\nstdout: '%s'" % self.stdout.decode(defenc) return ret diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 0fb3f35f..0ec6f656 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -17,7 +17,10 @@ from git.util import ( rmtree ) -from git.config import SectionConstraint +from git.config import ( + SectionConstraint, + cp +) from git.exc import ( InvalidGitRepositoryError, NoSuchPathError @@ -302,6 +305,7 @@ class Submodule(util.IndexObject, Iterable, Traversable): writer.set_value(cls.k_head_option, br.path) sm._branch_path = br.path # END handle path + writer.release() del(writer) # we deliberatly assume that our head matches our index ! @@ -419,7 +423,9 @@ class Submodule(util.IndexObject, Iterable, Traversable): # the default implementation will be offended and not update the repository # Maybe this is a good way to assure it doesn't get into our way, but # we want to stay backwards compatible too ... . Its so redundant ! - self.repo.config_writer().set_value(sm_section(self.name), 'url', self.url) + writer = self.repo.config_writer() + writer.set_value(sm_section(self.name), 'url', self.url) + writer.release() # END handle dry_run # END handle initalization @@ -576,6 +582,7 @@ class Submodule(util.IndexObject, Iterable, Traversable): writer = self.config_writer(index=index) # auto-write writer.set_value('path', module_path) self.path = module_path + writer.release() del(writer) # END handle configuration flag except Exception: @@ -700,8 +707,12 @@ class Submodule(util.IndexObject, Iterable, Traversable): # now git config - need the config intact, otherwise we can't query # inforamtion anymore - self.repo.config_writer().remove_section(sm_section(self.name)) - self.config_writer().remove_section() + writer = self.repo.config_writer() + writer.remove_section(sm_section(self.name)) + writer.release() + writer = self.config_writer() + writer.remove_section() + writer.release() # END delete configuration # void our data not to delay invalid access @@ -800,14 +811,18 @@ class Submodule(util.IndexObject, Iterable, Traversable): """ :return: True if the submodule exists, False otherwise. Please note that a submodule may exist (in the .gitmodules file) even though its module - doesn't exist""" + doesn't exist on disk""" # keep attributes for later, and restore them if we have no valid data # this way we do not actually alter the state of the object loc = locals() for attr in self._cache_attrs: - if hasattr(self, attr): - loc[attr] = getattr(self, attr) - # END if we have the attribute cache + try: + if hasattr(self, attr): + loc[attr] = getattr(self, attr) + # END if we have the attribute cache + except cp.NoSectionError: + # on PY3, this can happen apparently ... don't know why this doesn't happen on PY2 + pass # END for each attr self._clear_cache() diff --git a/git/refs/head.py b/git/refs/head.py index 0a14158c..750d15b6 100644 --- a/git/refs/head.py +++ b/git/refs/head.py @@ -148,6 +148,7 @@ class Head(Reference): writer.set_value(self.k_config_remote, remote_reference.remote_name) writer.set_value(self.k_config_remote_ref, Head.to_full_path(remote_reference.remote_head)) # END handle ref value + writer.release() return self diff --git a/git/repo/base.py b/git/repo/base.py index 3e0e51cc..2a63492b 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -551,6 +551,7 @@ class Repo(object): prefix = "?? " untracked_files = list() for line in proc.stdout: + line = line.decode(defenc) if not line.startswith(prefix): continue filename = line[len(prefix):].rstrip('\n') @@ -735,7 +736,7 @@ class Repo(object): writer = repo.remotes[0].config_writer writer.set_value('url', repo.remotes[0].url.replace("\\\\", "\\").replace("\\", "/")) # PY3: be sure cleanup is performed and lock is released - del writer + writer.release() # END handle remote repo return repo @@ -767,7 +768,7 @@ class Repo(object): def archive(self, ostream, treeish=None, prefix=None, **kwargs): """Archive the tree at the given revision. - :parm ostream: file compatible stream object to which the archive will be written + :parm ostream: file compatible stream object to which the archive will be written as bytes :parm treeish: is the treeish name/id, defaults to active branch :parm prefix: is the optional prefix to prepend to each filename in the archive :parm kwargs: diff --git a/git/repo/fun.py b/git/repo/fun.py index ac0fa6f1..233666c9 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -150,7 +150,7 @@ def to_commit(obj): def rev_parse(repo, rev): """ :return: Object at the given revision, either Commit, Tag, Tree or Blob - :param rev: git-rev-parse compatible revision specification, please see + :param rev: git-rev-parse compatible revision specification as string, please see http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html for details :note: Currently there is no access to the rev-log, rev-specs may only contain diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index eea594e7..bd679512 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -179,6 +179,7 @@ def with_rw_and_rw_remote_repo(working_tree_ref): pass crw.set(section, "receivepack", True) # release lock + crw.release() del(crw) # initialize the remote - first do it as local remote and pull, then diff --git a/git/test/test_index.py b/git/test/test_index.py index d81d08ef..f7504b32 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -382,6 +382,7 @@ class TestIndex(TestBase): writer = rw_repo.config_writer() writer.set_value("user", "name", uname) writer.set_value("user", "email", umail) + writer.release() # remove all of the files, provide a wild mix of paths, BaseIndexEntries, # IndexEntries diff --git a/git/test/test_refs.py b/git/test/test_refs.py index af33765a..14b91cfe 100644 --- a/git/test/test_refs.py +++ b/git/test/test_refs.py @@ -105,9 +105,11 @@ class TestRefs(TestBase): tv = "testopt" writer.set_value(tv, 1) assert writer.get_value(tv) == 1 - del(writer) + writer.release() assert head.config_reader().get_value(tv) == 1 - head.config_writer().remove_option(tv) + writer = head.config_writer() + writer.remove_option(tv) + writer.release() # after the clone, we might still have a tracking branch setup head.set_tracking_branch(None) diff --git a/git/test/test_repo.py b/git/test/test_repo.py index ae824086..f216039e 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -30,7 +30,10 @@ from git import ( from git.util import join_path_native from git.exc import BadObject from gitdb.util import bin_to_hex -from git.compat import string_types +from git.compat import ( + string_types, + defenc +) import os import sys @@ -259,13 +262,16 @@ class TestRepo(TestBase): assert self.rorepo.tag('refs/tags/0.1.5').commit def test_archive(self): - tmpfile = os.tmpfile() - self.rorepo.archive(tmpfile, '0.1.5') - assert tmpfile.tell() + tmpfile = tempfile.mktemp(suffix='archive-test') + stream = open(tmpfile, 'wb') + self.rorepo.archive(stream, '0.1.5') + assert stream.tell() + stream.close() + os.remove(tmpfile) @patch.object(Git, '_call_process') def test_should_display_blame_information(self, git): - git.return_value = fixture('blame') + git.return_value = fixture('blame').decode(defenc) b = self.rorepo.blame('master', 'lib/git.py') assert_equal(13, len(b)) assert_equal(2, len(b[0])) @@ -336,6 +342,7 @@ class TestRepo(TestBase): try: writer = self.rorepo.config_writer(config_level) assert not writer.read_only + writer.release() except IOError: # its okay not to get a writer for some configuration files if we # have no permissions @@ -350,7 +357,8 @@ class TestRepo(TestBase): tag = self.rorepo.create_tag("new_tag", "HEAD~2") self.rorepo.delete_tag(tag) - self.rorepo.config_writer() + writer = self.rorepo.config_writer() + writer.release() remote = self.rorepo.create_remote("new_remote", "git@server:repo.git") self.rorepo.delete_remote(remote) @@ -365,7 +373,7 @@ class TestRepo(TestBase): l1 = b"0123456789\n" l2 = b"abcdefghijklmnopqrstxy\n" l3 = b"z\n" - d = b"%s%s%s\n" % (l1, l2, l3) + d = l1 + l2 + l3 + b"\n" l1p = l1[:5] @@ -382,7 +390,7 @@ class TestRepo(TestBase): # readlines no limit s = mkfull() lines = s.readlines() - assert len(lines) == 3 and lines[-1].endswith('\n') + assert len(lines) == 3 and lines[-1].endswith(b'\n') assert s._stream.tell() == len(d) # must have scrubbed to the end # realines line limit @@ -566,7 +574,7 @@ class TestRepo(TestBase): # try partial parsing max_items = 40 for i, binsha in enumerate(self.rorepo.odb.sha_iter()): - assert rev_parse(bin_to_hex(binsha)[:8 - (i % 2)]).binsha == binsha + assert rev_parse(bin_to_hex(binsha)[:8 - (i % 2)].decode('ascii')).binsha == binsha if i > max_items: # this is rather slow currently, as rev_parse returns an object # which requires accessing packs, it has some additional overhead @@ -645,6 +653,6 @@ class TestRepo(TestBase): assert os.path.abspath(git_file_repo.git_dir) == real_path_abs # Test using an absolute gitdir path in the .git file. - open(git_file_path, 'wb').write('gitdir: %s\n' % real_path_abs) + open(git_file_path, 'wb').write(('gitdir: %s\n' % real_path_abs).encode('ascii')) git_file_repo = Repo(rwrepo.working_tree_dir) assert os.path.abspath(git_file_repo.git_dir) == real_path_abs diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py index 8c1580fe..499d6bac 100644 --- a/git/test/test_submodule.py +++ b/git/test/test_submodule.py @@ -99,7 +99,7 @@ class TestSubmodule(TestBase): # for faster checkout, set the url to the local path new_smclone_path = to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, sm.path)) writer.set_value('url', new_smclone_path) - del(writer) + writer.release() assert sm.config_reader().get_value('url') == new_smclone_path assert sm.url == new_smclone_path # END handle bare repo @@ -196,7 +196,9 @@ class TestSubmodule(TestBase): # adjust the path of the submodules module to point to the local destination new_csmclone_path = to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, sm.path, csm.path)) - csm.config_writer().set_value('url', new_csmclone_path) + writer = csm.config_writer() + writer.set_value('url', new_csmclone_path) + writer.release() assert csm.url == new_csmclone_path # dry-run does nothing @@ -257,8 +259,12 @@ class TestSubmodule(TestBase): # NOTE: As we did a few updates in the meanwhile, the indices were reset # Hence we create some changes csm.set_parent_commit(csm.repo.head.commit) - sm.config_writer().set_value("somekey", "somevalue") - csm.config_writer().set_value("okey", "ovalue") + writer = sm.config_writer() + writer.set_value("somekey", "somevalue") + writer.release() + writer = csm.config_writer() + writer.set_value("okey", "ovalue") + writer.release() self.failUnlessRaises(InvalidGitRepositoryError, sm.remove) # if we remove the dirty index, it would work sm.module().index.reset() @@ -406,7 +412,8 @@ class TestSubmodule(TestBase): assert len(rm.list_items(rm.module())) == 1 rm.config_reader() - rm.config_writer() + w = rm.config_writer() + w.release() # deep traversal gitdb / async rsmsp = [sm.path for sm in rm.traverse()] @@ -431,8 +438,9 @@ class TestSubmodule(TestBase): assert not sm.module_exists() # was never updated after rwrepo's clone # assure we clone from a local source - sm.config_writer().set_value( - 'url', to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, sm.path))) + writer = sm.config_writer() + writer.set_value('url', to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, sm.path))) + writer.release() # dry-run does nothing sm.update(recursive=False, dry_run=True, progress=prog) @@ -440,7 +448,9 @@ class TestSubmodule(TestBase): sm.update(recursive=False) assert sm.module_exists() - sm.config_writer().set_value('path', fp) # change path to something with prefix AFTER url change + writer = sm.config_writer() + writer.set_value('path', fp) # change path to something with prefix AFTER url change + writer.release() # update fails as list_items in such a situations cannot work, as it cannot # find the entry at the changed path @@ -504,7 +514,9 @@ class TestSubmodule(TestBase): # repository at the different url nsm.set_parent_commit(csmremoved) nsmurl = to_native_path_linux(join_path_native(self.rorepo.working_tree_dir, rsmsp[0])) - nsm.config_writer().set_value('url', nsmurl) + writer = nsm.config_writer() + writer.set_value('url', nsmurl) + writer.release() csmpathchange = rwrepo.index.commit("changed url") nsm.set_parent_commit(csmpathchange) @@ -532,7 +544,9 @@ class TestSubmodule(TestBase): nsmm = nsm.module() prev_commit = nsmm.head.commit for branch in ("some_virtual_branch", cur_branch.name): - nsm.config_writer().set_value(Submodule.k_head_option, git.Head.to_full_path(branch)) + writer = nsm.config_writer() + writer.set_value(Submodule.k_head_option, git.Head.to_full_path(branch)) + writer.release() csmbranchchange = rwrepo.index.commit("changed branch to %s" % branch) nsm.set_parent_commit(csmbranchchange) # END for each branch to change @@ -560,7 +574,9 @@ class TestSubmodule(TestBase): assert nsm.exists() and nsm.module_exists() and len(nsm.children()) >= 1 # assure we pull locally only nsmc = nsm.children()[0] - nsmc.config_writer().set_value('url', async_url) + writer = nsmc.config_writer() + writer.set_value('url', async_url) + writer.release() rm.update(recursive=True, progress=prog, dry_run=True) # just to run the code rm.update(recursive=True, progress=prog) -- cgit v1.2.3 From 8f219b53f339fc0133800fac96deaf75eb4f9bf6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 14:09:17 +0100 Subject: test_stat works --- git/test/test_stats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/test/test_stats.py b/git/test/test_stats.py index c4535b75..eb8b9dda 100644 --- a/git/test/test_stats.py +++ b/git/test/test_stats.py @@ -10,12 +10,12 @@ from git.test.lib import ( assert_equal ) from git import Stats - +from git.compat import defenc class TestStats(TestBase): def test_list_from_string(self): - output = fixture('diff_numstat') + output = fixture('diff_numstat').decode(defenc) stats = Stats._list_from_string(self.rorepo, output) assert_equal(2, stats.total['files']) -- cgit v1.2.3 From 4cfc682ff87d3629b31e0196004d1954810b206c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 14:11:48 +0100 Subject: test_submodule works --- git/objects/base.py | 2 +- git/test/test_base.py | 2 +- git/test/test_submodule.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git/objects/base.py b/git/objects/base.py index 004e3981..3f595d9d 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -60,7 +60,7 @@ class Object(LazyMixin): :param sha1: 20 byte binary sha1""" if sha1 == cls.NULL_BIN_SHA: # the NULL binsha is always the root commit - return get_object_type_by_name('commit')(repo, sha1) + return get_object_type_by_name(b'commit')(repo, sha1) # END handle special case oinfo = repo.odb.info(sha1) inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha) diff --git a/git/test/test_base.py b/git/test/test_base.py index edacbd80..9ae2dd2b 100644 --- a/git/test/test_base.py +++ b/git/test/test_base.py @@ -86,7 +86,7 @@ class TestBase(TestBase): assert base.Object in get_object_type_by_name(tname).mro() # END for each known type - assert_raises(ValueError, get_object_type_by_name, "doesntexist") + assert_raises(ValueError, get_object_type_by_name, b"doesntexist") def test_object_resolution(self): # objects must be resolved to shas so they compare equal diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py index 499d6bac..99996ce3 100644 --- a/git/test/test_submodule.py +++ b/git/test/test_submodule.py @@ -80,7 +80,7 @@ class TestSubmodule(TestBase): assert isinstance(sm.branch_path, string_types) # some commits earlier we still have a submodule, but its at a different commit - smold = Submodule.iter_items(rwrepo, self.k_subm_changed).next() + smold = next(Submodule.iter_items(rwrepo, self.k_subm_changed)) assert smold.binsha != sm.binsha assert smold != sm # the name changed -- cgit v1.2.3 From 95bb489a50f6c254f49ba4562d423dbf2201554f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 14:20:58 +0100 Subject: test_tree works --- doc/source/changes.rst | 1 + git/objects/tree.py | 12 ++++++++++-- git/test/test_tree.py | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 3b62ddc8..bb301f87 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -7,6 +7,7 @@ Changelog * Internally, hexadecimal SHA1 are treated as ascii encoded strings. Binary SHA1 are treated as bytes. * 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. * **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. +* The `Tree` now has a `.join('name')` method which is equivalent to `tree / 'name'` 0.3.3 ===== diff --git a/git/objects/tree.py b/git/objects/tree.py index 6776a15e..f9bee01e 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -159,7 +159,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path)) # END for each item - def __div__(self, file): + def join(self, file): """Find the named object in this tree's contents :return: ``git.Blob`` or ``git.Tree`` or ``git.Submodule`` @@ -192,6 +192,14 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): raise KeyError(msg % file) # END handle long paths + def __div__(self, file): + """For PY2 only""" + return self.join(file) + + def __truediv__(self, file): + """For PY3 only""" + return self.join(file) + @property def trees(self): """:return: list(Tree, ...) list of trees directly below this tree""" @@ -235,7 +243,7 @@ class Tree(IndexObject, diff.Diffable, util.Traversable, util.Serializable): if isinstance(item, string_types): # compatability - return self.__div__(item) + return self.join(item) # END index is basestring raise TypeError("Invalid index type: %r" % item) diff --git a/git/test/test_tree.py b/git/test/test_tree.py index 72bda485..246584ee 100644 --- a/git/test/test_tree.py +++ b/git/test/test_tree.py @@ -138,6 +138,7 @@ class TestTree(TestBase): # END check for slash # slashes in paths are supported as well + # NOTE: on py3, / doesn't work with strings anymore ... assert root[item.path] == item == root / item.path # END for each item assert found_slash -- cgit v1.2.3 From 725bde98d5cf680a087b6cb47a11dc469cfe956c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 14:28:32 +0100 Subject: test_base works --- git/test/test_base.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/git/test/test_base.py b/git/test/test_base.py index 9ae2dd2b..301384ef 100644 --- a/git/test/test_base.py +++ b/git/test/test_base.py @@ -4,10 +4,10 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php - -import git.objects.base as base import os +import tempfile +import git.objects.base as base from git.test.lib import ( TestBase, assert_raises, @@ -69,10 +69,13 @@ class TestBase(TestBase): data = data_stream.read() assert data - tmpfile = os.tmpfile() + tmpfilename = tempfile.mktemp(suffix='test-stream') + tmpfile = open(tmpfilename, 'wb+') assert item == item.stream_data(tmpfile) tmpfile.seek(0) assert tmpfile.read() == data + tmpfile.close() + os.remove(tmpfilename) # END stream to file directly # END for each object type to create @@ -113,7 +116,7 @@ class TestBase(TestBase): filename = u"שלום.txt" file_path = os.path.join(rw_repo.working_dir, filename) - open(file_path, "wb").write('something') + open(file_path, "wb").write(b'something') rw_repo.git.add(rw_repo.working_dir) rw_repo.index.commit('message') -- cgit v1.2.3 From 02e942b7c603163c87509195d76b2117c4997119 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 14:29:46 +0100 Subject: test_streams works However, there is a performance regression in test-odb --- git/test/performance/test_streams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/test/performance/test_streams.py b/git/test/performance/test_streams.py index ff664c10..aecb7728 100644 --- a/git/test/performance/test_streams.py +++ b/git/test/performance/test_streams.py @@ -80,7 +80,7 @@ class TestObjDBPerformance(TestBigRepoR): elapsed_readchunks = time() - st stream.seek(0) - assert ''.join(chunks) == stream.getvalue() + assert b''.join(chunks) == stream.getvalue() cs_kib = cs / 1000 print("Read %i KiB of %s data in %i KiB chunks from loose odb in %f s ( %f Read KiB / s)" -- cgit v1.2.3 From e0c65d6638698f4e3a9e726efca8c0bcf466cd62 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 15:38:20 +0100 Subject: Make flake8 happy --- git/cmd.py | 7 +++---- git/config.py | 4 ++-- git/objects/commit.py | 8 ++++---- git/objects/fun.py | 1 + git/refs/log.py | 2 +- git/refs/symbolic.py | 2 +- git/test/performance/test_commit.py | 1 - git/test/test_commit.py | 1 - git/test/test_config.py | 1 + git/test/test_stats.py | 1 + git/test/test_tree.py | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 9b85b14a..7a670c46 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -20,7 +20,6 @@ from .util import ( ) from .exc import GitCommandError from git.compat import ( - text_type, string_types, defenc, PY3 @@ -400,7 +399,8 @@ class Git(LazyMixin): if self.GIT_PYTHON_TRACE == 'full': cmdstr = " ".join(command) if stderr_value: - log.info("%s -> %d; stdout: '%s'; stderr: '%s'", cmdstr, status, stdout_value.decode(defenc), stderr_value.decode(defenc)) + log.info("%s -> %d; stdout: '%s'; stderr: '%s'", + cmdstr, status, stdout_value.decode(defenc), stderr_value.decode(defenc)) elif stdout_value: log.info("%s -> %d; stdout: '%s'", cmdstr, status, stdout_value.decode(defenc)) else: @@ -413,8 +413,7 @@ class Git(LazyMixin): else: raise GitCommandError(command, status, stderr_value) - - if isinstance(stdout_value, bytes): # could also be output_stream + if isinstance(stdout_value, bytes): # could also be output_stream stdout_value = stdout_value.decode(defenc) # Allow access to the command's status code diff --git a/git/config.py b/git/config.py index c7e8b7ac..eefab299 100644 --- a/git/config.py +++ b/git/config.py @@ -178,7 +178,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje # Used in python 3, needs to stay in sync with sections for underlying implementation to work if not hasattr(self, '_proxies'): self._proxies = self._dict() - + self._file_or_files = file_or_files self._read_only = read_only self._is_initialized = False @@ -206,7 +206,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje def release(self): """Flush changes and release the configuration write lock. This instance must not be used anymore afterwards. - In Python 3, it's required to explicitly release locks and flush changes, as __del__ is not called + In Python 3, it's required to explicitly release locks and flush changes, as __del__ is not called deterministically anymore.""" # checking for the lock here makes sure we do not raise during write() # in case an invalid parser was created who could not get a lock diff --git a/git/objects/commit.py b/git/objects/commit.py index 53af22cd..8f93d1b9 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -382,14 +382,14 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable): c = self.committer fmt = "%s %s <%s> %s %s\n" write((fmt % ("author", aname, a.email, - self.authored_date, - altz_to_utctz_str(self.author_tz_offset))).encode(self.encoding)) + self.authored_date, + altz_to_utctz_str(self.author_tz_offset))).encode(self.encoding)) # encode committer aname = c.name write((fmt % ("committer", aname, c.email, - self.committed_date, - altz_to_utctz_str(self.committer_tz_offset))).encode(self.encoding)) + self.committed_date, + altz_to_utctz_str(self.committer_tz_offset))).encode(self.encoding)) if self.encoding != self.default_encoding: write(("encoding %s\n" % self.encoding).encode('ascii')) diff --git a/git/objects/fun.py b/git/objects/fun.py index ba8dbcf4..c04f80b5 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -40,6 +40,7 @@ def tree_to_stream(entries, write): write(b''.join((mode_str, b' ', name, b'\0', binsha))) # END for each item + def tree_entries_from_data(data): """Reads the binary representation of a tree and returns tuples of Tree items :param data: data block with tree data (as bytes) diff --git a/git/refs/log.py b/git/refs/log.py index c3019d39..69189b51 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -256,7 +256,7 @@ class RefLog(list, Serializable): committer = isinstance(config_reader, Actor) and config_reader or Actor.committer(config_reader) entry = RefLogEntry(( bin_to_hex(oldbinsha).decode('ascii'), - bin_to_hex(newbinsha).decode('ascii'), + bin_to_hex(newbinsha).decode('ascii'), committer, (int(time.time()), time.altzone), message )) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 45a12385..cbb129d4 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -144,7 +144,7 @@ class SymbolicReference(object): for sha, path in cls._iter_packed_refs(repo): if path != ref_path: continue - # sha will be used as + # sha will be used tokens = sha, path break # END for each packed ref diff --git a/git/test/performance/test_commit.py b/git/test/performance/test_commit.py index 9e8c1325..7d3e87c4 100644 --- a/git/test/performance/test_commit.py +++ b/git/test/performance/test_commit.py @@ -15,7 +15,6 @@ from git.compat import xrange from git.test.test_commit import assert_commit_serialization - class TestPerformance(TestBigRepoRW): # ref with about 100 commits in its history diff --git a/git/test/test_commit.py b/git/test/test_commit.py index 37a54092..1f0f8c56 100644 --- a/git/test/test_commit.py +++ b/git/test/test_commit.py @@ -19,7 +19,6 @@ from git import ( Actor, ) from gitdb import IStream -from gitdb.util import hex_to_bin from git.compat import ( string_types, text_type diff --git a/git/test/test_config.py b/git/test/test_config.py index f02754d5..546a2fe1 100644 --- a/git/test/test_config.py +++ b/git/test/test_config.py @@ -18,6 +18,7 @@ import io from copy import copy from git.config import cp + class TestBase(TestCase): def _to_memcache(self, file_path): diff --git a/git/test/test_stats.py b/git/test/test_stats.py index eb8b9dda..884ab1ab 100644 --- a/git/test/test_stats.py +++ b/git/test/test_stats.py @@ -12,6 +12,7 @@ from git.test.lib import ( from git import Stats from git.compat import defenc + class TestStats(TestBase): def test_list_from_string(self): diff --git a/git/test/test_tree.py b/git/test/test_tree.py index 246584ee..7a16b777 100644 --- a/git/test/test_tree.py +++ b/git/test/test_tree.py @@ -138,7 +138,7 @@ class TestTree(TestBase): # END check for slash # slashes in paths are supported as well - # NOTE: on py3, / doesn't work with strings anymore ... + # NOTE: on py3, / doesn't work with strings anymore ... assert root[item.path] == item == root / item.path # END for each item assert found_slash -- cgit v1.2.3 From 1527b5734c0f2821fd6f38c1a1d70723a4bb88ab Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 15:40:35 +0100 Subject: fixed test_git --- git/test/test_git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/test/test_git.py b/git/test/test_git.py index 8087d6f8..532e6659 100644 --- a/git/test/test_git.py +++ b/git/test/test_git.py @@ -34,11 +34,11 @@ class TestGit(TestBase): def test_call_unpack_args_unicode(self): args = Git._Git__unpack_args(u'Unicode€™') - assert_equal(args, [b'Unicode\xe2\x82\xac\xe2\x84\xa2']) + assert_equal(args, ['Unicode\u20ac\u2122']) def test_call_unpack_args(self): args = Git._Git__unpack_args(['git', 'log', '--', u'Unicode€™']) - assert_equal(args, [b'git', b'log', b'--', b'Unicode\xe2\x82\xac\xe2\x84\xa2']) + assert_equal(args, ['git', 'log', '--', 'Unicode\u20ac\u2122']) @raises(GitCommandError) def test_it_raises_errors(self): -- cgit v1.2.3 From c8cf69b6f8bd73525b5375bd73f1fc79ae322a82 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 16:06:23 +0100 Subject: Fixed test_git once again --- git/test/test_git.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/git/test/test_git.py b/git/test/test_git.py index 532e6659..502e6091 100644 --- a/git/test/test_git.py +++ b/git/test/test_git.py @@ -17,6 +17,8 @@ from git.test.lib import (TestBase, from git import (Git, GitCommandError) +from git.compat import PY3 + class TestGit(TestBase): @@ -34,11 +36,19 @@ class TestGit(TestBase): def test_call_unpack_args_unicode(self): args = Git._Git__unpack_args(u'Unicode€™') - assert_equal(args, ['Unicode\u20ac\u2122']) + if PY3: + mangled_value = 'Unicode\u20ac\u2122' + else: + mangled_value = 'Unicode\xe2\x82\xac\xe2\x84\xa2' + assert_equal(args, [mangled_value]) def test_call_unpack_args(self): args = Git._Git__unpack_args(['git', 'log', '--', u'Unicode€™']) - assert_equal(args, ['git', 'log', '--', 'Unicode\u20ac\u2122']) + if PY3: + mangled_value = 'Unicode\u20ac\u2122' + else: + mangled_value = 'Unicode\xe2\x82\xac\xe2\x84\xa2' + assert_equal(args, ['git', 'log', '--', mangled_value]) @raises(GitCommandError) def test_it_raises_errors(self): -- cgit v1.2.3 From 68f8a43d1b643318732f30ee1cd75e1d315a4537 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 6 Jan 2015 16:09:47 +0100 Subject: Bumped version to 0.3.4 --- VERSION | 2 +- doc/source/changes.rst | 2 +- setup.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 1c09c74e..42045aca 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.3 +0.3.4 diff --git a/doc/source/changes.rst b/doc/source/changes.rst index bb301f87..b7479e4f 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,7 +2,7 @@ Changelog ========= -0.3.4 - python 3 support +0.3.4 - Python 3 Support ======================== * Internally, hexadecimal SHA1 are treated as ascii encoded strings. Binary SHA1 are treated as bytes. * 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. diff --git a/setup.py b/setup.py index e1f77058..bdd4a423 100755 --- a/setup.py +++ b/setup.py @@ -112,8 +112,8 @@ GitPython is a python library used to interact with Git repositories""", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", - # "Programming Language :: Python :: 3", - # "Programming Language :: Python :: 3.3", - # "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", ] ) -- cgit v1.2.3