From 2a876eb1d031c32de1a0aa84d79311478c5235ed Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Tue, 23 Sep 2025 19:44:00 +0200 Subject: [PATCH] __init__: don't run refresh on import Consumers of gitpython may not need to use it in all use cases, and may want to be able to run (without using gitpython) in environments where git is not available on PATH. While this can be worked around by setting the GIT_PYTHON_REFRESH environment variable, adding special handling for gitpython means that it can't be imported just like everything else in an import block at the top of the module, and environment variables have potentially undesired propagation behaviour. Previously, it was also nontrivial to distinguish gitpython failing to import because of missing git or because e.g. gitpython isn't installed at all, because the exception that's raised is an ImportError without further qualification (except in the error message). Thus, we now no longer perform `refresh` at the module top level, instead performing it lazily when an invocation of git is attempted. This also allows some functionality that doesn't rely on the git command to work without, e.g. ref listing. --- doc/source/changes.rst | 13 +++++++++++++ git/__init__.py | 5 ----- git/cmd.py | 4 +++- test/__init__.py | 4 ++++ test/test_git.py | 2 +- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 151059ed2..c065c97c4 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,6 +2,19 @@ Changelog ========= +4.0.0 +===== + +GitPython will no longer throw an ImportError when no git executable can +be found at import time. Instead, errors are deferred until the first +attempt at using it. Consumers with special handling for +the old ImportError behaviour should instead call `git.refresh` and handle +GitCommandNotFoundErrors themselves. + +See the following for all changes. +https://github.com/gitpython-developers/GitPython/releases/tag/4.0.0 + + 3.1.45 ====== diff --git a/git/__init__.py b/git/__init__.py index 1b2360e3a..2d8817015 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -292,9 +292,4 @@ def refresh(path: Optional[PathLike] = None) -> None: GIT_OK = True -try: - refresh() -except Exception as _exc: - raise ImportError("Failed to initialize: {0}".format(_exc)) from _exc - # } END initialize git executable path diff --git a/git/cmd.py b/git/cmd.py index 15d7820df..9a1ad6e9b 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -857,7 +857,7 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: if mode in warn: _logger.critical(err) else: - raise ImportError(err) + raise GitCommandNotFound(new_git, err) else: err = dedent( """\ @@ -1575,6 +1575,8 @@ def _call_process( default (especially ``as_process = False``, ``stdout_as_string = True``) and return :class:`str`. """ + if not self.GIT_PYTHON_GIT_EXECUTABLE: + self.refresh() # Handle optional arguments prior to calling transform_kwargs. # Otherwise these'll end up in args, which is bad. exec_kwargs = {k: v for k, v in kwargs.items() if k in execute_kwargs} diff --git a/test/__init__.py b/test/__init__.py index fbaebcd3b..ae33bd4ca 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -2,3 +2,7 @@ # # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ +# +import git + +git.refresh() diff --git a/test/test_git.py b/test/test_git.py index 4a54d0d9b..a9217ae18 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -434,7 +434,7 @@ def test_initial_refresh_from_bad_git_path_env_error(self, case): Git.GIT_PYTHON_GIT_EXECUTABLE = None # Simulate startup. with mock.patch.dict(os.environ, env_vars): - with self.assertRaisesRegex(ImportError, r"\ABad git executable.\n"): + with self.assertRaisesRegex(GitCommandNotFound, r"Bad git executable."): refresh() def test_initial_refresh_from_good_absolute_git_path_env(self):