-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathcommands.rs
More file actions
381 lines (324 loc) Β· 17.4 KB
/
Copy pathcommands.rs
File metadata and controls
381 lines (324 loc) Β· 17.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
// SPDX-FileCopyrightText: 2025 Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>
//
// SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT
#![doc = r#"
Command-line argument definitions for the `submod` tool.
Defines the CLI structure and commands using [`clap`] for managing git submodules with sparse checkout support.
# Overview
- Parses CLI arguments for the `submod` binary.
- Supports commands for adding, checking, initializing, updating, resetting, and syncing submodules.
- Allows specifying a custom configuration file (default: `submod.toml`).
# Commands
- [`Commands::Add`](src/commands.rs): Adds a new submodule configuration.
- [`Commands::Change`](src/commands.rs): Changes the configuration of an existing submodule.
- [`Commands::ChangeGlobal`](src/commands.rs): Changes global settings for all submodules in the current repository.
- [`Commands::Check`](src/commands.rs): Checks submodule status and configuration.
- [`Commands::Delete`](src/commands.rs): Deletes a submodule by name.
- [`Commands::Disable`](src/commands.rs): Disables a submodule by name.
- [`Commands::List`](src/commands.rs): Lists all submodules, optionally recursively.
- [`Commands::Init`](src/commands.rs): Initializes missing submodules.
- [`Commands::Update`](src/commands.rs): Updates all submodules.
- [`Commands::Reset`](src/commands.rs): Hard resets submodules (stash, reset --hard, clean).
- [`Commands::Sync`](src/commands.rs): Runs a full sync (check, init, update).
- [`Commands::GenerateConfig`](src/commands.rs): Generates a new configuration file.
- [`Commands::NukeItFromOrbit`](src/commands.rs): Deletes all submodules or specific ones, optionally leaving them dead. (reinits by default)
- [`Commands::Completions`](src/commands.rs): Generates shell completions for the specified shell.
# Usage Example
```sh
submod add https://github.com/example/my-lib.git --name my-lib --path libs/my-lib --sparse-paths "src/,include/"
submod change my-lib --branch "main" --sparse-paths "src/,include/" --fetch "always" --update "checkout"
submod check
submod init
submod update
submod reset --all
submod sync
submod completeme bash
```
# Configuration
Use the `--config` option to specify a custom config file location.
See the [README.md](../README.md) for full usage and configuration details.
"#]
use crate::shells::Shell;
use clap::{Parser, Subcommand};
use crate::long_abouts::COMPLETE_ME;
use crate::options::{
SerializableFetchRecurse as FetchRecurse, SerializableIgnore as Ignore,
SerializableUpdate as Update,
};
use std::{ffi::OsString, path::PathBuf};
/// Top-level CLI parser for the `submod` tool.
///
/// Accepts a command and an optional config file path.
#[derive(Parser, Debug)]
#[command(name = clap::crate_name!(), version = clap::crate_version!(), propagate_version = true, author = clap::crate_authors!(), about = clap::crate_description!(), infer_subcommands = true)]
pub struct Cli {
/// command to execute.
#[command(subcommand)]
pub command: Commands,
/// Path to the configuration file (default: submod.toml).
#[arg(long = "config", global = true, default_value = "submod.toml", value_parser = clap::value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath, help = "Optionally provide a different configuration file path. Defaults to submod.toml in the current directory.")]
pub config: PathBuf,
/// Enable verbose output with detailed status information.
#[arg(long, short, global = true)]
pub verbose: bool,
}
/// Supported commands for the `submod` tool.
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(
name = "add",
visible_alias = "a",
next_help_heading = "Add a Submodule",
about = "Add and initialize a new submodule."
)]
Add {
#[arg(required = true, action = clap::ArgAction::Set, value_parser = clap::value_parser!(String), help = "The URL or local path of the submodule's git repository.")]
url: String,
#[arg(short = 'n', long = "name", value_parser = clap::value_parser!(String), help = "Optional *nickname* for the submodule to use in your config and `submod` commands. Otherwise we'll use the relative path, which is what git uses.")]
name: Option<String>,
#[arg(short = 'p', long = "path", value_parser = clap::value_parser!(OsString), value_hint = clap::ValueHint::DirPath, help = "Local path where you want to put the submodule.")]
path: Option<OsString>,
#[arg(
short = 'b',
long = "branch",
help = "Branch to use for the submodule. If not provided, defaults to the submodule's default branch."
)]
branch: Option<String>,
#[arg(
short = 'i',
long = "ignore",
help = "What changes in the submodule git should ignore."
)]
ignore: Option<Ignore>,
#[arg(
short = 'x',
long = "sparse-paths",
value_delimiter = ',',
help = "Sparse checkout paths (comma-separated). Can be globs or paths"
)]
sparse_paths: Option<Vec<String>>,
#[arg(
long = "use-git-default-sparse-checkout",
num_args = 0..=1,
value_parser = clap::value_parser!(bool),
default_missing_value = "true",
help = "Opt out of submod's deny-all-by-default sparse-checkout model and use git's built-in behavior instead. When set, the `!/*` prefix is NOT prepended automatically."
)]
use_git_default_sparse_checkout: Option<bool>,
#[arg(
short = 'f',
long = "fetch",
help = "Sets the recursive fetch behavior for the submodule (like, if we should fetch its submodules)."
)]
fetch: Option<FetchRecurse>,
#[arg(
short = 'u',
long = "update",
help = "How git should update the submodule when you run `git submodule update`."
)]
update: Option<Update>,
#[arg(short = 's', long = "shallow", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true", help = "If given, sets the submodule as a shallow clone. It will only fetch the last commit of the branch, not the full history.")]
shallow: bool,
#[arg(long = "no-init", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true", help = "If given, we'll add the submodule to your submod.toml but not initialize it.")]
no_init: bool,
},
// TODO: Implement this command
#[command(
name = "change",
next_help_heading = "Change a Submodule's Settings",
about = "Change the configuration of an existing submodule. Any field you provide will overwrite an existing value (unless both are defaults). If you change the path, it will nuke-it-from-orbit (delete it and re-clone it)."
)]
Change {
#[arg(required = true, value_parser = clap::value_parser!(String), value_hint = clap::ValueHint::CommandName, help = "The name of the submodule to change. Must match an existing submodule.", long_help = "The name of the submodule to change. Must match an existing submodule in your submod.toml. Because we use this value to lookup your config, you cannot change the name from the CLI. You must manually change it in your submod.toml. All other options can be changed here.")]
name: String,
#[arg(short = 'p', long = "path", value_parser = clap::value_parser!(OsString), value_hint = clap::ValueHint::DirPath, help = "New local path for the submodule. Implies `nuke-it-from-orbit` (no-kill) if the path changes.")]
path: Option<OsString>,
#[arg(
short = 'b',
long = "branch",
help = "Branch to use for the submodule. If not provided, defaults to the submodule's default branch."
)]
branch: Option<String>,
#[arg(short = 'x', long = "sparse-paths", value_delimiter = ',', value_parser = clap::value_parser!(OsString), help = "Replace the sparse checkout paths (comma-separated), or add if not set. Use `--append` to append to existing sparse paths.", default_missing_value = "none")]
sparse_paths: Option<Vec<OsString>>,
#[arg(requires("sparse_paths"), short = 'a', long = "append", value_parser = clap::value_parser!(bool), default_value = "false", default_missing_value = "true", help = "If given, appends the new sparse paths to the existing ones.")]
append: bool,
#[arg(
long = "use-git-default-sparse-checkout",
num_args = 0..=1,
value_parser = clap::value_parser!(bool),
default_missing_value = "true",
help = "Opt out of submod's deny-all-by-default sparse-checkout model and use git's built-in behavior instead."
)]
use_git_default_sparse_checkout: Option<bool>,
#[arg(
short = 'i',
long = "ignore",
help = "Change the ignore settings for the submodule."
)]
ignore: Option<Ignore>,
#[arg(
short = 'f',
long = "fetch",
help = "Change the fetch settings for the submodule."
)]
fetch: Option<FetchRecurse>,
#[arg(
short = 'u',
long = "update",
help = "Change the update settings for the submodule."
)]
update: Option<Update>,
#[arg(
short = 's',
long = "shallow",
default_value = "false",
default_missing_value = "true",
help = "If true, sets the submodule as a shallow clone. Set false to disable shallow cloning."
)]
shallow: bool,
#[arg(short = 'U', long = "url", value_parser = clap::value_parser!(String), help = "Change the URL of the submodule. The submodule name from the url must match an existing submodule.")]
url: Option<String>,
#[arg(long = "active", num_args = 0..=1, value_parser = clap::value_parser!(bool), default_missing_value = "true", help = "Set to true/false to enable or disable the submodule. Omit to leave unchanged. For a quick disable, use `submod disable <name>` instead.")]
active: Option<bool>,
},
#[command(name = "change-global", visible_aliases = ["cg", "chgl", "global"], next_help_heading = "Change Global Settings", about = "Add or change the global settings for submodules, affecting all submodules in the current repository. Any individual submodule settings will override these global settings.")]
ChangeGlobal {
#[arg(
short = 'i',
long = "ignore",
help = "Sets the default ignore behavior for all submodules in this repository. This will override any individual submodule settings."
)]
ignore: Option<Ignore>,
#[arg(
short = 'f',
long = "fetch",
help = "Sets the default fetch behavior for all submodules in this repository. This will override any individual submodule settings."
)]
fetch: Option<FetchRecurse>,
#[arg(
short = 'u',
long = "update",
help = "Sets the default update behavior for all submodules in this repository. This will override any individual submodule settings."
)]
update: Option<Update>,
#[arg(
long = "use-git-default-sparse-checkout",
num_args = 0..=1,
value_parser = clap::value_parser!(bool),
default_missing_value = "true",
help = "Set the global default for sparse-checkout mode. When true, all submodules use git's built-in behavior instead of submod's deny-all-by-default model (unless overridden per-submodule)."
)]
use_git_default_sparse_checkout: Option<bool>,
},
#[command(
name = "check",
visible_alias = "c",
next_help_heading = "Check Submodules",
about = "Checks the status of submodules, ensuring they are initialized and up-to-date."
)]
Check,
#[command(name = "list", visible_aliases = ["ls", "l"], next_help_heading = "List Submodules", about = "Lists all submodules, optionally recursively.")]
List {
/// Recursively list all submodules for the current repository.
#[arg(short = 'r', long = "recursive", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true", help = "If given, lists all submodules recursively (like, the submodules of the submodules).")]
recursive: bool,
},
#[command(
name = "init",
visible_alias = "i",
next_help_heading = "Initialize Submodules",
about = "Initializes missing submodules based on the configuration file."
)]
Init,
// TODO: Implement this command (use git2 + fs to delete files)
#[command(
name = "delete",
visible_alias = "del",
next_help_heading = "Delete a Submodule",
about = "Deletes a submodule by name; removes it from the configuration and the filesystem."
)]
Delete {
/// Name of the submodule to delete.
#[arg(help = "Name of the submodule to delete.")]
name: String,
},
#[command(
name = "disable",
visible_alias = "d",
next_help_heading = "Disable a Submodule",
about = "Disables a submodule by name; sets its active status to false. Does not remove settings or files."
)]
Disable {
/// Name of the submodule to disable.
#[arg(help = "Name of the submodule to disable.")]
name: String,
},
#[command(
name = "update",
visible_alias = "u",
next_help_heading = "Update Submodules",
about = "Updates all submodules to their configured state."
)]
Update,
#[command(
name = "reset",
visible_alias = "r",
next_help_heading = "Reset Submodules",
about = "Hard resets submodules, stashing changes, resetting to the configured state, and cleaning untracked files."
)]
Reset {
#[arg(short = 'a', long = "all", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true", help = "If given, resets all submodules. If not given, you must specify specific submodules to reset.")]
all: bool,
#[arg(
required_unless_present = "all",
value_delimiter = ',',
help = "Names of specific submodules to reset. If `--all` is not given, you must specify at least one submodule name."
)]
names: Vec<String>,
},
#[command(
name = "sync",
visible_alias = "s",
next_help_heading = "Sync Submodules",
about = "Runs a full sync: check, init, update. Ensures all submodules are in sync with the configuration."
)]
Sync,
#[command(name = "generate-config", visible_aliases = ["gc", "genconf"], next_help_heading = "Generate a Config File", about = "Generates a new configuration file.")]
GenerateConfig {
/// Path to the new configuration file to generate.
#[arg(short = 'o', long = "output", value_parser = clap::value_parser!(PathBuf), value_hint = clap::ValueHint::FilePath, default_value = "submod.toml", help = "Path to the output configuration file. Defaults to submod.toml in the current directory.")]
output: PathBuf,
#[arg(
short = 's',
long = "from-setup",
num_args = 0,
default_missing_value = "true",
help = "Generates the config from your current repository's submodule settings."
)]
from_setup: Option<String>,
#[arg(short = 'f', long = "force", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true", help = "If given, overwrites the existing configuration file without prompting.")]
force: bool,
#[arg(short = 't', long = "template", help = "Generates a template configuration file with default values.", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true")]
template: bool,
},
#[command(name = "nuke-it-from-orbit", visible_aliases = ["nuke-em", "nuke-it", "nuke-them"], next_help_heading = "Nuke It From Orbit", about = "Deletes all submodules or specific ones, removing them from the configuration and the filesystem. Optionally leaves them dead. ππ₯πΎπ₯π.")]
NukeItFromOrbit {
#[arg(long = "all", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true", help = "Nuke 'em all? π€")]
all: bool,
#[arg(
required_unless_present = "all",
value_delimiter = ',',
help = "... or only specific ones? π (comma-separated list of names"
)]
names: Option<Vec<String>>,
#[arg(short = 'k', long = "kill", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true", help = "If given, DOES NOT reinitialize the submodules and DOES NOT add them back to the config. They will be truly dead. π")]
kill: bool,
},
// Shell completions are implemented using clap_complete/clap_complete_nushell
#[command(name = "completeme", visible_aliases = ["comp", "complete", "comp-me", "complete-me"], next_help_heading = "Generate Shell Completions", about = "Generates shell completions for the specified shell. Completions generated to stdout.", long_about = COMPLETE_ME)]
CompleteMe {
#[arg(value_enum, action = clap::ArgAction::Set, help = "The shell to generate completions for. Supported shells: `bash`, `zsh`, `fish`, `powershell`, `elvish`, `nushell`.")]
shell: Shell,
},
}