@@ -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,97 @@ 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+ """
117+ Determine the default branch of a Git repository using a remote HEAD.
118+ """
119+
120+ try :
121+ output = common .run (Command ("git" , "symbolic-ref" , "refs/remotes/origin/HEAD" ), cwd = repo_path )
122+ if output .stdout :
123+ return output .stdout .strip ().removeprefix ('refs/remotes/origin/' )
124+
125+ logger .fail (f"Failed to determine default branch for repository '{ repo_path } '" )
126+ raise GeneralError (f"Failed to determine default branch for repository '{ repo_path } '" )
127+
128+ except RunError as err :
129+ logger .fail (f"Failed to determine default branch for repository '{ repo_path } '" )
130+ raise GeneralError (f"Failed to determine default branch for repository '{ repo_path } '" ) from err
131+
132+
133+ def _fetch_remote (common : Common , repo_path : Path , logger : Logger ) -> None :
134+ """
135+ Fetch updates from the remote repository.
136+ """
137+ try :
138+ common .run (Command ("git" , "fetch" ), cwd = repo_path )
139+ except RunError as err :
140+ logger .fail (f"Failed to fetch remote for repository '{ repo_path } '" )
141+ raise GeneralError (f"Failed to fetch remote for repository '{ repo_path } '" ) from err
142+
143+
144+ def _update_branch (common : Common , repo_path : Path , branch : str , logger : Logger ) -> None :
145+ """
146+ Update the specified branch of a Git repository to match the remote counterpart.
147+ """
148+ try :
149+ common .run (Command ("git" , "reset" , "--hard" , f"origin/{ branch } " ), cwd = repo_path )
150+ except RunError as err :
151+ logger .fail (f"Failed to update branch '{ branch } ' for repository '{ repo_path } '" )
152+ raise GeneralError (f"Failed to update branch '{ branch } ' for repository '{ repo_path } '" ) from err
153+
154+
155+ def _is_branch_up_to_date (common : Common , repo_path : Path , branch : str ) -> bool :
156+ """
157+ Compare the specified branch of a Git repository with its remote counterpart.
158+
159+ :return: True if the branch is up to date with the remote, False otherwise.
160+ """
161+ try :
162+ common .run (Command ("git" , "diff" , "--quiet" , branch , f"origin/{ branch } " ), cwd = repo_path )
163+ return True
164+ except RunError :
165+ return False
166+
167+
168+ def _is_branch (common : Common , repo_path : Path , ref : str ) -> bool :
169+ """
170+ Check if the given ref is a branch in the Git repository.
171+
172+ :return: True if the ref is a branch, False otherwise.
173+ """
174+ try :
175+ common .run (Command ("git" , "show-ref" , "-q" , "--verify" , f"refs/heads/{ ref } " ), cwd = repo_path )
176+ return True
177+ except RunError :
178+ return False
179+
0 commit comments