Skip to content

WIP: use cygpath.exe to convert Windows paths and respect mount-points #639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
10 changes: 7 additions & 3 deletions doc/source/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Changelog
====================================

* support for worktrees
* fix(cygwin): use ``cygpath.exe`` to convert *Windows* paths and respect
different mount-points (e.g. *MSYS2* which is a *Cygwin* clone mounts drives
under root).


2.1.3 - Bugfixes
====================================
Expand Down Expand Up @@ -34,7 +38,7 @@ Notable fixes
* The `GIT_DIR` environment variable does not override the `path` argument when
initializing a `Repo` object anymore. However, if said `path` unset, `GIT_DIR`
will be used to fill the void.

All issues and PRs can be viewed in all detail when following this URL:
https://github.com/gitpython-developers/GitPython/issues?q=is%3Aclosed+milestone%3A%22v2.1.0+-+proper+windows+support%22

Expand Down Expand Up @@ -63,7 +67,7 @@ https://github.com/gitpython-developers/GitPython/issues?q=is%3Aclosed+milestone
2.0.7 - New Features
====================

* `IndexFile.commit(...,skip_hooks=False)` added. This parameter emulates the
* `IndexFile.commit(...,skip_hooks=False)` added. This parameter emulates the
behaviour of `--no-verify` on the command-line.

2.0.6 - Fixes and Features
Expand Down Expand Up @@ -103,7 +107,7 @@ https://github.com/gitpython-developers/GitPython/issues?q=is%3Aclosed+milestone
commit messages contained ``\r`` characters
* Fix: progress handler exceptions are not caught anymore, which would usually just hide bugs
previously.
* Fix: The `Git.execute` method will now redirect `stdout` to `devnull` if `with_stdout` is false,
* Fix: The `Git.execute` method will now redirect `stdout` to `devnull` if `with_stdout` is false,
which is the intended behaviour based on the parameter's documentation.

2.0.2 - Fixes
Expand Down
68 changes: 48 additions & 20 deletions git/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
from unittest import SkipTest
except ImportError:
from unittest2 import SkipTest
try:
from functools import lru_cache
except ImportError:
from repoze.lru import lru_cache

from gitdb.util import (# NOQA @IgnorePep8
make_sha,
Expand Down Expand Up @@ -221,7 +225,28 @@ def is_exec(fpath):
return progs


def _cygexpath(drive, path):
def _cygpath(winpath, inverse=False):
"""Invokes `cygpath` cmd to parse Windoews paths."""
import subprocess as sbp

cmd = ['cygpath', winpath]
if inverse:
cmd.insert(1, '-w')
try:
cygpath = sbp.check_output(cmd, universal_newlines=True)
if cygpath and cygpath[-1] == '\n':
cygpath = cygpath[:-1]
except Exception as ex:
log.warning("`cygpath.exe` failed on '%s' due to: %s"
" Using winpath as it is.",
winpath, ex)
else:
winpath = cygpath

return winpath


def _cyg_regex_path(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)
Expand All @@ -235,7 +260,7 @@ def _cygexpath(drive, path):
else:
p = cygpath(p)
elif drive:
p = '/cygdrive/%s/%s' % (drive.lower(), p)
return _cygpath('%s:\\%s' % (drive, p))

return p.replace('\\', '/')

Expand All @@ -249,12 +274,12 @@ def _cygexpath(drive, path):
),

(re.compile(r"\\\\\?\\(\w):[/\\](.*)"),
_cygexpath,
_cyg_regex_path,
False
),

(re.compile(r"(\w):[/\\](.*)"),
_cygexpath,
_cyg_regex_path,
False
),

Expand All @@ -268,39 +293,42 @@ def _cygexpath(drive, path):
)


@lru_cache(500) # Size arg required only for py3.2 backport `repoze.lru` lib.
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)
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 = _cyg_regex_path(None, path)

return path


_decygpath_regex = re.compile(r"/cygdrive/(\w)(/.*)?")


@lru_cache(500) # Size arg required only for py3.2 backport `repoze.lru` lib.
def decygpath(path):
m = _decygpath_regex.match(path)
if m:
drive, rest_path = m.groups()
path = '%s:%s' % (drive.upper(), rest_path or '')
if path:
winpath = _cygpath(path, inverse=True)
if path[-1] in '/\\' and winpath[-1] not in '/\\':
winpath += '\\'
path = winpath

return path.replace('/', '\\')
return path


#: Store boolean flags denoting if a specific Git executable
#: is from a Cygwin installation (since `cache_lru()` unsupported on PY2).
_is_cygwin_cache = {}


@lru_cache(50) # Size arg required only for py3.2 backport `repoze.lru` lib.
def is_cygwin_git(git_executable):
if not is_win:
return False
Expand All @@ -322,7 +350,7 @@ def is_cygwin_git(git_executable):
universal_newlines=True)
uname_out, _ = process.communicate()
#retcode = process.poll()
is_cygwin = 'CYGWIN' in uname_out
is_cygwin = 'CYGWIN' in uname_out or 'MSYS' in uname_out
except Exception as ex:
log.debug('Failed checking if running in CYGWIN due to: %r', ex)
_is_cygwin_cache[git_executable] = is_cygwin
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
gitdb>=0.6.4
ddt>=1.1.1
ordereddict; python_version < '2.7'
repoze.lru; python_version < '3.2'
unittest2; python_version < '2.7'
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def _stamp_version(filename):
install_requires = ['gitdb2 >= 2.0.0']
extras_require = {
':python_version == "2.6"': ['ordereddict'],
':python_version < "3.2"': ['repoze.lru'],
}
test_requires = ['ddt>=1.1.1']
if sys.version_info[:2] < (2, 7):
Expand Down