Skip to content

Commit 11e5958

Browse files
authored
feat(sync): vcspull sync with no arg shows help (#395)
2 parents e4476ea + b238d83 commit 11e5958

File tree

4 files changed

+162
-5
lines changed

4 files changed

+162
-5
lines changed

CHANGES

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

27+
- Empty command will now show help output
28+
29+
```console
30+
$ vcspull sync
31+
Usage: vcspull sync [OPTIONS] [REPO_TERMS]...
32+
33+
Options:
34+
-c, --config PATH Specify config
35+
-x, --exit-on-error Exit immediately when encountering an error syncing
36+
multiple repos
37+
-h, --help Show this message and exit.
38+
```
39+
40+
To achieve the equivalent behavior of syncing all repos, pass `'*'`:
41+
42+
```console
43+
$ vcspull sync '*'
44+
```
45+
46+
Depending on how shell escaping works in your shell setup with [wild card / asterisk], you may not need to quote `*`.
47+
48+
[wild card / asterisk]: https://tldp.org/LDP/abs/html/special-chars.html#:~:text=wild%20card%20%5Basterisk%5D.
49+
2750
- Terms with no match in config will show a notice (#394)
2851

2952
> No repo found in config(s) for "non_existent_repo"

docs/cli/sync.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,41 @@
44

55
# vcspull sync
66

7+
## Filtering repos
8+
9+
As of 1.13.x, `$ vcspull sync` with no args passed will show a help dialog:
10+
11+
```console
12+
$ vcspull sync
13+
Usage: vcspull sync [OPTIONS] [REPO_TERMS]...
14+
```
15+
16+
### Sync all repos
17+
18+
Depending on how your terminal works with shell escapes for expands such as the [wild card / asterisk], you may not need to quote `*`.
19+
20+
```console
21+
$ vcspull sync '*'
22+
```
23+
24+
[wild card / asterisk]: https://tldp.org/LDP/abs/html/special-chars.html#:~:text=wild%20card%20%5Basterisk%5D.
25+
26+
### Filtering
27+
28+
Filter all repos start with "django-":
29+
30+
```console
31+
$ vcspull sync 'django-*'
32+
```
33+
34+
### Multiple terms
35+
36+
Filter all repos start with "django-":
37+
38+
```console
39+
$ vcspull sync 'django-anymail' 'django-guardian'
40+
```
41+
742
## Error handling
843

944
### Repos not found in config

src/vcspull/cli/sync.py

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

6565

6666
@click.command(name="sync")
67+
@click.pass_context
6768
@click.argument(
6869
"repo_terms", type=click.STRING, nargs=-1, shell_complete=get_repo_completions
6970
)
@@ -83,7 +84,7 @@ def clamp(n, _min, _max):
8384
default=False,
8485
help="Exit immediately when encountering an error syncing multiple repos",
8586
)
86-
def sync(repo_terms, config, exit_on_error: bool) -> None:
87+
def sync(ctx, repo_terms, config, exit_on_error: bool) -> None:
8788
if config:
8889
configs = load_configs([config])
8990
else:
@@ -108,7 +109,8 @@ def sync(repo_terms, config, exit_on_error: bool) -> None:
108109
filter_repos(configs, dir=dir, vcs_url=vcs_url, name=name)
109110
)
110111
else:
111-
found_repos = configs
112+
click.echo(ctx.get_help(), color=ctx.color)
113+
ctx.exit()
112114

113115
for repo in found_repos:
114116
try:

tests/test_cli.py

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from click.testing import CliRunner
99

1010
from libvcs.sync.git import GitSync
11+
from vcspull.__about__ import __version__
1112
from vcspull.cli import cli
1213
from vcspull.cli.sync import EXIT_ON_ERROR_MSG, NO_REPOS_FOR_TERM_MSG
1314

