Skip to content

Commit 5ccb284

Browse files
authored
feat(cli-sync): Print msg if repo lookup not in config (#394)
Test fixtures tweaks vcspull sync now prints a warning if repo not found in config Fixes #388
2 parents 2e7844c + d4e26e6 commit 5ccb284

File tree

4 files changed

+126
-19
lines changed

4 files changed

+126
-19
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ $ pipx install --suffix=@next 'vcspull' --pip-args '\--pre' --force
2424
- Refreshed logo
2525
- `vcspull sync`:
2626

27+
- Terms with no match in config will show a notice (#394)
28+
29+
> No repo found in config(s) for "non_existent_repo"
30+
2731
- Syncing will now skip to the next repos if an error is encountered
2832

2933
- Learned `--exit-on-error` / `-x`

docs/cli/sync.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,35 @@
66

77
## Error handling
88

9+
### Repos not found in config
10+
11+
As of 1.13.x, if you enter a repo term (or terms) that aren't found throughout
12+
your configurations, it will show a warning:
13+
14+
```console
15+
$ vcspull sync non_existent_repo
16+
No repo found in config(s) for "non_existent_repo"
17+
```
18+
19+
```console
20+
$ vcspull sync non_existent_repo existing_repo
21+
No repo found in config(s) for "non_existent_repo"
22+
```
23+
24+
```console
25+
$ vcspull sync non_existent_repo existing_repo another_repo_not_in_config
26+
No repo found in config(s) for "non_existent_repo"
27+
No repo found in config(s) for "another_repo_not_in_config"
28+
```
29+
30+
Since syncing terms are treated as a filter rather than a lookup, the message is
31+
considered a warning, so will not exit even if `--exit-on-error` flag is used.
32+
33+
### Syncing
34+
935
As of 1.13.x, vcspull will continue to the next repo if an error is encountered when syncing multiple repos.
1036

11-
To imitate the old behavior, use `--exit-on-error` / `-x`:
37+
To imitate the old behavior, the `--exit-on-error` / `-x` flag:
1238

1339
```console
1440
$ vcspull sync --exit-on-error grako django

src/vcspull/cli/sync.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def clamp(n, _min, _max):
6060

6161

6262
EXIT_ON_ERROR_MSG = "Exiting via error (--exit-on-error passed)"
63+
NO_REPOS_FOR_TERM_MSG = 'No repo found in config(s) for "{name}"'
6364

6465

6566
@click.command(name="sync")
@@ -100,6 +101,9 @@ def sync(repo_terms, config, exit_on_error: bool) -> None:
100101
name = repo_term
101102

102103
# collect the repos from the config files
104+
found = filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name)
105+
if len(found) == 0:
106+
click.echo(NO_REPOS_FOR_TERM_MSG.format(name=name))
103107
found_repos.extend(
104108
filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name)
105109
)

tests/test_cli.py

Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,94 @@
99

1010
from libvcs.sync.git import GitSync
1111
from vcspull.cli import cli
12-
from vcspull.cli.sync import EXIT_ON_ERROR_MSG
12+
from vcspull.cli.sync import EXIT_ON_ERROR_MSG, NO_REPOS_FOR_TERM_MSG
1313

14+
if t.TYPE_CHECKING:
15+
from typing_extensions import TypeAlias
16+
17+
ExpectedOutput: TypeAlias = t.Optional[t.Union[str, t.List[str]]]
18+
19+
20+
class SyncCLINonExistentRepo(t.NamedTuple):
21+
test_id: str
22+
sync_args: list[str]
23+
expected_exit_code: int
24+
expected_in_output: "ExpectedOutput" = None
25+
expected_not_in_output: "ExpectedOutput" = None
26+
27+
28+
SYNC_CLI_EXISTENT_REPO_FIXTURES = [
29+
SyncCLINonExistentRepo(
30+
test_id="exists",
31+
sync_args=["my_git_project"],
32+
expected_exit_code=0,
33+
expected_in_output="Already on 'master'",
34+
expected_not_in_output=NO_REPOS_FOR_TERM_MSG.format(name="my_git_repo"),
35+
),
36+
SyncCLINonExistentRepo(
37+
test_id="non-existent-only",
38+
sync_args=["this_isnt_in_the_config"],
39+
expected_exit_code=0,
40+
expected_in_output=NO_REPOS_FOR_TERM_MSG.format(name="this_isnt_in_the_config"),
41+
),
42+
SyncCLINonExistentRepo(
43+
test_id="non-existent-mixed",
44+
sync_args=["this_isnt_in_the_config", "my_git_project", "another"],
45+
expected_exit_code=0,
46+
expected_in_output=[
47+
NO_REPOS_FOR_TERM_MSG.format(name="this_isnt_in_the_config"),
48+
NO_REPOS_FOR_TERM_MSG.format(name="another"),
49+
],
50+
expected_not_in_output=NO_REPOS_FOR_TERM_MSG.format(name="my_git_repo"),
51+
),
52+
]
53+
54+
55+
@pytest.mark.parametrize(
56+
list(SyncCLINonExistentRepo._fields),
57+
SYNC_CLI_EXISTENT_REPO_FIXTURES,
58+
ids=[test.test_id for test in SYNC_CLI_EXISTENT_REPO_FIXTURES],
59+
)
60+
def test_sync_cli_repo_term_non_existent(
61+
user_path: pathlib.Path,
62+
config_path: pathlib.Path,
63+
tmp_path: pathlib.Path,
64+
git_repo: GitSync,
65+
test_id: str,
66+
sync_args: list[str],
67+
expected_exit_code: int,
68+
expected_in_output: "ExpectedOutput",
69+
expected_not_in_output: "ExpectedOutput",
70+
) -> None:
71+
config = {
72+
"~/github_projects/": {
73+
"my_git_project": {
74+
"url": f"git+file://{git_repo.dir}",
75+
"remotes": {"test_remote": f"git+file://{git_repo.dir}"},
76+
},
77+
}
78+
}
79+
yaml_config = config_path / ".vcspull.yaml"
80+
yaml_config_data = yaml.dump(config, default_flow_style=False)
81+
yaml_config.write_text(yaml_config_data, encoding="utf-8")
1482

15-
def test_sync_cli_non_existent(tmp_path: pathlib.Path) -> None:
1683
runner = CliRunner()
1784
with runner.isolated_filesystem(temp_dir=tmp_path):
18-
result = runner.invoke(cli, ["sync", "hi"])
19-
assert result.exit_code == 0
20-
assert "" in result.output
85+
result = runner.invoke(cli, ["sync", *sync_args])
86+
assert result.exit_code == expected_exit_code
87+
output = "".join(list(result.output))
88+
89+
if expected_in_output is not None:
90+
if isinstance(expected_in_output, str):
91+
expected_in_output = [expected_in_output]
92+
for needle in expected_in_output:
93+
assert needle in output
94+
95+
if expected_not_in_output is not None:
96+
if isinstance(expected_not_in_output, str):
97+
expected_not_in_output = [expected_not_in_output]
98+
for needle in expected_not_in_output:
99+
assert needle not in output
21100

22101

23102
def test_sync(
@@ -51,12 +130,6 @@ def test_sync(
51130
assert "my_git_repo" in output
52131

53132

54-
if t.TYPE_CHECKING:
55-
from typing_extensions import TypeAlias
56-
57-
ExpectedOutput: TypeAlias = t.Optional[t.Union[str, t.List[str]]]
58-
59-
60133
class SyncBrokenFixture(t.NamedTuple):
61134
test_id: str
62135
sync_args: list[str]
@@ -86,25 +159,25 @@ class SyncBrokenFixture(t.NamedTuple):
86159
),
87160
SyncBrokenFixture(
88161
test_id="normal-first-broken",
89-
sync_args=["non_existent_repo", "my_git_repo"],
162+
sync_args=["my_git_repo_not_found", "my_git_repo"],
90163
expected_exit_code=0,
91164
expected_not_in_output=EXIT_ON_ERROR_MSG,
92165
),
93166
SyncBrokenFixture(
94167
test_id="normal-last-broken",
95-
sync_args=["my_git_repo", "non_existent_repo"],
168+
sync_args=["my_git_repo", "my_git_repo_not_found"],
96169
expected_exit_code=0,
97170
expected_not_in_output=EXIT_ON_ERROR_MSG,
98171
),
99172
SyncBrokenFixture(
100173
test_id="exit-on-error--exit-on-error-first-broken",
101-
sync_args=["non_existent_repo", "my_git_repo", "--exit-on-error"],
174+
sync_args=["my_git_repo_not_found", "my_git_repo", "--exit-on-error"],
102175
expected_exit_code=1,
103176
expected_in_output=EXIT_ON_ERROR_MSG,
104177
),
105178
SyncBrokenFixture(
106179
test_id="exit-on-error--x-first-broken",
107-
sync_args=["non_existent_repo", "my_git_repo", "-x"],
180+
sync_args=["my_git_repo_not_found", "my_git_repo", "-x"],
108181
expected_exit_code=1,
109182
expected_in_output=EXIT_ON_ERROR_MSG,
110183
expected_not_in_output="master",
@@ -114,13 +187,13 @@ class SyncBrokenFixture(t.NamedTuple):
114187
#
115188
SyncBrokenFixture(
116189
test_id="exit-on-error--exit-on-error-last-broken",
117-
sync_args=["my_git_repo", "non_existent_repo", "-x"],
190+
sync_args=["my_git_repo", "my_git_repo_not_found", "-x"],
118191
expected_exit_code=1,
119192
expected_in_output=[EXIT_ON_ERROR_MSG, "Already on 'master'"],
120193
),
121194
SyncBrokenFixture(
122195
test_id="exit-on-error--x-last-item",
123-
sync_args=["my_git_repo", "non_existent_repo", "--exit-on-error"],
196+
sync_args=["my_git_repo", "my_git_repo_not_found", "--exit-on-error"],
124197
expected_exit_code=1,
125198
expected_in_output=[EXIT_ON_ERROR_MSG, "Already on 'master'"],
126199
),
@@ -157,7 +230,7 @@ def test_sync_broken(
157230
"url": f"git+file://{git_repo.dir}",
158231
"remotes": {"test_remote": f"git+file://{git_repo.dir}"},
159232
},
160-
"non_existent_repo": {
233+
"my_git_repo_not_found": {
161234
"url": "git+file:///dev/null",
162235
},
163236
}

0 commit comments

Comments
 (0)