diff --git a/halig/__version__.py b/halig/__version__.py index c366355..906d362 100644 --- a/halig/__version__.py +++ b/halig/__version__.py @@ -1 +1 @@ -__version__ = "0.5.1a2" +__version__ = "0.6.0" diff --git a/halig/commands/git/commit.py b/halig/commands/git/commit.py index 56ae67a..76da289 100644 --- a/halig/commands/git/commit.py +++ b/halig/commands/git/commit.py @@ -2,6 +2,10 @@ from halig.commands.git.base import GitBaseCommand class GitCommitCommand(GitBaseCommand): + def __init__(self, message: str | None = None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.message = message or self.settings.default_commit_message + def run(self): """Add all .age files to git and commit them using gitpython""" self.repo.index.add( diff --git a/halig/commands/git/pull.py b/halig/commands/git/pull.py new file mode 100644 index 0000000..fc0df47 --- /dev/null +++ b/halig/commands/git/pull.py @@ -0,0 +1,19 @@ +from halig.commands.git.base import GitBaseCommand + + +class GitPullCommand(GitBaseCommand): + def __init__( + self, remotes: list[str] | None = None, ref: str | None = None, *args, **kwargs + ): + super().__init__(*args, **kwargs) + self.remotes = remotes + self.ref = ref + + def run(self): + """Pull all changes from the remote git repo""" + if not self.remotes: + self.repo.remotes.origin.pull(self.ref or "main") + return + + for remote in self.remotes: + self.repo.remotes[remote].pull(self.ref or "main") diff --git a/halig/commands/git/push.py b/halig/commands/git/push.py index 0ef9b2b..320d62d 100644 --- a/halig/commands/git/push.py +++ b/halig/commands/git/push.py @@ -2,11 +2,15 @@ from halig.commands.git.base import GitBaseCommand class GitPushCommand(GitBaseCommand): - def run(self, remotes: list[str] | None = None): + def __init__(self, remotes: list[str] | None = None, *args, **kwargs): + super().__init__(*args, **kwargs) + self.remotes = remotes + + def run(self): """Push all changes to the remote git repo""" - if not remotes: + if not self.remotes: self.repo.remotes.origin.push() return - for remote in remotes: + for remote in self.remotes: self.repo.remotes[remote].push() diff --git a/halig/literals.py b/halig/literals.py index 4cb6b4d..c9cd3e7 100644 --- a/halig/literals.py +++ b/halig/literals.py @@ -14,6 +14,7 @@ when new public keys have been added to the config file and you want the notes to be seen by the new pairing private keys""" COMMANDS_GIT_COMMIT_HELP = "Commit all .age files to git" COMMANDS_GIT_PUSH_HELP = "Push all .age files to git" +COMMANDS_GIT_PULL_HELP = "Pull all .age files from git" # OPTIONS OPTION_CONFIG_HELP = "Configuration file. Must be YAML and schema compatible" diff --git a/halig/main.py b/halig/main.py index 47c0ce0..ba38c5e 100644 --- a/halig/main.py +++ b/halig/main.py @@ -10,6 +10,7 @@ from halig import literals from halig.__version__ import __version__ from halig.commands.edit import EditCommand from halig.commands.git.commit import GitCommitCommand +from halig.commands.git.pull import GitPullCommand from halig.commands.git.push import GitPushCommand from halig.commands.import_unencrypted import ImportCommand from halig.commands.notebooks import NotebooksCommand @@ -168,10 +169,21 @@ def git_commit( @git_app.command(name="push", help=literals.COMMANDS_GIT_PUSH_HELP) def git_push( + remotes: list[str] | None = None, config: Path | None = config_option, ): settings = load_from_file(config) - command = GitPushCommand(settings=settings) + command = GitPushCommand(settings=settings, remotes=remotes) + command.run() + + +@git_app.command(name="pull", help=literals.COMMANDS_GIT_PULL_HELP) +def git_pull( + remotes: list[str] | None = None, + config: Path | None = config_option, +): + settings = load_from_file(config) + command = GitPullCommand(settings=settings, remotes=remotes) command.run() diff --git a/tests/commands/test_git/test_commit.py b/tests/commands/test_git/test_commit.py index db26003..b070a8e 100644 --- a/tests/commands/test_git/test_commit.py +++ b/tests/commands/test_git/test_commit.py @@ -8,7 +8,7 @@ from halig.settings import Settings @pytest.fixture def command(settings: Settings): - return GitCommitCommand(settings) + return GitCommitCommand(settings=settings) def test_repo_is_not_initialized(settings): @@ -16,7 +16,7 @@ def test_repo_is_not_initialized(settings): initializes the repo upon instantiation""" assert not (settings.notebooks_root_path / ".git").is_dir() - GitCommitCommand(settings) + GitCommitCommand(settings=settings) assert (settings.notebooks_root_path / ".git").is_dir() @@ -27,7 +27,7 @@ def test_repo_is_initialized(settings): p = subprocess.Popen(["git", "init"], cwd=settings.notebooks_root_path) p.wait() assert (settings.notebooks_root_path / ".git").is_dir() - GitCommitCommand(settings) + GitCommitCommand(settings=settings) assert (settings.notebooks_root_path / ".git").is_dir() diff --git a/tests/commands/test_git/test_pull.py b/tests/commands/test_git/test_pull.py new file mode 100644 index 0000000..7c1aec0 --- /dev/null +++ b/tests/commands/test_git/test_pull.py @@ -0,0 +1,37 @@ +import shutil + +import pytest +from git import Repo + +from halig.commands.git.pull import GitPullCommand + + +@pytest.fixture +def command(settings, faker): + """Configure a local remote for testing located at settings.notebooks_root_path/../remote, push some .age files to + that remote + """ + command = GitPullCommand(settings=settings) + + new_path = shutil.copytree(settings.notebooks_root_path, settings.notebooks_root_path / "../remote") + new_path = new_path.resolve() + + command.repo.create_remote("origin", str(new_path)) + + remote_repo = Repo(new_path) + for _ in range(10): + random_age_file = new_path / f"{faker.word()}.age" + random_age_file.touch() + remote_repo.index.add([str(random_age_file)]) + remote_repo.index.commit("Update notebooks") + + return command + +def test_pull_from_origin(command): + command.run() + +def test_pull_from_custom_origin(settings, command): + remote_path = settings.notebooks_root_path / "../remote" + command.repo.create_remote("custom", str(remote_path.resolve())) + command.remotes = ["custom"] + command.run() diff --git a/tests/commands/test_git/test_push.py b/tests/commands/test_git/test_push.py index a560c67..89f4a98 100644 --- a/tests/commands/test_git/test_push.py +++ b/tests/commands/test_git/test_push.py @@ -9,29 +9,30 @@ from halig.commands.git.push import GitPushCommand @pytest.fixture def command(settings, faker): """Configure a local remote for testing""" - commit_command = GitCommitCommand(settings) - new_path = shutil.copytree(settings.notebooks_root_path , settings.notebooks_root_path / "../remote") + commit_command = GitCommitCommand(settings=settings) + new_path = shutil.copytree(settings.notebooks_root_path, settings.notebooks_root_path / "../remote") new_path = new_path.resolve() for _ in range(10): random_age_file = settings.notebooks_root_path / f"{faker.word()}.age" random_age_file.touch() commit_command.run() - push_command = GitPushCommand(settings) + push_command = GitPushCommand(settings=settings) push_command.repo.create_remote("origin", str(new_path)) return push_command -def test_push_to_origin(settings, command): +def test_push_to_origin(command): """Test that the command pushes to the origin remote""" command.run() + def test_push_to_custom_remote(settings, command): """Test that the command pushes to a custom remote""" remote_path = settings.notebooks_root_path / "../remote" command.repo.create_remote("custom", str(remote_path.resolve())) - - command.run(remotes=["custom"]) \ No newline at end of file + command.remotes = ["custom"] + command.run() diff --git a/tests/test_settings.py b/tests/test_settings.py index 3968785..e994d6b 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -6,7 +6,8 @@ from halig.settings import Settings, load_from_file def test_settings_from_env(settings: Settings, notebooks_root_path_envvar): - from_env_settings = Settings(recipient_paths=settings.recipient_paths, identity_paths=settings.identity_paths) # type: ignore[call-arg] + from_env_settings = Settings(recipient_paths=settings.recipient_paths, + identity_paths=settings.identity_paths) # type: ignore[call-arg] assert from_env_settings.notebooks_root_path == settings.notebooks_root_path @@ -29,3 +30,10 @@ def test_load_from_non_existing_file_path_raises_file_not_found_error(halig_conf file = halig_config_path / "some_invalid_file.yml" with pytest.raises(FileNotFoundError, match=f"File {file} does not exist"): load_from_file(file) + + +def test_settings_identity_paths_is_not_list_is_converted(settings): + s = Settings(identity_paths=settings.identity_paths[0], recipient_paths=settings.recipient_paths[0], + notebooks_root_path=settings.notebooks_root_path) + assert s.identity_paths == [settings.identity_paths[0]] + assert s.recipient_paths == [settings.recipient_paths[0]]