Skip to content

Commit 2eb6cf0

Browse files
authored
Merge pull request #640 from njalerikson/adding_setup_for_git_executable
Adding setup for git executable
2 parents 2af601d + a56136f commit 2eb6cf0

File tree

5 files changed

+196
-18
lines changed

5 files changed

+196
-18
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Contributors are:
1919
-Timothy B. Hartman <tbhartman _at_ gmail.com>
2020
-Konstantin Popov <konstantin.popov.89 _at_ yandex.ru>
2121
-Peter Jones <pjones _at_ redhat.com>
22+
-Ken Odegard <ken.odegard _at_ gmail.com>
2223
-Alexis Horgix Chotard
2324

2425
Portions derived from other open source works and are clearly marked.

git/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,24 @@ def _init_externals():
6060

6161
__all__ = [name for name, obj in locals().items()
6262
if not (name.startswith('_') or inspect.ismodule(obj))]
63+
64+
65+
#{ Initialize git executable path
66+
GIT_OK = None
67+
68+
def refresh(path=None):
69+
"""Convenience method for setting the git executable path."""
70+
global GIT_OK
71+
GIT_OK = False
72+
73+
if not Git.refresh(path=path):
74+
return
75+
if not FetchInfo.refresh():
76+
return
77+
78+
GIT_OK = True
79+
#} END initialize git executable path
80+
81+
#################
82+
refresh()
83+
#################

git/cmd.py

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import subprocess
1818
import sys
1919
import threading
20+
from textwrap import dedent
2021

