@@ -50,7 +50,7 @@ def clear_tmp_dir(logger: Logger) -> None:
5050 raise GeneralError (f"Failed to clear repository clone directory '{ path } '" ) from err
5151
5252
53- def clone_repository (url : str , logger : Logger , ref : str | None = None ) -> Path :
53+ def clone_repository (url : str , logger : Logger ) -> Path :
5454 """
5555 Clone a Git repository to a unique path.
5656
@@ -71,15 +71,6 @@ def clone_repository(url: str, logger: Logger, ref: str | None = None) -> Path:
7171 # Clone with retry logic
7272 git_clone (url = url , destination = destination , logger = logger )
7373
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-
8374 return destination
8475
8576
@@ -92,17 +83,95 @@ def get_git_repository(url: str, logger: Logger, ref: str | None = None) -> Path
9283 :param ref: Optional ref to checkout
9384 :return: Path to the cloned repository
9485 :raises: GitUrlError if URL is invalid
95- :raises: GeneralError if clone fails
86+ :raises: GeneralError if cloning, fetching, or updating a branch fails
9687 :raises: AttributeError if ref doesn't exist
9788 """
9889 destination = get_unique_clone_path (url )
9990 if not destination .exists ():
100- clone_repository (url , logger , ref )
101- elif ref :
102- common = Common (logger = logger )
103- try :
104- common .run (Command ("git" , "checkout" , ref ), cwd = destination )
105- 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
91+ clone_repository (url , logger )
92+
93+ common = Common (logger = logger )
94+
95+ # Fetch remote refs
96+ _fetch_remote (common , destination , logger )
97+
98+ # If no ref is specified, the default branch is used
99+ if not ref :
100+ ref = _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 ) and not _is_branch_up_to_date (common , destination , ref ):
110+ _update_branch (common , destination , ref , logger )
111+
108112 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+ return output .stdout .strip ().removeprefix ("refs/remotes/origin/" )
123+
124+ logger .fail (f"Failed to determine default branch for repository '{ repo_path } '" )
125+ raise GeneralError (f"Failed to determine default branch for repository '{ repo_path } '" )
126+
127+ except RunError as err :
128+ logger .fail (f"Failed to determine default branch for repository '{ repo_path } '" )
129+ raise GeneralError (
130+ f"Failed to determine default branch for repository '{ repo_path } '"
131+ ) from err
132+
133+
134+ def _fetch_remote (common : Common , repo_path : Path , logger : Logger ) -> None :
135+ """Fetch updates from the remote repository."""
136+ try :
137+ common .run (Command ("git" , "fetch" ), cwd = repo_path )
138+ except RunError as err :
139+ logger .fail (f"Failed to fetch remote for repository '{ repo_path } '" )
140+ raise GeneralError (f"Failed to fetch remote for repository '{ repo_path } '" ) from err
141+
142+
143+ def _update_branch (common : Common , repo_path : Path , branch : str , logger : Logger ) -> None :
144+ """Update the specified branch of a Git repository to match the remote counterpart."""
145+ try :
146+ common .run (Command ("git" , "reset" , "--hard" , f"origin/{ branch } " ), cwd = repo_path )
147+ except RunError as err :
148+ logger .fail (f"Failed to update branch '{ branch } ' for repository '{ repo_path } '" )
149+ raise GeneralError (
150+ f"Failed to update branch '{ branch } ' for repository '{ repo_path } '"
151+ ) from err
152+
153+
154+ def _is_branch_up_to_date (common : Common , repo_path : Path , branch : str ) -> bool :
155+ """
156+ Compare the specified branch of a Git repository with its remote counterpart.
157+
158+ :return: True if the branch is up to date with the remote, False otherwise.
159+ """
160+ try :
161+ common .run (Command ("git" , "diff" , "--quiet" , branch , f"origin/{ branch } " ), cwd = repo_path )
162+ return True
163+ except RunError :
164+ return False
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