@@ -306,12 +306,18 @@ class Git:
306306 "cat_file_all" ,
307307 "cat_file_header" ,
308308 "_version_info" ,
309+ "_version_info_token" ,
309310 "_git_options" ,
310311 "_persistent_git_options" ,
311312 "_environment" ,
312313 )
313314
314- _excluded_ = ("cat_file_all" , "cat_file_header" , "_version_info" )
315+ _excluded_ = (
316+ "cat_file_all" ,
317+ "cat_file_header" ,
318+ "_version_info" ,
319+ "_version_info_token" ,
320+ )
315321
316322 re_unsafe_protocol = re .compile (r"(.+)::.+" )
317323
@@ -358,6 +364,8 @@ def __setstate__(self, d: Dict[str, Any]) -> None:
358364 the top level ``__init__``.
359365 """
360366
367+ _refresh_token = object () # Since None would match an initial _version_info_token.
368+
361369 @classmethod
362370 def refresh (cls , path : Union [None , PathLike ] = None ) -> bool :
363371 """This gets called by the refresh function (see the top level __init__)."""
@@ -370,7 +378,9 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool:
370378
371379 # Keep track of the old and new git executable path.
372380 old_git = cls .GIT_PYTHON_GIT_EXECUTABLE
381+ old_refresh_token = cls ._refresh_token
373382 cls .GIT_PYTHON_GIT_EXECUTABLE = new_git
383+ cls ._refresh_token = object ()
374384
375385 # Test if the new git executable path is valid. A GitCommandNotFound error is
376386 # spawned by us. A PermissionError is spawned if the git executable cannot be
@@ -399,6 +409,7 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool:
399409
400410 # Revert to whatever the old_git was.
401411 cls .GIT_PYTHON_GIT_EXECUTABLE = old_git
412+ cls ._refresh_token = old_refresh_token
402413
403414 if old_git is None :
404415 # On the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is None) we only
@@ -782,8 +793,11 @@ def __init__(self, working_dir: Union[None, PathLike] = None):
782793 # Extra environment variables to pass to git commands
783794 self ._environment : Dict [str , str ] = {}
784795
785- # Cached command slots
796+ # Cached version slots
786797 self ._version_info : Union [Tuple [int , int , int , int ], None ] = None
798+ self ._version_info_token : object = None
799+
800+ # Cached command slots
787801 self .cat_file_header : Union [None , TBD ] = None
788802 self .cat_file_all : Union [None , TBD ] = None
789803
@@ -824,7 +838,11 @@ def version_info(self) -> Tuple[int, int, int, int]:
824838
825839 This value is generated on demand and is cached.
826840 """
827- if self ._version_info is None :
841+ refresh_token = self ._refresh_token # Copy it, in case of a concurrent refresh.
842+
843+ # Ask git for its version if we haven't done so since the last refresh.
844+ # (Refreshing is global, but version information caching is per-instance.)
845+ if self ._version_info_token is not refresh_token :
828846 # We only use the first 4 numbers, as everything else could be strings in fact (on Windows).
829847 process_version = self ._call_process ("version" ) # Should be as default *args and **kwargs used.
830848 version_numbers = process_version .split (" " )[2 ]
@@ -833,6 +851,7 @@ def version_info(self) -> Tuple[int, int, int, int]:
833851 Tuple [int , int , int , int ],
834852 tuple (int (n ) for n in version_numbers .split ("." )[:4 ] if n .isdigit ()),
835853 )
854+ self ._version_info_token = refresh_token
836855
837856 return self ._version_info
838857
0 commit comments