66It uses tmt's Git utilities for robust clone operations with retry logic.
77"""
88
9+ import re
910from shutil import rmtree
1011
1112from tmt import Logger
@@ -50,7 +51,7 @@ def clear_tmp_dir(logger: Logger) -> None:
5051 raise GeneralError (f"Failed to clear repository clone directory '{ path } '" ) from err
5152
5253
53- def clone_repository (url : str , logger : Logger , ref : str | None = None ) -> Path :
54+ def clone_repository (url : str , logger : Logger ) -> Path :
5455 """
5556 Clone a Git repository to a unique path.
5657
@@ -71,15 +72,6 @@ def clone_repository(url: str, logger: Logger, ref: str | None = None) -> Path:
7172 # Clone with retry logic
7273 git_clone (url = url , destination = destination , logger = logger )
7374
74- # If ref provided, checkout after clone
75- if ref :
76- common = Common (logger = logger )
77- try :
78- common .run (Command ("git" , "checkout" , ref ), cwd = destination )
79- except RunError as err :
80- logger .fail (f"Failed to checkout ref '{ ref } '" )
81- raise AttributeError (f"Failed to checkout ref '{ ref } ': { err } " ) from err
82-
8375 return destination
8476
8577
@@ -92,17 +84,94 @@ def get_git_repository(url: str, logger: Logger, ref: str | None = None) -> Path
9284 :param ref: Optional ref to checkout
9385 :return: Path to the cloned repository
9486 :raises: GitUrlError if URL is invalid
95- :raises: GeneralError if clone fails
87+ :raises: GeneralError if cloning, fetching, or updating a branch fails
9688 :raises: AttributeError if ref doesn't exist
9789 """
9890 destination = get_unique_clone_path (url )
9991 if not destination .exists ():
100- clone_repository (url , logger , ref )
101- elif ref :
102- common = Common (logger = logger )
92+ clone_repository (url , logger )
93+
94+ common = Common (logger = logger )
95+
96+ # Fetch remote refs
97+ _fetch_remote (common , destination , logger )
98+
99+ # If no ref is specified, the default branch is used
100+ ref = ref or _get_default_branch (common , destination , logger )
101+
102+ try :
103+ common .run (Command ("git" , "checkout" , ref ), cwd = destination )
104+ except RunError as err :
105+ logger .fail (f"Failed to checkout ref '{ ref } '" )
106+ raise AttributeError (f"Failed to checkout ref '{ ref } '" ) from err
107+
108+ # If the ref is a branch, ensure it's up to date
109+ if _is_branch (common , destination , ref ):
110+ _update_branch (common , destination , ref , logger )
111+
112+ return destination
113+
114+
115+ def _get_default_branch (common : Common , repo_path : Path , logger : Logger ) -> str :
116+ """Determine the default branch of a Git repository using a remote HEAD."""
117+ try :
118+ output = common .run (
119+ Command ("git" , "symbolic-ref" , "refs/remotes/origin/HEAD" ), cwd = repo_path
120+ )
121+ if output .stdout :
122+ match = re .search (r"refs/remotes/origin/(.*)" , output .stdout .strip ())
123+ if match :
124+ return match .group (1 )
125+
126+ logger .fail (f"Failed to determine default branch for repository '{ repo_path } '" )
127+ raise GeneralError (f"Failed to determine default branch for repository '{ repo_path } '" )
128+
129+ except RunError as err :
130+ logger .fail (f"Failed to determine default branch for repository '{ repo_path } '" )
131+ raise GeneralError (
132+ f"Failed to determine default branch for repository '{ repo_path } '"
133+ ) from err
134+
135+
136+ def _fetch_remote (common : Common , repo_path : Path , logger : Logger ) -> None :
137+ """Fetch updates from the remote repository."""
138+ try :
139+ common .run (Command ("git" , "fetch" ), cwd = repo_path )
140+ except RunError as err :
141+ logger .fail (f"Failed to fetch remote for repository '{ repo_path } '" )
142+ raise GeneralError (f"Failed to fetch remote for repository '{ repo_path } '" ) from err
143+
144+
145+ def _update_branch (common : Common , repo_path : Path , branch : str , logger : Logger ) -> None :
146+ """Ensure the specified branch is up to date with its remote counterpart."""
147+ try :
148+ common .run (Command ("git" , "show-branch" , f"origin/{ branch } " ), cwd = repo_path )
149+ except RunError as err :
150+ logger .fail (f"Branch '{ branch } ' does not exist in repository '{ repo_path } '" )
151+ raise GeneralError (f"Branch { branch } ' does not exist in repository '{ repo_path } '" ) from err
152+ try :
153+ # Check if the branch is already up to date
154+ common .run (Command ("git" , "diff" , "--quiet" , branch , f"origin/{ branch } " ), cwd = repo_path )
155+ return
156+ except RunError :
157+ # Branch is not up to date, proceed with update
103158 try :
104- common .run (Command ("git" , "checkout " , ref ), cwd = destination )
159+ common .run (Command ("git" , "reset " , "--hard" , f"origin/ { branch } " ), cwd = repo_path )
105160 except RunError as err :
106- logger .fail (f"Failed to checkout ref '{ ref } '" )
107- raise AttributeError (f"Failed to checkout ref '{ ref } ': { err } " ) from err
108- return destination
161+ logger .fail (f"Failed to update branch '{ branch } ' for repository '{ repo_path } '" )
162+ raise GeneralError (
163+ f"Failed to update branch '{ branch } ' for repository '{ repo_path } '"
164+ ) from err
165+
166+
167+ def _is_branch (common : Common , repo_path : Path , ref : str ) -> bool :
168+ """
169+ Check if the given ref is a branch in the Git repository.
170+
171+ :return: True if the ref is a branch, False otherwise.
172+ """
173+ try :
174+ common .run (Command ("git" , "show-ref" , "-q" , "--verify" , f"refs/heads/{ ref } " ), cwd = repo_path )
175+ return True
176+ except RunError :
177+ return False
0 commit comments