Skip to content

Commit 37f695e

Browse files
committed
Fix git repository handling
1 parent e1d221c commit 37f695e

2 files changed

Lines changed: 96 additions & 21 deletions

File tree

src/tmt_web/utils/git_handler.py

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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+

tests/unit/test_git_handler.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,12 @@ def test_get_git_repository_existing_checkout_error(self, mocker, logger):
121121
assert path.exists()
122122

123123
# Mock checkout to fail
124-
cmd = Command("git", "checkout", "invalid-branch")
125-
mocker.patch("tmt.utils.Command.run", side_effect=RunError("Command failed", cmd, 1))
124+
def side_effect(cmd, *args, **kwargs):
125+
if cmd._command == ["git", "checkout", "invalid-branch"]:
126+
raise RunError("Command failed", cmd, 1)
127+
return mocker.DEFAULT
128+
129+
mocker.patch("tmt.utils.Command.run", side_effect=side_effect, autospec=True)
126130

127131
# Try to get same repo with invalid ref
128132
with pytest.raises(AttributeError, match="Failed to checkout ref"):

0 commit comments

Comments
 (0)