diff --git a/git/repo/base.py b/git/repo/base.py index 077ba4afe..6355615e8 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -35,7 +35,7 @@ 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 +from git.util import Actor, finalize_process, decygpath from .fun import rev_parse, is_git_dir, find_git_dir, touch @@ -99,6 +99,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 @@ -111,6 +113,9 @@ def __init__(self, path=None, odbt=DefaultDBType, search_parent_directories=Fals :raise InvalidGitRepositoryError: :raise NoSuchPathError: :return: git.Repo """ + if path and 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): diff --git a/git/test/test_repo.py b/git/test/test_repo.py index a0a6a5b00..314201eaa 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -50,7 +50,7 @@ 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 @@ -913,6 +913,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_util.py b/git/test/test_util.py index eb9e16b20..8f8d22725 100644 --- a/git/test/test_util.py +++ b/git/test/test_util.py @@ -29,6 +29,34 @@ Actor, IterableList, cygpath, + decygpath +) + + +_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'), +) + +_unc_cygpath_pairs = ( + (r'\\?\a:\com', '/cygdrive/a/com'), + (r'\\?\a:/com', '/cygdrive/a/com'), + + (r'\\?\UNC\server\D$\Apps', '//server/D$/Apps'), ) @@ -54,46 +82,45 @@ 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'foo\bar', 'foo/bar'), - (r'foo/bar', 'foo/bar'), (r'./bar', 'bar'), (r'.\bar', 'bar'), (r'../bar', '../bar'), (r'..\bar', '../bar'), (r'../bar/.\foo/../chu', '../bar/chu'), - - (r'C:\Users', '/cygdrive/c/Users'), - (r'C:\d/e', '/cygdrive/c/d/e'), - - (r'\\?\a:\com', '/cygdrive/a/com'), - (r'\\?\a:/com', '/cygdrive/a/com'), - - (r'\\server\C$\Users', '//server/C$/Users'), - (r'\\server\C$', '//server/C$'), - (r'\\server\BAR/', '//server/BAR/'), - (r'\\?\UNC\server\D$\Apps', '//server/D$/Apps'), - - (r'D:/Apps', '/cygdrive/d/Apps'), - (r'D:/Apps\fOO', '/cygdrive/d/Apps/fOO'), - (r'D:\Apps/123', '/cygdrive/d/Apps/123'), ) - def test_cygpath_ok(self, case): + def test_cygpath_norm_ok(self, case): wpath, cpath = case - self.assertEqual(cygpath(wpath), cpath or wpath) + cwpath = cygpath(wpath) + self.assertEqual(cwpath, cpath or wpath, wpath) @skipIf(not is_win, "Paths specifically for Windows.") @ddt.data( - (r'C:Relative', None), - (r'D:Apps\123', None), - (r'D:Apps/123', None), - (r'\\?\a:rel', None), - (r'\\share\a:rel', None), + 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, case): + 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 - self.assertEqual(cygpath(wpath), cpath or wpath.replace('\\', '/')) + 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')) diff --git a/git/util.py b/git/util.py index b7d18023c..9668f7b3f 100644 --- a/git/util.py +++ b/git/util.py @@ -222,7 +222,7 @@ def _cygexpath(drive, path): # It's an error, leave it alone just slashes) p = path else: - p = osp.normpath(osp.expandvars(os.path.expanduser(path))) + p = path and osp.normpath(osp.expandvars(os.path.expanduser(path))) if osp.isabs(p): if drive: # Confusing, maybe a remote system should expand vars. @@ -278,6 +278,18 @@ def cygpath(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 = {}