From a962464c1504d716d4acee7770d8831cd3a84b48 Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Sun, 9 Jul 2017 18:35:47 +0200 Subject: [PATCH 01/10] Preliminary implementation of setup/refresh functions Added one function (setup) and an alias (refresh simply calls setup). These functions give the developer one more way to configure the git executable path. This also allows the user to interactively adjust the git executable configured during runtime as these functions dynamically update the executable path for the entire git module. --- git/cmd.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 3637ac9e4..82bc9e907 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,69 @@ 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 + @classmethod + def refresh(cls, path=None): + """Convenience method for refreshing the git executable path.""" + cls.setup(path=path) + + @classmethod + def setup(cls, path=None): + """Convenience method for setting the git executable path.""" + if path is not None: + # use the path the user gave + os.environ[cls._git_exec_env_var] = path + elif cls._git_exec_env_var in os.environ: + # fall back to the environment variable that's already set + pass + else: + # hope that git can be found on the user's $PATH + pass + + old_git = cls.GIT_PYTHON_GIT_EXECUTABLE + new_git = os.environ.get(cls._git_exec_env_var, cls.git_exec_name) + cls.GIT_PYTHON_GIT_EXECUTABLE = new_git + + has_git = False + try: + cls().version() + has_git = True + except GitCommandNotFound: + pass + + if not has_git: + err = dedent("""\ + Bad git executable. The git executable must be specified in one of the following ways: + (1) be included in your $PATH, or + (2) be set via $GIT_PYTHON_GIT_EXECUTABLE, or + (3) explicitly call git.cmd.setup with the full path. + """) + + if old_git is None: + # on the first setup (when GIT_PYTHON_GIT_EXECUTABLE is + # None) we only warn the user and simply set the default + # executable + cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name + print("WARNING: %s" % err) + else: + # after the first setup (when GIT_PYTHON_GIT_EXECUTABLE + # is no longer None) we raise an exception and reset the + # GIT_PYTHON_GIT_EXECUTABLE to whatever the value was + # previously + cls.GIT_PYTHON_GIT_EXECUTABLE = old_name + raise GitCommandNotFound("git", err) + + _git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE" + # immediately set with the default value ("git") + GIT_PYTHON_GIT_EXECUTABLE = None + # see the setup performed below + @classmethod def is_cygwin(cls): return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE) @@ -828,13 +882,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``""" @@ -970,3 +1024,16 @@ def clear_cache(self): self.cat_file_all = None self.cat_file_header = None return self + + + +# this is where the git executable is setup +def setup(path=None): + Git.setup(path=path) + + +def refresh(path=None): + Git.refresh(path=path) + + +setup() From feed81ea1a332dc415ea9010c8b5204473a51bdf Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Sun, 9 Jul 2017 19:53:38 +0200 Subject: [PATCH 02/10] Moved setup function into top level __init__ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discovered that the remote module also relies on the git executable as such it also needs to be “refreshed” anytime the git executable is updated or changed. This was best solved by moving the setup function into the top level __init__ where the setup simply calls git.cmd.Git.refresh and git.remote.FetchInfo.refresh. --- git/__init__.py | 17 ++++++++++++++++ git/cmd.py | 54 ++++++++++++++++++------------------------------- git/remote.py | 40 +++++++++++++++++++++++++++--------- 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/git/__init__.py b/git/__init__.py index 8c31e3094..cc45efe17 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -57,3 +57,20 @@ def _init_externals(): __all__ = [name for name, obj in locals().items() if not (name.startswith('_') or inspect.ismodule(obj))] + +#{ Initialize git executable path +def setup(path=None): + """Convenience method for setting the git executable path.""" + if not Git.refresh(path=path): + return + if not FetchInfo.refresh(): + return + +def refresh(path=None): + """Convenience method for refreshing the git executable path.""" + setup(path=path) +#} END initialize git executable path + +################# +setup() +################# diff --git a/git/cmd.py b/git/cmd.py index 82bc9e907..0e9315a25 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -190,28 +190,26 @@ def __setstate__(self, d): USE_SHELL = False # Provide the full path to the git executable. Otherwise it assumes git is in the path - @classmethod - def refresh(cls, path=None): - """Convenience method for refreshing the git executable path.""" - cls.setup(path=path) + _git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE" + GIT_PYTHON_GIT_EXECUTABLE = None + # note that the git executable is actually found during the setup step in + # the top level __init__ @classmethod - def setup(cls, path=None): - """Convenience method for setting the git executable path.""" + def refresh(cls, path=None): + """This gets called by the setup function (see the top level __init__). + """ + # discern which path to refresh with if path is not None: - # use the path the user gave - os.environ[cls._git_exec_env_var] = path - elif cls._git_exec_env_var in os.environ: - # fall back to the environment variable that's already set - pass + new_git = os.path.abspath(path) else: - # hope that git can be found on the user's $PATH - pass + 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 - new_git = os.environ.get(cls._git_exec_env_var, cls.git_exec_name) cls.GIT_PYTHON_GIT_EXECUTABLE = new_git + # test if the new git executable path is valid has_git = False try: cls().version() @@ -219,12 +217,13 @@ def setup(cls, path=None): except GitCommandNotFound: 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: (1) be included in your $PATH, or (2) be set via $GIT_PYTHON_GIT_EXECUTABLE, or - (3) explicitly call git.cmd.setup with the full path. + (3) explicitly set via git.setup (or git.refresh). """) if old_git is None: @@ -232,19 +231,19 @@ def setup(cls, path=None): # None) we only warn the user and simply set the default # executable cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name - print("WARNING: %s" % err) + print(dedent("""\ + WARNING: %s + All git commands will error until this is rectified. + """) % err) else: # after the first setup (when GIT_PYTHON_GIT_EXECUTABLE # is no longer None) we raise an exception and reset the # GIT_PYTHON_GIT_EXECUTABLE to whatever the value was # previously - cls.GIT_PYTHON_GIT_EXECUTABLE = old_name + cls.GIT_PYTHON_GIT_EXECUTABLE = old_git raise GitCommandNotFound("git", err) - _git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE" - # immediately set with the default value ("git") - GIT_PYTHON_GIT_EXECUTABLE = None - # see the setup performed below + return has_git @classmethod def is_cygwin(cls): @@ -1024,16 +1023,3 @@ def clear_cache(self): self.cat_file_all = None self.cat_file_header = None return self - - - -# this is where the git executable is setup -def setup(path=None): - Git.setup(path=path) - - -def refresh(path=None): - Git.refresh(path=path) - - -setup() diff --git a/git/remote.py b/git/remote.py index fd76e592a..b6ac66cbd 100644 --- a/git/remote.py +++ b/git/remote.py @@ -210,17 +210,37 @@ 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 setup 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): """ From aba0494701292e916761076d6d9f8beafa44c421 Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Sun, 9 Jul 2017 22:07:31 +0200 Subject: [PATCH 03/10] Renamed refresh to setup and removed alias function & added unittest Renamed to simplify and avoid issue with nose tests trying to use `setup` as a setup for testing. Unittest implements basic test for refreshing with a bad git path versus a good git path. --- git/__init__.py | 9 +++------ git/test/test_git.py | 9 +++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/git/__init__.py b/git/__init__.py index cc45efe17..7005cd602 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -58,19 +58,16 @@ def _init_externals(): __all__ = [name for name, obj in locals().items() if not (name.startswith('_') or inspect.ismodule(obj))] + #{ Initialize git executable path -def setup(path=None): +def refresh(path=None): """Convenience method for setting the git executable path.""" if not Git.refresh(path=path): return if not FetchInfo.refresh(): return - -def refresh(path=None): - """Convenience method for refreshing the git executable path.""" - setup(path=path) #} END initialize git executable path ################# -setup() +refresh() ################# 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() From e8cb31db6b3970d1e983f10b0e0b5eeda8348c7e Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Sun, 9 Jul 2017 22:12:13 +0200 Subject: [PATCH 04/10] Modified AUTHORS file Added my name to list. --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index ad7c452c0..8e9ff0307 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,5 +19,6 @@ Contributors are: -Timothy B. Hartman -Konstantin Popov -Peter Jones +-Ken Odegard Portions derived from other open source works and are clearly marked. From b56d6778ee678081e22c1897ede1314ff074122a Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Sun, 9 Jul 2017 22:44:19 +0200 Subject: [PATCH 05/10] Added ability to silence initial warning Added the ability to silence the first refresh warning upon import by setting an environment variable. --- git/cmd.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 0e9315a25..ae7721df8 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -231,10 +231,19 @@ def refresh(cls, path=None): # None) we only warn the user and simply set the default # executable cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name - print(dedent("""\ - WARNING: %s - All git commands will error until this is rectified. - """) % err) + + # test if the user didn't want a warning + nowarn = os.environ.get("GIT_PYTHON_NOWARN", "false") + nowarn = nowarn.lower() in ["t", "true", "y", "yes"] + + if not nowarn: + print(dedent("""\ + WARNING: %s + All git commands will error until this is rectified. + + This initial warning can be silenced in the future by setting the environment variable: + export GIT_PYTHON_NOWARN=true + """) % err) else: # after the first setup (when GIT_PYTHON_GIT_EXECUTABLE # is no longer None) we raise an exception and reset the From 3430bde60ae65b54c08ffa73de1f16643c7c3bfd Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Thu, 13 Jul 2017 11:44:10 +0200 Subject: [PATCH 06/10] Expanded ability of import Renamed GIT_PYTHON_NOWARN to GIT_PYTHON_INITERR and added values for quiet import, warning import, and raise import. These respectively mean that no message or error is printed if git is non-existent, a plain warning is printed but the import succeeds, and an ImportError exception is raised. --- git/cmd.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index ae7721df8..0bf0fee4f 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -232,11 +232,20 @@ def refresh(cls, path=None): # executable cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name - # test if the user didn't want a warning - nowarn = os.environ.get("GIT_PYTHON_NOWARN", "false") - nowarn = nowarn.lower() in ["t", "true", "y", "yes"] - - if not nowarn: + # determine what the user wanted to happen + # we expect GIT_PYTHON_INITERR to either be unset or be one of + # the following values: + # q|quiet|s|silence + # w|warn|warning + # r|raise|e|error + initerr_quiet = ["q", "quiet", "s", "silence"] + initerr_warn = ["w", "warn", "warning"] + initerr_raise = ["r", "raise", "e", "error"] + + initerr = os.environ.get("GIT_PYTHON_INITERR", "warn").lower() + if initerr in initerr_quiet: + pass + elif initerr in initerr_warn: print(dedent("""\ WARNING: %s All git commands will error until this is rectified. @@ -244,6 +253,19 @@ def refresh(cls, path=None): This initial warning can be silenced in the future by setting the environment variable: export GIT_PYTHON_NOWARN=true """) % err) + elif initerr in initerr_raise: + raise ImportError(err) + else: + err = dedent("""\ + GIT_PYTHON_INITERR environment variable has been set but it has been set with an invalid value. + + Use only the following values: + (1) q|quiet|s|silence: for no warning or exception + (2) w|warn|warning: for a printed warning + (3) r|raise|e|error: for a raised exception + """) + raise ImportError(err) + else: # after the first setup (when GIT_PYTHON_GIT_EXECUTABLE # is no longer None) we raise an exception and reset the From 4aa750a96baf96ac766fc874c8c3714ceb4717ee Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Thu, 13 Jul 2017 11:50:25 +0200 Subject: [PATCH 07/10] Removed remaining references to git.setup function Removed few remaining references to git.setup function (as it was renamed to refresh). --- git/cmd.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 0bf0fee4f..8f79db0d7 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -192,12 +192,12 @@ def __setstate__(self, d): # 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 = None - # note that the git executable is actually found during the setup step in + # 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 setup function (see the top level __init__). + """This gets called by the refresh function (see the top level __init__). """ # discern which path to refresh with if path is not None: @@ -223,11 +223,11 @@ def refresh(cls, path=None): Bad git executable. The git executable must be specified in one of the following ways: (1) be included in your $PATH, or (2) be set via $GIT_PYTHON_GIT_EXECUTABLE, or - (3) explicitly set via git.setup (or git.refresh). + (3) explicitly set via git.refresh. """) if old_git is None: - # on the first setup (when GIT_PYTHON_GIT_EXECUTABLE is + # on the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is # None) we only warn the user and simply set the default # executable cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name @@ -267,7 +267,7 @@ def refresh(cls, path=None): raise ImportError(err) else: - # after the first setup (when GIT_PYTHON_GIT_EXECUTABLE + # after the first refresh (when GIT_PYTHON_GIT_EXECUTABLE # is no longer None) we raise an exception and reset the # GIT_PYTHON_GIT_EXECUTABLE to whatever the value was # previously From 2b3e769989c4928cf49e335f9e7e6f9465a6bf99 Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Thu, 13 Jul 2017 13:55:37 +0200 Subject: [PATCH 08/10] Renamed GIT_PYTHON_INITERR to GIT_PYTHON_REFRESH Renamed and cleaned up variable names. --- git/cmd.py | 63 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 8f79db0d7..935718ace 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -226,52 +226,61 @@ def refresh(cls, path=None): (3) explicitly set via git.refresh. """) + # 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 warn the user and simply set the default - # executable - cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name + # 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 - # determine what the user wanted to happen - # we expect GIT_PYTHON_INITERR to either be unset or be one of - # the following values: - # q|quiet|s|silence - # w|warn|warning - # r|raise|e|error - initerr_quiet = ["q", "quiet", "s", "silence"] - initerr_warn = ["w", "warn", "warning"] - initerr_raise = ["r", "raise", "e", "error"] - - initerr = os.environ.get("GIT_PYTHON_INITERR", "warn").lower() - if initerr in initerr_quiet: + mode = os.environ.get("GIT_PYTHON_REFRESH", "raise").lower() + + quiet = ["0", "q", "quiet", "s", "silence", "n", "none"] + warn = ["1", "w", "warn", "warning"] + error = ["2", "e", "error", "r", "raise"] + + if mode in quiet: pass - elif initerr in initerr_warn: + elif mode in warn: print(dedent("""\ WARNING: %s All git commands will error until this is rectified. This initial warning can be silenced in the future by setting the environment variable: - export GIT_PYTHON_NOWARN=true + export GIT_PYTHON_REFRESH=quiet """) % err) - elif initerr in initerr_raise: + elif mode in error: raise ImportError(err) else: err = dedent("""\ - GIT_PYTHON_INITERR environment variable has been set but it has been set with an invalid value. + GIT_PYTHON_REFRESH environment variable has been set but it has been set with an invalid value. Use only the following values: - (1) q|quiet|s|silence: for no warning or exception - (2) w|warn|warning: for a printed warning - (3) r|raise|e|error: for a raised exception - """) + (1) {quiet}: for no warning or exception + (2) {warn}: for a printed warning + (3) {error}: for a raised exception + """).format( + quiet="|".join(quiet), + warn="|".join(warn), + error="|".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 and reset the - # GIT_PYTHON_GIT_EXECUTABLE to whatever the value was - # previously - cls.GIT_PYTHON_GIT_EXECUTABLE = old_git + # is no longer None) we raise an exception raise GitCommandNotFound("git", err) return has_git From 79a36a54b8891839b455c2f39c5d7bc4331a4e03 Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Tue, 25 Jul 2017 10:09:52 -0500 Subject: [PATCH 09/10] Minor additional cleanup Added additional information in the import warning/error that tells the user how to silence the warning/error. Also added a GIT_OK variable that allows for a quick check whether the refresh has succeeded instead of needing to test an actual git command. --- git/__init__.py | 7 +++++ git/cmd.py | 77 ++++++++++++++++++++++++++++++++----------------- git/remote.py | 3 +- 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/git/__init__.py b/git/__init__.py index 7005cd602..4ba553057 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -60,12 +60,19 @@ def _init_externals(): #{ 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 ################# diff --git a/git/cmd.py b/git/cmd.py index 935718ace..329ad4346 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -191,13 +191,15 @@ def __setstate__(self, d): # 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__). + """This gets called by the refresh function (see the top level + __init__). """ # discern which path to refresh with if path is not None: @@ -214,17 +216,21 @@ def refresh(cls, path=None): try: cls().version() has_git = True - except GitCommandNotFound: + except (GitCommandNotFound, PermissionError): + # - a GitCommandNotFound error is spawned by ourselves + # - a PermissionError is spawned if the git executable provided + # cannot be executed for whatever reason 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: - (1) be included in your $PATH, or - (2) be set via $GIT_PYTHON_GIT_EXECUTABLE, or - (3) explicitly set via git.refresh. - """) + 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 @@ -241,36 +247,53 @@ def refresh(cls, path=None): # 1|w|warn|warning # 2|r|raise|e|error - mode = os.environ.get("GIT_PYTHON_REFRESH", "raise").lower() + mode = os.environ.get(cls._refresh_env_var, "raise").lower() - quiet = ["0", "q", "quiet", "s", "silence", "n", "none"] - warn = ["1", "w", "warn", "warning"] - error = ["2", "e", "error", "r", "raise"] + 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: - print(dedent("""\ - WARNING: %s + 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 in the future by setting the environment variable: - export GIT_PYTHON_REFRESH=quiet - """) % err) - elif mode in error: - raise ImportError(err) + 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("""\ - GIT_PYTHON_REFRESH environment variable has been set but it has been set with an invalid value. + %s environment variable has been set but it has been set with an invalid value. Use only the following values: - (1) {quiet}: for no warning or exception - (2) {warn}: for a printed warning - (3) {error}: for a raised exception - """).format( - quiet="|".join(quiet), - warn="|".join(warn), - error="|".join(error)) + - %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 diff --git a/git/remote.py b/git/remote.py index b6ac66cbd..39b722493 100644 --- a/git/remote.py +++ b/git/remote.py @@ -221,7 +221,8 @@ class FetchInfo(object): @classmethod def refresh(cls): - """This gets called by the setup function (see the top level __init__). + """This gets called by the refresh function (see the top level + __init__). """ # clear the old values in _flag_map try: From 90dc03da3ebe1daafd7f39d1255565b5c07757cb Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Wed, 26 Jul 2017 07:52:16 -0500 Subject: [PATCH 10/10] Minor bug fixes Added tilde expansion as part of the refresh function. Added python version check such that we properly capture PermissionError in Python >=3 and OSError in Python <3. --- git/cmd.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 329ad4346..232450cbd 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -203,7 +203,8 @@ def refresh(cls, path=None): """ # discern which path to refresh with if path is not None: - new_git = os.path.abspath(path) + 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) @@ -212,14 +213,23 @@ def refresh(cls, path=None): 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 (GitCommandNotFound, PermissionError): - # - a GitCommandNotFound error is spawned by ourselves - # - a PermissionError is spawned if the git executable provided - # cannot be executed for whatever reason + except exceptions: pass # warn or raise exception if test failed