1
- """CLI functionality for vcspull add."""
1
+ """Simplified CLI functionality for vcspull add."""
2
2
3
3
from __future__ import annotations
4
4
9
9
import yaml
10
10
11
11
from vcspull .config import (
12
- expand_dir ,
13
12
find_home_config_files ,
14
13
save_config_yaml ,
15
14
)
@@ -30,56 +29,70 @@ def create_add_subparser(parser: argparse.ArgumentParser) -> None:
30
29
help = "path to custom config file (default: .vcspull.yaml or ~/.vcspull.yaml)" ,
31
30
)
32
31
parser .add_argument (
33
- "repo_name_or_url" ,
34
- help = "Name of the repository or its URL. "
35
- "If only name, URL may be inferred or prompted." ,
32
+ "name" ,
33
+ help = "Name for the repository in the config" ,
36
34
)
37
35
parser .add_argument (
38
- "target_path" ,
39
- nargs = "?" ,
40
- help = "Optional local path for the repository (can be relative or absolute). "
41
- "Overrides default naming." ,
36
+ "url" ,
37
+ help = "Repository URL (e.g., https://github.com/user/repo.git)" ,
42
38
)
43
39
parser .add_argument (
44
- "--base-dir-key" ,
45
- help = "Specify the top-level directory key from vcspull config "
46
- "(e.g., '~/study/python/') under which to add this repo. "
47
- "If not given, vcspull will try to infer or use the current working directory." ,
40
+ "--path" ,
41
+ dest = "path" ,
42
+ help = "Local directory path where repo will be cloned "
43
+ "(determines base directory key if not specified with --dir)" ,
44
+ )
45
+ parser .add_argument (
46
+ "--dir" ,
47
+ dest = "base_dir" ,
48
+ help = "Base directory key in config (e.g., '~/projects/'). "
49
+ "If not specified, will be inferred from --path or use current directory." ,
48
50
)
49
51
50
52
51
53
def add_repo (
52
- repo_name_or_url : str ,
54
+ name : str ,
55
+ url : str ,
53
56
config_file_path_str : str | None ,
54
- target_path_str : str | None = None ,
55
- base_dir_key : str | None = None ,
57
+ path : str | None ,
58
+ base_dir : str | None ,
56
59
) -> None :
57
- """Add a repository to the vcspull configuration."""
60
+ """Add a repository to the vcspull configuration.
61
+
62
+ Parameters
63
+ ----------
64
+ name : str
65
+ Repository name for the config
66
+ url : str
67
+ Repository URL
68
+ config_file_path_str : str | None
69
+ Path to config file, or None to use default
70
+ path : str | None
71
+ Local path where repo will be cloned
72
+ base_dir : str | None
73
+ Base directory key to use in config
74
+ """
75
+ # Determine config file
58
76
config_file_path : pathlib .Path
59
77
if config_file_path_str :
60
78
config_file_path = pathlib .Path (config_file_path_str ).expanduser ().resolve ()
61
79
else :
62
- # Default to ~/.vcspull.yaml if no global --config is passed
63
- # Behavior of find_home_config_files might need adjustment if it returns a list
64
80
home_configs = find_home_config_files (filetype = ["yaml" ])
65
81
if not home_configs :
66
- # If no global --config and no home config, default to
67
- # creating .vcspull.yaml in CWD
68
82
config_file_path = pathlib .Path .cwd () / ".vcspull.yaml"
69
83
log .info (
70
84
f"No config specified and no default found, will create at "
71
85
f"{ config_file_path } " ,
72
86
)
73
87
elif len (home_configs ) > 1 :
74
- # This case should ideally be handled by the main CLI arg parsing
75
- # for --config
76
88
log .error (
77
- "Multiple home_config files found, please specify one with -c/--config" ,
89
+ "Multiple home config files found, please specify one with -c/--config" ,
78
90
)
79
91
return
80
92
else :
81
93
config_file_path = home_configs [0 ]
82
94
95
+ # Load existing config
83
96
raw_config : dict [str , t .Any ] = {}
84
97
if config_file_path .exists () and config_file_path .is_file ():
85
98
try :
@@ -99,163 +112,51 @@ def add_repo(
99
112
f"Config file { config_file_path } not found. A new one will be created." ,
100
113
)
101
114
102
- repo_name : str
103
- repo_url : str
104
-
105
- # Simplistic parsing of repo_name_or_url, assuming it's a URL if it
106
- # contains '://' or 'git@'. A more robust solution would involve libvcs or regex
107
- if "://" in repo_name_or_url or repo_name_or_url .startswith (
108
- ("git@" , "git+" , "hg+" , "svn+" ),
109
- ):
110
- repo_url = repo_name_or_url
111
- # Try to infer name from URL
112
- repo_name = pathlib .Path (repo_url .split ("/" )[- 1 ]).stem
113
- if target_path_str :
114
- repo_name = pathlib .Path (
115
- target_path_str ,
116
- ).name # if target_path is specified, use its name for repo_name
117
- else :
118
- repo_name = repo_name_or_url
119
- # URL is not provided, for now, we can make it an error or set a
120
- # placeholder. In future, could try to infer from a local git repo if
121
- # target_path_str points to one
122
- log .error (
123
- "Repository URL must be provided if repo_name_or_url is not a URL. "
124
- "Functionality to infer URL is not yet implemented." ,
125
- )
126
- # For now, let's assume a placeholder or require URL to be part of
127
- # repo_name_or_url. This part needs a decision: either make URL mandatory
128
- # with name, or implement inference. Let's require the user to provide a
129
- # URL for now by just using repo_name_or_url as URL if it seems like one.
130
- # This means the first arg must be a URL if only one arg is given and it's
131
- # not just a name. To simplify, let's assume if not detected as URL, then
132
- # it's just a name and we need target_path to be a URL. Or, the user
133
- # *must* provide two arguments: name then url. The current subparser takes
134
- # one `repo_name_or_url` and optional `target_path`. This argument parsing
135
- # needs refinement in `create_add_subparser` to accept name and url
136
- # separately. For now, this will likely fail if a plain name is given
137
- # without a clear URL source. Let's adjust the expectation: repo_name_or_url
138
- # IS THE URL, and repo_name is derived or taken from target_path. This means
139
- # first arg is always URL-like or repo name. Second is target_path (which
140
- # becomes name)
141
-
142
- # Re-evaluating argument strategy based on user request for
143
- # `vcspull add my-repo <url>`: `repo_name_or_url` will be `my-repo`
144
- # `target_path` will be `<url>`. This means `create_add_subparser` needs
145
- # adjustment. For now, let's proceed with the current subparser and assume
146
- # `repo_name_or_url` is the name, and `target_path` (if provided) is the
147
- # URL for this specific case. This is getting complicated. Let's simplify
148
- # the add command for now: `vcspull add <NAME> <URL>`
149
- # The current `repo_name_or_url` and `target_path` can be repurposed.
150
-
151
- # Let's rename parser args for clarity: repo_name, repo_url
152
- # This requires changing create_add_subparser and the cli call in __init__.py
153
- # Given the current structure, repo_name_or_url = actual name,
154
- # target_path = actual_url
155
- if target_path_str is None :
156
- log .error ("If first argument is a name, second argument (URL) is required." )
157
- return
158
- repo_name = repo_name_or_url
159
- repo_url = target_path_str
160
-
161
- # Determine the base directory key for the config
162
- actual_base_dir_key : str
163
- if base_dir_key :
164
- actual_base_dir_key = (
165
- base_dir_key if base_dir_key .endswith ("/" ) else base_dir_key + "/"
166
- )
167
- elif target_path_str and not (
168
- "://" in target_path_str or target_path_str .startswith ("git@" )
169
- ): # if target_path is a path, not url
170
- # Infer from target_path_str if it's a local path. This assumes
171
- # target_path_str is the actual clone path if provided and is not the URL
172
- # itself. This logic is getting tangled due to the argument overloading.
173
- # Let's stick to the idea that repo_name is derived or is first arg, url
174
- # is second or inferred. For simplicity, let's assume if base_dir_key is
175
- # not given, we use a default or CWD based key. This needs to be more
176
- # robust. Example: if repo is to be cloned to /home/user/study/python/myrepo
177
- # and vcspull.yaml has '~/study/python/': ..., then this is the key.
178
- # We can use os.path.commonpath or iterate through existing keys.
179
- # For now, default to current working directory's representation or a new
180
- # top-level key if no match.
181
- cwd_path = pathlib .Path .cwd ()
182
- resolved_target_dir_parent = (
183
- pathlib .Path (target_path_str ).parent .resolve ()
184
- if target_path_str
185
- and not ("://" in target_path_str or target_path_str .startswith ("git@" ))
186
- else cwd_path
187
- )
188
-
189
- found_key = None
190
- for key_path_str in raw_config :
191
- expanded_key_path = expand_dir (pathlib .Path (key_path_str ), cwd_path )
192
- if (
193
- resolved_target_dir_parent == expanded_key_path
194
- or resolved_target_dir_parent .is_relative_to (expanded_key_path )
195
- ):
196
- # Check if resolved_target_dir_parent is a subdirectory or same as
197
- # an existing key path
198
- try :
199
- if resolved_target_dir_parent .relative_to (expanded_key_path ):
200
- found_key = key_path_str
201
- break
202
- except ValueError : # Not a sub-path
203
- if resolved_target_dir_parent == expanded_key_path :
204
- found_key = key_path_str
205
- break
206
- if found_key :
207
- actual_base_dir_key = found_key
208
- else :
209
- # Default to a key based on the target repo's parent directory or CWD
210
- # Make it relative to home if possible, or absolute
211
- try :
212
- actual_base_dir_key = (
213
- "~/"
214
- + str (resolved_target_dir_parent .relative_to (pathlib .Path .home ()))
215
- + "/"
216
- )
217
- except ValueError :
218
- actual_base_dir_key = str (resolved_target_dir_parent ) + "/"
115
+ # Determine base directory key
116
+ if base_dir :
117
+ # Use explicit base directory
118
+ base_dir_key = base_dir if base_dir .endswith ("/" ) else base_dir + "/"
119
+ elif path :
120
+ # Infer from provided path
121
+ repo_path = pathlib .Path (path ).expanduser ().resolve ()
122
+ try :
123
+ # Try to make it relative to home
124
+ base_dir_key = "~/" + str (repo_path .relative_to (pathlib .Path .home ())) + "/"
125
+ except ValueError :
126
+ # Use absolute path
127
+ base_dir_key = str (repo_path ) + "/"
219
128
else :
220
- # Default base directory key if not specified and target_path is not a
221
- # local path
222
- # This could be "." or a user-configurable default
223
- actual_base_dir_key = "./" # Default to relative current directory key
224
-
225
- # Ensure trailing slash for consistency
226
- if not actual_base_dir_key .endswith ("/" ):
227
- actual_base_dir_key += "/"
129
+ # Default to current directory
130
+ base_dir_key = "./"
228
131
229
- # Ensure the base directory key exists in the config
230
- if actual_base_dir_key not in raw_config :
231
- raw_config [actual_base_dir_key ] = {}
232
- elif not isinstance (raw_config [actual_base_dir_key ], dict ):
132
+ # Ensure base directory key exists in config
133
+ if base_dir_key not in raw_config :
134
+ raw_config [base_dir_key ] = {}
135
+ elif not isinstance (raw_config [base_dir_key ], dict ):
233
136
log .error (
234
- f"Configuration section '{ actual_base_dir_key } ' is not a dictionary. "
137
+ f"Configuration section '{ base_dir_key } ' is not a dictionary. "
235
138
"Aborting." ,
236
139
)
237
140
return
238
141
239
- # Check if repo already exists under this base key
240
- if repo_name in raw_config [actual_base_dir_key ]:
142
+ # Check if repo already exists
143
+ if name in raw_config [base_dir_key ]:
241
144
log .warning (
242
- f"Repository '{ repo_name } ' already exists under '{ actual_base_dir_key } '."
243
- f" Current URL: { raw_config [actual_base_dir_key ][ repo_name ]} ."
244
- f" To update, remove and re-add, or edit the YAML file manually." ,
145
+ f"Repository '{ name } ' already exists under '{ base_dir_key } '. "
146
+ f"Current URL: { raw_config [base_dir_key ][ name ]} . "
147
+ f"To update, remove and re-add, or edit the YAML file manually." ,
245
148
)
246
149
return
247
150
248
151
# Add the repository
249
- # Simplest form: {repo_name: repo_url}
250
- # More complex form (if remotes, etc., were supported):
251
- # {repo_name: {"url": repo_url, ...}}
252
- raw_config [actual_base_dir_key ][repo_name ] = repo_url
152
+ raw_config [base_dir_key ][name ] = url
253
153
154
+ # Save config
254
155
try :
255
156
save_config_yaml (config_file_path , raw_config )
256
157
log .info (
257
- f"Successfully added '{ repo_name } ' ({ repo_url } ) to { config_file_path } "
258
- f"under '{ actual_base_dir_key } '." ,
158
+ f"Successfully added '{ name } ' ({ url } ) to { config_file_path } "
159
+ f"under '{ base_dir_key } '." ,
259
160
)
260
161
except Exception :
261
- log .exception (f"Error saving config to { config_file_path } " )
162
+ log .exception (f"Error saving config to { config_file_path } " )
0 commit comments