From ed8852a8c59220ecae550476045e82c24ad2ec09 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 5 Mar 2026 14:09:12 +0000 Subject: [PATCH 1/2] fix: sandbox tag are unique (and monotone). --- src/hive_cli/platform/base.py | 7 ++--- src/hive_cli/utils/git.py | 20 +-------------- src/hive_cli/utils/time.py | 4 +++ tests/utils/test_git.py | 48 ++++++----------------------------- tests/utils/test_time.py | 17 ------------- 5 files changed, 17 insertions(+), 79 deletions(-) diff --git a/src/hive_cli/platform/base.py b/src/hive_cli/platform/base.py index e070581..bd97b72 100644 --- a/src/hive_cli/platform/base.py +++ b/src/hive_cli/platform/base.py @@ -9,6 +9,7 @@ from hive_cli.config import HiveConfig from hive_cli.runtime.runtime import Runtime from hive_cli.utils import git, image +from hive_cli.utils import time as utime from hive_cli.utils.logger import logger @@ -96,7 +97,7 @@ def prepare_images(self, config: HiveConfig, push: bool = False) -> str: ) dest = Path(temp_repo_dir) / "repo" - hash = git.get_codebase(config.repo.source, str(dest), config.repo.branch) + git.get_codebase(config.repo.source, str(dest), config.repo.branch) logger.debug( f"Cloning repository {config.repo.source} to {dest}, the tree structure of the directory: {os.listdir('.')}, the tree structure of the {dest} directory: {os.listdir(dest)}" ) @@ -144,8 +145,8 @@ def prepare_images(self, config: HiveConfig, push: bool = False) -> str: "Unsupported cloud provider configuration. Please enable GCP or AWS." ) - # Use the git commit hash as the image tag to ensure uniqueness. - image_name = f"{image_registry}:{hash[:7]}" + tag = utime.now_us() + image_name = f"{image_registry}:{tag}" logger.debug( f"Building sandbox image {image_name} in {temp_sandbox_dir} with push={push}" diff --git a/src/hive_cli/utils/git.py b/src/hive_cli/utils/git.py index 0c8f3fb..46fef20 100644 --- a/src/hive_cli/utils/git.py +++ b/src/hive_cli/utils/git.py @@ -4,19 +4,16 @@ import git -from hive_cli.utils import time as utime from hive_cli.utils.logger import logger -def get_codebase(source: str, dest: str, branch: str = "main") -> str: +def get_codebase(source: str, dest: str, branch: str = "main") -> None: """ Copy/clone repository from the given source to the destination directory. Args: source (str): The URL or path of the repository to clone. dest (str): The directory where the repository will be cloned. branch (str): The branch to checkout after cloning. Default is "main". - Returns: - str: The commit hash of the cloned repository. """ # Case `source` is a URL, we clone it. if source.startswith("https://"): @@ -39,18 +36,3 @@ def get_codebase(source: str, dest: str, branch: str = "main") -> str: logger.debug(f"Copying repository from {source} to {dest}") shutil.copytree(source_path, dest, dirs_exist_ok=True) - # Get the current commit hash if it's a git repository. - if os.path.exists(os.path.join(source_path, ".git")): - repo = git.Repo(source_path) - else: - # If not a repo, return a timestamp-based identifier. - logger.warning( - f"Source path {source} is not a git repository. Using timestamp as hash." - ) - return utime.now_2_hash() - try: - code_version_id = repo.head.commit.hexsha - except Exception as e: - raise ValueError(f"Repository at {dest} has no commits yet: {e}") from e - logger.debug(f"Repository copied successfully with commit ID {code_version_id}") - return code_version_id diff --git a/src/hive_cli/utils/time.py b/src/hive_cli/utils/time.py index 1c9a66d..493f7a3 100644 --- a/src/hive_cli/utils/time.py +++ b/src/hive_cli/utils/time.py @@ -18,6 +18,10 @@ def humanize_time(timestamp: str) -> str: return age +def now_us() -> str: + return str(int(datetime.now(timezone.utc).timestamp() * 1_000_000)) + + def now_2_hash() -> str: timestamp = str(int(datetime.now(timezone.utc).timestamp())) unique_hash = hashlib.sha1(timestamp.encode()).hexdigest()[:7] diff --git a/tests/utils/test_git.py b/tests/utils/test_git.py index 5a88f1b..da96091 100644 --- a/tests/utils/test_git.py +++ b/tests/utils/test_git.py @@ -16,20 +16,17 @@ def __init__(self, hexsha): class _MockHead: - def __init__(self, hexsha=HEXSHA, raise_on_access=False): + def __init__(self, hexsha=HEXSHA): self._hexsha = hexsha - self._raise = raise_on_access @property def commit(self): - if self._raise: - raise RuntimeError("no commit") return _MockCommit(self._hexsha) class _MockRepo: - def __init__(self, hexsha=HEXSHA, raise_on_access=False): - self.head = _MockHead(hexsha=hexsha, raise_on_access=raise_on_access) + def __init__(self, hexsha=HEXSHA): + self.head = _MockHead(hexsha=hexsha) self.checked_out = None self.git = types.SimpleNamespace( checkout=lambda branch: setattr(self, "checked_out", branch) @@ -92,7 +89,7 @@ def test_clone_url_without_token(monkeypatch, tmp_path, mock_git): url = "https://github.com/org/repo.git" dest = tmp_path / "dest" - hexsha = get_codebase(url, str(dest), branch="develop") + get_codebase(url, str(dest), branch="develop") # clone_from called with original URL (no token injected) called_url, called_dest = mock_git["clone_args"] @@ -102,9 +99,6 @@ def test_clone_url_without_token(monkeypatch, tmp_path, mock_git): # checkout called with the given branch assert mock_git["repo"].checked_out == "develop" - # returned commit hash matches mock - assert hexsha == mock_git["repo"].head.commit.hexsha - def test_clone_url_with_token_injected(monkeypatch, tmp_path, mock_git): monkeypatch.setenv("GITHUB_TOKEN", "SECRET123") @@ -112,7 +106,7 @@ def test_clone_url_with_token_injected(monkeypatch, tmp_path, mock_git): url = "https://github.com/org/repo.git" dest = tmp_path / "dest" - _ = get_codebase(url, str(dest)) + get_codebase(url, str(dest)) called_url, _ = mock_git["clone_args"] assert called_url.startswith("https://x-access-token:SECRET123@") @@ -141,7 +135,7 @@ def test_local_git_repo_copy(monkeypatch, tmp_path, mock_git, mock_copytree): dest = tmp_path / "dest" - hexsha = get_codebase(str(src), str(dest)) + get_codebase(str(src), str(dest)) # copytree was invoked with dirs_exist_ok=True assert mock_copytree["args"] == (src.resolve(), dest, True) @@ -149,40 +143,14 @@ def test_local_git_repo_copy(monkeypatch, tmp_path, mock_git, mock_copytree): # git.Repo was constructed with the SOURCE path (not dest) assert Path(mock_git["repo_arg"]).resolve() == src.resolve() - # Returned hash is from the mock repo - assert hexsha == HEXSHA - -def test_local_non_git_returns_timestamp(tmp_path, mock_copytree, caplog): +def test_local_non_git_copy(tmp_path, mock_copytree): # No .git directory -> non-git path src = tmp_path / "src" src.mkdir() dest = tmp_path / "dest" - result = get_codebase(str(src), str(dest)) + get_codebase(str(src), str(dest)) # copytree still happens assert mock_copytree["args"] == (src.resolve(), dest, True) - - assert len(result) == 7 - - # warning logged (optional but nice to assert) - assert any("is not a git repository" in rec.getMessage() for rec in caplog.records) - - -def test_repo_without_commits_raises_valueerror(monkeypatch, tmp_path, mock_git): - import hive_cli.utils.git as target_module - - # Make clone_from return a repo whose head.commit access raises - def bad_clone(url, dest, *args, **kwargs): - return _MockRepo(raise_on_access=True) - - monkeypatch.delenv("GITHUB_TOKEN", raising=False) - monkeypatch.setattr(target_module.git.Repo, "clone_from", bad_clone, raising=True) - - url = "https://github.com/org/repo.git" - dest = tmp_path / "dest" - - with pytest.raises(ValueError) as exc: - target_module.get_codebase(url, str(dest)) - assert "has no commits yet" in str(exc.value) diff --git a/tests/utils/test_time.py b/tests/utils/test_time.py index fb20b4c..44bdc1d 100644 --- a/tests/utils/test_time.py +++ b/tests/utils/test_time.py @@ -1,23 +1,6 @@ -import hashlib from datetime import datetime, timezone from unittest.mock import patch -from src.hive_cli.utils.time import now_2_hash - - -def test_now_2_hash(): - fixed_timestamp = 1700000000 - fixed_datetime = datetime.fromtimestamp(fixed_timestamp, tz=timezone.utc) - - class FixedDateTime(datetime): - @classmethod - def now(cls, tz=None): - return fixed_datetime - - with patch("src.hive_cli.utils.time.datetime", FixedDateTime): - expected_hash = hashlib.sha1(str(fixed_timestamp).encode()).hexdigest()[:7] - assert now_2_hash() == expected_hash - def test_humanize_time(): from src.hive_cli.utils.time import humanize_time From fe1052b7200bdbc47533fe3e1dcbb3673b3f33a8 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 5 Mar 2026 14:17:49 +0000 Subject: [PATCH 2/2] fix: local code doesn't use Git anymore. --- tests/utils/test_git.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/utils/test_git.py b/tests/utils/test_git.py index da96091..a746cfa 100644 --- a/tests/utils/test_git.py +++ b/tests/utils/test_git.py @@ -140,9 +140,6 @@ def test_local_git_repo_copy(monkeypatch, tmp_path, mock_git, mock_copytree): # copytree was invoked with dirs_exist_ok=True assert mock_copytree["args"] == (src.resolve(), dest, True) - # git.Repo was constructed with the SOURCE path (not dest) - assert Path(mock_git["repo_arg"]).resolve() == src.resolve() - def test_local_non_git_copy(tmp_path, mock_copytree): # No .git directory -> non-git path