@@ -306,12 +306,18 @@ class Git:
306
306
"cat_file_all" ,
307
307
"cat_file_header" ,
308
308
"_version_info" ,
309
+ "_version_info_token" ,
309
310
"_git_options" ,
310
311
"_persistent_git_options" ,
311
312
"_environment" ,
312
313
)
313
314
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
+ )
315
321
316
322
re_unsafe_protocol = re .compile (r"(.+)::.+" )
317
323
@@ -358,6 +364,8 @@ def __setstate__(self, d: Dict[str, Any]) -> None:
358
364
the top level ``__init__``.
359
365
"""
360
366
367
+ _refresh_token = object () # Since None would match an initial _version_info_token.
368
+
361
369
@classmethod
362
370
def refresh (cls , path : Union [None , PathLike ] = None ) -> bool :
363
371
"""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:
370
378
371
379
# Keep track of the old and new git executable path.
372
380
old_git = cls .GIT_PYTHON_GIT_EXECUTABLE
381
+ old_refresh_token = cls ._refresh_token
373
382
cls .GIT_PYTHON_GIT_EXECUTABLE = new_git
383
+ cls ._refresh_token = object ()
374
384
375
385
# Test if the new git executable path is valid. A GitCommandNotFound error is
376
386
# 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:
399
409
400
410
# Revert to whatever the old_git was.
401
411
cls .GIT_PYTHON_GIT_EXECUTABLE = old_git
412
+ cls ._refresh_token = old_refresh_token
402
413
403
414
if old_git is None :
404
415
# 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):
782
793
# Extra environment variables to pass to git commands
783
794
self ._environment : Dict [str , str ] = {}
784
795
785
- # Cached command slots
796
+ # Cached version slots
786
797
self ._version_info : Union [Tuple [int , int , int , int ], None ] = None
798
+ self ._version_info_token : object = None
799
+
800
+ # Cached command slots
787
801
self .cat_file_header : Union [None , TBD ] = None
788
802
self .cat_file_all : Union [None , TBD ] = None
789
803
@@ -824,7 +838,11 @@ def version_info(self) -> Tuple[int, int, int, int]:
824
838
825
839
This value is generated on demand and is cached.
826
840
"""
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 :
828
846
# We only use the first 4 numbers, as everything else could be strings in fact (on Windows).
829
847
process_version = self ._call_process ("version" ) # Should be as default *args and **kwargs used.
830
848
version_numbers = process_version .split (" " )[2 ]
@@ -833,6 +851,7 @@ def version_info(self) -> Tuple[int, int, int, int]:
833
851
Tuple [int , int , int , int ],
834
852
tuple (int (n ) for n in version_numbers .split ("." )[:4 ] if n .isdigit ()),
835
853
)
854
+ self ._version_info_token = refresh_token
836
855
837
856
return self ._version_info
838
857
0 commit comments