66It uses tmt's Git utilities for robust clone operations with retry logic.
77"""
88
9+ import hashlib
910import re
1011from shutil import rmtree
1112
2021ROOT_DIR = Path (__file__ ).resolve ().parents [2 ]
2122
2223
24+ def create_hash (text : str ):
25+ """Create hash of the given text that is consistent across runs."""
26+ hashed_text = hashlib .new ("sha1" , usedforsecurity = False )
27+ hashed_text .update (text .encode ())
28+ return hashed_text .hexdigest ()
29+
30+
2331def get_unique_clone_path (url : str ) -> Path :
2432 """
2533 Generate a unique path for cloning a repository.
2634
2735 :param url: Repository URL
2836 :return: Unique path for cloning
2937 """
30- url = url .rstrip ("/" )
31- clone_dir_name = str ( abs ( hash ( url )) )
38+ url = url .rstrip ("/" ). removesuffix ( ".git" )
39+ clone_dir_name = create_hash ( url )
3240 return ROOT_DIR / settings .CLONE_DIR_PATH / clone_dir_name
3341
3442
@@ -102,9 +110,11 @@ def get_git_repository(url: str, logger: Logger, ref: str | None = None) -> Path
102110 try :
103111 common .run (Command ("git" , "checkout" , ref ), cwd = destination )
104112 except RunError as err :
105- logger .fail (f"Failed to checkout ref '{ ref } '" )
113+ logger .fail (f"Failed to checkout ref '{ ref } ': { err . stderr } " )
106114 raise AttributeError (f"Failed to checkout ref '{ ref } '" ) from err
107115
116+ _ensure_no_changes (common , destination , logger )
117+
108118 # If the ref is a branch, ensure it's up to date
109119 if _is_branch (common , destination , ref ):
110120 _update_branch (common , destination , ref , logger )
@@ -127,7 +137,9 @@ def _get_default_branch(common: Common, repo_path: Path, logger: Logger) -> str:
127137 raise GeneralError (f"Failed to determine default branch for repository '{ repo_path } '" )
128138
129139 except RunError as err :
130- logger .fail (f"Failed to determine default branch for repository '{ repo_path } '" )
140+ logger .fail (
141+ f"Failed to determine default branch for repository '{ repo_path } ': { err .stderr } "
142+ )
131143 raise GeneralError (
132144 f"Failed to determine default branch for repository '{ repo_path } '"
133145 ) from err
@@ -136,9 +148,21 @@ def _get_default_branch(common: Common, repo_path: Path, logger: Logger) -> str:
136148def _fetch_remote (common : Common , repo_path : Path , logger : Logger ) -> None :
137149 """Fetch updates from the remote repository."""
138150 try :
139- common .run (Command ("git" , "fetch" ), cwd = repo_path )
151+ # Fetch all branches and tags, prune deleted ones
152+ common .run (
153+ Command (
154+ "git" ,
155+ "fetch" ,
156+ "origin" ,
157+ "--prune" ,
158+ "--prune-tags" ,
159+ "+refs/heads/*:refs/remotes/origin/*" ,
160+ "+refs/tags/*:refs/tags/*" ,
161+ ),
162+ cwd = repo_path ,
163+ )
140164 except RunError as err :
141- logger .fail (f"Failed to fetch remote for repository '{ repo_path } '" )
165+ logger .fail (f"Failed to fetch remote for repository '{ repo_path } ': { err . stderr } " )
142166 raise GeneralError (f"Failed to fetch remote for repository '{ repo_path } '" ) from err
143167
144168
@@ -147,7 +171,7 @@ def _update_branch(common: Common, repo_path: Path, branch: str, logger: Logger)
147171 try :
148172 common .run (Command ("git" , "show-branch" , f"origin/{ branch } " ), cwd = repo_path )
149173 except RunError as err :
150- logger .fail (f"Branch '{ branch } ' does not exist in repository '{ repo_path } '" )
174+ logger .fail (f"Branch '{ branch } ' does not exist in repository '{ repo_path } ': { err . stderr } " )
151175 raise GeneralError (f"Branch { branch } ' does not exist in repository '{ repo_path } '" ) from err
152176 try :
153177 # Check if the branch is already up to date
@@ -158,12 +182,37 @@ def _update_branch(common: Common, repo_path: Path, branch: str, logger: Logger)
158182 try :
159183 common .run (Command ("git" , "reset" , "--hard" , f"origin/{ branch } " ), cwd = repo_path )
160184 except RunError as err :
161- logger .fail (f"Failed to update branch '{ branch } ' for repository '{ repo_path } '" )
185+ logger .fail (
186+ f"Failed to update branch '{ branch } ' for repository '{ repo_path } ': { err .stderr } "
187+ )
162188 raise GeneralError (
163189 f"Failed to update branch '{ branch } ' for repository '{ repo_path } '"
164190 ) from err
165191
166192
193+ def _ensure_no_changes (common : Common , repo_path : Path , logger : Logger ) -> None :
194+ """Ensure there are no changes in the repository."""
195+ try :
196+ output = common .run (Command ("git" , "status" , "--porcelain" ), cwd = repo_path )
197+ if not output .stdout or not output .stdout .strip ():
198+ return
199+ logger .warning (f"Repository '{ repo_path } ' has changes:\n { output .stdout .strip ()} " )
200+ except RunError as err :
201+ logger .fail (f"Failed to check repository status for '{ repo_path } ': { err .stderr } " )
202+ raise GeneralError (f"Failed to check repository status for '{ repo_path } '" ) from err
203+
204+ try :
205+ common .run (Command ("git" , "restore" , "." ), cwd = repo_path )
206+ common .run (Command ("git" , "clean" , "-fdx" ), cwd = repo_path )
207+ except RunError as err :
208+ logger .fail (
209+ f"Repository '{ repo_path } ' has changes that could not be reverted: { err .stderr } "
210+ )
211+ raise GeneralError (
212+ f"Repository '{ repo_path } ' has changes that could not be reverted"
213+ ) from err
214+
215+
167216def _is_branch (common : Common , repo_path : Path , ref : str ) -> bool :
168217 """
169218 Check if the given ref is a branch in the Git repository.
0 commit comments