From a9f8af332fc57890ef5413e2e1401f3b015e94ca Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 16:59:58 -0500 Subject: [PATCH 1/8] build(deps): Bump sphinx to 5.2.1 --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index cae14f4ff..f0373cb17 100644 --- a/poetry.lock +++ b/poetry.lock @@ -629,7 +629,7 @@ python-versions = ">=3.6" [[package]] name = "Sphinx" -version = "5.2.0.post0" +version = "5.2.1" description = "Python documentation generator" category = "dev" optional = false @@ -1279,8 +1279,8 @@ soupsieve = [ {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, ] Sphinx = [ - {file = "Sphinx-5.2.0.post0.tar.gz", hash = "sha256:68e7833263a961521f45302fa87285f9395ecf385f1eefd85cd61ddff0b15bc1"}, - {file = "sphinx-5.2.0.post0-py3-none-any.whl", hash = "sha256:db93dc52cc90d12ef38c9f506eab9171813041204d8270e30ffad2be511e7ced"}, + {file = "Sphinx-5.2.1.tar.gz", hash = "sha256:c009bb2e9ac5db487bcf53f015504005a330ff7c631bb6ab2604e0d65bae8b54"}, + {file = "sphinx-5.2.1-py3-none-any.whl", hash = "sha256:3dcf00fcf82cf91118db9b7177edea4fc01998976f893928d0ab0c58c54be2ca"}, ] sphinx-autobuild = [ {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, From 9fddb55d9db374fa9d99926206eff4036efb5b54 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 17:01:34 -0500 Subject: [PATCH 2/8] build(deps): Bump gp-libs (sphinx autodoc toc fix removed) --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index f0373cb17..29a4a0fe5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -195,7 +195,7 @@ sphinx-basic-ng = "*" [[package]] name = "gp-libs" -version = "0.0.1a11" +version = "0.0.1a12" description = "Internal utilities for projects following git-pull python package spec" category = "dev" optional = false @@ -1042,8 +1042,8 @@ furo = [ {file = "furo-2022.9.15.tar.gz", hash = "sha256:4a7ef1c8a3b615171592da4d2ad8a53cc4aacfbe111958890f5f9ff7279066ab"}, ] gp-libs = [ - {file = "gp-libs-0.0.1a11.tar.gz", hash = "sha256:2fa1d886aae88f17614c052652509ce6347adc5f39e1b35223ef0ee2b56069e5"}, - {file = "gp_libs-0.0.1a11-py3-none-any.whl", hash = "sha256:75248e0409e8af142cd2ccdbf3382300d26eb73f0155b0de721368c3543e5c35"}, + {file = "gp-libs-0.0.1a12.tar.gz", hash = "sha256:3a9a3018fa524a0008dd2a88197b2ab503a769bfa780337bf00f5753e1b95552"}, + {file = "gp_libs-0.0.1a12-py3-none-any.whl", hash = "sha256:7115eb6f65de812352fd08da1316a31458d3ceedede3fb9f7f4d2236aae0ca27"}, ] idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, From 2f94fcfd13cdcec57a2377bd6cb93e66b8c9db4d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 19:09:17 -0500 Subject: [PATCH 3/8] feat(pytest_plugin): Allow passing init flags to repo fixtures --- src/libvcs/pytest_plugin.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/libvcs/pytest_plugin.py b/src/libvcs/pytest_plugin.py index d84891f53..0c57d9f80 100644 --- a/src/libvcs/pytest_plugin.py +++ b/src/libvcs/pytest_plugin.py @@ -192,6 +192,7 @@ def __call__( remote_repos_path: pathlib.Path = ..., remote_repo_name: Optional[str] = ..., remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = ..., + init_cmd_args: Optional[list[str]] = ..., ) -> pathlib.Path: ... @@ -200,9 +201,12 @@ def _create_git_remote_repo( remote_repos_path: pathlib.Path, remote_repo_name: str, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, + init_cmd_args: Optional[list[str]] = None, ) -> pathlib.Path: + if init_cmd_args is None: + init_cmd_args = [] remote_repo_path = remote_repos_path / remote_repo_name - run(["git", "init", remote_repo_name], cwd=remote_repos_path) + run(["git", "init", remote_repo_name, *init_cmd_args], cwd=remote_repos_path) if remote_repo_post_init is not None and callable(remote_repo_post_init): remote_repo_post_init(remote_repo_path=remote_repo_path) @@ -221,6 +225,7 @@ def fn( remote_repos_path: pathlib.Path = remote_repos_path, remote_repo_name: Optional[str] = None, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, + init_cmd_args: Optional[list[str]] = None, ) -> pathlib.Path: return _create_git_remote_repo( remote_repos_path=remote_repos_path, @@ -228,6 +233,7 @@ def fn( if remote_repo_name is not None else unique_repo_name(remote_repos_path=remote_repos_path), remote_repo_post_init=remote_repo_post_init, + init_cmd_args=init_cmd_args, ) return fn @@ -256,11 +262,14 @@ def _create_svn_remote_repo( remote_repos_path: pathlib.Path, remote_repo_name: str, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, + init_cmd_args: Optional[list[str]] = None, ) -> pathlib.Path: """Create a test SVN repo to for checkout / commit purposes""" + if init_cmd_args is None: + init_cmd_args = [] remote_repo_path = remote_repos_path / remote_repo_name - run(["svnadmin", "create", remote_repo_path]) + run(["svnadmin", "create", remote_repo_path, *init_cmd_args]) if remote_repo_post_init is not None and callable(remote_repo_post_init): remote_repo_post_init(remote_repo_path=remote_repo_path) @@ -279,6 +288,7 @@ def fn( remote_repos_path: pathlib.Path = remote_repos_path, remote_repo_name: Optional[str] = None, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, + init_cmd_args: Optional[list[str]] = None, ) -> pathlib.Path: return _create_svn_remote_repo( remote_repos_path=remote_repos_path, @@ -286,6 +296,7 @@ def fn( if remote_repo_name is not None else unique_repo_name(remote_repos_path=remote_repos_path), remote_repo_post_init=remote_repo_post_init, + init_cmd_args=init_cmd_args, ) return fn @@ -309,10 +320,14 @@ def _create_hg_remote_repo( remote_repos_path: pathlib.Path, remote_repo_name: str, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, + init_cmd_args: Optional[list[str]] = None, ) -> pathlib.Path: """Create a test hg repo to for checkout / commit purposes""" + if init_cmd_args is None: + init_cmd_args = [] + remote_repo_path = remote_repos_path / remote_repo_name - run(["hg", "init", remote_repo_name], cwd=remote_repos_path) + run(["hg", "init", remote_repo_name, *init_cmd_args], cwd=remote_repos_path) if remote_repo_post_init is not None and callable(remote_repo_post_init): remote_repo_post_init(remote_repo_path=remote_repo_path) @@ -340,6 +355,7 @@ def fn( remote_repos_path: pathlib.Path = remote_repos_path, remote_repo_name: Optional[str] = None, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, + init_cmd_args: Optional[list[str]] = None, ) -> pathlib.Path: return _create_hg_remote_repo( remote_repos_path=remote_repos_path, @@ -347,6 +363,7 @@ def fn( if remote_repo_name is not None else unique_repo_name(remote_repos_path=remote_repos_path), remote_repo_post_init=remote_repo_post_init, + init_cmd_args=init_cmd_args, ) return fn From 91cd1638cb149c032260d02e0e8820b2edb0cf35 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 19:12:28 -0500 Subject: [PATCH 4/8] chore(pytest_plugin): Use TypeAlias --- src/libvcs/pytest_plugin.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libvcs/pytest_plugin.py b/src/libvcs/pytest_plugin.py index 0c57d9f80..45af1c7e3 100644 --- a/src/libvcs/pytest_plugin.py +++ b/src/libvcs/pytest_plugin.py @@ -5,7 +5,7 @@ import random import shutil import textwrap -from typing import Any, Optional, Protocol +from typing import TYPE_CHECKING, Any, Optional, Protocol import pytest @@ -14,6 +14,9 @@ from libvcs.sync.hg import HgSync from libvcs.sync.svn import SvnSync +if TYPE_CHECKING: + from typing_extensions import TypeAlias + skip_if_git_missing = pytest.mark.skipif( not shutil.which("git"), reason="git is not available" ) @@ -181,6 +184,9 @@ def unique_repo_name(remote_repos_path: pathlib.Path, max_retries: int = 15) -> return remote_repo_name +InitCmdArgs: "TypeAlias" = Optional[list[str]] + + class CreateProjectCallbackProtocol(Protocol): def __call__(self, remote_repo_path: pathlib.Path) -> None: ... @@ -192,7 +198,7 @@ def __call__( remote_repos_path: pathlib.Path = ..., remote_repo_name: Optional[str] = ..., remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = ..., - init_cmd_args: Optional[list[str]] = ..., + init_cmd_args: InitCmdArgs = ..., ) -> pathlib.Path: ... @@ -201,7 +207,7 @@ def _create_git_remote_repo( remote_repos_path: pathlib.Path, remote_repo_name: str, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, - init_cmd_args: Optional[list[str]] = None, + init_cmd_args: InitCmdArgs = None, ) -> pathlib.Path: if init_cmd_args is None: init_cmd_args = [] @@ -225,7 +231,7 @@ def fn( remote_repos_path: pathlib.Path = remote_repos_path, remote_repo_name: Optional[str] = None, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, - init_cmd_args: Optional[list[str]] = None, + init_cmd_args: InitCmdArgs = None, ) -> pathlib.Path: return _create_git_remote_repo( remote_repos_path=remote_repos_path, @@ -262,7 +268,7 @@ def _create_svn_remote_repo( remote_repos_path: pathlib.Path, remote_repo_name: str, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, - init_cmd_args: Optional[list[str]] = None, + init_cmd_args: InitCmdArgs = None, ) -> pathlib.Path: """Create a test SVN repo to for checkout / commit purposes""" if init_cmd_args is None: @@ -288,7 +294,7 @@ def fn( remote_repos_path: pathlib.Path = remote_repos_path, remote_repo_name: Optional[str] = None, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, - init_cmd_args: Optional[list[str]] = None, + init_cmd_args: InitCmdArgs = None, ) -> pathlib.Path: return _create_svn_remote_repo( remote_repos_path=remote_repos_path, @@ -320,7 +326,7 @@ def _create_hg_remote_repo( remote_repos_path: pathlib.Path, remote_repo_name: str, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, - init_cmd_args: Optional[list[str]] = None, + init_cmd_args: InitCmdArgs = None, ) -> pathlib.Path: """Create a test hg repo to for checkout / commit purposes""" if init_cmd_args is None: @@ -355,7 +361,7 @@ def fn( remote_repos_path: pathlib.Path = remote_repos_path, remote_repo_name: Optional[str] = None, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, - init_cmd_args: Optional[list[str]] = None, + init_cmd_args: InitCmdArgs = None, ) -> pathlib.Path: return _create_hg_remote_repo( remote_repos_path=remote_repos_path, From 21e643e6dbf816c79189481a22cb7fe7c7bee9ff Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 19:14:22 -0500 Subject: [PATCH 5/8] refactor!(pytest_plugin): create_git_remote_repo: Use bare by default --- src/libvcs/pytest_plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libvcs/pytest_plugin.py b/src/libvcs/pytest_plugin.py index 45af1c7e3..303171547 100644 --- a/src/libvcs/pytest_plugin.py +++ b/src/libvcs/pytest_plugin.py @@ -207,7 +207,7 @@ def _create_git_remote_repo( remote_repos_path: pathlib.Path, remote_repo_name: str, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, - init_cmd_args: InitCmdArgs = None, + init_cmd_args: InitCmdArgs = ["--bare"], ) -> pathlib.Path: if init_cmd_args is None: init_cmd_args = [] @@ -231,7 +231,7 @@ def fn( remote_repos_path: pathlib.Path = remote_repos_path, remote_repo_name: Optional[str] = None, remote_repo_post_init: Optional[CreateProjectCallbackProtocol] = None, - init_cmd_args: InitCmdArgs = None, + init_cmd_args: InitCmdArgs = ["--bare"], ) -> pathlib.Path: return _create_git_remote_repo( remote_repos_path=remote_repos_path, @@ -261,6 +261,7 @@ def git_remote_repo(remote_repos_path: pathlib.Path) -> pathlib.Path: remote_repos_path=remote_repos_path, remote_repo_name="dummyrepo", remote_repo_post_init=git_remote_repo_single_commit_post_init, + init_cmd_args=None, # Don't do --bare ) @@ -450,6 +451,7 @@ def add_doctest_fixtures( doctest_namespace["create_git_remote_repo"] = functools.partial( create_git_remote_repo, remote_repo_post_init=git_remote_repo_single_commit_post_init, + init_cmd_args=None, ) doctest_namespace["create_git_remote_repo_bare"] = create_git_remote_repo doctest_namespace["git_local_clone"] = git_repo From ab81dff67f2d3e63f853ddc59babc6a4a5898515 Mon Sep 17 00:00:00 2001 From: Jhon Pedroza Date: Sun, 25 Sep 2022 17:16:32 -0500 Subject: [PATCH 6/8] fix(git): Fix update_repo when there are untracked files --- CHANGES | 1 + src/libvcs/sync/git.py | 4 +-- tests/sync/test_git.py | 65 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 3242cd546..6cba649fa 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,7 @@ URL renamings (#417): - `URLProtocol`: Fix `is_valid` to use `classmethod` - All: Fix `is_valid` to use default of `None` to avoid implicitly filtering - Reduce duplicated code in methods by using `super()` + - `git`: Fix `update_repo` when there are only untracked files ### Packaging diff --git a/src/libvcs/sync/git.py b/src/libvcs/sync/git.py index c54a70d63..3c019e2ad 100644 --- a/src/libvcs/sync/git.py +++ b/src/libvcs/sync/git.py @@ -408,7 +408,7 @@ def update_repo(self, set_remotes: bool = False, *args: Any, **kwargs: Any) -> N if is_remote_ref: # Check if stash is needed try: - process = self.run(["status", "--porcelain"]) + process = self.run(["status", "--porcelain", "--untracked-files=no"]) except exc.CommandError: self.log.error("Failed to get the status") return @@ -435,7 +435,7 @@ def update_repo(self, set_remotes: bool = False, *args: Any, **kwargs: Any) -> N try: process = self.run(["rebase", git_remote_name + "/" + git_tag]) except exc.CommandError as e: - if "invalid_upstream" in str(e): + if any(msg in str(e) for msg in ["invalid_upstream", "Aborting"]): self.log.error(e) else: # Rebase failed: Restore previous state. diff --git a/tests/sync/test_git.py b/tests/sync/test_git.py index 7c1fb4cbe..fe2597683 100644 --- a/tests/sync/test_git.py +++ b/tests/sync/test_git.py @@ -2,6 +2,7 @@ import datetime import os import pathlib +import random import shutil import textwrap from typing import Callable, TypedDict @@ -141,6 +142,7 @@ def test_repo_update_handle_cases( ) -> None: git_repo: GitSync = constructor(**lazy_constructor_options(**locals())) git_repo.obtain() # clone initial repo + mocka = mocker.spy(git_repo, "run") git_repo.update_repo() @@ -154,6 +156,69 @@ def test_repo_update_handle_cases( assert mocker.call(["symbolic-ref", "--short", "HEAD"]) not in mocka.mock_calls +@pytest.mark.parametrize( + "has_untracked_files,needs_stash,has_remote_changes", + [ + [True, True, True], + [True, True, False], + [True, False, True], + [True, False, False], + [False, True, True], + [False, True, False], + [False, False, True], + [False, False, False], + ], +) +def test_repo_update_stash_cases( + tmp_path: pathlib.Path, + create_git_remote_repo: CreateProjectCallbackFixtureProtocol, + mocker: MockerFixture, + has_untracked_files: bool, + needs_stash: bool, + has_remote_changes: bool, +) -> None: + git_remote_repo = create_git_remote_repo() + + git_repo: GitSync = GitSync( + url=f"file://{git_remote_repo}", + 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]) + git_repo.run(["commit", "-m", "a commit"]) + git_repo.run(["push"]) + + if has_remote_changes: + some_file = make_file("some_file") + 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") + + if needs_stash: + some_file = make_file("some_stashed_file") + git_repo.run(["add", some_file]) + + mocka = mocker.spy(git_repo, "run") + git_repo.update_repo() + + mocka.assert_any_call(["symbolic-ref", "--short", "HEAD"]) + + @pytest.mark.parametrize( # Postpone evaluation of options so fixture variables can interpolate "constructor,lazy_constructor_options", From e575dda10cb088c2a03a18643e8eb2f90bc88d2d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 20:45:44 -0500 Subject: [PATCH 7/8] docs(CHANGES): Note #425 and organize / tidy up --- CHANGES | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 6cba649fa..199295145 100644 --- a/CHANGES +++ b/CHANGES @@ -29,13 +29,17 @@ URL renamings (#417): ### Improvements -- URLs (#423): - - `hg`: Add `HgBaseURL`, `HgPipURL` - - `svn`: Add `SvnBaseURL`, `SvnPipURL` - - `URLProtocol`: Fix `is_valid` to use `classmethod` - - All: Fix `is_valid` to use default of `None` to avoid implicitly filtering - - Reduce duplicated code in methods by using `super()` - - `git`: Fix `update_repo` when there are only untracked files +Sync: + +- `git`: Fix `update_repo` when there are only untracked files (#425, credit: @jfpedroza) + +URLs (#423): + +- `hg`: Add `HgBaseURL`, `HgPipURL` +- `svn`: Add `SvnBaseURL`, `SvnPipURL` +- `URLProtocol`: Fix `is_valid` to use `classmethod` +- All: Fix `is_valid` to use default of `None` to avoid implicitly filtering +- Reduce duplicated code in methods by using `super()` ### Packaging From 6d329bd7511477cf72c41ccb591bef74a909d7ca Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 20:48:11 -0500 Subject: [PATCH 8/8] Tag v0.17.0a1 (includes #425 update_repo fix) --- pyproject.toml | 2 +- src/libvcs/__about__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ef2b65834..db35e0d07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "libvcs" -version = "0.17.0a0" +version = "0.17.0a1" description = "Lite, typed, python utilities for Git, SVN, Mercurial, etc." license = "MIT" authors = ["Tony Narlock "] diff --git a/src/libvcs/__about__.py b/src/libvcs/__about__.py index 94866e464..64d53ef9f 100644 --- a/src/libvcs/__about__.py +++ b/src/libvcs/__about__.py @@ -1,7 +1,7 @@ __title__ = "libvcs" __package_name__ = "libvcs" __description__ = "Lite, typed, python utilities for Git, SVN, Mercurial, etc." -__version__ = "0.17.0a0" +__version__ = "0.17.0a1" __author__ = "Tony Narlock" __github__ = "https://github.com/vcs-python/libvcs" __docs__ = "https://libvcs.git-pull.com"