Skip to content

Commit 4a36d9a

Browse files
authored
Merge branch 'master' into master
2 parents 4fa7ede + 93e0938 commit 4a36d9a

File tree

5 files changed

+196
-18
lines changed

5 files changed

+196
-18
lines changed

Diff for: AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Contributors are:
2020
-Konstantin Popov <konstantin.popov.89 _at_ yandex.ru>
2121
-Peter Jones <pjones _at_ redhat.com>
2222
-Anson Mansfield <anson.mansfield _at_ gmail.com>
23+
-Ken Odegard <ken.odegard _at_ gmail.com>
2324
-Alexis Horgix Chotard
2425

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

Diff for: git/__init__.py

+21
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+
#################

Diff for: git/cmd.py

+134-8
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)
@@ -835,13 +961,13 @@ def _call_process(self, method, *args, **kwargs):
835961
- "command options" to be converted by :meth:`transform_kwargs()`;
836962
- the `'insert_kwargs_after'` key which its value must match one of ``*args``,
837963
and any cmd-options will be appended after the matched arg.
838-
964+
839965
Examples::
840-
966+
841967
git.rev_list('master', max_count=10, header=True)
842-
968+
843969
turns into::
844-
970+
845971
git rev-list max-count 10 --header master
846972
847973
:return: Same as ``execute``"""

Diff for: git/remote.py

+31-10
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
"""

Diff for: git/test/test_git.py

+9
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,
@@ -171,6 +172,14 @@ def test_cmd_override(self):
171172
type(self.git).GIT_PYTHON_GIT_EXECUTABLE = prev_cmd
172173
# END undo adjustment
173174

175+
def test_refresh(self):
176+
# test a bad git path refresh
177+
self.assertRaises(GitCommandNotFound, refresh, "yada")
178+
179+
# test a good path refresh
180+
path = os.popen("which git").read().strip()
181+
refresh(path)
182+
174183
def test_options_are_passed_to_git(self):
175184
# This work because any command after git --version is ignored
176185
git_version = self.git(version=True).NoOp()

0 commit comments

Comments
 (0)