diff --git a/README.md b/README.md index 21ad9a5..38402b1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🔭 Git Pulsar (v0.10.0) +# 🔭 Git Pulsar (v0.10.1) [![Tests](https://github.com/jacksonfergusondev/git-pulsar/actions/workflows/ci.yml/badge.svg)](https://github.com/jacksonfergusondev/git-pulsar/actions) [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/) diff --git a/pyproject.toml b/pyproject.toml index eac6b17..0934961 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "git-pulsar" -version = "0.10.0" +version = "0.10.1" description = "Automated, paranoid git backups for students and casual coding." readme = "README.md" authors = [ @@ -14,8 +14,8 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project.scripts] -git-pulsar = "src.cli:main" -git-pulsar-daemon = "src.daemon:main" +git-pulsar = "git_pulsar.cli:main" +git-pulsar-daemon = "git_pulsar.daemon:main" [dependency-groups] dev = [ @@ -54,7 +54,7 @@ pythonpath = ["."] omit = ["src/service.py"] [tool.bumpversion] -current_version = "0.10.0" +current_version = "0.10.1" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}" diff --git a/src/cli.py b/src/git_pulsar/cli.py similarity index 100% rename from src/cli.py rename to src/git_pulsar/cli.py diff --git a/src/constants.py b/src/git_pulsar/constants.py similarity index 100% rename from src/constants.py rename to src/git_pulsar/constants.py diff --git a/src/daemon.py b/src/git_pulsar/daemon.py similarity index 100% rename from src/daemon.py rename to src/git_pulsar/daemon.py diff --git a/src/git_wrapper.py b/src/git_pulsar/git_wrapper.py similarity index 100% rename from src/git_wrapper.py rename to src/git_pulsar/git_wrapper.py diff --git a/src/ops.py b/src/git_pulsar/ops.py similarity index 100% rename from src/ops.py rename to src/git_pulsar/ops.py diff --git a/src/service.py b/src/git_pulsar/service.py similarity index 100% rename from src/service.py rename to src/git_pulsar/service.py diff --git a/src/system.py b/src/git_pulsar/system.py similarity index 98% rename from src/system.py rename to src/git_pulsar/system.py index bc4e83d..0cb5f31 100644 --- a/src/system.py +++ b/src/git_pulsar/system.py @@ -4,7 +4,7 @@ import sys from pathlib import Path -from constants import MACHINE_ID_FILE +from .constants import MACHINE_ID_FILE class SystemStrategy: diff --git a/tests/test_cli.py b/tests/test_cli.py index 07f1c4e..ca6ec56 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,7 +4,7 @@ import pytest -from src import cli +from git_pulsar import cli def test_setup_repo_initializes_git(tmp_path: Path, mocker: MagicMock) -> None: @@ -15,7 +15,7 @@ def test_setup_repo_initializes_git(tmp_path: Path, mocker: MagicMock) -> None: mock_run = mocker.patch("subprocess.run") # 2. Mock GitRepo for subsequent operations - mock_git_cls = mocker.patch("src.cli.GitRepo") + mock_git_cls = mocker.patch("git_pulsar.cli.GitRepo") mock_repo = mock_git_cls.return_value fake_registry = tmp_path / ".registry" @@ -35,7 +35,7 @@ def test_setup_repo_initializes_git(tmp_path: Path, mocker: MagicMock) -> None: def test_main_triggers_bootstrap(mocker: MagicMock) -> None: """Ensure --env flag calls ops.bootstrap_env.""" - mock_bootstrap = mocker.patch("src.cli.ops.bootstrap_env") + mock_bootstrap = mocker.patch("git_pulsar.cli.ops.bootstrap_env") mocker.patch("sys.argv", ["git-pulsar", "--env"]) cli.main() @@ -45,7 +45,7 @@ def test_main_triggers_bootstrap(mocker: MagicMock) -> None: def test_main_default_behavior(mocker: MagicMock) -> None: """Ensure running without flags defaults to setup_repo.""" - mock_setup = mocker.patch("src.cli.setup_repo") + mock_setup = mocker.patch("git_pulsar.cli.setup_repo") mocker.patch("sys.argv", ["git-pulsar"]) cli.main() @@ -55,7 +55,7 @@ def test_main_default_behavior(mocker: MagicMock) -> None: def test_finalize_command(mocker: MagicMock) -> None: """Ensure 'finalize' command calls ops.finalize_work.""" - mock_finalize = mocker.patch("src.cli.ops.finalize_work") + mock_finalize = mocker.patch("git_pulsar.cli.ops.finalize_work") mocker.patch("sys.argv", ["git-pulsar", "finalize"]) cli.main() @@ -65,7 +65,7 @@ def test_finalize_command(mocker: MagicMock) -> None: def test_restore_command(mocker: MagicMock) -> None: """Ensure 'restore' command calls ops.restore_file.""" - mock_restore = mocker.patch("src.cli.ops.restore_file") + mock_restore = mocker.patch("git_pulsar.cli.ops.restore_file") mocker.patch("sys.argv", ["git-pulsar", "restore", "file.py"]) cli.main() @@ -98,7 +98,7 @@ def test_status_reports_pause_state( os.chdir(tmp_path) # Mock GitRepo - mock_cls = mocker.patch("src.cli.GitRepo") + mock_cls = mocker.patch("git_pulsar.cli.GitRepo") mock_repo = mock_cls.return_value mock_repo.get_last_commit_time.return_value = "15 minutes ago" mock_repo.status_porcelain.return_value = [] @@ -119,7 +119,7 @@ def test_diff_shows_untracked_files( (tmp_path / ".git").mkdir() os.chdir(tmp_path) - mock_cls = mocker.patch("src.cli.GitRepo") + mock_cls = mocker.patch("git_pulsar.cli.GitRepo") mock_repo = mock_cls.return_value mock_repo.get_untracked_files.return_value = ["new_script.py"] diff --git a/tests/test_daemon.py b/tests/test_daemon.py index cd3f84e..9ad5d1b 100644 --- a/tests/test_daemon.py +++ b/tests/test_daemon.py @@ -1,8 +1,8 @@ from pathlib import Path from unittest.mock import MagicMock -from src import daemon -from src.daemon import Config +from git_pulsar import daemon +from git_pulsar.daemon import Config def test_run_backup_shadow_commit_flow(tmp_path: Path, mocker: MagicMock) -> None: @@ -13,17 +13,17 @@ def test_run_backup_shadow_commit_flow(tmp_path: Path, mocker: MagicMock) -> Non (tmp_path / ".git").mkdir() # 1. Mock System & Identity - mocker.patch("src.daemon.SYSTEM.is_under_load", return_value=False) - mocker.patch("src.daemon.SYSTEM.get_battery", return_value=(100, True)) - mocker.patch("src.daemon.get_machine_id", return_value="test-unit") + mocker.patch("git_pulsar.daemon.SYSTEM.is_under_load", return_value=False) + mocker.patch("git_pulsar.daemon.SYSTEM.get_battery", return_value=(100, True)) + mocker.patch("git_pulsar.daemon.get_machine_id", return_value="test-unit") mocker.patch("socket.gethostname", return_value="test-unit") # 2. Mock Configuration (CRITICAL: Ensure tests don't read ~/.config) # We provide a fresh default configuration - mocker.patch("src.daemon.CONFIG", Config()) + mocker.patch("git_pulsar.daemon.CONFIG", Config()) # 3. Mock GitRepo - mock_cls = mocker.patch("src.daemon.GitRepo") + mock_cls = mocker.patch("git_pulsar.daemon.GitRepo") repo = mock_cls.return_value repo.path = tmp_path repo.current_branch.return_value = "main" @@ -34,8 +34,8 @@ def test_run_backup_shadow_commit_flow(tmp_path: Path, mocker: MagicMock) -> Non repo.rev_parse.side_effect = lambda x: "parent_sha" if "HEAD" in x else None # 4. Mock Network - mocker.patch("src.daemon.get_remote_host", return_value="github.com") - mocker.patch("src.daemon.is_remote_reachable", return_value=True) + mocker.patch("git_pulsar.daemon.get_remote_host", return_value="github.com") + mocker.patch("git_pulsar.daemon.is_remote_reachable", return_value=True) # ACTION daemon.run_backup(str(tmp_path)) @@ -71,13 +71,12 @@ def test_run_backup_skips_if_no_changes(tmp_path: Path, mocker: MagicMock) -> No """Optimization check: Don't commit if tree matches parent backup.""" (tmp_path / ".git").mkdir() - mocker.patch("src.daemon.SYSTEM.is_under_load", return_value=False) - mocker.patch("src.daemon.get_machine_id", return_value="test-unit") + mocker.patch("git_pulsar.daemon.SYSTEM.is_under_load", return_value=False) + mocker.patch("git_pulsar.daemon.get_machine_id", return_value="test-unit") # Mock Config here as well - mocker.patch("src.daemon.CONFIG", Config()) - - mock_cls = mocker.patch("src.daemon.GitRepo") + mocker.patch("git_pulsar.daemon.CONFIG", Config()) + mock_cls = mocker.patch("git_pulsar.daemon.GitRepo") repo = mock_cls.return_value repo.current_branch.return_value = "main" diff --git a/tests/test_ops.py b/tests/test_ops.py index 77c5db9..0e11622 100644 --- a/tests/test_ops.py +++ b/tests/test_ops.py @@ -5,7 +5,7 @@ import pytest -from src import ops +from git_pulsar import ops def test_bootstrap_env_enforces_macos(mocker: MagicMock) -> None: @@ -52,7 +52,7 @@ def test_configure_identity_creates_file(tmp_path: Path, mocker: MagicMock) -> N """Should create machine_id file if missing.""" mocker.patch("builtins.input", return_value="my-laptop") mock_id_file = tmp_path / "machine_id" - mocker.patch("src.ops.get_machine_id_file", return_value=mock_id_file) + mocker.patch("git_pulsar.ops.get_machine_id_file", return_value=mock_id_file) ops.configure_identity() @@ -63,7 +63,7 @@ def test_configure_identity_skips_existing(tmp_path: Path, mocker: MagicMock) -> """Should do nothing if file exists.""" mock_id_file = tmp_path / "machine_id" mock_id_file.write_text("existing-id") - mocker.patch("src.ops.get_machine_id_file", return_value=mock_id_file) + mocker.patch("git_pulsar.ops.get_machine_id_file", return_value=mock_id_file) mock_input = mocker.patch("builtins.input") ops.configure_identity() @@ -76,13 +76,13 @@ def test_configure_identity_skips_existing(tmp_path: Path, mocker: MagicMock) -> def test_restore_clean(mocker: MagicMock) -> None: """Should checkout the file if working tree is clean.""" - mock_cls = mocker.patch("src.ops.GitRepo") + mock_cls = mocker.patch("git_pulsar.ops.GitRepo") mock_repo = mock_cls.return_value mock_repo.status_porcelain.return_value = [] # Mock current branch and machine ID for ref construction mock_repo.current_branch.return_value = "main" - mocker.patch("src.ops.get_machine_id", return_value="test-unit") + mocker.patch("git_pulsar.ops.get_machine_id", return_value="test-unit") ops.restore_file("script.py") @@ -95,7 +95,7 @@ def test_restore_dirty_fails(tmp_path: Path, mocker: MagicMock) -> None: os.chdir(tmp_path) (tmp_path / "script.py").touch() - mock_cls = mocker.patch("src.ops.GitRepo") + mock_cls = mocker.patch("git_pulsar.ops.GitRepo") mock_repo = mock_cls.return_value mock_repo.status_porcelain.return_value = ["M script.py"] mock_repo.current_branch.return_value = "main" @@ -106,8 +106,8 @@ def test_restore_dirty_fails(tmp_path: Path, mocker: MagicMock) -> None: def test_sync_session_success(mocker: MagicMock) -> None: """Should find latest backup and checkout.""" - mocker.patch("src.ops.GitRepo") - repo = mocker.patch("src.ops.GitRepo").return_value + mocker.patch("git_pulsar.ops.GitRepo") + repo = mocker.patch("git_pulsar.ops.GitRepo").return_value repo.current_branch.return_value = "main" # 1. Setup candidates @@ -157,7 +157,7 @@ def mock_run(cmd: list[str], *args: Any, **kwargs: Any) -> str: def test_finalize_octopus_merge(mocker: MagicMock) -> None: """Should squash merge multiple backup streams.""" - repo = mocker.patch("src.ops.GitRepo").return_value + repo = mocker.patch("git_pulsar.ops.GitRepo").return_value repo.status_porcelain.return_value = [] repo.current_branch.return_value = "main" diff --git a/tests/test_properties.py b/tests/test_properties.py index 79b2198..abe22a4 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -5,7 +5,7 @@ from hypothesis import given from hypothesis import strategies as st -from src import daemon +from git_pulsar import daemon # Strategy: Generate a list of non-empty strings that don't contain ANY line breaks. paths_strategy = st.lists( @@ -40,8 +40,8 @@ def test_prune_registry_removes_only_target( # We patch REGISTRY_FILE to point to our temp file # We patch SYSTEM.notify to suppress desktop notifications with ( - patch("src.daemon.REGISTRY_FILE", registry_file), - patch("src.daemon.SYSTEM.notify"), + patch("git_pulsar.daemon.REGISTRY_FILE", registry_file), + patch("git_pulsar.daemon.SYSTEM.notify"), ): # 3. Action daemon.prune_registry(target) diff --git a/uv.lock b/uv.lock index d7dfa07..053911a 100644 --- a/uv.lock +++ b/uv.lock @@ -200,7 +200,7 @@ wheels = [ [[package]] name = "git-pulsar" -version = "0.10.0" +version = "0.10.1" source = { editable = "." } [package.dev-dependencies]