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 @@ -7,6 +7,10 @@
- Bugfix: Fetch the LFS objects of a `git checkout` of a tag or a commit as well. Previously, only a checkout of a
branch triggered an LFS fetch of the mirror and the LFS objects of a tag or commit were downloaded by the git-lfs
filters of the checkout itself.
- Bugfix: Skip the gitcache executable itself when searching for the real git command. Previously, only a symlink
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.

## v1.0.34

Expand Down
9 changes: 6 additions & 3 deletions src/git_cache/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import os
import platform
import re
import sys
from typing import Any, Callable, Dict, List, Optional

import pytimeparse
Expand Down Expand Up @@ -92,9 +93,11 @@ def find_git() -> str:
cmd = "git.exe" if on_windows else "git"
for candidate in path.split(os.path.pathsep):
candidate = os.path.join(candidate, cmd)
if os.path.exists(candidate) and os.access(candidate, os.X_OK):
if not os.path.islink(candidate):
return candidate
if not os.path.exists(candidate) or not os.access(candidate, os.X_OK):
continue
if os.path.samefile(candidate, sys.executable):
continue
return candidate

LOG.warning("Can't find git command! Please specify manually in the config file!")
return "/usr/bin/git"
Expand Down
65 changes: 65 additions & 0 deletions test/unittests/test_git_cache_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
# -----------------------------------------------------------------------------
import importlib
import os
import platform
import shutil
import sys
import tempfile
from unittest import TestCase

import mock
Expand Down Expand Up @@ -157,6 +161,67 @@ def test_str_rep(self):
self.assertEqual(str(config), expected_config_str)


class GitCacheFindGitTest(TestCase):
"""Test the :func:`git_cache.config.find_git` function."""

ON_WINDOWS = platform.system().lower().startswith("win")
GIT_CMD = "git.exe" if ON_WINDOWS else "git"
GITCACHE_CMD = "gitcache.exe" if ON_WINDOWS else "gitcache"

def setUp(self):
"""Set up the test case with a gitcache executable and a real git command."""
self.tmp_dir = tempfile.mkdtemp()
self.gitcache_cmd = self._create_executable(self.tmp_dir, self.GITCACHE_CMD)

self.real_git_dir = os.path.join(self.tmp_dir, "real")
os.makedirs(self.real_git_dir)
self.real_git_cmd = self._create_executable(self.real_git_dir, self.GIT_CMD)

self.gitcache_dir = os.path.join(self.tmp_dir, "gitcache_bin")
os.makedirs(self.gitcache_dir)

def tearDown(self):
"""Tear down the test case."""
shutil.rmtree(self.tmp_dir, ignore_errors=True)

@staticmethod
def _create_executable(directory, name):
"""Create an executable file and return its path."""
path = os.path.join(directory, name)
with open(path, "w", encoding="utf-8") as file_handle:
file_handle.write("#!/bin/sh\nexit 0\n")
os.chmod(path, 0o755)
return path

def _find_git(self, executable):
"""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):
return git_cache.config.find_git()

def test_gitcache_executable(self):
"""git_cache.config.find_git: Find the real git command."""
self.assertEqual(self._find_git(self.gitcache_cmd), self.real_git_cmd)

def test_skip_git_symlink(self):
"""git_cache.config.find_git: Skip a symlink to the gitcache executable."""
try:
os.symlink(self.gitcache_cmd, os.path.join(self.gitcache_dir, self.GIT_CMD))
except OSError:
self.skipTest("This system does not allow the creation of symlinks.")
self.assertEqual(self._find_git(self.gitcache_cmd), self.real_git_cmd)

def test_skip_git_hardlink(self):
"""git_cache.config.find_git: Skip a hard link to the gitcache executable."""
os.link(self.gitcache_cmd, os.path.join(self.gitcache_dir, self.GIT_CMD))
self.assertEqual(self._find_git(self.gitcache_cmd), self.real_git_cmd)

def test_skip_git_executable(self):
"""git_cache.config.find_git: Skip the gitcache executable itself named git."""
gitcache_as_git = self._create_executable(self.gitcache_dir, self.GIT_CMD)
self.assertEqual(self._find_git(gitcache_as_git), self.real_git_cmd)


# -----------------------------------------------------------------------------
# EOF
# -----------------------------------------------------------------------------