Skip to content

Commit 19d0bec

Browse files
committed
Add filelock for git operations
1 parent 29250e0 commit 19d0bec

2 files changed

Lines changed: 47 additions & 21 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies = [
2828
"httpx~=0.27",
2929
"uvicorn~=0.30",
3030
"valkey",
31+
"filelock",
3132
]
3233

3334
[project.urls]

src/tmt_web/utils/git_handler.py

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import re
1111
from shutil import rmtree
1212

13+
from filelock import FileLock
1314
from tmt import Logger
1415
from tmt._compat.pathlib import Path
1516
from tmt.utils import Command, Common, GeneralError, RunError
@@ -21,23 +22,39 @@
2122
ROOT_DIR = Path(__file__).resolve().parents[2]
2223

2324

24-
def create_hash(text: str):
25+
def _create_hash(text: str):
2526
"""Create hash of the given text that is consistent across runs."""
2627
hashed_text = hashlib.new("sha1", usedforsecurity=False)
2728
hashed_text.update(text.encode())
2829
return hashed_text.hexdigest()
2930

3031

32+
def get_repo_hash(url: str) -> str:
33+
url = url.rstrip("/").removesuffix(".git")
34+
return _create_hash(url)
35+
36+
37+
def get_repo_lock_path(url: str) -> Path:
38+
"""
39+
Get the path for a repository lock file.
40+
41+
:param url: Repository URL
42+
:return: Path to the lock file for this repository
43+
"""
44+
lock_path = ROOT_DIR / settings.CLONE_DIR_PATH / f"{get_repo_hash(url)}.lock"
45+
if not lock_path.parent.exists():
46+
lock_path.parent.mkdir(parents=True, exist_ok=True)
47+
return lock_path
48+
49+
3150
def get_unique_clone_path(url: str) -> Path:
3251
"""
3352
Generate a unique path for cloning a repository.
3453
3554
:param url: Repository URL
3655
:return: Unique path for cloning
3756
"""
38-
url = url.rstrip("/").removesuffix(".git")
39-
clone_dir_name = create_hash(url)
40-
return ROOT_DIR / settings.CLONE_DIR_PATH / clone_dir_name
57+
return ROOT_DIR / settings.CLONE_DIR_PATH / get_repo_hash(url)
4158

4259

4360
def clear_tmp_dir(logger: Logger) -> None:
@@ -77,6 +94,8 @@ def clone_repository(url: str, logger: Logger) -> Path:
7794
# Get unique path
7895
destination = get_unique_clone_path(url)
7996

97+
rmtree(destination, ignore_errors=True)
98+
8099
# Clone with retry logic
81100
git_clone(url=url, destination=destination, logger=logger)
82101

@@ -95,29 +114,35 @@ def get_git_repository(url: str, logger: Logger, ref: str | None = None) -> Path
95114
:raises: GeneralError if cloning, fetching, or updating a branch fails
96115
:raises: AttributeError if ref doesn't exist
97116
"""
98-
destination = get_unique_clone_path(url)
99-
if not destination.exists():
100-
clone_repository(url, logger)
117+
lock_path = get_repo_lock_path(url)
118+
with FileLock(lock_path):
119+
destination = get_unique_clone_path(url)
120+
if not destination.exists():
121+
clone_repository(url, logger)
101122

102-
common = Common(logger=logger)
123+
common = Common(logger=logger)
103124

104-
# Fetch remote refs
105-
_fetch_remote(common, destination, logger)
125+
try:
126+
# Fetch remote refs
127+
_fetch_remote(common, destination, logger)
128+
except GeneralError:
129+
logger.warning("Unable to fetch remote repository. Trying to clone again.")
130+
clone_repository(url, logger)
106131

107-
# If no ref is specified, the default branch is used
108-
ref = ref or _get_default_branch(common, destination, logger)
132+
# If no ref is specified, the default branch is used
133+
ref = ref or _get_default_branch(common, destination, logger)
109134

110-
try:
111-
common.run(Command("git", "checkout", ref), cwd=destination)
112-
except RunError as err:
113-
logger.fail(f"Failed to checkout ref '{ref}': {err.stderr}")
114-
raise AttributeError(f"Failed to checkout ref '{ref}'") from err
135+
try:
136+
common.run(Command("git", "checkout", ref), cwd=destination)
137+
except RunError as err:
138+
logger.fail(f"Failed to checkout ref '{ref}': {err.stderr}")
139+
raise AttributeError(f"Failed to checkout ref '{ref}'") from err
115140

116-
_ensure_no_changes(common, destination, logger)
141+
_ensure_no_changes(common, destination, logger)
117142

118-
# If the ref is a branch, ensure it's up to date
119-
if _is_branch(common, destination, ref):
120-
_update_branch(common, destination, ref, logger)
143+
# If the ref is a branch, ensure it's up to date
144+
if _is_branch(common, destination, ref):
145+
_update_branch(common, destination, ref, logger)
121146

122147
return destination
123148

0 commit comments

Comments
 (0)