← 返回首页
Merge branch 'master' into master · gitpython-developers/GitPython@2e482a2 · GitHub
Skip to content

Navigation Menu

Toggle navigation
Sign in
Appearance settings
Search or jump to...

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Resetting focus

Commit 2e482a2

Browse files
authored
Merge branch 'master' into master
2 parents 58998fb + 2eb6cf0 commit 2e482a2

5 files changed

Lines changed: 196 additions & 18 deletions

File tree

‎AUTHORS‎

Lines changed: 1 addition & 0 deletions
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.

‎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)
@@ -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``"""

‎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,
@@ -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)

Footer

© 2026 GitHub, Inc.