diff --git a/.appveyor.yml b/.appveyor.yml index 0e9a94732..701fc4ac2 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -21,18 +21,17 @@ environment: IS_CONDA: "yes" GIT_PATH: "%GIT_DAEMON_PATH%" - # ## Cygwin - # # - # - PYTHON: "C:\\Miniconda-x64" - # PYTHON_VERSION: "2.7" - # IS_CONDA: "yes" - # GIT_PATH: "%CYGWIN_GIT_PATH%" - # - PYTHON: "C:\\Python34-x64" - # PYTHON_VERSION: "3.4" - # GIT_PATH: "%CYGWIN_GIT_PATH%" - # - PYTHON: "C:\\Python35-x64" - # PYTHON_VERSION: "3.5" - # GIT_PATH: "%CYGWIN64_GIT_PATH%" + ## Cygwin + # + - PYTHON: "C:\\Miniconda-x64" + PYTHON_VERSION: "2.7" + IS_CONDA: "yes" + IS_CYGWIN: "yes" + GIT_PATH: "%CYGWIN_GIT_PATH%" + - PYTHON: "C:\\Python35-x64" + PYTHON_VERSION: "3.5" + GIT_PATH: "%CYGWIN64_GIT_PATH%" + IS_CYGWIN: "yes" install: @@ -48,14 +47,12 @@ install: python --version python -c "import struct; print(struct.calcsize('P') * 8)" - - IF "%IS_CONDA%"=="yes" ( + - IF "%IS_CONDA%" == "yes" ( conda info -a & conda install --yes --quiet pip ) - - pip install nose ddt wheel codecov - - IF "%PYTHON_VERSION%"=="2.7" ( - pip install mock - ) + - pip install -r test-requirements.txt + - pip install codecov ## Copied from `init-tests-after-clone.sh`. # @@ -79,7 +76,15 @@ install: build: false test_script: - - IF "%PYTHON_VERSION%"=="3.5" (nosetests -v --with-coverage) ELSE (nosetests -v) + - IF "%IS_CYGWIN%" == "yes" ( + nosetests -v + ) ELSE ( + IF "%PYTHON_VERSION%" == "3.5" ( + nosetests -v --with-coverage + ) ELSE ( + nosetests -v + ) + ) on_success: - - IF "%PYTHON_VERSION%"=="3.5" (codecov) + - IF "%PYTHON_VERSION%" == "3.5" IF NOT "%IS_CYGWIN%" == "yes" (codecov) diff --git a/.travis.yml b/.travis.yml index 4c191db26..f7dd247ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,10 @@ language: python python: - - "2.6" - "2.7" - "3.3" - "3.4" - "3.5" # - "pypy" - won't work as smmap doesn't work (see gitdb/.travis.yml for details) -matrix: - allow_failures: - - python: "2.6" git: # a higher depth is needed for most of the tests - must be high enough to not actually be shallow # as we clone our own repository in the process @@ -17,7 +13,8 @@ install: - python --version; git --version - git submodule update --init --recursive - git fetch --tags - - pip install codecov flake8 ddt sphinx + - pip install -r test-requirements.txt + - pip install codecov sphinx # generate some reflog as git-python tests need it (in master) - ./init-tests-after-clone.sh diff --git a/git/__init__.py b/git/__init__.py index 58e4e7b65..0514d545b 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -5,9 +5,12 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php # flake8: noqa #@PydevCodeAnalysisIgnore +import inspect import os import sys -import inspect + +import os.path as osp + __version__ = 'git' @@ -16,7 +19,7 @@ def _init_externals(): """Initialize external projects by putting them into the path""" if __version__ == 'git': - sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'ext', 'gitdb')) + sys.path.insert(0, osp.join(osp.dirname(__file__), 'ext', 'gitdb')) try: import gitdb diff --git a/git/cmd.py b/git/cmd.py index f07573017..c43fac564 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -31,6 +31,7 @@ ) from git.exc import CommandError from git.odict import OrderedDict +from git.util import is_cygwin_git, cygpath from .exc import ( GitCommandError, @@ -191,8 +192,26 @@ def __setstate__(self, d): USE_SHELL = False @classmethod - def polish_url(cls, url): - return url.replace("\\\\", "\\").replace("\\", "/") + def is_cygwin(cls): + return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE) + + @classmethod + def polish_url(cls, url, is_cygwin=None): + if is_cygwin is None: + is_cygwin = cls.is_cygwin() + + if is_cygwin: + url = cygpath(url) + else: + """Remove any backslahes from urls to be written in config files. + + Windows might create config-files containing paths with backslashed, + but git stops liking them as it will escape the backslashes. + Hence we undo the escaping just to be sure. + """ + url = url.replace("\\\\", "\\").replace("\\", "/") + + return url class AutoInterrupt(object): """Kill/Interrupt the stored process instance once this instance goes out of scope. It is diff --git a/git/config.py b/git/config.py index eddfac151..a0b258226 100644 --- a/git/config.py +++ b/git/config.py @@ -6,21 +6,13 @@ """Module containing module parser implementation able to properly read and write configuration files""" -import re -try: - import ConfigParser as cp -except ImportError: - # PY3 - import configparser as cp +import abc +from functools import wraps import inspect import logging -import abc import os +import re -from functools import wraps - -from git.odict import OrderedDict -from git.util import LockFile from git.compat import ( string_types, FileType, @@ -29,6 +21,18 @@ with_metaclass, PY3 ) +from git.odict import OrderedDict +from git.util import LockFile + +import os.path as osp + + +try: + import ConfigParser as cp +except ImportError: + # PY3 + import configparser as cp + __all__ = ('GitConfigParser', 'SectionConstraint') @@ -408,15 +412,15 @@ def read(self): if self._has_includes(): for _, include_path in self.items('include'): if include_path.startswith('~'): - include_path = os.path.expanduser(include_path) - if not os.path.isabs(include_path): + include_path = osp.expanduser(include_path) + if not osp.isabs(include_path): if not file_ok: continue # end ignore relative paths if we don't know the configuration file path - assert os.path.isabs(file_path), "Need absolute paths to be sure our cycle checks will work" - include_path = os.path.join(os.path.dirname(file_path), include_path) + assert osp.isabs(file_path), "Need absolute paths to be sure our cycle checks will work" + include_path = osp.join(osp.dirname(file_path), include_path) # end make include path absolute - include_path = os.path.normpath(include_path) + include_path = osp.normpath(include_path) if include_path in seen or not os.access(include_path, os.R_OK): continue seen.add(include_path) diff --git a/git/db.py b/git/db.py index 39b9872a2..653fa7daa 100644 --- a/git/db.py +++ b/git/db.py @@ -1,12 +1,9 @@ """Module with our own gitdb implementation - it uses the git command""" +from git.util import bin_to_hex, hex_to_bin from gitdb.base import ( OInfo, OStream ) -from gitdb.util import ( - bin_to_hex, - hex_to_bin -) from gitdb.db import GitDB # @UnusedImport from gitdb.db import LooseObjectDB diff --git a/git/diff.py b/git/diff.py index 35c7ff86a..52dbf3a7f 100644 --- a/git/diff.py +++ b/git/diff.py @@ -5,18 +5,17 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php import re -from gitdb.util import hex_to_bin +from git.cmd import handle_process_output +from git.compat import ( + defenc, + PY3 +) +from git.util import finalize_process, hex_to_bin from .compat import binary_type from .objects.blob import Blob from .objects.util import mode_str_to_int -from git.compat import ( - defenc, - PY3 -) -from git.cmd import handle_process_output -from git.util import finalize_process __all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE') diff --git a/git/ext/gitdb b/git/ext/gitdb index 38866bc7c..97035c64f 160000 --- a/git/ext/gitdb +++ b/git/ext/gitdb @@ -1 +1 @@ -Subproject commit 38866bc7c4956170c681a62c4508f934ac826469 +Subproject commit 97035c64f429c229629c25becc54ae44dd95e49d diff --git a/git/index/base.py b/git/index/base.py index ac2d30190..95ad8b05a 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -3,34 +3,28 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -import tempfile -import os -import sys -import subprocess import glob from io import BytesIO - +import os from stat import S_ISLNK +import subprocess +import sys +import tempfile -from .typ import ( - BaseIndexEntry, - IndexEntry, -) - -from .util import ( - TemporaryFileSwap, - post_clear_cache, - default_index, - git_working_dir +from git.compat import ( + izip, + xrange, + string_types, + force_bytes, + defenc, + mviter, + is_win ) - -import git.diff as diff from git.exc import ( GitCommandError, CheckoutError, InvalidGitRepositoryError ) - from git.objects import ( Blob, Submodule, @@ -38,26 +32,21 @@ Object, Commit, ) - from git.objects.util import Serializable -from git.compat import ( - izip, - xrange, - string_types, - force_bytes, - defenc, - mviter, - is_win -) - from git.util import ( LazyMixin, LockedFD, join_path_native, file_contents_ro, to_native_path_linux, - unbare_repo + unbare_repo, + to_bin_sha ) +from gitdb.base import IStream +from gitdb.db import MemoryDB + +import git.diff as diff +import os.path as osp from .fun import ( entry_key, @@ -69,10 +58,17 @@ S_IFGITLINK, run_commit_hook ) +from .typ import ( + BaseIndexEntry, + IndexEntry, +) +from .util import ( + TemporaryFileSwap, + post_clear_cache, + default_index, + git_working_dir +) -from gitdb.base import IStream -from gitdb.db import MemoryDB -from gitdb.util import to_bin_sha __all__ = ('IndexFile', 'CheckoutError') @@ -354,7 +350,7 @@ def from_tree(cls, repo, *treeish, **kwargs): index.entries # force it to read the file as we will delete the temp-file del(index_handler) # release as soon as possible finally: - if os.path.exists(tmp_index): + if osp.exists(tmp_index): os.remove(tmp_index) # END index merge handling @@ -374,8 +370,8 @@ def raise_exc(e): rs = r + os.sep for path in paths: abs_path = path - if not os.path.isabs(abs_path): - abs_path = os.path.join(r, path) + if not osp.isabs(abs_path): + abs_path = osp.join(r, path) # END make absolute path try: @@ -407,7 +403,7 @@ def raise_exc(e): for root, dirs, files in os.walk(abs_path, onerror=raise_exc): # @UnusedVariable for rela_file in files: # add relative paths only - yield os.path.join(root.replace(rs, ''), rela_file) + yield osp.join(root.replace(rs, ''), rela_file) # END for each file in subdir # END for each subdirectory except OSError: @@ -569,7 +565,7 @@ def _process_diff_args(self, args): def _to_relative_path(self, path): """:return: Version of path relative to our git directory or raise ValueError if it is not within our git direcotory""" - if not os.path.isabs(path): + if not osp.isabs(path): return path if self.repo.bare: raise InvalidGitRepositoryError("require non-bare repository") @@ -617,12 +613,12 @@ def _entries_for_paths(self, paths, path_rewriter, fprogress, entries): entries_added = list() if path_rewriter: for path in paths: - if os.path.isabs(path): + if osp.isabs(path): abspath = path gitrelative_path = path[len(self.repo.working_tree_dir) + 1:] else: gitrelative_path = path - abspath = os.path.join(self.repo.working_tree_dir, gitrelative_path) + abspath = osp.join(self.repo.working_tree_dir, gitrelative_path) # end obtain relative and absolute paths blob = Blob(self.repo, Blob.NULL_BIN_SHA, diff --git a/git/index/fun.py b/git/index/fun.py index 7a7593fed..d9c0f2153 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -1,6 +1,8 @@ # Contains standalone functions to accompany the index implementation and make it # more versatile # NOTE: Autodoc hates it if this is a docstring +from io import BytesIO +import os from stat import ( S_IFDIR, S_IFLNK, @@ -9,13 +11,18 @@ S_IFMT, S_IFREG, ) - -from io import BytesIO -import os import subprocess -from git.util import IndexFileSHA1Writer, finalize_process from git.cmd import PROC_CREATIONFLAGS, handle_process_output +from git.compat import ( + PY3, + defenc, + force_text, + force_bytes, + is_posix, + safe_encode, + safe_decode, +) from git.exc import ( UnmergedEntriesError, HookExecutionError @@ -25,6 +32,11 @@ traverse_tree_recursive, traverse_trees_recursive ) +from git.util import IndexFileSHA1Writer, finalize_process +from gitdb.base import IStream +from gitdb.typ import str_tree_type + +import os.path as osp from .typ import ( BaseIndexEntry, @@ -32,23 +44,11 @@ CE_NAMEMASK, CE_STAGESHIFT ) - from .util import ( pack, unpack ) -from gitdb.base import IStream -from gitdb.typ import str_tree_type -from git.compat import ( - PY3, - defenc, - force_text, - force_bytes, - is_posix, - safe_encode, - safe_decode, -) S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule CE_NAMEMASK_INV = ~CE_NAMEMASK @@ -59,7 +59,7 @@ def hook_path(name, git_dir): """:return: path to the given named hook in the given git repository directory""" - return os.path.join(git_dir, 'hooks', name) + return osp.join(git_dir, 'hooks', name) def run_commit_hook(name, index): diff --git a/git/index/util.py b/git/index/util.py index ce798851d..3c59b1d8c 100644 --- a/git/index/util.py +++ b/git/index/util.py @@ -1,12 +1,14 @@ """Module containing index utilities""" +from functools import wraps +import os import struct import tempfile -import os - -from functools import wraps from git.compat import is_win +import os.path as osp + + __all__ = ('TemporaryFileSwap', 'post_clear_cache', 'default_index', 'git_working_dir') #{ Aliases @@ -32,8 +34,8 @@ def __init__(self, file_path): pass def __del__(self): - if os.path.isfile(self.tmp_file_path): - if is_win and os.path.exists(self.file_path): + if osp.isfile(self.tmp_file_path): + if is_win and osp.exists(self.file_path): os.remove(self.file_path) os.rename(self.tmp_file_path, self.file_path) # END temp file exists diff --git a/git/objects/base.py b/git/objects/base.py index 0b8499601..d60807791 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -3,14 +3,13 @@ # # 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 gitdb.util import ( - bin_to_hex, - basename -) +from git.util import LazyMixin, join_path_native, stream_copy, bin_to_hex import gitdb.typ as dbtyp +import os.path as osp + +from .util import get_object_type_by_name + _assertion_msg_format = "Created object %r whose python type %r disagrees with the acutal git object type %r" @@ -170,7 +169,7 @@ def _set_cache_(self, attr): @property def name(self): """:return: Name portion of the path, effectively being the basename""" - return basename(self.path) + return osp.basename(self.path) @property def abspath(self): diff --git a/git/objects/commit.py b/git/objects/commit.py index 1534c5529..859558069 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -5,8 +5,8 @@ # 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 ( + hex_to_bin, Actor, Iterable, Stats, diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 9bb563d7b..d2c6e0209 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -1,21 +1,18 @@ -from .util import ( - mkhead, - sm_name, - sm_section, - SubmoduleConfigParser, - find_first_remote_branch -) -from git.objects.util import Traversable -from io import BytesIO # need a dict to set bloody .name field -from git.util import ( - Iterable, - join_path_native, - to_native_path_linux, - RemoteProgress, - rmtree, - unbare_repo -) +# need a dict to set bloody .name field +from io import BytesIO +import logging +import os +import stat +from unittest.case import SkipTest +import uuid +import git +from git.cmd import Git +from git.compat import ( + string_types, + defenc, + is_win, +) from git.config import ( SectionConstraint, GitConfigParser, @@ -26,22 +23,28 @@ NoSuchPathError, RepositoryDirtyError ) -from git.compat import ( - string_types, - defenc, - is_win, +from git.objects.base import IndexObject, Object +from git.objects.util import Traversable +from git.util import ( + Iterable, + join_path_native, + to_native_path_linux, + RemoteProgress, + rmtree, + unbare_repo ) +from git.util import HIDE_WINDOWS_KNOWN_ERRORS -import stat -import git +import os.path as osp + +from .util import ( + mkhead, + sm_name, + sm_section, + SubmoduleConfigParser, + find_first_remote_branch +) -import os -import logging -import uuid -from unittest.case import SkipTest -from git.util import HIDE_WINDOWS_KNOWN_ERRORS -from git.objects.base import IndexObject, Object -from git.cmd import Git __all__ = ["Submodule", "UpdateProgress"] @@ -120,7 +123,7 @@ def _set_cache_(self, attr): self.path = reader.get_value('path') except cp.NoSectionError: raise ValueError("This submodule instance does not exist anymore in '%s' file" - % os.path.join(self.repo.working_tree_dir, '.gitmodules')) + % osp.join(self.repo.working_tree_dir, '.gitmodules')) # end self._url = reader.get_value('url') # git-python extension values - optional @@ -181,7 +184,7 @@ def _config_parser(cls, repo, parent_commit, read_only): # end hanlde parent_commit if not repo.bare and parent_matches_head: - fp_module = os.path.join(repo.working_tree_dir, cls.k_modules_file) + fp_module = osp.join(repo.working_tree_dir, cls.k_modules_file) else: assert parent_commit is not None, "need valid parent_commit in bare repositories" try: @@ -229,9 +232,9 @@ def _config_parser_constrained(self, read_only): @classmethod def _module_abspath(cls, parent_repo, path, name): if cls._need_gitfile_submodules(parent_repo.git): - return os.path.join(parent_repo.git_dir, 'modules', name) + return osp.join(parent_repo.git_dir, 'modules', name) else: - return os.path.join(parent_repo.working_tree_dir, path) + return osp.join(parent_repo.working_tree_dir, path) # end @classmethod @@ -246,10 +249,10 @@ def _clone_repo(cls, repo, url, path, name, **kwargs): module_checkout_path = module_abspath if cls._need_gitfile_submodules(repo.git): kwargs['separate_git_dir'] = module_abspath - module_abspath_dir = os.path.dirname(module_abspath) - if not os.path.isdir(module_abspath_dir): + module_abspath_dir = osp.dirname(module_abspath) + if not osp.isdir(module_abspath_dir): os.makedirs(module_abspath_dir) - module_checkout_path = os.path.join(repo.working_tree_dir, path) + module_checkout_path = osp.join(repo.working_tree_dir, path) # end clone = git.Repo.clone_from(url, module_checkout_path, **kwargs) @@ -267,7 +270,7 @@ def _to_relative_path(cls, parent_repo, path): path = path[:-1] # END handle trailing slash - if os.path.isabs(path): + if osp.isabs(path): working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir) if not path.startswith(working_tree_linux): raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'" @@ -291,18 +294,18 @@ def _write_git_file_and_module_config(cls, working_tree_dir, module_abspath): :param working_tree_dir: directory to write the .git file into :param module_abspath: absolute path to the bare repository """ - git_file = os.path.join(working_tree_dir, '.git') - rela_path = os.path.relpath(module_abspath, start=working_tree_dir) + git_file = osp.join(working_tree_dir, '.git') + rela_path = osp.relpath(module_abspath, start=working_tree_dir) if is_win: - if os.path.isfile(git_file): + if osp.isfile(git_file): os.remove(git_file) with open(git_file, 'wb') as fp: fp.write(("gitdir: %s" % rela_path).encode(defenc)) - with GitConfigParser(os.path.join(module_abspath, 'config'), + with GitConfigParser(osp.join(module_abspath, 'config'), read_only=False, merge_includes=False) as writer: writer.set_value('core', 'worktree', - to_native_path_linux(os.path.relpath(working_tree_dir, start=module_abspath))) + to_native_path_linux(osp.relpath(working_tree_dir, start=module_abspath))) #{ Edit Interface @@ -501,7 +504,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress= # there is no git-repository yet - but delete empty paths checkout_module_abspath = self.abspath - if not dry_run and os.path.isdir(checkout_module_abspath): + if not dry_run and osp.isdir(checkout_module_abspath): try: os.rmdir(checkout_module_abspath) except OSError: @@ -671,7 +674,7 @@ def move(self, module_path, configuration=True, module=True): # END handle no change module_checkout_abspath = join_path_native(self.repo.working_tree_dir, module_checkout_path) - if os.path.isfile(module_checkout_abspath): + if osp.isfile(module_checkout_abspath): raise ValueError("Cannot move repository onto a file: %s" % module_checkout_abspath) # END handle target files @@ -684,12 +687,12 @@ def move(self, module_path, configuration=True, module=True): # remove existing destination if module: - if os.path.exists(module_checkout_abspath): + if osp.exists(module_checkout_abspath): if len(os.listdir(module_checkout_abspath)): raise ValueError("Destination module directory was not empty") # END handle non-emptiness - if os.path.islink(module_checkout_abspath): + if osp.islink(module_checkout_abspath): os.remove(module_checkout_abspath) else: os.rmdir(module_checkout_abspath) @@ -704,11 +707,11 @@ def move(self, module_path, configuration=True, module=True): # move the module into place if possible cur_path = self.abspath renamed_module = False - if module and os.path.exists(cur_path): + if module and osp.exists(cur_path): os.renames(cur_path, module_checkout_abspath) renamed_module = True - if os.path.isfile(os.path.join(module_checkout_abspath, '.git')): + if osp.isfile(osp.join(module_checkout_abspath, '.git')): module_abspath = self._module_abspath(self.repo, self.path, self.name) self._write_git_file_and_module_config(module_checkout_abspath, module_abspath) # end handle git file rewrite @@ -804,11 +807,11 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False): # state. Delete the .git folders last, start with the submodules first mp = self.abspath method = None - if os.path.islink(mp): + if osp.islink(mp): method = os.remove - elif os.path.isdir(mp): + elif osp.isdir(mp): method = rmtree - elif os.path.exists(mp): + elif osp.exists(mp): raise AssertionError("Cannot forcibly delete repository as it was neither a link, nor a directory") # END handle brutal deletion if not dry_run: @@ -865,7 +868,7 @@ def remove(self, module=True, force=False, configuration=True, dry_run=False): # END delete tree if possible # END handle force - if not dry_run and os.path.isdir(git_dir): + if not dry_run and osp.isdir(git_dir): self._clear_cache() try: rmtree(git_dir) diff --git a/git/objects/tag.py b/git/objects/tag.py index cefff0838..19cb04bf2 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -5,12 +5,9 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php """ Module containing all object based types. """ from . import base -from .util import ( - get_object_type_by_name, - parse_actor_and_date -) -from gitdb.util import hex_to_bin -from git.compat import defenc +from .util import get_object_type_by_name, parse_actor_and_date +from ..util import hex_to_bin +from ..compat import defenc __all__ = ("TagObject", ) diff --git a/git/objects/tree.py b/git/objects/tree.py index 4f853f92a..46fe63e7d 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -5,7 +5,7 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php from git.util import join_path import git.diff as diff -from gitdb.util import to_bin_sha +from git.util import to_bin_sha from . import util from .base import IndexObject @@ -18,7 +18,7 @@ tree_to_stream ) -from gitdb.utils.compat import PY3 +from git.compat import PY3 if PY3: cmp = lambda a, b: (a > b) - (a < b) diff --git a/git/refs/log.py b/git/refs/log.py index 3078355d6..bab6ae044 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -1,31 +1,29 @@ +import re +import time + +from git.compat import ( + PY3, + xrange, + string_types, + defenc +) +from git.objects.util import ( + parse_date, + Serializable, + altz_to_utctz_str, +) from git.util import ( Actor, LockedFD, LockFile, assure_directory_exists, to_native_path, -) - -from gitdb.util import ( bin_to_hex, - join, - file_contents_ro_filepath, + file_contents_ro_filepath ) -from git.objects.util import ( - parse_date, - Serializable, - altz_to_utctz_str, -) -from git.compat import ( - PY3, - xrange, - string_types, - defenc -) +import os.path as osp -import time -import re __all__ = ["RefLog", "RefLogEntry"] @@ -185,7 +183,7 @@ def path(cls, ref): instance would be found. The path is not guaranteed to point to a valid file though. :param ref: SymbolicReference instance""" - return join(ref.repo.git_dir, "logs", to_native_path(ref.path)) + return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path)) @classmethod def iter_entries(cls, stream): diff --git a/git/refs/remote.py b/git/refs/remote.py index 1f256b752..4fc784d15 100644 --- a/git/refs/remote.py +++ b/git/refs/remote.py @@ -1,9 +1,10 @@ +import os + from git.util import join_path -from gitdb.util import join -from .head import Head +import os.path as osp -import os +from .head import Head __all__ = ["RemoteReference"] @@ -36,7 +37,7 @@ def delete(cls, repo, *refs, **kwargs): # and delete remainders manually for ref in refs: try: - os.remove(join(repo.git_dir, ref.path)) + os.remove(osp.join(repo.git_dir, ref.path)) except OSError: pass # END for each ref diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index ebaff8ca4..3a93d81c6 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -1,34 +1,28 @@ import os +from git.compat import ( + string_types, + defenc +) from git.objects import Object, Commit from git.util import ( join_path, join_path_native, to_native_path_linux, - assure_directory_exists + assure_directory_exists, + hex_to_bin, + LockedFD ) - from gitdb.exc import ( BadObject, BadName ) -from gitdb.util import ( - join, - dirname, - isdir, - exists, - isfile, - rename, - hex_to_bin, - LockedFD -) -from git.compat import ( - string_types, - defenc -) + +import os.path as osp from .log import RefLog + __all__ = ["SymbolicReference"] @@ -81,7 +75,7 @@ def abspath(self): @classmethod def _get_packed_refs_path(cls, repo): - return join(repo.git_dir, 'packed-refs') + return osp.join(repo.git_dir, 'packed-refs') @classmethod def _iter_packed_refs(cls, repo): @@ -134,7 +128,7 @@ def _get_ref_info(cls, repo, ref_path): point to, or None""" tokens = None try: - with open(join(repo.git_dir, ref_path), 'rt') as fp: + with open(osp.join(repo.git_dir, ref_path), 'rt') as fp: value = fp.read().rstrip() # Don't only split on spaces, but on whitespace, which allows to parse lines like # 60b64ef992065e2600bfef6187a97f92398a9144 branch 'master' of git-server:/path/to/repo @@ -418,8 +412,8 @@ def delete(cls, repo, path): or just "myreference", hence 'refs/' is implied. Alternatively the symbolic reference to be deleted""" full_ref_path = cls.to_full_path(path) - abs_path = join(repo.git_dir, full_ref_path) - if exists(abs_path): + abs_path = osp.join(repo.git_dir, full_ref_path) + if osp.exists(abs_path): os.remove(abs_path) else: # check packed refs @@ -458,7 +452,7 @@ def delete(cls, repo, path): # delete the reflog reflog_path = RefLog.path(cls(repo, full_ref_path)) - if os.path.isfile(reflog_path): + if osp.isfile(reflog_path): os.remove(reflog_path) # END remove reflog @@ -470,14 +464,14 @@ def _create(cls, repo, path, resolve, reference, force, logmsg=None): corresponding object and a detached symbolic reference will be created instead""" full_ref_path = cls.to_full_path(path) - abs_ref_path = join(repo.git_dir, full_ref_path) + abs_ref_path = osp.join(repo.git_dir, full_ref_path) # figure out target data target = reference if resolve: target = repo.rev_parse(str(reference)) - if not force and isfile(abs_ref_path): + if not force and osp.isfile(abs_ref_path): target_data = str(target) if isinstance(target, SymbolicReference): target_data = target.path @@ -544,9 +538,9 @@ def rename(self, new_path, force=False): if self.path == new_path: return self - new_abs_path = join(self.repo.git_dir, new_path) - cur_abs_path = join(self.repo.git_dir, self.path) - if isfile(new_abs_path): + new_abs_path = osp.join(self.repo.git_dir, new_path) + cur_abs_path = osp.join(self.repo.git_dir, self.path) + if osp.isfile(new_abs_path): if not force: # if they point to the same file, its not an error with open(new_abs_path, 'rb') as fd1: @@ -561,12 +555,12 @@ def rename(self, new_path, force=False): os.remove(new_abs_path) # END handle existing target file - dname = dirname(new_abs_path) - if not isdir(dname): + dname = osp.dirname(new_abs_path) + if not osp.isdir(dname): os.makedirs(dname) # END create directory - rename(cur_abs_path, new_abs_path) + os.rename(cur_abs_path, new_abs_path) self.path = new_path return self diff --git a/git/remote.py b/git/remote.py index 71585a41b..b920079dc 100644 --- a/git/remote.py +++ b/git/remote.py @@ -5,8 +5,25 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php # Module implementing a remote object allowing easy access to git remotes +import logging import re +from git.cmd import handle_process_output, Git +from git.compat import (defenc, force_text, is_win) +from git.exc import GitCommandError +from git.util import ( + LazyMixin, + Iterable, + IterableList, + RemoteProgress, + CallableRemoteProgress +) +from git.util import ( + join_path, +) + +import os.path as osp + from .config import ( SectionConstraint, cp, @@ -18,21 +35,7 @@ SymbolicReference, TagReference ) -from git.util import ( - LazyMixin, - Iterable, - IterableList, - RemoteProgress, - CallableRemoteProgress -) -from git.util import ( - join_path, -) -from git.cmd import handle_process_output, Git -from gitdb.util import join -from git.compat import (defenc, force_text, is_win) -import logging -from git.exc import GitCommandError + log = logging.getLogger('git.remote') @@ -644,7 +647,7 @@ def _get_fetch_info_from_stderr(self, proc, progress): continue # read head information - with open(join(self.repo.git_dir, 'FETCH_HEAD'), 'rb') as fp: + with open(osp.join(self.repo.git_dir, 'FETCH_HEAD'), 'rb') as fp: fetch_head_info = [l.decode(defenc) for l in fp.readlines()] l_fil = len(fetch_info_lines) diff --git a/git/repo/base.py b/git/repo/base.py index c5cdce7c6..21d129e98 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -4,52 +4,16 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from git.exc import ( - InvalidGitRepositoryError, - NoSuchPathError, - GitCommandError -) +from collections import namedtuple +import logging +import os +import re +import sys + from git.cmd import ( Git, handle_process_output ) -from git.refs import ( - HEAD, - Head, - Reference, - TagReference, -) -from git.objects import ( - Submodule, - RootModule, - Commit -) -from git.util import ( - Actor, - finalize_process -) -from git.index import IndexFile -from git.config import GitConfigParser -from git.remote import ( - Remote, - add_progress, - to_progress_instance -) - -from git.db import GitCmdObjectDB - -from gitdb.util import ( - join, - isfile, - hex_to_bin -) - -from .fun import ( - rev_parse, - is_git_dir, - find_git_dir, - touch, -) from git.compat import ( text_type, defenc, @@ -58,12 +22,19 @@ range, is_win, ) +from git.config import GitConfigParser +from git.db import GitCmdObjectDB +from git.exc import InvalidGitRepositoryError, NoSuchPathError, GitCommandError +from git.index import IndexFile +from git.objects import Submodule, RootModule, Commit +from git.refs import HEAD, Head, Reference, TagReference +from git.remote import Remote, add_progress, to_progress_instance +from git.util import Actor, finalize_process, decygpath, hex_to_bin + +import os.path as osp + +from .fun import rev_parse, is_git_dir, find_git_dir, touch -import os -import sys -import re -import logging -from collections import namedtuple log = logging.getLogger(__name__) @@ -79,7 +50,7 @@ def _expand_path(p): - return os.path.abspath(os.path.expandvars(os.path.expanduser(p))) + return osp.abspath(osp.expandvars(osp.expanduser(p))) class Repo(object): @@ -124,6 +95,8 @@ def __init__(self, path=None, odbt=DefaultDBType, search_parent_directories=Fals repo = Repo("~/Development/git-python.git") repo = Repo("$REPOSITORIES/Development/git-python.git") + In *Cygwin*, path may be a `'cygdrive/...'` prefixed path. + :param odbt: Object DataBase type - a type which is constructed by providing the directory containing the database objects, i.e. .git/objects. It will @@ -136,9 +109,12 @@ def __init__(self, path=None, odbt=DefaultDBType, search_parent_directories=Fals :raise InvalidGitRepositoryError: :raise NoSuchPathError: :return: git.Repo """ + if Git.is_cygwin(): + path = decygpath(path) + epath = _expand_path(path or os.getcwd()) self.git = None # should be set for __del__ not to fail in case we raise - if not os.path.exists(epath): + if not osp.exists(epath): raise NoSuchPathError(epath) self.working_dir = None @@ -148,24 +124,24 @@ def __init__(self, path=None, odbt=DefaultDBType, search_parent_directories=Fals # walk up the path to find the .git dir while curpath: - # ABOUT os.path.NORMPATH + # ABOUT osp.NORMPATH # It's important to normalize the paths, as submodules will otherwise initialize their # repo instances with paths that depend on path-portions that will not exist after being # removed. It's just cleaner. if is_git_dir(curpath): - self.git_dir = os.path.normpath(curpath) - self._working_tree_dir = os.path.dirname(self.git_dir) + self.git_dir = osp.normpath(curpath) + self._working_tree_dir = osp.dirname(self.git_dir) break - gitpath = find_git_dir(join(curpath, '.git')) + gitpath = find_git_dir(osp.join(curpath, '.git')) if gitpath is not None: - self.git_dir = os.path.normpath(gitpath) + self.git_dir = osp.normpath(gitpath) self._working_tree_dir = curpath break if not search_parent_directories: break - curpath, dummy = os.path.split(curpath) + curpath, dummy = osp.split(curpath) if not dummy: break # END while curpath @@ -190,7 +166,7 @@ def __init__(self, path=None, odbt=DefaultDBType, search_parent_directories=Fals self.git = self.GitCommandWrapperType(self.working_dir) # special handling, in special times - args = [join(self.git_dir, 'objects')] + args = [osp.join(self.git_dir, 'objects')] if issubclass(odbt, GitCmdObjectDB): args.append(self.git) self.odb = odbt(*args) @@ -212,12 +188,12 @@ def __hash__(self): # Description property def _get_description(self): - filename = join(self.git_dir, 'description') + filename = osp.join(self.git_dir, 'description') with open(filename, 'rb') as fp: return fp.read().rstrip().decode(defenc) def _set_description(self, descr): - filename = join(self.git_dir, 'description') + filename = osp.join(self.git_dir, 'description') with open(filename, 'wb') as fp: fp.write((descr + '\n').encode(defenc)) @@ -381,12 +357,12 @@ def _get_config_path(self, config_level): if config_level == "system": return "/etc/gitconfig" elif config_level == "user": - config_home = os.environ.get("XDG_CONFIG_HOME") or os.path.join(os.environ.get("HOME", '~'), ".config") - return os.path.normpath(os.path.expanduser(join(config_home, "git", "config"))) + config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(os.environ.get("HOME", '~'), ".config") + return osp.normpath(osp.expanduser(osp.join(config_home, "git", "config"))) elif config_level == "global": - return os.path.normpath(os.path.expanduser("~/.gitconfig")) + return osp.normpath(osp.expanduser("~/.gitconfig")) elif config_level == "repository": - return os.path.normpath(join(self.git_dir, "config")) + return osp.normpath(osp.join(self.git_dir, "config")) raise ValueError("Invalid configuration level: %r" % config_level) @@ -530,12 +506,12 @@ def is_ancestor(self, ancestor_rev, rev): return True def _get_daemon_export(self): - filename = join(self.git_dir, self.DAEMON_EXPORT_FILE) - return os.path.exists(filename) + filename = osp.join(self.git_dir, self.DAEMON_EXPORT_FILE) + return osp.exists(filename) def _set_daemon_export(self, value): - filename = join(self.git_dir, self.DAEMON_EXPORT_FILE) - fileexists = os.path.exists(filename) + filename = osp.join(self.git_dir, self.DAEMON_EXPORT_FILE) + fileexists = osp.exists(filename) if value and not fileexists: touch(filename) elif not value and fileexists: @@ -550,9 +526,9 @@ def _get_alternates(self): """The list of alternates for this repo from which objects can be retrieved :return: list of strings being pathnames of alternates""" - alternates_path = join(self.git_dir, 'objects', 'info', 'alternates') + alternates_path = osp.join(self.git_dir, 'objects', 'info', 'alternates') - if os.path.exists(alternates_path): + if osp.exists(alternates_path): with open(alternates_path, 'rb') as f: alts = f.read().decode(defenc) return alts.strip().splitlines() @@ -570,9 +546,9 @@ def _set_alternates(self, alts): :note: The method does not check for the existance of the paths in alts as the caller is responsible.""" - alternates_path = join(self.git_dir, 'objects', 'info', 'alternates') + alternates_path = osp.join(self.git_dir, 'objects', 'info', 'alternates') if not alts: - if isfile(alternates_path): + if osp.isfile(alternates_path): os.remove(alternates_path) else: with open(alternates_path, 'wb') as f: @@ -601,7 +577,7 @@ def is_dirty(self, index=True, working_tree=True, untracked_files=False, default_args.append(path) if index: # diff index against HEAD - if isfile(self.index.path) and \ + if osp.isfile(self.index.path) and \ len(self.git.diff('--cached', *default_args)): return True # END index handling @@ -861,7 +837,7 @@ def init(cls, path=None, mkdir=True, odbt=DefaultDBType, **kwargs): :return: ``git.Repo`` (the newly created repo)""" if path: path = _expand_path(path) - if mkdir and path and not os.path.exists(path): + if mkdir and path and not osp.exists(path): os.makedirs(path, 0o755) # git command automatically chdir into the directory @@ -875,19 +851,32 @@ def _clone(cls, git, url, path, odb_default_type, progress, **kwargs): progress = to_progress_instance(progress) odbt = kwargs.pop('odbt', odb_default_type) - proc = git.clone(url, path, with_extended_output=True, as_process=True, + + ## A bug win cygwin's Git, when `--bare` or `--separate-git-dir` + # it prepends the cwd or(?) the `url` into the `path, so:: + # git clone --bare /cygwin/d/foo.git C:\\Work + # becomes:: + # git clone --bare /cygwin/d/foo.git /cygwin/d/C:\\Work + # + clone_path = (Git.polish_url(path) + if Git.is_cygwin() and 'bare'in kwargs + else path) + sep_dir = kwargs.get('separate_git_dir') + if sep_dir: + kwargs['separate_git_dir'] = Git.polish_url(sep_dir) + proc = git.clone(Git.polish_url(url), clone_path, with_extended_output=True, as_process=True, v=True, **add_progress(kwargs, git, progress)) if progress: handle_process_output(proc, None, progress.new_message_handler(), finalize_process) else: - (stdout, stderr) = proc.communicate() # FIXME: Will block of outputs are big! + (stdout, stderr) = proc.communicate() # FIXME: Will block if outputs are big! log.debug("Cmd(%s)'s unused stdout: %s", getattr(proc, 'args', ''), stdout) finalize_process(proc, stderr=stderr) # our git command could have a different working dir than our actual # environment, hence we prepend its working dir if required - if not os.path.isabs(path) and git.working_dir: - path = join(git._working_dir, path) + if not osp.isabs(path) and git.working_dir: + path = osp.join(git._working_dir, path) # adjust remotes - there may be operating systems which use backslashes, # These might be given as initial paths, but when handling the config file @@ -965,7 +954,7 @@ def has_separate_working_tree(self): """ if self.bare: return False - return os.path.isfile(os.path.join(self.working_tree_dir, '.git')) + return osp.isfile(osp.join(self.working_tree_dir, '.git')) rev_parse = rev_parse diff --git a/git/repo/fun.py b/git/repo/fun.py index 320eb1c8d..7ea45e6b7 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -2,22 +2,18 @@ import os from string import digits +from git.compat import xrange +from git.exc import WorkTreeRepositoryUnsupported +from git.objects import Object +from git.refs import SymbolicReference +from git.util import hex_to_bin, bin_to_hex, decygpath from gitdb.exc import ( BadObject, BadName, ) -from git.refs import SymbolicReference -from git.objects import Object -from gitdb.util import ( - join, - isdir, - isfile, - dirname, - hex_to_bin, - bin_to_hex -) -from git.exc import WorkTreeRepositoryUnsupported -from git.compat import xrange + +import os.path as osp +from git.cmd import Git __all__ = ('rev_parse', 'is_git_dir', 'touch', 'find_git_dir', 'name_to_object', 'short_to_long', 'deref_tag', @@ -38,13 +34,15 @@ def is_git_dir(d): but at least clearly indicates that we don't support it. There is the unlikely danger to throw if we see directories which just look like a worktree dir, but are none.""" - if isdir(d): - if isdir(join(d, 'objects')) and isdir(join(d, 'refs')): - headref = join(d, 'HEAD') - return isfile(headref) or \ - (os.path.islink(headref) and + if osp.isdir(d): + if osp.isdir(osp.join(d, 'objects')) and osp.isdir(osp.join(d, 'refs')): + headref = osp.join(d, 'HEAD') + return osp.isfile(headref) or \ + (osp.islink(headref) and os.readlink(headref).startswith('refs')) - elif isfile(join(d, 'gitdir')) and isfile(join(d, 'commondir')) and isfile(join(d, 'gitfile')): + elif (osp.isfile(osp.join(d, 'gitdir')) and + osp.isfile(osp.join(d, 'commondir')) and + osp.isfile(osp.join(d, 'gitfile'))): raise WorkTreeRepositoryUnsupported(d) return False @@ -62,8 +60,11 @@ def find_git_dir(d): else: if content.startswith('gitdir: '): path = content[8:] - if not os.path.isabs(path): - path = join(dirname(d), path) + if Git.is_cygwin(): + ## Cygwin creates submodules prefixed with `/cygdrive/...` suffixes. + path = decygpath(path) + if not osp.isabs(path): + path = osp.join(osp.dirname(d), path) return find_git_dir(path) # end handle exception return None diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index c5a003ea1..871cbe93c 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -5,6 +5,7 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php from __future__ import print_function +import contextlib from functools import wraps import io import logging @@ -16,7 +17,7 @@ import unittest from git.compat import string_types, is_win, PY3 -from git.util import rmtree +from git.util import rmtree, cwd import os.path as osp @@ -32,7 +33,7 @@ 'GIT_REPO', 'GIT_DAEMON_PORT' ) -log = logging.getLogger('git.util') +log = logging.getLogger(__name__) #{ Routines @@ -118,7 +119,7 @@ def repo_creator(self): if bare: prefix = '' # END handle prefix - repo_dir = tempfile.mktemp("%sbare_%s" % (prefix, func.__name__)) + repo_dir = tempfile.mktemp(prefix="%sbare_%s" % (prefix, func.__name__)) rw_repo = self.rorepo.clone(repo_dir, shared=True, bare=bare, n=True) rw_repo.head.commit = rw_repo.commit(working_tree_ref) @@ -151,32 +152,66 @@ def repo_creator(self): return argument_passer -def launch_git_daemon(base_path, ip, port): - from git import Git - if is_win: - ## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\, - # but if invoked as 'git daemon', it detaches from parent `git` cmd, - # and then CANNOT DIE! - # So, invoke it as a single command. - ## Cygwin-git has no daemon. But it can use MINGW's. - # - daemon_cmd = ['git-daemon', - '--enable=receive-pack', - '--listen=%s' % ip, - '--port=%s' % port, - '--base-path=%s' % base_path, - base_path] - gd = Git().execute(daemon_cmd, as_process=True) +@contextlib.contextmanager +def git_daemon_launched(base_path, ip, port): + from git import Git # Avoid circular deps. + + gd = None + try: + if is_win: + ## On MINGW-git, daemon exists in .\Git\mingw64\libexec\git-core\, + # but if invoked as 'git daemon', it detaches from parent `git` cmd, + # and then CANNOT DIE! + # So, invoke it as a single command. + ## Cygwin-git has no daemon. But it can use MINGW's. + # + daemon_cmd = ['git-daemon', + '--enable=receive-pack', + '--listen=%s' % ip, + '--port=%s' % port, + '--base-path=%s' % base_path, + base_path] + gd = Git().execute(daemon_cmd, as_process=True) + else: + gd = Git().daemon(base_path, + enable='receive-pack', + listen=ip, + port=port, + base_path=base_path, + as_process=True) + # yes, I know ... fortunately, this is always going to work if sleep time is just large enough + time.sleep(0.5 * (1 + is_win)) + except Exception as ex: + msg = textwrap.dedent(""" + Launching git-daemon failed due to: %s + Probably test will fail subsequently. + + BUT you may start *git-daemon* manually with this command:" + git daemon --enable=receive-pack --listen=%s --port=%s --base-path=%s %s + You may also run the daemon on a different port by passing --port=" + and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to + """) + if is_win: + msg += textwrap.dedent(""" + + On Windows, + the `git-daemon.exe` must be in PATH. + For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear. + CYGWIN has no daemon, but if one exists, it gets along fine (but has also paths problems).""") + log.warning(msg, ex, ip, port, base_path, base_path, exc_info=1) + + yield # OK, assume daemon started manually. + else: - gd = Git().daemon(base_path, - enable='receive-pack', - listen=ip, - port=port, - base_path=base_path, - as_process=True) - # yes, I know ... fortunately, this is always going to work if sleep time is just large enough - time.sleep(0.5) - return gd + yield # Yield outside try, to avoid catching + finally: + if gd: + try: + log.debug("Killing git-daemon...") + gd.proc.kill() + except Exception as ex: + ## Either it has died (and we're here), or it won't die, again here... + log.debug("Hidden error while Killing git-daemon: %s", ex, exc_info=1) def with_rw_and_rw_remote_repo(working_tree_ref): @@ -193,10 +228,10 @@ def with_rw_and_rw_remote_repo(working_tree_ref): directories in it. The following scetch demonstrates this:: - rorepo ------> rw_remote_repo ------> rw_repo + rorepo ------> rw_daemon_repo ------> rw_repo The test case needs to support the following signature:: - def case(self, rw_repo, rw_remote_repo) + def case(self, rw_repo, rw_daemon_repo) This setup allows you to test push and pull scenarios and hooks nicely. @@ -211,94 +246,65 @@ def argument_passer(func): @wraps(func) def remote_repo_creator(self): - remote_repo_dir = tempfile.mktemp("remote_repo_%s" % func.__name__) - repo_dir = tempfile.mktemp("remote_clone_non_bare_repo") + rw_daemon_repo_dir = tempfile.mktemp(prefix="daemon_repo-%s-" % func.__name__) + rw_repo_dir = tempfile.mktemp(prefix="daemon_cloned_repo-%s-" % func.__name__) - rw_remote_repo = self.rorepo.clone(remote_repo_dir, shared=True, bare=True) + rw_daemon_repo = self.rorepo.clone(rw_daemon_repo_dir, shared=True, bare=True) # recursive alternates info ? - rw_repo = rw_remote_repo.clone(repo_dir, shared=True, bare=False, n=True) - rw_repo.head.commit = working_tree_ref - rw_repo.head.reference.checkout() - - # prepare for git-daemon - rw_remote_repo.daemon_export = True - - # this thing is just annoying ! - with rw_remote_repo.config_writer() as crw: - section = "daemon" - try: - crw.add_section(section) - except Exception: - pass - crw.set(section, "receivepack", True) - - # Initialize the remote - first do it as local remote and pull, then - # we change the url to point to the daemon. - d_remote = Remote.create(rw_repo, "daemon_origin", remote_repo_dir) - d_remote.fetch() - - base_path, rel_repo_dir = osp.split(remote_repo_dir) - - remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, rel_repo_dir)) - with d_remote.config_writer as cw: - cw.set('url', remote_repo_url) - + rw_repo = rw_daemon_repo.clone(rw_repo_dir, shared=True, bare=False, n=True) try: - gd = launch_git_daemon(Git.polish_url(base_path), '127.0.0.1', GIT_DAEMON_PORT) - except Exception as ex: - if is_win: - msg = textwrap.dedent(""" - The `git-daemon.exe` must be in PATH. - For MINGW, look into .\Git\mingw64\libexec\git-core\), but problems with paths might appear. - CYGWIN has no daemon, but if one exists, it gets along fine (has also paths problems) - Anyhow, alternatively try starting `git-daemon` manually:""") - else: - msg = "Please try starting `git-daemon` manually:" - msg += textwrap.dedent(""" - git daemon --enable=receive-pack --base-path=%s %s - You can also run the daemon on a different port by passing --port=" - and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to - """ % (base_path, base_path)) - raise AssertionError(ex, msg) - # END make assertion - else: - # Try listing remotes, to diagnose whether the daemon is up. - rw_repo.git.ls_remote(d_remote) - - # adjust working dir - prev_cwd = os.getcwd() - os.chdir(rw_repo.working_dir) + rw_repo.head.commit = working_tree_ref + rw_repo.head.reference.checkout() - try: - return func(self, rw_repo, rw_remote_repo) - except: - log.info("Keeping repos after failure: repo_dir = %s, remote_repo_dir = %s", - repo_dir, remote_repo_dir) - repo_dir = remote_repo_dir = None - raise - finally: - os.chdir(prev_cwd) + # prepare for git-daemon + rw_daemon_repo.daemon_export = True + + # this thing is just annoying ! + with rw_daemon_repo.config_writer() as crw: + section = "daemon" + try: + crw.add_section(section) + except Exception: + pass + crw.set(section, "receivepack", True) + + # Initialize the remote - first do it as local remote and pull, then + # we change the url to point to the daemon. + d_remote = Remote.create(rw_repo, "daemon_origin", rw_daemon_repo_dir) + d_remote.fetch() + + base_daemon_path, rel_repo_dir = osp.split(rw_daemon_repo_dir) + + remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, rel_repo_dir)) + with d_remote.config_writer as cw: + cw.set('url', remote_repo_url) + + with git_daemon_launched(Git.polish_url(base_daemon_path, is_cygwin=False), # No daemon in Cygwin. + '127.0.0.1', + GIT_DAEMON_PORT): + # Try listing remotes, to diagnose whether the daemon is up. + rw_repo.git.ls_remote(d_remote) + + with cwd(rw_repo.working_dir): + try: + return func(self, rw_repo, rw_daemon_repo) + except: + log.info("Keeping repos after failure: \n rw_repo_dir: %s \n rw_daemon_repo_dir: %s", + rw_repo_dir, rw_daemon_repo_dir) + rw_repo_dir = rw_daemon_repo_dir = None + raise finally: - try: - log.debug("Killing git-daemon...") - gd.proc.kill() - except: - ## Either it has died (and we're here), or it won't die, again here... - pass - rw_repo.git.clear_cache() - rw_remote_repo.git.clear_cache() - rw_repo = rw_remote_repo = None + rw_daemon_repo.git.clear_cache() + del rw_repo + del rw_daemon_repo import gc gc.collect() - if repo_dir: - rmtree(repo_dir) - if remote_repo_dir: - rmtree(remote_repo_dir) - - if gd is not None: - gd.proc.wait() + if rw_repo_dir: + rmtree(rw_repo_dir) + if rw_daemon_repo_dir: + rmtree(rw_daemon_repo_dir) # END cleanup # END bare repo creator return remote_repo_creator diff --git a/git/test/performance/lib.py b/git/test/performance/lib.py index b57b9b714..700fee98f 100644 --- a/git/test/performance/lib.py +++ b/git/test/performance/lib.py @@ -1,21 +1,23 @@ """Contains library functions""" +import logging import os -from git.test.lib import ( - TestBase -) import tempfile -import logging +from git import ( + Repo +) from git.db import ( GitCmdObjectDB, GitDB ) - -from git import ( - Repo +from git.test.lib import ( + TestBase ) from git.util import rmtree +import os.path as osp + + #{ Invvariants k_env_git_repo = "GIT_PYTHON_TEST_GIT_REPO_BASE" @@ -52,7 +54,7 @@ def setUp(self): logging.info( ("You can set the %s environment variable to a .git repository of" % k_env_git_repo) + "your choice - defaulting to the gitpython repository") - repo_path = os.path.dirname(__file__) + repo_path = osp.dirname(__file__) # end set some repo path self.gitrorepo = Repo(repo_path, odbt=GitCmdObjectDB, search_parent_directories=True) self.puregitrorepo = Repo(repo_path, odbt=GitDB, search_parent_directories=True) diff --git a/git/test/performance/test_streams.py b/git/test/performance/test_streams.py index 42cbade5b..3909d8ff1 100644 --- a/git/test/performance/test_streams.py +++ b/git/test/performance/test_streams.py @@ -1,26 +1,27 @@ """Performance data streaming performance""" from __future__ import print_function -from time import time import os -import sys import subprocess +import sys +from time import time from git.test.lib import ( with_rw_repo ) -from gitdb.util import bin_to_hex +from git.util import bin_to_hex +from gitdb import ( + LooseObjectDB, + IStream +) from gitdb.test.lib import make_memory_file +import os.path as osp + from .lib import ( TestBigRepoR ) -from gitdb import ( - LooseObjectDB, - IStream -) - class TestObjDBPerformance(TestBigRepoR): @@ -31,7 +32,7 @@ class TestObjDBPerformance(TestBigRepoR): def test_large_data_streaming(self, rwrepo): # TODO: This part overlaps with the same file in gitdb.test.performance.test_stream # It should be shared if possible - ldb = LooseObjectDB(os.path.join(rwrepo.git_dir, 'objects')) + ldb = LooseObjectDB(osp.join(rwrepo.git_dir, 'objects')) for randomize in range(2): desc = (randomize and 'random ') or '' @@ -47,7 +48,7 @@ def test_large_data_streaming(self, rwrepo): elapsed_add = time() - st assert ldb.has_object(binsha) db_file = ldb.readable_db_object_path(bin_to_hex(binsha)) - fsize_kib = os.path.getsize(db_file) / 1000 + fsize_kib = osp.getsize(db_file) / 1000 size_kib = size / 1000 msg = "Added %i KiB (filesize = %i KiB) of %s data to loose odb in %f s ( %f Write KiB / s)" @@ -109,7 +110,7 @@ def test_large_data_streaming(self, rwrepo): assert gitsha == bin_to_hex(binsha) # we do it the same way, right ? # as its the same sha, we reuse our path - fsize_kib = os.path.getsize(db_file) / 1000 + fsize_kib = osp.getsize(db_file) / 1000 msg = "Added %i KiB (filesize = %i KiB) of %s data to using git-hash-object in %f s ( %f Write KiB / s)" msg %= (size_kib, fsize_kib, desc, gelapsed_add, size_kib / gelapsed_add) print(msg, file=sys.stderr) diff --git a/git/test/test_base.py b/git/test/test_base.py index 7fc3096f3..cec40de82 100644 --- a/git/test/test_base.py +++ b/git/test/test_base.py @@ -9,22 +9,24 @@ import tempfile from unittest import skipIf -import git.objects.base as base -from git.test.lib import ( - TestBase, - assert_raises, - with_rw_repo, - with_rw_and_rw_remote_repo -) from git import ( Blob, Tree, Commit, TagObject ) -from git.objects.util import get_object_type_by_name -from gitdb.util import hex_to_bin from git.compat import is_win +from git.objects.util import get_object_type_by_name +from git.test.lib import ( + TestBase, + assert_raises, + with_rw_repo, + with_rw_and_rw_remote_repo +) +from git.util import hex_to_bin + +import git.objects.base as base +import os.path as osp class TestBase(TestBase): @@ -103,19 +105,19 @@ def test_object_resolution(self): @with_rw_repo('HEAD', bare=True) def test_with_bare_rw_repo(self, bare_rw_repo): assert bare_rw_repo.config_reader("repository").getboolean("core", "bare") - assert os.path.isfile(os.path.join(bare_rw_repo.git_dir, 'HEAD')) + assert osp.isfile(osp.join(bare_rw_repo.git_dir, 'HEAD')) @with_rw_repo('0.1.6') def test_with_rw_repo(self, rw_repo): assert not rw_repo.config_reader("repository").getboolean("core", "bare") - assert os.path.isdir(os.path.join(rw_repo.working_tree_dir, 'lib')) + assert osp.isdir(osp.join(rw_repo.working_tree_dir, 'lib')) #@skipIf(HIDE_WINDOWS_FREEZE_ERRORS, "FIXME: Freezes! sometimes...") @with_rw_and_rw_remote_repo('0.1.6') def test_with_rw_remote_and_rw_repo(self, rw_repo, rw_remote_repo): 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')) + assert osp.isdir(osp.join(rw_repo.working_tree_dir, 'lib')) @skipIf(sys.version_info < (3,) and is_win, "Unicode woes, see https://github.com/gitpython-developers/GitPython/pull/519") @@ -123,7 +125,7 @@ def test_with_rw_remote_and_rw_repo(self, rw_repo, rw_remote_repo): def test_add_unicode(self, rw_repo): filename = u"שלום.txt" - file_path = os.path.join(rw_repo.working_dir, filename) + file_path = osp.join(rw_repo.working_dir, filename) # verify first that we could encode file name in this environment try: diff --git a/git/test/test_commit.py b/git/test/test_commit.py index fd9777fb8..fbb1c244e 100644 --- a/git/test/test_commit.py +++ b/git/test/test_commit.py @@ -6,34 +6,36 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php from __future__ import print_function -from git.test.lib import ( - TestBase, - assert_equal, - assert_not_equal, - with_rw_repo, - fixture_path, - StringProcessAdapter -) +from datetime import datetime +from io import BytesIO +import re +import sys +import time + from git import ( Commit, Actor, ) -from gitdb import IStream -from git.test.lib import with_rw_directory +from git import Repo from git.compat import ( string_types, text_type ) -from git import Repo +from git.objects.util import tzoffset, utc from git.repo.fun import touch +from git.test.lib import ( + TestBase, + assert_equal, + assert_not_equal, + with_rw_repo, + fixture_path, + StringProcessAdapter +) +from git.test.lib import with_rw_directory +from gitdb import IStream + +import os.path as osp -from io import BytesIO -import time -import sys -import re -import os -from datetime import datetime -from git.objects.util import tzoffset, utc try: from unittest.mock import Mock @@ -232,8 +234,8 @@ def test_rev_list_bisect_all(self): @with_rw_directory def test_ambiguous_arg_iteration(self, rw_dir): - rw_repo = Repo.init(os.path.join(rw_dir, 'test_ambiguous_arg')) - path = os.path.join(rw_repo.working_tree_dir, 'master') + rw_repo = Repo.init(osp.join(rw_dir, 'test_ambiguous_arg')) + path = osp.join(rw_repo.working_tree_dir, 'master') touch(path) rw_repo.index.add([path]) rw_repo.index.commit('initial commit') diff --git a/git/test/test_config.py b/git/test/test_config.py index 32873f243..0dfadda64 100644 --- a/git/test/test_config.py +++ b/git/test/test_config.py @@ -6,7 +6,6 @@ import glob import io -import os from git import ( GitConfigParser @@ -91,7 +90,7 @@ def test_read_write(self): @with_rw_directory def test_lock_reentry(self, rw_dir): - fpl = os.path.join(rw_dir, 'l') + fpl = osp.join(rw_dir, 'l') gcp = GitConfigParser(fpl, read_only=False) with gcp as cw: cw.set_value('include', 'some_value', 'a') @@ -103,7 +102,7 @@ def test_lock_reentry(self, rw_dir): GitConfigParser(fpl, read_only=False) # but work when the lock is removed with GitConfigParser(fpl, read_only=False): - assert os.path.exists(fpl) + assert osp.exists(fpl) # reentering with an existing lock must fail due to exclusive access with self.assertRaises(IOError): gcp.__enter__() @@ -178,17 +177,17 @@ def check_test_value(cr, value): # end # PREPARE CONFIG FILE A - fpa = os.path.join(rw_dir, 'a') + fpa = osp.join(rw_dir, 'a') with GitConfigParser(fpa, read_only=False) as cw: write_test_value(cw, 'a') - fpb = os.path.join(rw_dir, 'b') - fpc = os.path.join(rw_dir, 'c') + fpb = osp.join(rw_dir, 'b') + fpc = osp.join(rw_dir, 'c') cw.set_value('include', 'relative_path_b', 'b') cw.set_value('include', 'doesntexist', 'foobar') cw.set_value('include', 'relative_cycle_a_a', 'a') cw.set_value('include', 'absolute_cycle_a_a', fpa) - assert os.path.exists(fpa) + assert osp.exists(fpa) # PREPARE CONFIG FILE B with GitConfigParser(fpb, read_only=False) as cw: diff --git a/git/test/test_db.py b/git/test/test_db.py index 5dcf592a8..8f67dd48b 100644 --- a/git/test/test_db.py +++ b/git/test/test_db.py @@ -3,17 +3,18 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from git.test.lib import TestBase from git.db import GitCmdObjectDB -from gitdb.util import bin_to_hex from git.exc import BadObject -import os +from git.test.lib import TestBase +from git.util import bin_to_hex + +import os.path as osp class TestDB(TestBase): def test_base(self): - gdb = GitCmdObjectDB(os.path.join(self.rorepo.git_dir, 'objects'), self.rorepo.git) + gdb = GitCmdObjectDB(osp.join(self.rorepo.git_dir, 'objects'), self.rorepo.git) # partial to complete - works with everything hexsha = bin_to_hex(gdb.partial_to_complete_sha_hex("0.1.6")) diff --git a/git/test/test_diff.py b/git/test/test_diff.py index d34d84e39..48a5a641f 100644 --- a/git/test/test_diff.py +++ b/git/test/test_diff.py @@ -4,8 +4,15 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -import os - +import ddt +from git import ( + Repo, + GitCommandError, + Diff, + DiffIndex, + NULL_TREE, +) +from git.cmd import Git from git.test.lib import ( TestBase, StringProcessAdapter, @@ -14,17 +21,9 @@ assert_true, ) - from git.test.lib import with_rw_directory -from git import ( - Repo, - GitCommandError, - Diff, - DiffIndex, - NULL_TREE, -) -import ddt +import os.path as osp @ddt.ddt @@ -53,10 +52,10 @@ def _assert_diff_format(self, diffs): def test_diff_with_staged_file(self, rw_dir): # SETUP INDEX WITH MULTIPLE STAGES r = Repo.init(rw_dir) - fp = os.path.join(rw_dir, 'hello.txt') + fp = osp.join(rw_dir, 'hello.txt') with open(fp, 'w') as fs: fs.write("hello world") - r.git.add(fp) + r.git.add(Git.polish_url(fp)) r.git.commit(message="init") with open(fp, 'w') as fs: diff --git a/git/test/test_docs.py b/git/test/test_docs.py index f3c75f79f..bb937d93c 100644 --- a/git/test/test_docs.py +++ b/git/test/test_docs.py @@ -9,6 +9,8 @@ from git.test.lib import TestBase from git.test.lib.helper import with_rw_directory +import os.path as osp + class Tutorials(TestBase): @@ -23,7 +25,7 @@ def tearDown(self): def test_init_repo_object(self, rw_dir): # [1-test_init_repo_object] from git import Repo - join = os.path.join + join = osp.join # rorepo is a Repo instance pointing to the git-python repository. # For all you know, the first argument to Repo is a path to the repository @@ -62,7 +64,7 @@ def test_init_repo_object(self, rw_dir): # repository paths # [7-test_init_repo_object] - assert os.path.isdir(cloned_repo.working_tree_dir) # directory with your work files + assert osp.isdir(cloned_repo.working_tree_dir) # directory with your work files assert cloned_repo.git_dir.startswith(cloned_repo.working_tree_dir) # directory containing the git repository assert bare_repo.working_tree_dir is None # bare repositories have no working tree # ![7-test_init_repo_object] @@ -146,7 +148,7 @@ def update(self, op_code, cur_count, max_count=None, message=''): self.assertEqual(new_branch.checkout(), cloned_repo.active_branch) # checking out branch adjusts the wtree self.assertEqual(new_branch.commit, past.commit) # Now the past is checked out - new_file_path = os.path.join(cloned_repo.working_tree_dir, 'my-new-file') + new_file_path = osp.join(cloned_repo.working_tree_dir, 'my-new-file') open(new_file_path, 'wb').close() # create new file in working tree cloned_repo.index.add([new_file_path]) # add it to the index # Commit the changes to deviate masters history @@ -162,7 +164,7 @@ def update(self, op_code, cur_count, max_count=None, message=''): # now new_branch is ahead of master, which probably should be checked out and reset softly. # note that all these operations didn't touch the working tree, as we managed it ourselves. # This definitely requires you to know what you are doing :) ! - assert os.path.basename(new_file_path) in new_branch.commit.tree # new file is now in tree + assert osp.basename(new_file_path) in new_branch.commit.tree # new file is now in tree master.commit = new_branch.commit # let master point to most recent commit cloned_repo.head.reference = master # we adjusted just the reference, not the working tree or index # ![13-test_init_repo_object] @@ -192,7 +194,7 @@ def update(self, op_code, cur_count, max_count=None, message=''): def test_references_and_objects(self, rw_dir): # [1-test_references_and_objects] import git - repo = git.Repo.clone_from(self._small_repo_url(), os.path.join(rw_dir, 'repo'), branch='master') + repo = git.Repo.clone_from(self._small_repo_url(), osp.join(rw_dir, 'repo'), branch='master') heads = repo.heads master = heads.master # lists can be accessed by name for convenience @@ -264,7 +266,7 @@ def test_references_and_objects(self, rw_dir): # [11-test_references_and_objects] hct.blobs[0].data_stream.read() # stream object to read data from - hct.blobs[0].stream_data(open(os.path.join(rw_dir, 'blob_data'), 'wb')) # write data to given stream + hct.blobs[0].stream_data(open(osp.join(rw_dir, 'blob_data'), 'wb')) # write data to given stream # ![11-test_references_and_objects] # [12-test_references_and_objects] @@ -350,11 +352,11 @@ def test_references_and_objects(self, rw_dir): # Access blob objects for (path, stage), entry in index.entries.items(): # @UnusedVariable pass - new_file_path = os.path.join(repo.working_tree_dir, 'new-file-name') + new_file_path = osp.join(repo.working_tree_dir, 'new-file-name') open(new_file_path, 'w').close() index.add([new_file_path]) # add a new file to the index index.remove(['LICENSE']) # remove an existing one - assert os.path.isfile(os.path.join(repo.working_tree_dir, 'LICENSE')) # working tree is untouched + assert osp.isfile(osp.join(repo.working_tree_dir, 'LICENSE')) # working tree is untouched self.assertEqual(index.commit("my commit message").type, 'commit') # commit changed index repo.active_branch.commit = repo.commit('HEAD~1') # forget last commit @@ -373,11 +375,11 @@ def test_references_and_objects(self, rw_dir): # merge two trees three-way into memory merge_index = IndexFile.from_tree(repo, 'HEAD~10', 'HEAD', repo.merge_base('HEAD~10', 'HEAD')) # and persist it - merge_index.write(os.path.join(rw_dir, 'merged_index')) + merge_index.write(osp.join(rw_dir, 'merged_index')) # ![24-test_references_and_objects] # [25-test_references_and_objects] - empty_repo = git.Repo.init(os.path.join(rw_dir, 'empty')) + empty_repo = git.Repo.init(osp.join(rw_dir, 'empty')) origin = empty_repo.create_remote('origin', repo.remotes.origin.url) assert origin.exists() assert origin == empty_repo.remotes.origin == empty_repo.remotes['origin'] @@ -480,8 +482,8 @@ def test_submodules(self): def test_add_file_and_commit(self, rw_dir): import git - repo_dir = os.path.join(rw_dir, 'my-new-repo') - file_name = os.path.join(repo_dir, 'new-file') + repo_dir = osp.join(rw_dir, 'my-new-repo') + file_name = osp.join(repo_dir, 'new-file') r = git.Repo.init(repo_dir) # This function just creates an empty file ... diff --git a/git/test/test_fun.py b/git/test/test_fun.py index 02f338dd5..9d4366537 100644 --- a/git/test/test_fun.py +++ b/git/test/test_fun.py @@ -1,6 +1,11 @@ -from git.test.lib import ( - TestBase, - with_rw_repo +from io import BytesIO +from stat import S_IFDIR, S_IFREG, S_IFLNK +from unittest.case import skipIf + +from git.compat import PY3 +from git.index import IndexFile +from git.index.fun import ( + aggressive_tree_merge ) from git.objects.fun import ( traverse_tree_recursive, @@ -8,25 +13,13 @@ tree_to_stream, tree_entries_from_data ) - -from git.index.fun import ( - aggressive_tree_merge +from git.test.lib import ( + TestBase, + with_rw_repo ) - -from gitdb.util import bin_to_hex +from git.util import bin_to_hex from gitdb.base import IStream from gitdb.typ import str_tree_type -from git.compat import PY3 - -from unittest.case import skipIf -from stat import ( - S_IFDIR, - S_IFREG, - S_IFLNK -) - -from git.index import IndexFile -from io import BytesIO class TestFun(TestBase): @@ -262,7 +255,7 @@ def test_tree_traversal_single(self): def test_tree_entries_from_data_with_failing_name_decode_py2(self): r = tree_entries_from_data(b'100644 \x9f\0aaa') assert r == [('aaa', 33188, u'\udc9f')], r - + @skipIf(not PY3, 'odd types returned ... maybe figure it out one day') def test_tree_entries_from_data_with_failing_name_decode_py3(self): r = tree_entries_from_data(b'100644 \x9f\0aaa') diff --git a/git/test/test_git.py b/git/test/test_git.py index bd8ebee2c..7d7130225 100644 --- a/git/test/test_git.py +++ b/git/test/test_git.py @@ -5,9 +5,17 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php import os -import sys import subprocess +import sys +from git import ( + Git, + GitCommandError, + GitCommandNotFound, + Repo, + cmd +) +from git.compat import PY3, is_darwin from git.test.lib import ( TestBase, patch, @@ -17,18 +25,12 @@ assert_match, fixture_path ) -from git import ( - Git, - GitCommandError, - GitCommandNotFound, - Repo, - cmd -) from git.test.lib import with_rw_directory - -from git.compat import PY3, is_darwin from git.util import finalize_process +import os.path as osp + + try: from unittest import mock except ImportError: @@ -147,7 +149,7 @@ def test_cmd_override(self): exc = GitCommandNotFound try: # set it to something that doens't exist, assure it raises - type(self.git).GIT_PYTHON_GIT_EXECUTABLE = os.path.join( + type(self.git).GIT_PYTHON_GIT_EXECUTABLE = osp.join( "some", "path", "which", "doesn't", "exist", "gitbinary") self.failUnlessRaises(exc, self.git.version) finally: @@ -198,13 +200,13 @@ def test_environment(self, rw_dir): self.assertEqual(new_env, {'VARKEY': 'VARVALUE'}) self.assertEqual(self.git.environment(), {}) - path = os.path.join(rw_dir, 'failing-script.sh') + path = osp.join(rw_dir, 'failing-script.sh') with open(path, 'wt') as stream: stream.write("#!/usr/bin/env sh\n" "echo FOO\n") os.chmod(path, 0o777) - rw_repo = Repo.init(os.path.join(rw_dir, 'repo')) + rw_repo = Repo.init(osp.join(rw_dir, 'repo')) remote = rw_repo.create_remote('ssh-origin', "ssh://git@server/foo") with rw_repo.git.custom_environment(GIT_SSH=path): diff --git a/git/test/test_index.py b/git/test/test_index.py index d851743ef..1abe22f48 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -26,7 +26,7 @@ GitCommandError, CheckoutError, ) -from git.compat import string_types, is_win +from git.compat import string_types, is_win, PY3 from git.exc import ( HookExecutionError, InvalidGitRepositoryError @@ -43,11 +43,13 @@ fixture, with_rw_repo ) -from git.util import HIDE_WINDOWS_KNOWN_ERRORS from git.test.lib import with_rw_directory from git.util import Actor, rmtree +from git.util import HIDE_WINDOWS_KNOWN_ERRORS, hex_to_bin from gitdb.base import IStream -from gitdb.util import hex_to_bin + +import os.path as osp +from git.cmd import Git class TestIndex(TestBase): @@ -85,7 +87,7 @@ def _reset_progress(self): def _assert_entries(self, entries): for entry in entries: assert isinstance(entry, BaseIndexEntry) - assert not os.path.isabs(entry.path) + assert not osp.isabs(entry.path) assert "\\" not in entry.path # END for each entry @@ -329,7 +331,7 @@ def test_index_file_diffing(self, rw_repo): # reset the working copy as well to current head,to pull 'back' as well new_data = b"will be reverted" - file_path = os.path.join(rw_repo.working_tree_dir, "CHANGES") + file_path = osp.join(rw_repo.working_tree_dir, "CHANGES") with open(file_path, "wb") as fp: fp.write(new_data) index.reset(rev_head_parent, working_tree=True) @@ -340,26 +342,26 @@ def test_index_file_diffing(self, rw_repo): assert fp.read() != new_data # test full checkout - test_file = os.path.join(rw_repo.working_tree_dir, "CHANGES") + test_file = osp.join(rw_repo.working_tree_dir, "CHANGES") with open(test_file, 'ab') as fd: fd.write(b"some data") rval = index.checkout(None, force=True, fprogress=self._fprogress) assert 'CHANGES' in list(rval) self._assert_fprogress([None]) - assert os.path.isfile(test_file) + assert osp.isfile(test_file) os.remove(test_file) rval = index.checkout(None, force=False, fprogress=self._fprogress) assert 'CHANGES' in list(rval) self._assert_fprogress([None]) - assert os.path.isfile(test_file) + assert osp.isfile(test_file) # individual file os.remove(test_file) rval = index.checkout(test_file, fprogress=self._fprogress) self.assertEqual(list(rval)[0], 'CHANGES') self._assert_fprogress([test_file]) - assert os.path.exists(test_file) + assert osp.exists(test_file) # checking out non-existing file throws self.failUnlessRaises(CheckoutError, index.checkout, "doesnt_exist_ever.txt.that") @@ -373,7 +375,7 @@ def test_index_file_diffing(self, rw_repo): index.checkout(test_file) except CheckoutError as e: self.assertEqual(len(e.failed_files), 1) - self.assertEqual(e.failed_files[0], os.path.basename(test_file)) + self.assertEqual(e.failed_files[0], osp.basename(test_file)) self.assertEqual(len(e.failed_files), len(e.failed_reasons)) self.assertIsInstance(e.failed_reasons[0], string_types) self.assertEqual(len(e.valid_files), 0) @@ -388,7 +390,7 @@ def test_index_file_diffing(self, rw_repo): assert not open(test_file, 'rb').read().endswith(append_data) # checkout directory - rmtree(os.path.join(rw_repo.working_tree_dir, "lib")) + rmtree(osp.join(rw_repo.working_tree_dir, "lib")) rval = index.checkout('lib') assert len(list(rval)) > 1 @@ -399,11 +401,17 @@ def _count_existing(self, repo, files): existing = 0 basedir = repo.working_tree_dir for f in files: - existing += os.path.isfile(os.path.join(basedir, f)) + existing += osp.isfile(osp.join(basedir, f)) # END for each deleted file return existing # END num existing helper + @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and Git.is_cygwin(), + """FIXME: File "C:\projects\gitpython\git\test\test_index.py", line 642, in test_index_mutation + self.assertEqual(fd.read(), link_target) + AssertionError: '!\xff\xfe/\x00e\x00t\x00c\x00/\x00t\x00h\x00a\x00t\x00\x00\x00' + != '/etc/that' + """) @with_rw_repo('0.1.6') def test_index_mutation(self, rw_repo): index = rw_repo.index @@ -458,7 +466,7 @@ def mixed_iterator(): self.failUnlessRaises(TypeError, index.remove, [1]) # absolute path - deleted_files = index.remove([os.path.join(rw_repo.working_tree_dir, "lib")], r=True) + deleted_files = index.remove([osp.join(rw_repo.working_tree_dir, "lib")], r=True) assert len(deleted_files) > 1 self.failUnlessRaises(ValueError, index.remove, ["/doesnt/exists"]) @@ -525,9 +533,9 @@ def mixed_iterator(): # re-add all files in lib # get the lib folder back on disk, but get an index without it index.reset(new_commit.parents[0], working_tree=True).reset(new_commit, working_tree=False) - lib_file_path = os.path.join("lib", "git", "__init__.py") + lib_file_path = osp.join("lib", "git", "__init__.py") assert (lib_file_path, 0) not in index.entries - assert os.path.isfile(os.path.join(rw_repo.working_tree_dir, lib_file_path)) + assert osp.isfile(osp.join(rw_repo.working_tree_dir, lib_file_path)) # directory entries = index.add(['lib'], fprogress=self._fprogress_add) @@ -536,14 +544,14 @@ def mixed_iterator(): assert len(entries) > 1 # glob - entries = index.reset(new_commit).add([os.path.join('lib', 'git', '*.py')], fprogress=self._fprogress_add) + entries = index.reset(new_commit).add([osp.join('lib', 'git', '*.py')], fprogress=self._fprogress_add) self._assert_entries(entries) self._assert_fprogress(entries) self.assertEqual(len(entries), 14) # same file entries = index.reset(new_commit).add( - [os.path.join(rw_repo.working_tree_dir, 'lib', 'git', 'head.py')] * 2, fprogress=self._fprogress_add) + [osp.join(rw_repo.working_tree_dir, 'lib', 'git', 'head.py')] * 2, fprogress=self._fprogress_add) self._assert_entries(entries) self.assertEqual(entries[0].mode & 0o644, 0o644) # would fail, test is too primitive to handle this case @@ -583,7 +591,7 @@ def mixed_iterator(): for target in ('/etc/nonexisting', '/etc/passwd', '/etc'): basename = "my_real_symlink" - link_file = os.path.join(rw_repo.working_tree_dir, basename) + link_file = osp.join(rw_repo.working_tree_dir, basename) os.symlink(target, link_file) entries = index.reset(new_commit).add([link_file], fprogress=self._fprogress_add) self._assert_entries(entries) @@ -645,7 +653,7 @@ def mixed_iterator(): # TEST RENAMING def assert_mv_rval(rval): for source, dest in rval: - assert not os.path.exists(source) and os.path.exists(dest) + assert not osp.exists(source) and osp.exists(dest) # END for each renamed item # END move assertion utility @@ -661,7 +669,7 @@ def assert_mv_rval(rval): paths = ['LICENSE', 'VERSION', 'doc'] rval = index.move(paths, dry_run=True) self.assertEqual(len(rval), 2) - assert os.path.exists(paths[0]) + assert osp.exists(paths[0]) # again, no dry run rval = index.move(paths) @@ -719,8 +727,8 @@ def make_paths(): index.add(files, write=True) if is_win: hp = hook_path('pre-commit', index.repo.git_dir) - hpd = os.path.dirname(hp) - if not os.path.isdir(hpd): + hpd = osp.dirname(hp) + if not osp.isdir(hpd): os.mkdir(hpd) with open(hp, "wt") as fp: fp.write("#!/usr/bin/env sh\necho stdout; echo stderr 1>&2; exit 1") @@ -766,7 +774,7 @@ def make_paths(): for fkey in keys: assert fkey in index.entries for absfile in absfiles: - assert os.path.isfile(absfile) + assert osp.isfile(absfile) @with_rw_repo('HEAD') def test_compare_write_tree(self, rw_repo): @@ -815,21 +823,21 @@ def test_index_bare_add(self, rw_bare_repo): # Adding using a path should still require a non-bare repository. asserted = False - path = os.path.join('git', 'test', 'test_index.py') + path = osp.join('git', 'test', 'test_index.py') try: rw_bare_repo.index.add([path]) except InvalidGitRepositoryError: asserted = True assert asserted, "Adding using a filename is not correctly asserted." - @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and sys.version_info[:2] == (2, 7), r""" + @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and not PY3, r""" FIXME: File "C:\projects\gitpython\git\util.py", line 125, in to_native_path_linux return path.replace('\\', '/') UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)""") @with_rw_directory def test_add_utf8P_path(self, rw_dir): # NOTE: fp is not a Unicode object in python 2 (which is the source of the problem) - fp = os.path.join(rw_dir, 'ø.txt') + fp = osp.join(rw_dir, 'ø.txt') with open(fp, 'wb') as fs: fs.write(u'content of ø'.encode('utf-8')) @@ -840,7 +848,7 @@ def test_add_utf8P_path(self, rw_dir): @with_rw_directory def test_add_a_file_with_wildcard_chars(self, rw_dir): # see issue #407 - fp = os.path.join(rw_dir, '[.exe') + fp = osp.join(rw_dir, '[.exe') with open(fp, "wb") as f: f.write(b'something') diff --git a/git/test/test_reflog.py b/git/test/test_reflog.py index dffedf3b6..20495be14 100644 --- a/git/test/test_reflog.py +++ b/git/test/test_reflog.py @@ -1,17 +1,18 @@ -from git.test.lib import ( - TestBase, - fixture_path -) +import os +import tempfile + from git.objects import IndexObject from git.refs import ( RefLogEntry, RefLog ) -from git.util import Actor, rmtree -from gitdb.util import hex_to_bin +from git.test.lib import ( + TestBase, + fixture_path +) +from git.util import Actor, rmtree, hex_to_bin -import tempfile -import os +import os.path as osp class TestRefLog(TestBase): @@ -42,7 +43,7 @@ def test_base(self): os.mkdir(tdir) rlp_master_ro = RefLog.path(self.rorepo.head) - assert os.path.isfile(rlp_master_ro) + assert osp.isfile(rlp_master_ro) # simple read reflog = RefLog.from_file(rlp_master_ro) @@ -70,7 +71,7 @@ def test_base(self): cr = self.rorepo.config_reader() for rlp in (rlp_head, rlp_master): reflog = RefLog.from_file(rlp) - tfile = os.path.join(tdir, os.path.basename(rlp)) + tfile = osp.join(tdir, osp.basename(rlp)) reflog.to_file(tfile) assert reflog.write() is reflog diff --git a/git/test/test_refs.py b/git/test/test_refs.py index 43f1dcc72..fd0be1080 100644 --- a/git/test/test_refs.py +++ b/git/test/test_refs.py @@ -4,10 +4,8 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from git.test.lib import ( - TestBase, - with_rw_repo -) +from itertools import chain + from git import ( Reference, Head, @@ -18,11 +16,15 @@ GitCommandError, RefLog ) -import git.refs as refs -from git.util import Actor from git.objects.tag import TagObject -from itertools import chain -import os +from git.test.lib import ( + TestBase, + with_rw_repo +) +from git.util import Actor + +import git.refs as refs +import os.path as osp class TestRefs(TestBase): @@ -268,10 +270,10 @@ def test_head_reset(self, rw_repo): assert tmp_head == new_head and tmp_head.object == new_head.object logfile = RefLog.path(tmp_head) - assert os.path.isfile(logfile) + assert osp.isfile(logfile) Head.delete(rw_repo, tmp_head) # deletion removes the log as well - assert not os.path.isfile(logfile) + assert not osp.isfile(logfile) heads = rw_repo.heads assert tmp_head not in heads and new_head not in heads # force on deletion testing would be missing here, code looks okay though ;) @@ -450,12 +452,12 @@ def test_head_reset(self, rw_repo): symbol_ref_path = "refs/symbol_ref" symref = SymbolicReference(rw_repo, symbol_ref_path) assert symref.path == symbol_ref_path - symbol_ref_abspath = os.path.join(rw_repo.git_dir, symref.path) + symbol_ref_abspath = osp.join(rw_repo.git_dir, symref.path) # set it symref.reference = new_head assert symref.reference == new_head - assert os.path.isfile(symbol_ref_abspath) + assert osp.isfile(symbol_ref_abspath) assert symref.commit == new_head.commit for name in ('absname', 'folder/rela_name'): @@ -507,7 +509,7 @@ def test_head_reset(self, rw_repo): rw_repo.head.reference = Head.create(rw_repo, "master") # At least the head should still exist - assert os.path.isfile(os.path.join(rw_repo.git_dir, 'HEAD')) + assert osp.isfile(osp.join(rw_repo.git_dir, 'HEAD')) refs = list(SymbolicReference.iter_items(rw_repo)) assert len(refs) == 1 diff --git a/git/test/test_repo.py b/git/test/test_repo.py index a0a6a5b00..8b644f7ff 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -50,10 +50,9 @@ assert_true, raises ) -from git.util import HIDE_WINDOWS_KNOWN_ERRORS +from git.util import HIDE_WINDOWS_KNOWN_ERRORS, cygpath from git.test.lib import with_rw_directory -from git.util import join_path_native, rmtree, rmfile -from gitdb.util import bin_to_hex +from git.util import join_path_native, rmtree, rmfile, bin_to_hex from unittest import SkipTest import functools as fnt @@ -203,8 +202,8 @@ def test_init(self): prev_cwd = os.getcwd() os.chdir(tempfile.gettempdir()) git_dir_rela = "repos/foo/bar.git" - del_dir_abs = os.path.abspath("repos") - git_dir_abs = os.path.abspath(git_dir_rela) + del_dir_abs = osp.abspath("repos") + git_dir_abs = osp.abspath(git_dir_rela) try: # with specific path for path in (git_dir_rela, git_dir_abs): @@ -212,7 +211,7 @@ def test_init(self): self.assertIsInstance(r, Repo) assert r.bare is True assert not r.has_separate_working_tree() - assert os.path.isdir(r.git_dir) + assert osp.isdir(r.git_dir) self._assert_empty_repo(r) @@ -306,16 +305,16 @@ def test_is_dirty(self): def test_is_dirty_with_path(self, rwrepo): assert rwrepo.is_dirty(path="git") is False - with open(os.path.join(rwrepo.working_dir, "git", "util.py"), "at") as f: + with open(osp.join(rwrepo.working_dir, "git", "util.py"), "at") as f: f.write("junk") assert rwrepo.is_dirty(path="git") is True assert rwrepo.is_dirty(path="doc") is False - rwrepo.git.add(os.path.join("git", "util.py")) + rwrepo.git.add(Git.polish_url(osp.join("git", "util.py"))) assert rwrepo.is_dirty(index=False, path="git") is False assert rwrepo.is_dirty(path="git") is True - with open(os.path.join(rwrepo.working_dir, "doc", "no-such-file.txt"), "wt") as f: + with open(osp.join(rwrepo.working_dir, "doc", "no-such-file.txt"), "wt") as f: f.write("junk") assert rwrepo.is_dirty(path="doc") is False assert rwrepo.is_dirty(untracked_files=True, path="doc") is True @@ -412,6 +411,14 @@ def test_blame_complex_revision(self, git): self.assertEqual(len(res), 1) self.assertEqual(len(res[0][1]), 83, "Unexpected amount of parsed blame lines") + @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and Git.is_cygwin(), + """FIXME: File "C:\projects\gitpython\git\cmd.py", line 671, in execute + raise GitCommandError(command, status, stderr_value, stdout_value) + GitCommandError: Cmd('git') failed due to: exit code(128) + cmdline: git add 1__��ava verb��ten 1_test _myfile 1_test_other_file + 1_��ava-----verb��ten + stderr: 'fatal: pathspec '"1__çava verböten"' did not match any files' + """) @with_rw_repo('HEAD', bare=False) def test_untracked_files(self, rwrepo): for run, (repo_add, is_invoking_git) in enumerate(( @@ -494,10 +501,10 @@ def test_tilde_and_env_vars_in_repo_path(self, rw_dir): ph = os.environ.get('HOME') try: os.environ['HOME'] = rw_dir - Repo.init(os.path.join('~', 'test.git'), bare=True) + Repo.init(osp.join('~', 'test.git'), bare=True) os.environ['FOO'] = rw_dir - Repo.init(os.path.join('$FOO', 'test.git'), bare=True) + Repo.init(osp.join('$FOO', 'test.git'), bare=True) finally: if ph: os.environ['HOME'] = ph @@ -785,7 +792,7 @@ def test_submodule_update(self, rwrepo): @with_rw_repo('HEAD') def test_git_file(self, rwrepo): # Move the .git directory to another location and create the .git file. - real_path_abs = os.path.abspath(join_path_native(rwrepo.working_tree_dir, '.real')) + real_path_abs = osp.abspath(join_path_native(rwrepo.working_tree_dir, '.real')) os.rename(rwrepo.git_dir, real_path_abs) git_file_path = join_path_native(rwrepo.working_tree_dir, '.git') with open(git_file_path, 'wb') as fp: @@ -793,13 +800,13 @@ def test_git_file(self, rwrepo): # Create a repo and make sure it's pointing to the relocated .git directory. git_file_repo = Repo(rwrepo.working_tree_dir) - self.assertEqual(os.path.abspath(git_file_repo.git_dir), real_path_abs) + self.assertEqual(osp.abspath(git_file_repo.git_dir), real_path_abs) # Test using an absolute gitdir path in the .git file. with open(git_file_path, 'wb') as fp: fp.write(('gitdir: %s\n' % real_path_abs).encode('ascii')) git_file_repo = Repo(rwrepo.working_tree_dir) - self.assertEqual(os.path.abspath(git_file_repo.git_dir), real_path_abs) + self.assertEqual(osp.abspath(git_file_repo.git_dir), real_path_abs) @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and PY3, "FIXME: smmp fails with: TypeError: Can't convert 'bytes' object to str implicitly") @@ -840,7 +847,7 @@ def test_empty_repo(self, rw_dir): # It's expected to not be able to access a tree self.failUnlessRaises(ValueError, r.tree) - new_file_path = os.path.join(rw_dir, "new_file.ext") + new_file_path = osp.join(rw_dir, "new_file.ext") touch(new_file_path) r.index.add([new_file_path]) r.index.commit("initial commit\nBAD MESSAGE 1\n") @@ -901,9 +908,6 @@ def test_is_ancestor(self): for i, j in itertools.permutations([c1, 'ffffff', ''], r=2): self.assertRaises(GitCommandError, repo.is_ancestor, i, j) - # @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, - # "FIXME: helper.wrapper fails with: PermissionError: [WinError 5] Access is denied: " - # "'C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\test_work_tree_unsupportedryfa60di\\master_repo\\.git\\objects\\pack\\pack-bc9e0787aef9f69e1591ef38ea0a6f566ec66fe3.idx") # noqa E501 @with_rw_directory def test_work_tree_unsupported(self, rw_dir): git = Git(rw_dir) @@ -913,6 +917,8 @@ def test_work_tree_unsupported(self, rw_dir): rw_master = self.rorepo.clone(join_path_native(rw_dir, 'master_repo')) rw_master.git.checkout('HEAD~10') worktree_path = join_path_native(rw_dir, 'worktree_repo') + if Git.is_cygwin(): + worktree_path = cygpath(worktree_path) try: rw_master.git.worktree('add', worktree_path, 'master') except Exception as ex: diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py index 9db4f9c90..fcaad04ba 100644 --- a/git/test/test_submodule.py +++ b/git/test/test_submodule.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php import os @@ -6,24 +7,34 @@ import git from git.cmd import Git -from git.compat import string_types, is_win +from git.compat import ( + string_types, + is_win, +) from git.exc import ( InvalidGitRepositoryError, RepositoryDirtyError ) from git.objects.submodule.base import Submodule -from git.objects.submodule.root import RootModule, RootUpdateProgress +from git.objects.submodule.root import ( + RootModule, + RootUpdateProgress, +) from git.repo.fun import ( find_git_dir, - touch + touch, ) from git.test.lib import ( TestBase, - with_rw_repo + with_rw_repo, ) from git.test.lib import with_rw_directory -from git.util import HIDE_WINDOWS_KNOWN_ERRORS -from git.util import to_native_path_linux, join_path_native +from git.util import ( + to_native_path_linux, + join_path_native, + HIDE_WINDOWS_KNOWN_ERRORS, +) + import os.path as osp @@ -673,6 +684,13 @@ def test_add_empty_repo(self, rwdir): url=empty_repo_dir, no_checkout=checkout_mode and True or False) # end for each checkout mode + @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and Git.is_cygwin(), + """FIXME: ile "C:\projects\gitpython\git\cmd.py", line 671, in execute + raise GitCommandError(command, status, stderr_value, stdout_value) + GitCommandError: Cmd('git') failed due to: exit code(128) + cmdline: git add 1__Xava verbXXten 1_test _myfile 1_test_other_file 1_XXava-----verbXXten + stderr: 'fatal: pathspec '"1__çava verböten"' did not match any files' + """) @with_rw_directory def test_git_submodules_and_add_sm_with_new_commit(self, rwdir): parent = git.Repo.init(osp.join(rwdir, 'parent')) @@ -705,7 +723,7 @@ def test_git_submodules_and_add_sm_with_new_commit(self, rwdir): fp = osp.join(smm.working_tree_dir, 'empty-file') with open(fp, 'w'): pass - smm.git.add(fp) + smm.git.add(Git.polish_url(fp)) smm.git.commit(m="new file added") # submodules are retrieved from the current commit's tree, therefore we can't really get a new submodule @@ -730,7 +748,7 @@ def test_git_submodules_and_add_sm_with_new_commit(self, rwdir): assert commit_sm.binsha == sm_too.binsha assert sm_too.binsha != sm.binsha - # @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, + # @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, ## ACTUALLY skipped by `git.submodule.base#L869`. # "FIXME: helper.wrapper fails with: PermissionError: [WinError 5] Access is denied: " # "'C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\test_work_tree_unsupportedryfa60di\\master_repo\\.git\\objects\\pack\\pack-bc9e0787aef9f69e1591ef38ea0a6f566ec66fe3.idx") # noqa E501 @with_rw_directory diff --git a/git/test/test_tree.py b/git/test/test_tree.py index f36c43378..f92598743 100644 --- a/git/test/test_tree.py +++ b/git/test/test_tree.py @@ -5,7 +5,6 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php from io import BytesIO -import os import sys from unittest.case import skipIf @@ -13,8 +12,10 @@ Tree, Blob ) -from git.util import HIDE_WINDOWS_KNOWN_ERRORS from git.test.lib import TestBase +from git.util import HIDE_WINDOWS_KNOWN_ERRORS + +import os.path as osp class TestTree(TestBase): @@ -90,12 +91,12 @@ def test_traverse(self): assert len(set(b for b in root if isinstance(b, Blob)) | set(root.blobs)) == len(root.blobs) subitem = trees[0][0] assert "/" in subitem.path - assert subitem.name == os.path.basename(subitem.path) + assert subitem.name == osp.basename(subitem.path) # assure that at some point the traversed paths have a slash in them found_slash = False for item in root.traverse(): - assert os.path.isabs(item.abspath) + assert osp.isabs(item.abspath) if '/' in item.path: found_slash = True # END check for slash diff --git a/git/test/test_util.py b/git/test/test_util.py index e07417b4b..8f8d22725 100644 --- a/git/test/test_util.py +++ b/git/test/test_util.py @@ -5,7 +5,19 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php import tempfile +import time +from unittest.case import skipIf + +import ddt +from git.cmd import dashify +from git.compat import string_types, is_win +from git.objects.util import ( + altz_to_utctz_str, + utctz_to_altz, + verify_utctz, + parse_date, +) from git.test.lib import ( TestBase, assert_equal @@ -15,19 +27,37 @@ BlockingLockFile, get_user_id, Actor, - IterableList + IterableList, + cygpath, + decygpath ) -from git.objects.util import ( - altz_to_utctz_str, - utctz_to_altz, - verify_utctz, - parse_date, + + +_norm_cygpath_pairs = ( + (r'foo\bar', 'foo/bar'), + (r'foo/bar', 'foo/bar'), + + (r'C:\Users', '/cygdrive/c/Users'), + (r'C:\d/e', '/cygdrive/c/d/e'), + + ('C:\\', '/cygdrive/c/'), + + (r'\\server\C$\Users', '//server/C$/Users'), + (r'\\server\C$', '//server/C$'), + ('\\\\server\\c$\\', '//server/c$/'), + (r'\\server\BAR/', '//server/BAR/'), + + (r'D:/Apps', '/cygdrive/d/Apps'), + (r'D:/Apps\fOO', '/cygdrive/d/Apps/fOO'), + (r'D:\Apps/123', '/cygdrive/d/Apps/123'), ) -from git.cmd import dashify -from git.compat import string_types, is_win -import time -import ddt +_unc_cygpath_pairs = ( + (r'\\?\a:\com', '/cygdrive/a/com'), + (r'\\?\a:/com', '/cygdrive/a/com'), + + (r'\\?\UNC\server\D$\Apps', '//server/D$/Apps'), +) class TestIterableMember(object): @@ -52,6 +82,46 @@ def setup(self): "array": [42], } + @skipIf(not is_win, "Paths specifically for Windows.") + @ddt.idata(_norm_cygpath_pairs + _unc_cygpath_pairs) + def test_cygpath_ok(self, case): + wpath, cpath = case + cwpath = cygpath(wpath) + self.assertEqual(cwpath, cpath, wpath) + + @skipIf(not is_win, "Paths specifically for Windows.") + @ddt.data( + (r'./bar', 'bar'), + (r'.\bar', 'bar'), + (r'../bar', '../bar'), + (r'..\bar', '../bar'), + (r'../bar/.\foo/../chu', '../bar/chu'), + ) + def test_cygpath_norm_ok(self, case): + wpath, cpath = case + cwpath = cygpath(wpath) + self.assertEqual(cwpath, cpath or wpath, wpath) + + @skipIf(not is_win, "Paths specifically for Windows.") + @ddt.data( + r'C:', + r'C:Relative', + r'D:Apps\123', + r'D:Apps/123', + r'\\?\a:rel', + r'\\share\a:rel', + ) + def test_cygpath_invalids(self, wpath): + cwpath = cygpath(wpath) + self.assertEqual(cwpath, wpath.replace('\\', '/'), wpath) + + @skipIf(not is_win, "Paths specifically for Windows.") + @ddt.idata(_norm_cygpath_pairs) + def test_decygpath(self, case): + wpath, cpath = case + wcpath = decygpath(cpath) + self.assertEqual(wcpath, wpath.replace('/', '\\'), cpath) + def test_it_should_dashify(self): assert_equal('this-is-my-argument', dashify('this_is_my_argument')) assert_equal('foo', dashify('foo')) diff --git a/git/util.py b/git/util.py index d00de1e4b..c3c8e562f 100644 --- a/git/util.py +++ b/git/util.py @@ -5,6 +5,8 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php from __future__ import unicode_literals +import contextlib +from functools import wraps import getpass import logging import os @@ -13,19 +15,21 @@ import shutil import stat import time +from unittest.case import SkipTest -from functools import wraps - -from git.compat import is_win from gitdb.util import (# NOQA @IgnorePep8 make_sha, LockedFD, # @UnusedImport file_contents_ro, # @UnusedImport + file_contents_ro_filepath, # @UnusedImport LazyMixin, # @UnusedImport to_hex_sha, # @UnusedImport - to_bin_sha # @UnusedImport + to_bin_sha, # @UnusedImport + bin_to_hex, # @UnusedImport + hex_to_bin, # @UnusedImport ) +from git.compat import is_win import os.path as osp from .compat import ( @@ -34,7 +38,6 @@ PY3 ) from .exc import InvalidGitRepositoryError -from unittest.case import SkipTest # NOTE: Some of the unused imports might be used/imported by others. @@ -47,11 +50,13 @@ 'RemoteProgress', 'CallableRemoteProgress', 'rmtree', 'unbare_repo', 'HIDE_WINDOWS_KNOWN_ERRORS') +log = logging.getLogger(__name__) + #: We need an easy way to see if Appveyor TCs start failing, #: so the errors marked with this var are considered "acknowledged" ones, awaiting remedy, #: till then, we wish to hide them. HIDE_WINDOWS_KNOWN_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_KNOWN_ERRORS', True) -HIDE_WINDOWS_FREEZE_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_FREEZE_ERRORS', HIDE_WINDOWS_KNOWN_ERRORS) +HIDE_WINDOWS_FREEZE_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_FREEZE_ERRORS', True) #{ Utility Methods @@ -70,6 +75,16 @@ def wrapper(self, *args, **kwargs): return wrapper +@contextlib.contextmanager +def cwd(new_dir): + old_dir = os.getcwd() + os.chdir(new_dir) + try: + yield new_dir + finally: + os.chdir(old_dir) + + def rmtree(path): """Remove the given recursively. @@ -116,7 +131,7 @@ def stream_copy(source, destination, chunk_size=512 * 1024): def join_path(a, *p): - """Join path tokens together similar to os.path.join, but always use + """Join path tokens together similar to osp.join, but always use '/' instead of possibly '\' on windows.""" path = a for b in p: @@ -162,14 +177,154 @@ def assure_directory_exists(path, is_file=False): Otherwise it must be a directory :return: True if the directory was created, False if it already existed""" if is_file: - path = os.path.dirname(path) + path = osp.dirname(path) # END handle file - if not os.path.isdir(path): + if not osp.isdir(path): os.makedirs(path) return True return False +def _get_exe_extensions(): + try: + winprog_exts = tuple(p.upper() for p in os.environ['PATHEXT'].split(os.pathsep)) + except: + winprog_exts = ('.BAT', 'COM', '.EXE') + + return winprog_exts + + +def py_where(program, path=None): + # From: http://stackoverflow.com/a/377028/548792 + try: + winprog_exts = tuple(p.upper() for p in os.environ['PATHEXT'].split(os.pathsep)) + except: + winprog_exts = is_win and ('.BAT', 'COM', '.EXE') or () + + def is_exec(fpath): + return osp.isfile(fpath) and os.access(fpath, os.X_OK) and ( + os.name != 'nt' or not winprog_exts or any(fpath.upper().endswith(ext) + for ext in winprog_exts)) + + progs = [] + if not path: + path = os.environ["PATH"] + for folder in path.split(os.pathsep): + folder = folder.strip('"') + if folder: + exe_path = osp.join(folder, program) + for f in [exe_path] + ['%s%s' % (exe_path, e) for e in winprog_exts]: + if is_exec(f): + progs.append(f) + return progs + + +def _cygexpath(drive, path): + if osp.isabs(path) and not drive: + ## Invoked from `cygpath()` directly with `D:Apps\123`? + # It's an error, leave it alone just slashes) + p = path + else: + p = path and osp.normpath(osp.expandvars(osp.expanduser(path))) + if osp.isabs(p): + if drive: + # Confusing, maybe a remote system should expand vars. + p = path + else: + p = cygpath(p) + elif drive: + p = '/cygdrive/%s/%s' % (drive.lower(), p) + + return p.replace('\\', '/') + + +_cygpath_parsers = ( + ## See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + ## and: https://www.cygwin.com/cygwin-ug-net/using.html#unc-paths + (re.compile(r"\\\\\?\\UNC\\([^\\]+)\\([^\\]+)(?:\\(.*))?"), + (lambda server, share, rest_path: '//%s/%s/%s' % (server, share, rest_path.replace('\\', '/'))), + False + ), + + (re.compile(r"\\\\\?\\(\w):[/\\](.*)"), + _cygexpath, + False + ), + + (re.compile(r"(\w):[/\\](.*)"), + _cygexpath, + False + ), + + (re.compile(r"file:(.*)", re.I), + (lambda rest_path: rest_path), + True), + + (re.compile(r"(\w{2,}:.*)"), # remote URL, do nothing + (lambda url: url), + False), +) + + +def cygpath(path): + """Use :meth:`git.cmd.Git.polish_url()` instead, that works on any environment.""" + if not path.startswith(('/cygdrive', '//')): + for regex, parser, recurse in _cygpath_parsers: + match = regex.match(path) + if match: + path = parser(*match.groups()) + if recurse: + path = cygpath(path) + break + else: + path = _cygexpath(None, path) + + return path + + +_decygpath_regex = re.compile(r"/cygdrive/(\w)(/.*)?") + + +def decygpath(path): + m = _decygpath_regex.match(path) + if m: + drive, rest_path = m.groups() + path = '%s:%s' % (drive.upper(), rest_path or '') + + return path.replace('/', '\\') + + +#: Store boolean flags denoting if a specific Git executable +#: is from a Cygwin installation (since `cache_lru()` unsupported on PY2). +_is_cygwin_cache = {} + + +def is_cygwin_git(git_executable): + if not is_win: + return False + + from subprocess import check_output + + is_cygwin = _is_cygwin_cache.get(git_executable) + if is_cygwin is None: + is_cygwin = False + try: + git_dir = osp.dirname(git_executable) + if not git_dir: + res = py_where(git_executable) + git_dir = osp.dirname(res[0]) if res else None + + ## Just a name given, not a real path. + uname_cmd = osp.join(git_dir, 'uname') + uname = check_output(uname_cmd, universal_newlines=True) + is_cygwin = 'CYGWIN' in uname + except Exception as ex: + log.debug('Failed checking if running in CYGWIN due to: %r', ex) + _is_cygwin_cache[git_executable] = is_cygwin + + return is_cygwin + + def get_user_id(): """:return: string identifying the currently active system user as name@node""" return "%s@%s" % (getpass.getuser(), platform.node()) @@ -589,7 +744,7 @@ def _obtain_lock_or_raise(self): if self._has_lock(): return lock_file = self._lock_file_path() - if os.path.isfile(lock_file): + if osp.isfile(lock_file): raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" % (self._file_path, lock_file)) @@ -659,7 +814,7 @@ def _obtain_lock(self): # synity check: if the directory leading to the lockfile is not # readable anymore, raise an execption curtime = time.time() - if not os.path.isdir(os.path.dirname(self._lock_file_path())): + if not osp.isdir(osp.dirname(self._lock_file_path())): msg = "Directory containing the lockfile %r was not readable anymore after waiting %g seconds" % ( self._lock_file_path(), curtime - starttime) raise IOError(msg) diff --git a/requirements.txt b/requirements.txt index 85d25511e..396446062 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ gitdb>=0.6.4 -ddt -mock \ No newline at end of file +ddt>=1.1.1 diff --git a/setup.py b/setup.py index 737e52a67..cbf5cf57c 100755 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ def _stamp_version(filename): extras_require = { ':python_version == "2.6"': ['ordereddict'], } -test_requires = ['ddt'] +test_requires = ['ddt>=1.1.1'] if sys.version_info[:2] < (2, 7): test_requires.append('mock') diff --git a/test-requirements.txt b/test-requirements.txt index 4d08a5018..8f13395d5 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,4 +3,4 @@ coverage flake8 nose -mock +mock; python_version<='2.7'