2122
from git.compat import (
2223
string_types,
@@ -182,16 +183,141 @@ def __setstate__(self, d):
182183
# Enables debugging of GitPython's git commands
183184
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
184185

185-
# Provide the full path to the git executable. Otherwise it assumes git is in the path
186-
_git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE"
187-
GIT_PYTHON_GIT_EXECUTABLE = os.environ.get(_git_exec_env_var, git_exec_name)
188-
189186
# If True, a shell will be used when executing git commands.
190187
# This should only be desirable on Windows, see https://github.com/gitpython-developers/GitPython/pull/126
191188
# and check `git/test_repo.py:TestRepo.test_untracked_files()` TC for an example where it is required.
192189
# Override this value using `Git.USE_SHELL = True`
193190
USE_SHELL = False
194191

192+
# Provide the full path to the git executable. Otherwise it assumes git is in the path
193+
_git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE"
194+
_refresh_env_var = "GIT_PYTHON_REFRESH"
195+
GIT_PYTHON_GIT_EXECUTABLE = None
196+
# note that the git executable is actually found during the refresh step in
197+
# the top level __init__
198+
199+
@classmethod
200+
def refresh(cls, path=None):
201+
"""This gets called by the refresh function (see the top level
202+
__init__).
203+
"""
204+
# discern which path to refresh with
205+
if path is not None:
206+
new_git = os.path.expanduser(path)
207+
new_git = os.path.abspath(new_git)
208+
else:
209+
new_git = os.environ.get(cls._git_exec_env_var, cls.git_exec_name)
210+
211+
# keep track of the old and new git executable path
212+
old_git = cls.GIT_PYTHON_GIT_EXECUTABLE
213+
cls.GIT_PYTHON_GIT_EXECUTABLE = new_git
214+
215+
# test if the new git executable path is valid
216+
217+
if sys.version_info < (3,):
218+
# - a GitCommandNotFound error is spawned by ourselves
219+
# - a OSError is spawned if the git executable provided
220+
# cannot be executed for whatever reason
221+
exceptions = (GitCommandNotFound, OSError)
222+
else:
223+
# - a GitCommandNotFound error is spawned by ourselves
224+
# - a PermissionError is spawned if the git executable provided
225+
# cannot be executed for whatever reason
226+
exceptions = (GitCommandNotFound, PermissionError)
227+
228+
has_git = False
229+
try:
230+
cls().version()
231+
has_git = True
232+
except exceptions:
233+
pass
234+
235+
# warn or raise exception if test failed
236+
if not has_git:
237+
err = dedent("""\
238+
Bad git executable.
239+
The git executable must be specified in one of the following ways:
240+
- be included in your $PATH
241+
- be set via $%s
242+
- explicitly set via git.refresh()
243+
""") % cls._git_exec_env_var
244+
245+
# revert to whatever the old_git was
246+
cls.GIT_PYTHON_GIT_EXECUTABLE = old_git
247+
248+
if old_git is None:
249+
# on the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is
250+
# None) we only are quiet, warn, or error depending on the
251+
# GIT_PYTHON_REFRESH value
252+
253+
# determine what the user wants to happen during the initial
254+
# refresh we expect GIT_PYTHON_REFRESH to either be unset or
255+
# be one of the following values:
256+
# 0|q|quiet|s|silence
257+
# 1|w|warn|warning
258+
# 2|r|raise|e|error
259+
260+
mode = os.environ.get(cls._refresh_env_var, "raise").lower()
261+
262+
quiet = ["quiet", "q", "silence", "s", "none", "n", "0"]
263+
warn = ["warn", "w", "warning", "1"]
264+
error = ["error", "e", "raise", "r", "2"]
265+
266+
if mode in quiet:
267+
pass
268+
elif mode in warn or mode in error:
269+
err = dedent("""\
270+
%s
271+
All git commands will error until this is rectified.
272+
273+
This initial warning can be silenced or aggravated in the future by setting the
274+
$%s environment variable. Use one of the following values:
275+
- %s: for no warning or exception
276+
- %s: for a printed warning
277+
- %s: for a raised exception
278+
279+
Example:
280+
export %s=%s
281+
""") % (
282+
err,
283+
cls._refresh_env_var,
284+
"|".join(quiet),
285+
"|".join(warn),
286+
"|".join(error),
287+
cls._refresh_env_var,
288+
quiet[0])
289+
290+
if mode in warn:
291+
print("WARNING: %s" % err)
292+
else:
293+
raise ImportError(err)
294+
else:
295+
err = dedent("""\
296+
%s environment variable has been set but it has been set with an invalid value.
297+
298+
Use only the following values:
299+
- %s: for no warning or exception
300+
- %s: for a printed warning
301+
- %s: for a raised exception
302+
""") % (
303+
cls._refresh_env_var,
304+
"|".join(quiet),
305+
"|".join(warn),
306+
"|".join(error))
307+
raise ImportError(err)
308+
309+
# we get here if this was the init refresh and the refresh mode
310+
# was not error, go ahead and set the GIT_PYTHON_GIT_EXECUTABLE
311+
# such that we discern the difference between a first import
312+
# and a second import
313+
cls.GIT_PYTHON_GIT_EXECUTABLE = cls.git_exec_name
314+
else:
315+
# after the first refresh (when GIT_PYTHON_GIT_EXECUTABLE
316+
# is no longer None) we raise an exception
317+
raise GitCommandNotFound("git", err)
318+
319+
return has_git
320+
195321
@classmethod
196322
def is_cygwin(cls):
197323
return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE)
@@ -828,13 +954,13 @@ def _call_process(self, method, *args, **kwargs):
828954
- "command options" to be converted by :meth:`transform_kwargs()`;
829955
- the `'insert_kwargs_after'` key which its value must match one of ``*args``,
830956
and any cmd-options will be appended after the matched arg.
831-
957+
832958
Examples::
833-
959+
834960
git.rev_list('master', max_count=10, header=True)
835-
961+
836962
turns into::
837-
963+
838964
git rev-list max-count 10 --header master
839965
840966
:return: Same as ``execute``"""

git/remote.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,17 +211,38 @@ class FetchInfo(object):
211211

212212
_re_fetch_result = re.compile(r'^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([^\s]+)( \(.*\)?$)?')
213213

214-
_flag_map = {'!': ERROR,
215-
'+': FORCED_UPDATE,
216-
'*': 0,
217-
'=': HEAD_UPTODATE,
218-
' ': FAST_FORWARD}
214+
_flag_map = {
215+
'!': ERROR,
216+
'+': FORCED_UPDATE,
217+
'*': 0,
218+
'=': HEAD_UPTODATE,
219+
' ': FAST_FORWARD,
220+
'-': TAG_UPDATE,
221+
}
219222

220-
v = Git().version_info[:2]
221-
if v >= (2, 10):
222-
_flag_map['t'] = TAG_UPDATE
223-
else:
224-
_flag_map['-'] = TAG_UPDATE
223+
@classmethod
224+
def refresh(cls):
225+
"""This gets called by the refresh function (see the top level
226+
__init__).
227+
"""
228+
# clear the old values in _flag_map
229+
try:
230+
del cls._flag_map["t"]
231+
except KeyError:
232+
pass
233+
234+
try:
235+
del cls._flag_map["-"]
236+
except KeyError:
237+
pass
238+
239+
# set the value given the git version
240+
if Git().version_info[:2] >= (2, 10):
241+
cls._flag_map["t"] = cls.TAG_UPDATE
242+
else:
243+
cls._flag_map["-"] = cls.TAG_UPDATE
244+
245+
return True
225246

226247
def __init__(self, ref, flags, note='', old_commit=None, remote_ref_path=None):
227248
"""

git/test/test_git.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from git import (
1212
Git,
13+
refresh,
1314
GitCommandError,
1415
GitCommandNotFound,
1516
Repo,
@@ -156,6 +157,14 @@ def test_cmd_override(self):
156157
type(self.git).GIT_PYTHON_GIT_EXECUTABLE = prev_cmd
157158
# END undo adjustment
158159

160+
def test_refresh(self):
161+
# test a bad git path refresh
162+
self.assertRaises(GitCommandNotFound, refresh, "yada")
163+
164+
# test a good path refresh
165+
path = os.popen("which git").read().strip()
166+
refresh(path)
167+
159168
def test_options_are_passed_to_git(self):
160169
# This work because any command after git --version is ignored
161170
git_version = self.git(version=True).NoOp()

0 commit comments

Comments
 (0)
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