Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
named `git` was skipped, so that renaming or hard-linking the gitcache executable to `git` resulted in an
infinite recursion. As the check now compares the candidates with the gitcache executable, a symlink pointing to
the real git command is no longer skipped either.
- Bugfix: Also skip the gitcache script when it is run by a Python interpreter, as in a pip installation. There,
`sys.executable` is the interpreter, so a `git` symlink to the installed `gitcache` script was returned as the
real git command whenever no `RealGit` was configured, e.g. with `HOME` pointing to a fresh directory, and each
call spawned itself until the system ran out of memory.
- Bugfix: Keep the user of `ssh://` and scp-style URLs when normalizing URLs and stripping credentials. The
user of an SSH URL is part of the address, not a secret. Dropping it left mirrors created from SSH URLs
unable to authenticate on `gitcache -u` and whenever the same repository was reached through an HTTPS URL
Expand Down
15 changes: 14 additions & 1 deletion src/git_cache/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ def has_git_lfs_cmd() -> bool:
return has_git_lfs_cmd.has_git_lfs # type: ignore


def _is_gitcache_executable(candidate: str) -> bool:
"""Check whether the given path is the running gitcache executable.

A frozen gitcache is ``sys.executable``. A pip-installed gitcache is a script
started by the Python interpreter, so ``sys.executable`` is the interpreter and
the script is ``sys.argv[0]``.
"""
own_paths = [sys.executable]
if sys.argv and sys.argv[0]:
own_paths.append(sys.argv[0])
return any(os.path.exists(own) and os.path.samefile(candidate, own) for own in own_paths)


def find_git() -> str:
"""Locate the real git command.

Expand All @@ -95,7 +108,7 @@ def find_git() -> str:
candidate = os.path.join(candidate, cmd)
if not os.path.exists(candidate) or not os.access(candidate, os.X_OK):
continue
if os.path.samefile(candidate, sys.executable):
if _is_gitcache_executable(candidate):
continue
return candidate

Expand Down
20 changes: 18 additions & 2 deletions test/unittests/test_git_cache_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,11 @@ def _create_executable(directory, name):
os.chmod(path, 0o755)
return path

def _find_git(self, executable):
def _find_git(self, executable, argv0=None):
"""Call find_git() as the given executable with the gitcache dir in front of the real git dir."""
path = os.path.pathsep.join([self.gitcache_dir, self.real_git_dir])
with mock.patch.object(sys, "executable", executable), mockenv(PATH=path):
argv = [argv0 if argv0 is not None else executable]
with mock.patch.object(sys, "executable", executable), mock.patch.object(sys, "argv", argv), mockenv(PATH=path):
return git_cache.config.find_git()

def test_gitcache_executable(self):
Expand All @@ -221,6 +222,21 @@ def test_skip_git_executable(self):
gitcache_as_git = self._create_executable(self.gitcache_dir, self.GIT_CMD)
self.assertEqual(self._find_git(gitcache_as_git), self.real_git_cmd)

def test_skip_git_symlink_to_script(self):
"""git_cache.config.find_git: Skip a symlink to a gitcache script run by a separate interpreter."""
interpreter = self._create_executable(self.tmp_dir, "python")
git_symlink = os.path.join(self.gitcache_dir, self.GIT_CMD)
try:
os.symlink(self.gitcache_cmd, git_symlink)
except OSError:
self.skipTest("This system does not allow the creation of symlinks.")
self.assertEqual(self._find_git(interpreter, argv0=git_symlink), self.real_git_cmd)

def test_skip_without_argv0(self):
"""git_cache.config.find_git: Ignore an empty sys.argv[0]."""
interpreter = self._create_executable(self.tmp_dir, "python")
self.assertEqual(self._find_git(interpreter, argv0=""), self.real_git_cmd)


# -----------------------------------------------------------------------------
# EOF
Expand Down