Skip to content

Commit 5481465

Browse files
committed
Fix git repository handling
1 parent e1d221c commit 5481465

File tree

2 files changed

+94
-21
lines changed

2 files changed

+94
-21
lines changed

src/tmt_web/utils/git_handler.py

Lines changed: 88 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,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

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)