From a962464c1504d716d4acee7770d8831cd3a84b48 Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Sun, 9 Jul 2017 18:35:47 +0200 Subject: 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(-) (limited to 'git/cmd.py') diff --git a/git/cmd.py b/git/cmd.py index 3637ac9e..82bc9e90 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -17,6 +17,7 @@ from subprocess import ( import subprocess import sys import threading +from textwrap import dedent from git.compat import ( string_types, @@ -182,16 +183,69 @@ class Git(LazyMixin): # 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 @@ class Git(LazyMixin): - "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 @@ class Git(LazyMixin): 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() -- cgit v1.2.1 From feed81ea1a332dc415ea9010c8b5204473a51bdf Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Sun, 9 Jul 2017 19:53:38 +0200 Subject: 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/cmd.py | 54 ++++++++++++++++++++---------------------------------- 1 file changed, 20 insertions(+), 34 deletions(-) (limited to 'git/cmd.py') diff --git a/git/cmd.py b/git/cmd.py index 82bc9e90..0e9315a2 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -190,28 +190,26 @@ class Git(LazyMixin): 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 @@ class Git(LazyMixin): 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 @@ class Git(LazyMixin): # 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 @@ class Git(LazyMixin): 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() -- cgit v1.2.1 From b56d6778ee678081e22c1897ede1314ff074122a Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Sun, 9 Jul 2017 22:44:19 +0200 Subject: 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(-) (limited to 'git/cmd.py') diff --git a/git/cmd.py b/git/cmd.py index 0e9315a2..ae7721df 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -231,10 +231,19 @@ class Git(LazyMixin): # 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 -- cgit v1.2.1 From 3430bde60ae65b54c08ffa73de1f16643c7c3bfd Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Thu, 13 Jul 2017 11:44:10 +0200 Subject: 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(-) (limited to 'git/cmd.py') diff --git a/git/cmd.py b/git/cmd.py index ae7721df..0bf0fee4 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -232,11 +232,20 @@ class Git(LazyMixin): # 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 @@ class Git(LazyMixin): 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 -- cgit v1.2.1 From 4aa750a96baf96ac766fc874c8c3714ceb4717ee Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Thu, 13 Jul 2017 11:50:25 +0200 Subject: 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(-) (limited to 'git/cmd.py') diff --git a/git/cmd.py b/git/cmd.py index 0bf0fee4..8f79db0d 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -192,12 +192,12 @@ class Git(LazyMixin): # 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 @@ class Git(LazyMixin): 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 @@ class Git(LazyMixin): 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 -- cgit v1.2.1 From 2b3e769989c4928cf49e335f9e7e6f9465a6bf99 Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Thu, 13 Jul 2017 13:55:37 +0200 Subject: 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(-) (limited to 'git/cmd.py') diff --git a/git/cmd.py b/git/cmd.py index 8f79db0d..935718ac 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -226,52 +226,61 @@ class Git(LazyMixin): (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 -- cgit v1.2.1 From 79a36a54b8891839b455c2f39c5d7bc4331a4e03 Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Tue, 25 Jul 2017 10:09:52 -0500 Subject: 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/cmd.py | 77 ++++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 27 deletions(-) (limited to 'git/cmd.py') diff --git a/git/cmd.py b/git/cmd.py index 935718ac..329ad434 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -191,13 +191,15 @@ class Git(LazyMixin): # 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 @@ class Git(LazyMixin): 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 @@ class Git(LazyMixin): # 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 -- cgit v1.2.1 From 90dc03da3ebe1daafd7f39d1255565b5c07757cb Mon Sep 17 00:00:00 2001 From: "Odegard, Ken" Date: Wed, 26 Jul 2017 07:52:16 -0500 Subject: 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(-) (limited to 'git/cmd.py') diff --git a/git/cmd.py b/git/cmd.py index 329ad434..232450cb 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -203,7 +203,8 @@ class Git(LazyMixin): """ # 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 @@ class Git(LazyMixin): 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 -- cgit v1.2.1