@@ -99,11 +100,96 @@ def test_sync_cli_repo_term_non_existent(
99100
assert needle not in output
100101

101102

103+
class SyncFixture(t.NamedTuple):
104+
test_id: str
105+
sync_args: list[str]
106+
expected_exit_code: int
107+
expected_in_output: "ExpectedOutput" = None
108+
expected_not_in_output: "ExpectedOutput" = None
109+
110+
111+
SYNC_REPO_FIXTURES = [
112+
# Empty (root command)
113+
SyncFixture(
114+
test_id="empty",
115+
sync_args=[],
116+
expected_exit_code=0,
117+
expected_in_output=["Options:", "Commands:"],
118+
),
119+
# Version
120+
SyncFixture(
121+
test_id="--version",
122+
sync_args=["--version"],
123+
expected_exit_code=0,
124+
expected_in_output=[__version__, ", libvcs"],
125+
),
126+
SyncFixture(
127+
test_id="-V",
128+
sync_args=["-V"],
129+
expected_exit_code=0,
130+
expected_in_output=[__version__, ", libvcs"],
131+
),
132+
# Help
133+
SyncFixture(
134+
test_id="--help",
135+
sync_args=["--help"],
136+
expected_exit_code=0,
137+
expected_in_output=["Options:", "Commands:"],
138+
),
139+
SyncFixture(
140+
test_id="-h",
141+
sync_args=["-h"],
142+
expected_exit_code=0,
143+
expected_in_output=["Options:", "Commands:"],
144+
),
145+
# Sync
146+
SyncFixture(
147+
test_id="sync--empty",
148+
sync_args=["sync"],
149+
expected_exit_code=0,
150+
expected_in_output="Options:",
151+
expected_not_in_output="Commands:",
152+
),
153+
# Sync: Help
154+
SyncFixture(
155+
test_id="sync---help",
156+
sync_args=["sync", "--help"],
157+
expected_exit_code=0,
158+
expected_in_output="Options:",
159+
expected_not_in_output="Commands:",
160+
),
161+
SyncFixture(
162+
test_id="sync--h",
163+
sync_args=["sync", "-h"],
164+
expected_exit_code=0,
165+
expected_in_output="Options:",
166+
expected_not_in_output="Commands:",
167+
),
168+
# Sync: Repo terms
169+
SyncFixture(
170+
test_id="sync--one-repo-term",
171+
sync_args=["sync", "my_git_repo"],
172+
expected_exit_code=0,
173+
expected_in_output="my_git_repo",
174+
),
175+
]
176+
177+
178+
@pytest.mark.parametrize(
179+
list(SyncFixture._fields),
180+
SYNC_REPO_FIXTURES,
181+
ids=[test.test_id for test in SYNC_REPO_FIXTURES],
182+
)
102183
def test_sync(
103184
user_path: pathlib.Path,
104185
config_path: pathlib.Path,
105186
tmp_path: pathlib.Path,
106187
git_repo: GitSync,
188+
test_id: str,
189+
sync_args: list[str],
190+
expected_exit_code: int,
191+
expected_in_output: "ExpectedOutput",
192+
expected_not_in_output: "ExpectedOutput",
107193
) -> None:
108194
runner = CliRunner()
109195
with runner.isolated_filesystem(temp_dir=tmp_path):
@@ -124,10 +210,21 @@ def test_sync(
124210
yaml_config.write_text(yaml_config_data, encoding="utf-8")
125211

126212
# CLI can sync
127-
result = runner.invoke(cli, ["sync", "my_git_repo"])
128-
assert result.exit_code == 0
213+
result = runner.invoke(cli, sync_args)
214+
assert result.exit_code == expected_exit_code
129215
output = "".join(list(result.output))
130-
assert "my_git_repo" in output
216+
217+
if expected_in_output is not None:
218+
if isinstance(expected_in_output, str):
219+
expected_in_output = [expected_in_output]
220+
for needle in expected_in_output:
221+
assert needle in output
222+
223+
if expected_not_in_output is not None:
224+
if isinstance(expected_not_in_output, str):
225+
expected_not_in_output = [expected_not_in_output]
226+
for needle in expected_not_in_output:
227+
assert needle not in output
131228

132229

133230
class SyncBrokenFixture(t.NamedTuple):

0 commit comments

Comments
 (0)