Skip to content

Commit eb8bea9

Browse files
committed
cli(refactor[add/add_from_fs]): Complete refactoring to match vcspull patterns
why: Ensure consistency with project patterns and improve code maintainability. what: - Updated module and function docstrings to match NumPy style - Cleaned up imports and removed unnecessary comments - Fixed error handling to match sync.py patterns - Fixed type annotations for mypy compliance - Fixed non-recursive scanning bug in add_from_fs.py - Removed unused helper functions in tests
1 parent 68985fb commit eb8bea9

File tree

5 files changed

+567
-813
lines changed

5 files changed

+567
-813
lines changed

src/vcspull/cli/__init__.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def create_parser(
5050
get_all_subparsers: t.Literal[True],
5151
) -> tuple[
5252
argparse.ArgumentParser,
53-
argparse._SubParsersAction,
53+
argparse._SubParsersAction[argparse.ArgumentParser],
5454
dict[str, argparse.ArgumentParser],
5555
]: ...
5656

@@ -63,7 +63,7 @@ def create_parser(
6363
| tuple[argparse.ArgumentParser, t.Any]
6464
| tuple[
6565
argparse.ArgumentParser,
66-
argparse._SubParsersAction,
66+
argparse._SubParsersAction[argparse.ArgumentParser],
6767
dict[str, argparse.ArgumentParser],
6868
]
6969
):
@@ -140,26 +140,18 @@ def cli(_args: list[str] | None = None) -> None:
140140
return
141141
if args.subparser_name == "sync":
142142
sync_parser = all_parsers["sync"]
143-
sync_kwargs = {
144-
"repo_patterns": args.repo_patterns
145-
if hasattr(args, "repo_patterns")
146-
else None,
147-
"config": (
143+
sync(
144+
repo_patterns=args.repo_patterns if hasattr(args, "repo_patterns") else [],
145+
config=(
148146
pathlib.Path(args.config)
149147
if hasattr(args, "config") and args.config
150148
else None
151149
),
152-
"exit_on_error": args.exit_on_error
150+
exit_on_error=args.exit_on_error
153151
if hasattr(args, "exit_on_error")
154152
else False,
155-
"parser": sync_parser,
156-
}
157-
sync_kwargs = {
158-
k: v
159-
for k, v in sync_kwargs.items()
160-
if v is not None or k in {"parser", "exit_on_error", "config"}
161-
}
162-
sync(**sync_kwargs)
153+
parser=sync_parser,
154+
)
163155
elif args.subparser_name == "add":
164156
all_parsers["add"]
165157
add_repo_kwargs = {

src/vcspull/cli/add.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Simplified CLI functionality for vcspull add."""
1+
"""Add repository functionality for vcspull."""
22

33
from __future__ import annotations
44

@@ -8,10 +8,7 @@
88

99
import yaml
1010

11-
from vcspull.config import (
12-
find_home_config_files,
13-
save_config_yaml,
14-
)
11+
from vcspull.config import find_home_config_files, save_config_yaml
1512

1613
if t.TYPE_CHECKING:
1714
import argparse
@@ -20,7 +17,7 @@
2017

2118

2219
def create_add_subparser(parser: argparse.ArgumentParser) -> None:
23-
"""Configure :py:class:`argparse.ArgumentParser` for ``vcspull add``."""
20+
"""Create ``vcspull add`` argument subparser."""
2421
parser.add_argument(
2522
"-c",
2623
"--config",
@@ -58,7 +55,7 @@ def add_repo(
5855
base_dir: str | None,
5956
) -> None:
6057
"""Add a repository to the vcspull configuration.
61-
58+
6259
Parameters
6360
----------
6461
name : str
@@ -105,7 +102,11 @@ def add_repo(
105102
)
106103
return
107104
except Exception:
108-
log.exception(f"Error loading YAML from {config_file_path}. Aborting.")
105+
log.error(f"Error loading YAML from {config_file_path}. Aborting.")
106+
if log.isEnabledFor(logging.DEBUG):
107+
import traceback
108+
109+
traceback.print_exc()
109110
return
110111
else:
111112
log.info(
@@ -134,8 +135,7 @@ def add_repo(
134135
raw_config[base_dir_key] = {}
135136
elif not isinstance(raw_config[base_dir_key], dict):
136137
log.error(
137-
f"Configuration section '{base_dir_key}' is not a dictionary. "
138-
"Aborting.",
138+
f"Configuration section '{base_dir_key}' is not a dictionary. Aborting.",
139139
)
140140
return
141141

@@ -159,4 +159,9 @@ def add_repo(
159159
f"under '{base_dir_key}'.",
160160
)
161161
except Exception:
162-
log.exception(f"Error saving config to {config_file_path}")
162+
log.error(f"Error saving config to {config_file_path}")
163+
if log.isEnabledFor(logging.DEBUG):
164+
import traceback
165+
166+
traceback.print_exc()
167+
raise

src/vcspull/cli/add_from_fs.py

Lines changed: 106 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,36 @@
1-
"""CLI functionality for vcspull add-from-fs."""
1+
"""Filesystem scanning functionality for vcspull."""
22

33
from __future__ import annotations
44

55
import logging
6-
import os # For os.walk
6+
import os
77
import pathlib
8-
import subprocess # For git commands
8+
import subprocess
99
import typing as t
1010

11-
import yaml # For YAML processing
11+
import yaml
1212

13-
from vcspull.config import ( # Assuming these are useful
14-
expand_dir,
15-
find_home_config_files,
16-
save_config_yaml,
17-
)
13+
from vcspull.config import expand_dir, find_home_config_files, save_config_yaml
1814

1915
if t.TYPE_CHECKING:
2016
import argparse
21-
# Add any necessary type imports
2217

2318
log = logging.getLogger(__name__)
2419

2520

2621
def get_git_origin_url(repo_path: pathlib.Path) -> str | None:
27-
"""Get the URL of the 'origin' remote for a git repository."""
22+
"""Get the origin URL from a git repository.
23+
24+
Parameters
25+
----------
26+
repo_path : pathlib.Path
27+
Path to the git repository
28+
29+
Returns
30+
-------
31+
str | None
32+
The origin URL if found, None otherwise
33+
"""
2834
try:
2935
result = subprocess.run(
3036
["git", "config", "--get", "remote.origin.url"],
@@ -40,7 +46,7 @@ def get_git_origin_url(repo_path: pathlib.Path) -> str | None:
4046

4147

4248
def create_add_from_fs_subparser(parser: argparse.ArgumentParser) -> None:
43-
"""Configure :py:class:`argparse.ArgumentParser` for ``vcspull add-from-fs``."""
49+
"""Create ``vcspull add-from-fs`` argument subparser."""
4450
parser.add_argument(
4551
"-c",
4652
"--config",
@@ -82,7 +88,21 @@ def add_from_filesystem(
8288
base_dir_key_arg: str | None,
8389
yes: bool,
8490
) -> None:
85-
"""Scan a directory and add found git repositories to the vcspull configuration."""
91+
"""Scan filesystem for git repositories and add to vcspull config.
92+
93+
Parameters
94+
----------
95+
scan_dir_str : str
96+
Directory to scan for git repositories
97+
config_file_path_str : str | None
98+
Path to config file, or None to use default
99+
recursive : bool
100+
Whether to scan subdirectories recursively
101+
base_dir_key_arg : str | None
102+
Base directory key to use in config (overrides automatic detection)
103+
yes : bool
104+
Whether to skip confirmation prompt
105+
"""
86106
scan_dir = expand_dir(pathlib.Path(scan_dir_str))
87107

88108
config_file_path: pathlib.Path
@@ -115,8 +135,12 @@ def add_from_filesystem(
115135
"Aborting.",
116136
)
117137
return
118-
except Exception as e:
119-
log.exception(f"Error loading YAML from {config_file_path}. Aborting.")
138+
except Exception:
139+
log.error(f"Error loading YAML from {config_file_path}. Aborting.")
140+
if log.isEnabledFor(logging.DEBUG):
141+
import traceback
142+
143+
traceback.print_exc()
120144
return
121145
else:
122146
log.info(
@@ -127,46 +151,72 @@ def add_from_filesystem(
127151
tuple[str, str, str]
128152
] = [] # (repo_name, repo_url, determined_base_key)
129153

130-
for root, dirs, _ in os.walk(scan_dir):
131-
if ".git" in dirs:
132-
repo_path = pathlib.Path(root)
133-
repo_name = repo_path.name
134-
repo_url = get_git_origin_url(repo_path)
154+
if recursive:
155+
for root, dirs, _ in os.walk(scan_dir):
156+
if ".git" in dirs:
157+
repo_path = pathlib.Path(root)
158+
repo_name = repo_path.name
159+
repo_url = get_git_origin_url(repo_path)
135160

136-
if not repo_url:
137-
log.warning(
138-
f"Could not determine remote URL for git repository at "
139-
f"{repo_path}. Skipping.",
140-
)
141-
continue
142-
143-
determined_base_key: str
144-
if base_dir_key_arg:
145-
determined_base_key = (
146-
base_dir_key_arg
147-
if base_dir_key_arg.endswith("/")
148-
else base_dir_key_arg + "/"
149-
)
150-
else:
151-
# Try to make it relative to home if possible, otherwise absolute path
152-
# of scan_dir parent. This should be the parent of the repo_path itself,
153-
# relative to scan_dir or absolute. Or simply use scan_dir as the key.
154-
# Let's use the provided scan_dir (normalized) as the default key if
155-
# not given.
156-
try:
161+
if not repo_url:
162+
log.warning(
163+
f"Could not determine remote URL for git repository at "
164+
f"{repo_path}. Skipping.",
165+
)
166+
continue
167+
168+
determined_base_key: str
169+
if base_dir_key_arg:
157170
determined_base_key = (
158-
"~/" + str(scan_dir.relative_to(pathlib.Path.home())) + "/"
171+
base_dir_key_arg
172+
if base_dir_key_arg.endswith("/")
173+
else base_dir_key_arg + "/"
159174
)
160-
except ValueError:
161-
determined_base_key = str(scan_dir.resolve()) + "/"
175+
else:
176+
try:
177+
determined_base_key = (
178+
"~/" + str(scan_dir.relative_to(pathlib.Path.home())) + "/"
179+
)
180+
except ValueError:
181+
determined_base_key = str(scan_dir.resolve()) + "/"
162182

163-
if not determined_base_key.endswith("/"): # Ensure trailing slash
164-
determined_base_key += "/"
183+
if not determined_base_key.endswith("/"):
184+
determined_base_key += "/"
165185

166-
found_repos.append((repo_name, repo_url, determined_base_key))
186+
found_repos.append((repo_name, repo_url, determined_base_key))
187+
else:
188+
# Non-recursive: only check immediate subdirectories
189+
for item in scan_dir.iterdir():
190+
if item.is_dir() and (item / ".git").is_dir():
191+
repo_name = item.name
192+
repo_url = get_git_origin_url(item)
193+
194+
if not repo_url:
195+
log.warning(
196+
f"Could not determine remote URL for git repository at "
197+
f"{item}. Skipping.",
198+
)
199+
continue
200+
201+
determined_base_key: str
202+
if base_dir_key_arg:
203+
determined_base_key = (
204+
base_dir_key_arg
205+
if base_dir_key_arg.endswith("/")
206+
else base_dir_key_arg + "/"
207+
)
208+
else:
209+
try:
210+
determined_base_key = (
211+
"~/" + str(scan_dir.relative_to(pathlib.Path.home())) + "/"
212+
)
213+
except ValueError:
214+
determined_base_key = str(scan_dir.resolve()) + "/"
167215

168-
if not recursive:
169-
break # Only process top-level if not recursive
216+
if not determined_base_key.endswith("/"):
217+
determined_base_key += "/"
218+
219+
found_repos.append((repo_name, repo_url, determined_base_key))
170220

171221
if not found_repos:
172222
log.info(f"No git repositories found in {scan_dir}. Nothing to add.")
@@ -223,13 +273,17 @@ def add_from_filesystem(
223273
f"Adding '{repo_name}' ({repo_url}) under '{determined_base_key}'.",
224274
)
225275
changes_made = True
226-
# else: already handled by the confirmation logic or --yes skip
227276

228277
if changes_made:
229278
try:
230279
save_config_yaml(config_file_path, raw_config)
231280
log.info(f"Successfully updated {config_file_path}.")
232-
except Exception as e:
233-
log.exception(f"Error saving config to {config_file_path}")
281+
except Exception:
282+
log.error(f"Error saving config to {config_file_path}")
283+
if log.isEnabledFor(logging.DEBUG):
284+
import traceback
285+
286+
traceback.print_exc()
287+
raise
234288
else:
235289
log.info("No changes made to the configuration.")

0 commit comments

Comments
 (0)