@@ -20,6 +20,7 @@ Contributors are: | |||
| 20 | 20 | -Konstantin Popov <konstantin.popov.89 _at_ yandex.ru> | |
| 21 | 21 | -Peter Jones <pjones _at_ redhat.com> | |
| 22 | 22 | -Anson Mansfield <anson.mansfield _at_ gmail.com> | |
| 23 | + -Ken Odegard <ken.odegard _at_ gmail.com> | ||
| 23 | 24 | -Alexis Horgix Chotard | |
| 24 | 25 | ||
| 25 | 26 | Portions derived from other open source works and are clearly marked. | |
@@ -60,3 +60,24 @@ def _init_externals(): | |||
| 60 | 60 | ||
| 61 | 61 | __all__ = [name for name, obj in locals().items() | |
| 62 | 62 | 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 | + ################# | ||
@@ -17,6 +17,7 @@ | |||
| 17 | 17 | import subprocess | |
| 18 | 18 | import sys | |
| 19 | 19 | import threading | |
| 20 | + from textwrap import dedent | ||
| 20 | 21 | ||
| 21 | 22 | from git.compat import ( | |
| 22 | 23 | string_types, | |
@@ -182,16 +183,141 @@ def __setstate__(self, d): | |||
| 182 | 183 | # Enables debugging of GitPython's git commands | |
| 183 | 184 | GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False) | |
| 184 | 185 | ||
| 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 | - | ||
| 189 | 186 | # If True, a shell will be used when executing git commands. | |
| 190 | 187 | # This should only be desirable on Windows, see https://github.com/gitpython-developers/GitPython/pull/126 | |
| 191 | 188 | # and check `git/test_repo.py:TestRepo.test_untracked_files()` TC for an example where it is required. | |
| 192 | 189 | # Override this value using `Git.USE_SHELL = True` | |
| 193 | 190 | USE_SHELL = False | |
| 194 | 191 | ||
| 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 | + | ||
| 195 | 321 | @classmethod | |
| 196 | 322 | def is_cygwin(cls): | |
| 197 | 323 | return is_cygwin_git(cls.GIT_PYTHON_GIT_EXECUTABLE) | |
@@ -835,13 +961,13 @@ def _call_process(self, method, *args, **kwargs): | |||
| 835 | 961 | - "command options" to be converted by :meth:`transform_kwargs()`; | |
| 836 | 962 | - the `'insert_kwargs_after'` key which its value must match one of ``*args``, | |
| 837 | 963 | and any cmd-options will be appended after the matched arg. | |
| 838 | - | ||
| 964 | + | ||
| 839 | 965 | Examples:: | |
| 840 | - | ||
| 966 | + | ||
| 841 | 967 | git.rev_list('master', max_count=10, header=True) | |
| 842 | - | ||
| 968 | + | ||
| 843 | 969 | turns into:: | |
| 844 | - | ||
| 970 | + | ||
| 845 | 971 | git rev-list max-count 10 --header master | |
| 846 | 972 | ||
| 847 | 973 | :return: Same as ``execute``""" | |
@@ -211,17 +211,38 @@ class FetchInfo(object): | |||
| 211 | 211 | ||
| 212 | 212 | _re_fetch_result = re.compile(r'^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([^\s]+)( \(.*\)?$)?') | |
| 213 | 213 | ||
| 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 | + } | ||
| 219 | 222 | ||
| 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 | ||
| 225 | 246 | ||
| 226 | 247 | def __init__(self, ref, flags, note='', old_commit=None, remote_ref_path=None): | |
| 227 | 248 | """ | |
@@ -10,6 +10,7 @@ | |||
| 10 | 10 | ||
| 11 | 11 | from git import ( | |
| 12 | 12 | Git, | |
| 13 | + refresh, | ||
| 13 | 14 | GitCommandError, | |
| 14 | 15 | GitCommandNotFound, | |
| 15 | 16 | Repo, | |
@@ -171,6 +172,14 @@ def test_cmd_override(self): | |||
| 171 | 172 | type(self.git).GIT_PYTHON_GIT_EXECUTABLE = prev_cmd | |
| 172 | 173 | # END undo adjustment | |
| 173 | 174 | ||
| 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 | + | ||
| 174 | 183 | def test_options_are_passed_to_git(self): | |
| 175 | 184 | # This work because any command after git --version is ignored | |
| 176 | 185 | git_version = self.git(version=True).NoOp() | |
0 commit comments