diff --git a/AUTHORS b/AUTHORS index f0c02e338..0d3bdacfe 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Contributors are: -Timothy B. Hartman -Konstantin Popov -Peter Jones +-Ken Odegard -Alexis Horgix Chotard Portions derived from other open source works and are clearly marked. diff --git a/git/__init__.py b/git/__init__.py index 75247dbf8..74609e79f 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -60,3 +60,24 @@ def _init_externals(): __all__ = [name for name, obj in locals().items() if not (name.startswith('_') or inspect.ismodule(obj))] + + +#{ Initialize git executable path +GIT_OK = None + +def refresh(path=None): + """Convenience method for setting the git executable path.""" + global GIT_OK + GIT_OK = False + + if not Git.refresh(path=path): + return + if not FetchInfo.refresh(): + return + + GIT_OK = True +#} END initialize git executable path + +################# +refresh() +################# diff --git a/git/cmd.py b/git/cmd.py index a2fcf50d9..312f933a6 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -17,6 +17,7 @@ import subprocess import sys import threading +from textwrap import dedent from git.compat import ( string_types, @@ -182,16 +183,141 @@ def __setstate__(self, d): # Enables debugging of GitPython's git commands GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False) - # Provide the full path to the git executable. Otherwise it assumes git is in the path - _git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE" - GIT_PYTHON_GIT_EXECUTABLE = os.environ.get(_git_exec_env_var, git_exec_name) - # If True, a shell will be used when executing git commands. # This should only be desirable on Windows, see https://github.com/gitpython-developers/GitPython/pull/126 # and check `git/test_repo.py:TestRepo.test_untracked_files()` TC for an example where it is required. # Override this value using `Git.USE_SHELL = True` USE_SHELL = False + # Provide the full path to the git executable. Otherwise it assumes git is in the path + _git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE" + _refresh_env_var = "GIT_PYTHON_REFRESH" + GIT_PYTHON_GIT_EXECUTABLE = None + # note that the git executable is actually found during the refresh step in + # the top level __init__ + + @classmethod + def refresh(cls, path=None): + """This gets called by the refresh function (see the top level + __init__). + """ + # discern which path to refresh with + if path is not None: + new_git = os.path.expanduser(path) + new_git = os.path.abspath(new_git) + else: + new_git = os.environ.get(cls._git_exec_env_var, cls.git_exec_name) + + # keep track of the old and new git executable path + old_git = cls.GIT_PYTHON_GIT_EXECUTABLE + cls.GIT_PYTHON_GIT_EXECUTABLE = new_git + + # test if the new git executable path is valid + + if sys.version_info < (3,): + # - a GitCommandNotFound error is spawned by ourselves + # - a OSError is spawned if the git executable provided + # cannot be executed for whatever reason + exceptions = (GitCommandNotFound, OSError) + else: + # - a GitCommandNotFound error is spawned by ourselves + # - a PermissionError is spawned if the git executable provided + # cannot be executed for whatever reason + exceptions = (GitCommandNotFound, PermissionError) + + has_git = False + try: + cls().version() + has_git = True + except exceptions: + pass + + # warn or raise exception if test failed + if not has_git: + err = dedent("""\ + Bad git executable. + The git executable must be specified in one of the following ways: + - be included in your $PATH + - be set via $%s + - explicitly set via git.refresh() + """) % cls._git_exec_env_var + + # revert to whatever the old_git was + cls.GIT_PYTHON_GIT_EXECUTABLE = old_git + + if old_git is None: + # on the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is + # None) we only are quiet, warn, or error depending on the + # GIT_PYTHON_REFRESH value + + # determine what the user wants to happen during the initial + # refresh we expect GIT_PYTHON_REFRESH to either be unset or + # be one of the following values: + # 0|q|quiet|s|silence + # 1|w|warn|warning + # 2|r|raise|e|error + + mode = os.environ.get(cls._refresh_env_var, "raise").lower() + + quiet = ["quiet", "q", "silence", "s", "none", "n", "0"] + warn = ["warn", "w", "warning", "1"] + error = ["error", "e", "raise", "r", "2"] + + if mode in quiet: + pass + elif mode in warn or mode in error: + err = dedent("""\ + %s + All git commands will error until this is rectified. + + This initial warning can be silenced or aggravated in the future by setting the + $%s environment variable. Use one of the following values: + - %s: for no warning or exception + - %s: for a printed warning + - %s: for a raised exception + + Example: + export %s=%s + """) % ( + err, + cls._refresh_env_var, + "|".join(quiet), + "|".join(warn), + "|".join(error), + cls._refresh_env_var, + quiet[0]) + + if mode in warn: + print("WARNING: %s" % err) + else: + raise ImportError(err) + else: + err = dedent("""\ + %s environment variable has been set but it has been set with an invalid value. + + Use only the following values: + - %s: for no warning or exception + - %s: for a printed warning + - %s: for a raised exception + """) % ( + cls._refresh_env_var, + "|".join(quiet), + "|".join(warn), + "|".join(error)) + raise ImportError(err) + + # we get here if this was the init refresh and the refresh mode + # was not error, go ahead and set the GIT_PYTHON_GIT_EXECUTABLE + # such that we discern the difference between a first import + # and a second import + cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name + else: + # after the first refresh (when GIT_PYTHON_GIT_EXECUTABLE + # is no longer None) we raise an exception + raise GitCommandNotFound("git", err) + + return has_git + @classmethod def is_cygwin(cls): return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE) @@ -828,13 +954,13 @@ def _call_process(self, method, *args, **kwargs): - "command options" to be converted by :meth:`transform_kwargs()`; - the `'insert_kwargs_after'` key which its value must match one of ``*args``, and any cmd-options will be appended after the matched arg. - + Examples:: - + git.rev_list('master', max_count=10, header=True) - + turns into:: - + git rev-list max-count 10 --header master :return: Same as ``execute``""" diff --git a/git/remote.py b/git/remote.py index 81719f4b3..7261be813 100644 --- a/git/remote.py +++ b/git/remote.py @@ -211,17 +211,38 @@ class FetchInfo(object): _re_fetch_result = re.compile(r'^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([^\s]+)( \(.*\)?$)?') - _flag_map = {'!': ERROR, - '+': FORCED_UPDATE, - '*': 0, - '=': HEAD_UPTODATE, - ' ': FAST_FORWARD} + _flag_map = { + '!': ERROR, + '+': FORCED_UPDATE, + '*': 0, + '=': HEAD_UPTODATE, + ' ': FAST_FORWARD, + '-': TAG_UPDATE, + } - v = Git().version_info[:2] - if v >= (2, 10): - _flag_map['t'] = TAG_UPDATE - else: - _flag_map['-'] = TAG_UPDATE + @classmethod + def refresh(cls): + """This gets called by the refresh function (see the top level + __init__). + """ + # clear the old values in _flag_map + try: + del cls._flag_map["t"] + except KeyError: + pass + + try: + del cls._flag_map["-"] + except KeyError: + pass + + # set the value given the git version + if Git().version_info[:2] >= (2, 10): + cls._flag_map["t"] = cls.TAG_UPDATE + else: + cls._flag_map["-"] = cls.TAG_UPDATE + + return True def __init__(self, ref, flags, note='', old_commit=None, remote_ref_path=None): """ diff --git a/git/test/test_git.py b/git/test/test_git.py index 3c8b6f828..00577e33a 100644 --- a/git/test/test_git.py +++ b/git/test/test_git.py @@ -10,6 +10,7 @@ from git import ( Git, + refresh, GitCommandError, GitCommandNotFound, Repo, @@ -156,6 +157,14 @@ def test_cmd_override(self): type(self.git).GIT_PYTHON_GIT_EXECUTABLE = prev_cmd # END undo adjustment + def test_refresh(self): + # test a bad git path refresh + self.assertRaises(GitCommandNotFound, refresh, "yada") + + # test a good path refresh + path = os.popen("which git").read().strip() + refresh(path) + def test_options_are_passed_to_git(self): # This work because any command after git --version is ignored git_version = self.git(version=True).NoOp() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy