From 72bea243c04a625e4dc9f8d8722d8aa828c04d34 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 15:09:07 -0500 Subject: [PATCH 1/5] tests(test_cli): Update fixture name --- tests/test_cli.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index cd6a8080..8c46acef 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -86,25 +86,25 @@ class SyncBrokenFixture(t.NamedTuple): ), SyncBrokenFixture( test_id="normal-first-broken", - sync_args=["non_existent_repo", "my_git_repo"], + sync_args=["my_git_repo_not_found", "my_git_repo"], expected_exit_code=0, expected_not_in_output=EXIT_ON_ERROR_MSG, ), SyncBrokenFixture( test_id="normal-last-broken", - sync_args=["my_git_repo", "non_existent_repo"], + sync_args=["my_git_repo", "my_git_repo_not_found"], expected_exit_code=0, expected_not_in_output=EXIT_ON_ERROR_MSG, ), SyncBrokenFixture( test_id="exit-on-error--exit-on-error-first-broken", - sync_args=["non_existent_repo", "my_git_repo", "--exit-on-error"], + sync_args=["my_git_repo_not_found", "my_git_repo", "--exit-on-error"], expected_exit_code=1, expected_in_output=EXIT_ON_ERROR_MSG, ), SyncBrokenFixture( test_id="exit-on-error--x-first-broken", - sync_args=["non_existent_repo", "my_git_repo", "-x"], + sync_args=["my_git_repo_not_found", "my_git_repo", "-x"], expected_exit_code=1, expected_in_output=EXIT_ON_ERROR_MSG, expected_not_in_output="master", @@ -114,13 +114,13 @@ class SyncBrokenFixture(t.NamedTuple): # SyncBrokenFixture( test_id="exit-on-error--exit-on-error-last-broken", - sync_args=["my_git_repo", "non_existent_repo", "-x"], + sync_args=["my_git_repo", "my_git_repo_not_found", "-x"], expected_exit_code=1, expected_in_output=[EXIT_ON_ERROR_MSG, "Already on 'master'"], ), SyncBrokenFixture( test_id="exit-on-error--x-last-item", - sync_args=["my_git_repo", "non_existent_repo", "--exit-on-error"], + sync_args=["my_git_repo", "my_git_repo_not_found", "--exit-on-error"], expected_exit_code=1, expected_in_output=[EXIT_ON_ERROR_MSG, "Already on 'master'"], ), @@ -157,7 +157,7 @@ def test_sync_broken( "url": f"git+file://{git_repo.dir}", "remotes": {"test_remote": f"git+file://{git_repo.dir}"}, }, - "non_existent_repo": { + "my_git_repo_not_found": { "url": "git+file:///dev/null", }, } From df8f49fd818bef9255c5b81797ce1afe84ab60df Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 16:00:14 -0500 Subject: [PATCH 2/5] feat(sync): Show output repo term not found in config --- src/vcspull/cli/sync.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vcspull/cli/sync.py b/src/vcspull/cli/sync.py index 99280f19..f91328ea 100644 --- a/src/vcspull/cli/sync.py +++ b/src/vcspull/cli/sync.py @@ -60,6 +60,7 @@ def clamp(n, _min, _max): EXIT_ON_ERROR_MSG = "Exiting via error (--exit-on-error passed)" +NO_REPOS_FOR_TERM_MSG = 'No repo found in config(s) for "{name}"' @click.command(name="sync") @@ -100,6 +101,9 @@ def sync(repo_terms, config, exit_on_error: bool) -> None: name = repo_term # collect the repos from the config files + found = filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name) + if len(found) == 0: + click.echo(NO_REPOS_FOR_TERM_MSG.format(name=name)) found_repos.extend( filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name) ) From c947b8ee96a9aef139d229db799a398a31794177 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 16:01:22 -0500 Subject: [PATCH 3/5] test(test_cli): Test repo lookup not found --- tests/test_cli.py | 95 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 8c46acef..00fd9f59 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -9,15 +9,94 @@ from libvcs.sync.git import GitSync from vcspull.cli import cli -from vcspull.cli.sync import EXIT_ON_ERROR_MSG +from vcspull.cli.sync import EXIT_ON_ERROR_MSG, NO_REPOS_FOR_TERM_MSG +if t.TYPE_CHECKING: + from typing_extensions import TypeAlias + + ExpectedOutput: TypeAlias = t.Optional[t.Union[str, t.List[str]]] + + +class SyncCLINonExistentRepo(t.NamedTuple): + test_id: str + sync_args: list[str] + expected_exit_code: int + expected_in_output: "ExpectedOutput" = None + expected_not_in_output: "ExpectedOutput" = None + + +SYNC_CLI_EXISTENT_REPO_FIXTURES = [ + SyncCLINonExistentRepo( + test_id="exists", + sync_args=["my_git_project"], + expected_exit_code=0, + expected_in_output="Already on 'master'", + expected_not_in_output=NO_REPOS_FOR_TERM_MSG.format(name="my_git_repo"), + ), + SyncCLINonExistentRepo( + test_id="non-existent-only", + sync_args=["this_isnt_in_the_config"], + expected_exit_code=0, + expected_in_output=NO_REPOS_FOR_TERM_MSG.format(name="this_isnt_in_the_config"), + ), + SyncCLINonExistentRepo( + test_id="non-existent-mixed", + sync_args=["this_isnt_in_the_config", "my_git_project", "another"], + expected_exit_code=0, + expected_in_output=[ + NO_REPOS_FOR_TERM_MSG.format(name="this_isnt_in_the_config"), + NO_REPOS_FOR_TERM_MSG.format(name="another"), + ], + expected_not_in_output=NO_REPOS_FOR_TERM_MSG.format(name="my_git_repo"), + ), +] + + +@pytest.mark.parametrize( + list(SyncCLINonExistentRepo._fields), + SYNC_CLI_EXISTENT_REPO_FIXTURES, + ids=[test.test_id for test in SYNC_CLI_EXISTENT_REPO_FIXTURES], +) +def test_sync_cli_repo_term_non_existent( + user_path: pathlib.Path, + config_path: pathlib.Path, + tmp_path: pathlib.Path, + git_repo: GitSync, + test_id: str, + sync_args: list[str], + expected_exit_code: int, + expected_in_output: "ExpectedOutput", + expected_not_in_output: "ExpectedOutput", +) -> None: + config = { + "~/github_projects/": { + "my_git_project": { + "url": f"git+file://{git_repo.dir}", + "remotes": {"test_remote": f"git+file://{git_repo.dir}"}, + }, + } + } + yaml_config = config_path / ".vcspull.yaml" + yaml_config_data = yaml.dump(config, default_flow_style=False) + yaml_config.write_text(yaml_config_data, encoding="utf-8") -def test_sync_cli_non_existent(tmp_path: pathlib.Path) -> None: runner = CliRunner() with runner.isolated_filesystem(temp_dir=tmp_path): - result = runner.invoke(cli, ["sync", "hi"]) - assert result.exit_code == 0 - assert "" in result.output + result = runner.invoke(cli, ["sync", *sync_args]) + assert result.exit_code == expected_exit_code + output = "".join(list(result.output)) + + if expected_in_output is not None: + if isinstance(expected_in_output, str): + expected_in_output = [expected_in_output] + for needle in expected_in_output: + assert needle in output + + if expected_not_in_output is not None: + if isinstance(expected_not_in_output, str): + expected_not_in_output = [expected_not_in_output] + for needle in expected_not_in_output: + assert needle not in output def test_sync( @@ -51,12 +130,6 @@ def test_sync( assert "my_git_repo" in output -if t.TYPE_CHECKING: - from typing_extensions import TypeAlias - - ExpectedOutput: TypeAlias = t.Optional[t.Union[str, t.List[str]]] - - class SyncBrokenFixture(t.NamedTuple): test_id: str sync_args: list[str] From 0576a168a776f9ee8151fa27f68e0e55175310d1 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 16:05:21 -0500 Subject: [PATCH 4/5] docs(CHANGES): Note repo not in config behavior --- CHANGES | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 827bc78a..76b35660 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,10 @@ $ pipx install --suffix=@next 'vcspull' --pip-args '\--pre' --force - Refreshed logo - `vcspull sync`: + - Terms with no match in config will show a notice (#394) + + > No repo found in config(s) for "non_existent_repo" + - Syncing will now skip to the next repos if an error is encountered - Learned `--exit-on-error` / `-x` From d4e26e6a32eeb70ab54699c11a8bcf39a4bd4fed Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 25 Sep 2022 16:10:39 -0500 Subject: [PATCH 5/5] docs(sync): Note repos not found behavior --- docs/cli/sync.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/cli/sync.md b/docs/cli/sync.md index 5153ef95..1cc636fc 100644 --- a/docs/cli/sync.md +++ b/docs/cli/sync.md @@ -6,9 +6,35 @@ ## Error handling +### Repos not found in config + +As of 1.13.x, if you enter a repo term (or terms) that aren't found throughout +your configurations, it will show a warning: + +```console +$ vcspull sync non_existent_repo +No repo found in config(s) for "non_existent_repo" +``` + +```console +$ vcspull sync non_existent_repo existing_repo +No repo found in config(s) for "non_existent_repo" +``` + +```console +$ vcspull sync non_existent_repo existing_repo another_repo_not_in_config +No repo found in config(s) for "non_existent_repo" +No repo found in config(s) for "another_repo_not_in_config" +``` + +Since syncing terms are treated as a filter rather than a lookup, the message is +considered a warning, so will not exit even if `--exit-on-error` flag is used. + +### Syncing + As of 1.13.x, vcspull will continue to the next repo if an error is encountered when syncing multiple repos. -To imitate the old behavior, use `--exit-on-error` / `-x`: +To imitate the old behavior, the `--exit-on-error` / `-x` flag: ```console $ vcspull sync --exit-on-error grako django