diff --git a/docs/cmd/git.md b/docs/cmd/git/index.md similarity index 50% rename from docs/cmd/git.md rename to docs/cmd/git/index.md index 64872c7d9..18d200661 100644 --- a/docs/cmd/git.md +++ b/docs/cmd/git/index.md @@ -2,13 +2,25 @@ For `git(1)`. -Compare to: [`fabtools.git`](https://fabtools.readthedocs.io/en/0.19.0/api/git.html#git-module), +_Compare to: [`fabtools.git`](https://fabtools.readthedocs.io/en/0.19.0/api/git.html#git-module), [`salt.modules.git`](https://docs.saltproject.io/en/latest/ref/modules/all/salt.modules.git.html), -[`ansible.builtin.git`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/git_module.html) +[`ansible.builtin.git`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/git_module.html)_ + +```{toctree} +:caption: Subcommands +:maxdepth: 1 + +submodule +remote +stash +``` ```{eval-rst} .. automodule:: libvcs.cmd.git :members: :show-inheritance: :undoc-members: + :exclude-members: GitSubmoduleCmd, + GitRemoteCmd, + GitStashCmd ``` diff --git a/docs/cmd/git/remote.md b/docs/cmd/git/remote.md new file mode 100644 index 000000000..13891e37b --- /dev/null +++ b/docs/cmd/git/remote.md @@ -0,0 +1,10 @@ +# `remote` + +For `git-remote(1)`. + +```{eval-rst} +.. autoclass:: libvcs.cmd.git.GitRemoteCmd + :members: + :show-inheritance: + :undoc-members: +``` diff --git a/docs/cmd/git/stash.md b/docs/cmd/git/stash.md new file mode 100644 index 000000000..15e138a2b --- /dev/null +++ b/docs/cmd/git/stash.md @@ -0,0 +1,10 @@ +# `stash` + +For `git-stash(1)`. + +```{eval-rst} +.. autoclass:: libvcs.cmd.git.GitStashCmd + :members: + :show-inheritance: + :undoc-members: +``` diff --git a/docs/cmd/git/submodule.md b/docs/cmd/git/submodule.md new file mode 100644 index 000000000..d799ddd09 --- /dev/null +++ b/docs/cmd/git/submodule.md @@ -0,0 +1,10 @@ +# `submodule` + +For `git-submodule(1)`. + +```{eval-rst} +.. autoclass:: libvcs.cmd.git.GitSubmoduleCmd + :members: + :show-inheritance: + :undoc-members: +``` diff --git a/docs/cmd/index.md b/docs/cmd/index.md index c581e8397..23726bc08 100644 --- a/docs/cmd/index.md +++ b/docs/cmd/index.md @@ -16,7 +16,7 @@ versions. ```{toctree} :caption: API -git +git/index hg svn ``` diff --git a/docs/redirects.txt b/docs/redirects.txt index f0b66a35b..f8b60f687 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -20,3 +20,4 @@ "projects/git.md" "sync/git.md" "projects/hg.md" "sync/hg.md" "projects/svn.md" "sync/svn.md" +"cmd/git.md" "cmd/git/index.md" diff --git a/src/libvcs/cmd/git.py b/src/libvcs/cmd/git.py index 46633d60d..d4f81a488 100644 --- a/src/libvcs/cmd/git.py +++ b/src/libvcs/cmd/git.py @@ -24,7 +24,7 @@ def __init__( dir: StrPath, progress_callback: Optional[ProgressCallbackProtocol] = None, ) -> None: - """Lite, typed, pythonic wrapper for git(1). + r"""Lite, typed, pythonic wrapper for git(1). Parameters ---------- @@ -33,8 +33,28 @@ def __init__( Examples -------- - >>> Git(dir=tmp_path) + >>> git = Git(dir=git_local_clone.dir) + >>> git + + Subcommands: + + >>> git.remote.show() + 'origin' + + >>> git.remote.add( + ... name='my_remote', url=f'file:///dev/null' + ... ) + '' + + >>> git.remote.show() + 'my_remote\norigin' + + >>> git.stash.save(message="Message") + 'No local changes to save' + + >>> git.submodule.init() + '' """ #: Directory to check out self.dir: pathlib.Path @@ -2370,7 +2390,7 @@ def run( check_returncode: Optional[bool] = None, **kwargs: Any, ) -> str: - r"""Wraps `git submodule `_. + r"""Wraps `git remote `_. Examples -------- @@ -2405,7 +2425,7 @@ def add( log_in_real_time: bool = False, check_returncode: Optional[bool] = None, ) -> str: - """git submodule add + """git remote add Examples -------- @@ -2441,7 +2461,7 @@ def rename( log_in_real_time: bool = False, check_returncode: Optional[bool] = None, ) -> str: - """git submodule rename + """git remote rename Examples -------- @@ -2474,7 +2494,7 @@ def remove( log_in_real_time: bool = False, check_returncode: Optional[bool] = None, ) -> str: - """git submodule remove + """git remote remove Examples -------- @@ -2503,7 +2523,7 @@ def show( log_in_real_time: bool = False, check_returncode: Optional[bool] = None, ) -> str: - """git submodule show + """git remote show Examples -------- @@ -2538,7 +2558,7 @@ def prune( log_in_real_time: bool = False, check_returncode: Optional[bool] = None, ) -> str: - """git submodule prune + """git remote prune Examples -------- @@ -2572,7 +2592,7 @@ def get_url( log_in_real_time: bool = False, check_returncode: Optional[bool] = None, ) -> str: - """git submodule get-url + """git remote get-url Examples -------- @@ -2614,7 +2634,7 @@ def set_url( log_in_real_time: bool = False, check_returncode: Optional[bool] = None, ) -> str: - """git submodule set-url + """git remote set-url Examples -------- diff --git a/tests/sync/test_git.py b/tests/sync/test_git.py index 874298a5f..f6cfce021 100644 --- a/tests/sync/test_git.py +++ b/tests/sync/test_git.py @@ -5,7 +5,7 @@ import random import shutil import textwrap -from typing import Callable, TypedDict +import typing as t import pytest @@ -26,9 +26,9 @@ pytestmark = pytest.mark.skip(reason="git is not available") -ProjectTestFactory = Callable[..., GitSync] -ProjectTestFactoryLazyKwargs = Callable[..., dict] -ProjectTestFactoryRemoteLazyExpected = Callable[..., dict[str, GitRemote]] +ProjectTestFactory = t.Callable[..., GitSync] +ProjectTestFactoryLazyKwargs = t.Callable[..., dict] +ProjectTestFactoryRemoteLazyExpected = t.Callable[..., dict[str, GitRemote]] @pytest.mark.parametrize( @@ -38,7 +38,7 @@ [ GitSync, lambda bare_dir, tmp_path, **kwargs: { - "url": f"file://{bare_dir}", + "url": bare_dir.as_uri(), "dir": tmp_path / "obtaining a bare repo", "vcs": "git", }, @@ -46,7 +46,7 @@ [ create_project, lambda bare_dir, tmp_path, **kwargs: { - "url": f"git+file://{bare_dir}", + "url": f"git+{bare_dir.as_uri()}", "dir": tmp_path / "obtaining a bare repo", "vcs": "git", }, @@ -81,7 +81,7 @@ def test_repo_git_obtain_initial_commit_repo( [ GitSync, lambda git_remote_repo, tmp_path, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": tmp_path / "myrepo", "vcs": "git", }, @@ -89,7 +89,7 @@ def test_repo_git_obtain_initial_commit_repo( [ create_project, lambda git_remote_repo, tmp_path, **kwargs: { - "url": f"git+file://{git_remote_repo}", + "url": f"git+{git_remote_repo.as_uri()}", "dir": tmp_path / "myrepo", "vcs": "git", }, @@ -118,7 +118,7 @@ def test_repo_git_obtain_full( [ GitSync, lambda git_remote_repo, tmp_path, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": tmp_path / "myrepo", "vcs": "git", }, @@ -126,7 +126,7 @@ def test_repo_git_obtain_full( [ create_project, lambda git_remote_repo, tmp_path, **kwargs: { - "url": f"git+file://{git_remote_repo}", + "url": f"git+{git_remote_repo.as_uri()}", "dir": tmp_path / "myrepo", "vcs": "git", }, @@ -180,38 +180,37 @@ def test_repo_update_stash_cases( git_remote_repo = create_git_remote_repo() git_repo: GitSync = GitSync( - url=f"file://{git_remote_repo}", + url=git_remote_repo.as_uri(), dir=tmp_path / "myrepo", vcs="git", ) git_repo.obtain() # clone initial repo - def make_file(filename: str) -> pathlib.Path: - some_file = git_repo.dir.joinpath(filename) - with open(some_file, "w") as file: - file.write("some content: " + str(random.random())) - - return some_file - # Make an initial commit so we can reset - some_file = make_file("initial_file") - git_repo.run(["add", some_file]) + initial_file = git_repo.dir / "initial_file" + initial_file.write_text(f"some content: {random.random()}", encoding="utf-8") + git_repo.run(["add", str(initial_file)]) git_repo.run(["commit", "-m", "a commit"]) git_repo.run(["push"]) if has_remote_changes: - some_file = make_file("some_file") + some_file = git_repo.dir / "some_file" + some_file.write_text(f"some content: {random.random()}", encoding="utf-8") git_repo.run(["add", some_file]) git_repo.run(["commit", "-m", "a commit"]) git_repo.run(["push"]) git_repo.run(["reset", "--hard", "HEAD^"]) if has_untracked_files: - make_file("some_file") + some_file = git_repo.dir / "some_file" + some_file.write_text(f"some content: {random.random()}", encoding="utf-8") if needs_stash: - some_file = make_file("some_stashed_file") - git_repo.run(["add", some_file]) + some_stashed_file = git_repo.dir / "some_stashed_file" + some_stashed_file.write_text( + f"some content: {random.random()}", encoding="utf-8" + ) + git_repo.run(["add", some_stashed_file]) cmd_mock = mocker.spy(git_repo.cmd, "symbolic_ref") git_repo.update_repo() @@ -226,7 +225,7 @@ def make_file(filename: str) -> pathlib.Path: [ GitSync, lambda git_remote_repo, tmp_path, progress_callback, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": tmp_path / "myrepo", "progress_callback": progress_callback, "vcs": "git", @@ -235,7 +234,7 @@ def make_file(filename: str) -> pathlib.Path: [ create_project, lambda git_remote_repo, tmp_path, progress_callback, **kwargs: { - "url": f"git+file://{git_remote_repo}", + "url": f"git+{git_remote_repo.as_uri()}", "dir": tmp_path / "myrepo", "progress_callback": progress_callback, "vcs": "git", @@ -272,142 +271,142 @@ def progress_callback_spy(output: str, timestamp: datetime.datetime) -> None: [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, }, lambda git_remote_repo, **kwargs: { "origin": GitRemote( name="origin", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, ], [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, - "remotes": {"origin": f"file://{git_remote_repo}"}, + "remotes": {"origin": git_remote_repo.as_uri()}, }, lambda git_remote_repo, **kwargs: { "origin": GitRemote( name="origin", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, ], [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, "remotes": { - "origin": f"file://{git_remote_repo}", - "second_remote": f"file://{git_remote_repo}", + "origin": git_remote_repo.as_uri(), + "second_remote": git_remote_repo.as_uri(), }, }, lambda git_remote_repo, **kwargs: { "origin": GitRemote( name="origin", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), "second_remote": GitRemote( name="second_remote", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, ], [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, "remotes": { - "second_remote": f"file://{git_remote_repo}", + "second_remote": git_remote_repo.as_uri(), }, }, lambda git_remote_repo, **kwargs: { "origin": GitRemote( name="origin", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), "second_remote": GitRemote( name="second_remote", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, ], [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, "remotes": { "origin": GitRemote( name="origin", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), "second_remote": GitRemote( name="second_remote", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, }, lambda git_remote_repo, **kwargs: { "origin": GitRemote( name="second_remote", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), "second_remote": GitRemote( name="second_remote", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, ], [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, "vcs": "git", "remotes": { "second_remote": GitRemote( name="second_remote", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, }, lambda git_remote_repo, **kwargs: { "second_remote": GitRemote( name="second_remote", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, ], [ create_project, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"git+file://{git_remote_repo}", + "url": f"git+{git_remote_repo.as_uri()}", "dir": projects_path / repo_name, "vcs": "git", }, lambda git_remote_repo, **kwargs: { "origin": GitRemote( name="second_remote", - fetch_url=f"file://{git_remote_repo}", - push_url=f"file://{git_remote_repo}", + fetch_url=git_remote_repo.as_uri(), + push_url=git_remote_repo.as_uri(), ), }, ], @@ -446,66 +445,66 @@ def test_remotes( [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, "remotes": { - "origin": f"file://{git_remote_repo}", + "origin": git_remote_repo.as_uri(), }, }, lambda git_remote_repo, **kwargs: { "second_remote": GitRemote( **{ "name": "second_remote", - "fetch_url": f"file://{git_remote_repo}", - "push_url": f"file://{git_remote_repo}", + "fetch_url": git_remote_repo.as_uri(), + "push_url": git_remote_repo.as_uri(), } ) }, lambda git_remote_repo, **kwargs: { "origin": GitRemote( name="origin", - push_url=f"file://{git_remote_repo}", - fetch_url=f"file://{git_remote_repo}", + push_url=git_remote_repo.as_uri(), + fetch_url=git_remote_repo.as_uri(), ), "second_remote": GitRemote( name="second_remote", - push_url=f"file://{git_remote_repo}", - fetch_url=f"file://{git_remote_repo}", + push_url=git_remote_repo.as_uri(), + fetch_url=git_remote_repo.as_uri(), ), }, ], [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, "remotes": { - "origin": f"file://{git_remote_repo}", + "origin": git_remote_repo.as_uri(), # accepts short-hand form since it's inputted in the constructor - "second_remote": f"file://{git_remote_repo}", + "second_remote": git_remote_repo.as_uri(), }, }, lambda git_remote_repo, **kwargs: {}, lambda git_remote_repo, **kwargs: { "origin": GitRemote( name="origin", - push_url=f"file://{git_remote_repo}", - fetch_url=f"file://{git_remote_repo}", + push_url=git_remote_repo.as_uri(), + fetch_url=git_remote_repo.as_uri(), ), "second_remote": GitRemote( name="second_remote", - push_url=f"file://{git_remote_repo}", - fetch_url=f"file://{git_remote_repo}", + push_url=git_remote_repo.as_uri(), + fetch_url=git_remote_repo.as_uri(), ), }, ], [ GitSync, lambda git_remote_repo, projects_path, repo_name, **kwargs: { - "url": f"file://{git_remote_repo}", + "url": git_remote_repo.as_uri(), "dir": projects_path / repo_name, "remotes": { - "origin": f"file://{git_remote_repo}", + "origin": git_remote_repo.as_uri(), }, }, lambda git_remote_repo, second_git_remote_repo, **kwargs: { @@ -587,15 +586,15 @@ def test_git_get_url_and_rev_from_pip_url() -> None: [ GitSync, lambda git_remote_repo, dir, **kwargs: { - "url": f"file://{git_remote_repo}", - "dir": str(dir), + "url": git_remote_repo.as_uri(), + "dir": dir, "vcs": "git", }, ], [ create_project, lambda git_remote_repo, dir, **kwargs: { - "url": f"git+file://{git_remote_repo}", + "url": f"git+{git_remote_repo.as_uri()}", "dir": dir, "vcs": "git", }, @@ -630,7 +629,7 @@ def test_remotes_preserves_git_ssh( [ GitSync, lambda bare_dir, tmp_path, **kwargs: { - "url": f"file://{bare_dir}", + "url": bare_dir.as_uri(), "dir": tmp_path / "obtaining a bare repo", "vcs": "git", }, @@ -638,7 +637,7 @@ def test_remotes_preserves_git_ssh( [ create_project, lambda bare_dir, tmp_path, **kwargs: { - "url": f"git+file://{bare_dir}", + "url": f"git+{bare_dir.as_uri()}", "dir": tmp_path / "obtaining a bare repo", "vcs": "git", }, @@ -665,6 +664,9 @@ def test_ls_remotes(git_repo: GitSync) -> None: remotes = git_repo.remotes() assert "origin" in remotes + assert git_repo.cmd.remote.show() == "origin" + assert "origin" in git_repo.cmd.remote.show(name="origin") + assert "origin" in git_repo.cmd.remote.show(name="origin", no_query_remotes=True) assert git_repo.remotes()["origin"].name == "origin" @@ -725,9 +727,7 @@ def test_get_current_remote_name(git_repo: GitSync) -> None: ), "branch w/o upstream should return branch only" new_remote_name = "new_remote_name" - git_repo.set_remote( - name=new_remote_name, url=f"file://{git_repo.dir}", overwrite=True - ) + git_repo.set_remote(name=new_remote_name, url=git_repo.dir.as_uri(), overwrite=True) git_repo.run(["fetch", new_remote_name]) git_repo.run(["branch", "--set-upstream-to", f"{new_remote_name}/{new_branch}"]) assert ( @@ -767,7 +767,7 @@ def test_GitRemote_from_stdout() -> None: ) == GitStatus.from_stdout(FIXTURE_A) -class GitBranchComplexResult(TypedDict): +class GitBranchComplexResult(t.TypedDict): branch_oid: str branch_head: str branch_upstream: str @@ -827,7 +827,7 @@ def test_GitRemote__from_stdout_b(fixture: str, expected_result: GitStatus) -> N assert GitStatus.from_stdout(textwrap.dedent(fixture)) == expected_result -class GitBranchResult(TypedDict): +class GitBranchResult(t.TypedDict): branch_ab: str branch_ahead: str branch_behind: str @@ -895,7 +895,7 @@ def test_repo_git_remote_checkout( ) -> None: git_server = create_git_remote_repo() git_repo_checkout_dir = projects_path / "my_git_checkout" - git_repo = GitSync(dir=str(git_repo_checkout_dir), url=f"file://{git_server!s}") + git_repo = GitSync(dir=git_repo_checkout_dir, url=git_server.as_uri()) git_repo.obtain() git_repo.update_repo()