1010import re
1111from shutil import rmtree
1212
13+ from filelock import FileLock
1314from tmt import Logger
1415from tmt ._compat .pathlib import Path
1516from tmt .utils import Command , Common , GeneralError , RunError
2122ROOT_DIR = Path (__file__ ).resolve ().parents [2 ]
2223
2324
24- def create_hash (text : str ):
25+ def _create_hash (text : str ):
2526 """Create hash of the given text that is consistent across runs."""
2627 hashed_text = hashlib .new ("sha1" , usedforsecurity = False )
2728 hashed_text .update (text .encode ())
2829 return hashed_text .hexdigest ()
2930
3031
32+ def get_repo_hash (url : str ) -> str :
33+ url = url .rstrip ("/" ).removesuffix (".git" )
34+ return _create_hash (url )
35+
36+
37+ def get_repo_lock_path (url : str ) -> Path :
38+ """
39+ Get the path for a repository lock file.
40+
41+ :param url: Repository URL
42+ :return: Path to the lock file for this repository
43+ """
44+ lock_path = ROOT_DIR / settings .CLONE_DIR_PATH / f"{ get_repo_hash (url )} .lock"
45+ if not lock_path .parent .exists ():
46+ lock_path .parent .mkdir (parents = True , exist_ok = True )
47+ return lock_path
48+
49+
3150def get_unique_clone_path (url : str ) -> Path :
3251 """
3352 Generate a unique path for cloning a repository.
3453
3554 :param url: Repository URL
3655 :return: Unique path for cloning
3756 """
38- url = url .rstrip ("/" ).removesuffix (".git" )
39- clone_dir_name = create_hash (url )
40- return ROOT_DIR / settings .CLONE_DIR_PATH / clone_dir_name
57+ return ROOT_DIR / settings .CLONE_DIR_PATH / get_repo_hash (url )
4158
4259
4360def clear_tmp_dir (logger : Logger ) -> None :
@@ -77,6 +94,8 @@ def clone_repository(url: str, logger: Logger) -> Path:
7794 # Get unique path
7895 destination = get_unique_clone_path (url )
7996
97+ rmtree (destination , ignore_errors = True )
98+
8099 # Clone with retry logic
81100 git_clone (url = url , destination = destination , logger = logger )
82101
@@ -95,29 +114,35 @@ def get_git_repository(url: str, logger: Logger, ref: str | None = None) -> Path
95114 :raises: GeneralError if cloning, fetching, or updating a branch fails
96115 :raises: AttributeError if ref doesn't exist
97116 """
98- destination = get_unique_clone_path (url )
99- if not destination .exists ():
100- clone_repository (url , logger )
117+ lock_path = get_repo_lock_path (url )
118+ with FileLock (lock_path ):
119+ destination = get_unique_clone_path (url )
120+ if not destination .exists ():
121+ clone_repository (url , logger )
101122
102- common = Common (logger = logger )
123+ common = Common (logger = logger )
103124
104- # Fetch remote refs
105- _fetch_remote (common , destination , logger )
125+ try :
126+ # Fetch remote refs
127+ _fetch_remote (common , destination , logger )
128+ except GeneralError :
129+ logger .warning ("Unable to fetch remote repository. Trying to clone again." )
130+ clone_repository (url , logger )
106131
107- # If no ref is specified, the default branch is used
108- ref = ref or _get_default_branch (common , destination , logger )
132+ # If no ref is specified, the default branch is used
133+ ref = ref or _get_default_branch (common , destination , logger )
109134
110- try :
111- common .run (Command ("git" , "checkout" , ref ), cwd = destination )
112- except RunError as err :
113- logger .fail (f"Failed to checkout ref '{ ref } ': { err .stderr } " )
114- raise AttributeError (f"Failed to checkout ref '{ ref } '" ) from err
135+ try :
136+ common .run (Command ("git" , "checkout" , ref ), cwd = destination )
137+ except RunError as err :
138+ logger .fail (f"Failed to checkout ref '{ ref } ': { err .stderr } " )
139+ raise AttributeError (f"Failed to checkout ref '{ ref } '" ) from err
115140
116- _ensure_no_changes (common , destination , logger )
141+ _ensure_no_changes (common , destination , logger )
117142
118- # If the ref is a branch, ensure it's up to date
119- if _is_branch (common , destination , ref ):
120- _update_branch (common , destination , ref , logger )
143+ # If the ref is a branch, ensure it's up to date
144+ if _is_branch (common , destination , ref ):
145+ _update_branch (common , destination , ref , logger )
121146
122147 return destination
123148
0 commit comments