Skip to content

Commit e6d8c71

Browse files
Copilotbashandbone
andcommitted
Address all PR review suggestions
Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
1 parent ea448c3 commit e6d8c71

10 files changed

Lines changed: 42 additions & 46 deletions

File tree

src/commands.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ Defines the CLI structure and commands using [`clap`] for managing git submodule
3333
# Usage Example
3434
3535
```sh
36-
submod add my-lib libs/my-lib https://github.com/example/my-lib.git --sparse-paths "src/,include/" --settings "ignore=all"
36+
submod add https://github.com/example/my-lib.git --name my-lib --path libs/my-lib --sparse-paths "src/,include/"
3737
submod change my-lib --branch "main" --sparse-paths "src/,include/" --fetch "always" --update "checkout"
3838
submod check
3939
submod init
4040
submod update
4141
submod reset --all
4242
submod sync
43+
submod completeme bash
4344
```
4445
4546
# Configuration
@@ -110,7 +111,7 @@ pub enum Commands {
110111
no_init: bool,
111112
},
112113
// TODO: Implement this command
113-
#[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).")]
114+
#[command(name = "change", hide = true, 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).")]
114115
Change {
115116
#[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.")]
116117
name: String,
@@ -146,7 +147,7 @@ pub enum Commands {
146147
active: bool,
147148
},
148149
// TODO: Implement this command
149-
#[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.")]
150+
#[command(name = "change-global", hide = true, 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.")]
150151
ChangeGlobal {
151152

152153
#[arg(short = 'i', long = "ignore", help = "Sets the default ignore behavior for all submodules in this repository. This will override any individual submodule settings.")]
@@ -163,7 +164,7 @@ pub enum Commands {
163164
Check,
164165

165166
// TODO: Implement this command
166-
#[command(name = "list", visible_aliases = ["ls", "l"], next_help_heading = "List Submodules", about = "Lists all submodules, optionally recursively.")]
167+
#[command(name = "list", hide = true, visible_aliases = ["ls", "l"], next_help_heading = "List Submodules", about = "Lists all submodules, optionally recursively.")]
167168
List {
168169
/// Recursively list all submodules for the current repository.
169170
#[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).")]
@@ -174,11 +175,11 @@ pub enum Commands {
174175
Init,
175176

176177
// TODO: Implement this command (use git2 + fs to delete files)
177-
#[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.")]
178+
#[command(name = "delete", hide = true, visible_alias = "del", next_help_heading = "Delete a Submodule", about = "Deletes a submodule by name; removes it from the configuration and the filesystem.")]
178179
Delete,
179180

180181
// TODO: Implement this command (use git2). Functionally this changes a module to `active = false` in our config and `.gitmodules`, but does not delete the submodule from the filesystem.
181-
#[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.")]
182+
#[command(name = "disable", hide = true, 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.")]
182183
Disable,
183184

184185
#[command(name = "update", visible_alias = "u", next_help_heading = "Update Submodules", about = "Updates all submodules to their configured state.")]
@@ -198,7 +199,7 @@ pub enum Commands {
198199
Sync,
199200

200201
// TODO: Implement this command
201-
#[command(name = "generate-config", visible_aliases = ["gc", "genconf"], next_help_heading = "Generate a Config File", about = "Generates a new configuration file.")]
202+
#[command(name = "generate-config", hide = true, visible_aliases = ["gc", "genconf"], next_help_heading = "Generate a Config File", about = "Generates a new configuration file.")]
202203
GenerateConfig {
203204
/// Path to the new configuration file to generate.
204205
#[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.")]
@@ -215,7 +216,7 @@ pub enum Commands {
215216
},
216217

217218
// TODO: Implement this command (use git2) (not we can leverage this logic for `delete` because the `kill` option is the same.)
218-
#[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. 🚀💥👾💥💀.")]
219+
#[command(name = "nuke-it-from-orbit", hide = true, 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. 🚀💥👾💥💀.")]
219220
NukeItFromOrbit {
220221
#[arg(long = "all", default_value = "false", action = clap::ArgAction::SetTrue, default_missing_value = "true", help = "Nuke 'em all? 🤓")]
221222
all: bool,
@@ -226,8 +227,8 @@ pub enum Commands {
226227
kill: bool,
227228
},
228229

229-
// TODO: Implement this command (super simple with clap_complete/clap_complete_nushell. The latter is just another enum variant that implements the `Generator` trait like all of the other clap_complete shells.)
230-
#[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, value_parser = clap::value_parser!(Shell))]
230+
// Shell completions are implemented using clap_complete/clap_complete_nushell
231+
#[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)]
231232
CompleteMe {
232233
#[arg(value_enum, action = clap::ArgAction::Set, help = "The shell to generate completions for. Supported shells: `bash`, `zsh`, `fish`, `powershell`, `elvish`, `nushell`.")]
233234
shell: Shell,

src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ impl SubmoduleEntry {
492492
};
493493
let branch = SerializableBranch::from_gitmodules(entries.get("branch").map_or("", |b| b.as_str())).ok();
494494
let ignore = entries.get("ignore").and_then(|i| SerializableIgnore::from_gitmodules(i).ok());
495-
let fetch_recurse = entries.get("fetchRecurse").and_then(|fr| SerializableFetchRecurse::from_gitmodules(fr).ok());
495+
let fetch_recurse = entries.get("fetchRecurseSubmodules").and_then(|fr| SerializableFetchRecurse::from_gitmodules(fr).ok());
496496
let update = entries.get("update").and_then(|u| SerializableUpdate::from_gitmodules(u).ok());
497497
let active = entries.get("active").and_then(|a| a.parse::<bool>().ok()).unwrap_or(true);
498498
let shallow = entries.get("shallow").and_then(|s| s.parse::<bool>().ok()).unwrap_or(false);

src/git_ops/git2_ops.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ impl Git2Operations {
9090

9191
match config.get_string(&key) {
9292
Ok(fetch_str) => match fetch_str.as_str() {
93-
"true" | "on-demand" => Ok(Some(SerializableFetchRecurse::OnDemand)),
93+
"true" => Ok(Some(SerializableFetchRecurse::Always)),
94+
"on-demand" => Ok(Some(SerializableFetchRecurse::OnDemand)),
9495
"false" | "no" => Ok(Some(SerializableFetchRecurse::Never)),
9596
_ => Ok(None),
9697
},

src/git_ops/gix_ops.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ impl GixOperations {
7474
for section in as_config_file.sections() {
7575
// we need to convert everything to String and add to map
7676
let mut section_entries = std::collections::HashMap::new();
77-
let name = if section.header().subsection_name().is_some() {
78-
section.header().name().to_string()
77+
let name = if let Some(subsection) = section.header().subsection_name() {
78+
subsection.to_str_lossy().to_string()
7979
} else {
8080
section.header().name().to_string()
8181
};

src/git_ops/simple_gix.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-LicenseIdentifier: MIT OR Apache-2.0
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
22
//
33
// SPDX-FileCopyrightText: 2018-2025 Sebastian Thiel and [contributors](https://github.com/byron/gitoxide/contributors)
44
// SPDX-FileCopyrightText: 2025 Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>

src/main.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use crate::options::SerializableBranch as Branch;
3535
use crate::git_manager::GitManager;
3636
use anyhow::Result;
3737
use clap::Parser;
38+
use clap_complete::generate;
3839

3940

4041
fn main() -> Result<()> {
@@ -141,7 +142,7 @@ fn main() -> Result<()> {
141142
}
142143
}
143144
Commands::Sync => {
144-
let mut manager = GitManager::new(cli.config)
145+
let mut manager = GitManager::new(config_path)
145146
.map_err(|e| anyhow::anyhow!("Failed to create manager: {}", e))?;
146147

147148
// Run check, init, and update in sequence
@@ -189,8 +190,10 @@ fn main() -> Result<()> {
189190
Commands::NukeItFromOrbit { .. } => {
190191
return Err(anyhow::anyhow!("NukeItFromOrbit command not yet implemented"));
191192
}
192-
Commands::CompleteMe { .. } => {
193-
return Err(anyhow::anyhow!("Completions command not yet implemented"));
193+
Commands::CompleteMe { shell } => {
194+
let mut cmd = <Cli as clap::CommandFactory>::command();
195+
let name = cmd.get_name().to_string();
196+
generate(shell, &mut cmd, name, &mut std::io::stdout());
194197
}
195198
}
196199

src/options.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -423,25 +423,14 @@ impl GitmodulesConvert for SerializableBranch {
423423
/// Convert to gitmodules string (what you would get from the .gitmodules or .git/config)
424424
fn to_gitmodules(&self) -> String {
425425
match self {
426-
SerializableBranch::CurrentInSuperproject => get_current_repository()
427-
.map(|repo| {
428-
let branch = get_current_branch(Some(&repo))
429-
.unwrap_or_else(|_| "current-in-super-project".to_string());
430-
if branch.is_empty() {
431-
"current-in-super-project".to_string()
432-
} else {
433-
branch
434-
}
435-
})
436-
.unwrap_or_else(|_| "current-in-super-project".to_string()),
426+
SerializableBranch::CurrentInSuperproject => ".".to_string(),
437427
SerializableBranch::Name(name) => name.to_string(),
438428
}
439429
}
440430

441431
/// Convert from gitmodules string (what you would get from the .gitmodules or .git/config)
442432
fn from_gitmodules(options: &str) -> Result<Self, ()> {
443-
444-
if options == "." || options == "current" || options == "current-in-super-project" || options == "superproject" || options == "super" || options == SerializableBranch::current_in_superproject().unwrap_or_default() {
433+
if options == "." {
445434
return Ok(SerializableBranch::CurrentInSuperproject);
446435
}
447436
Ok(SerializableBranch::Name(options.to_string()))

src/shells.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ impl clap::ValueEnum for Shell {
4949
Shell::Bash => PossibleValue::new("bash"),
5050
Shell::Elvish => PossibleValue::new("elvish"),
5151
Shell::Fish => PossibleValue::new("fish"),
52-
Shell::PowerShell => PossibleValue::new("powershell"),
52+
Shell::PowerShell => PossibleValue::new("powershell").alias("pwsh"),
5353
Shell::Zsh => PossibleValue::new("zsh"),
5454
Shell::Nushell => PossibleValue::new("nushell"),
5555
})

src/utilities.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,13 @@ pub fn get_main_root(
8282

8383
/// Get the current branch name from the repository.
8484
pub fn get_current_branch(repo: Option<&gix::Repository>) -> Result<String, anyhow::Error> {
85+
let owned;
8586
let repo = match repo {
8687
Some(r) => r,
87-
None => &get_current_repository()?,
88+
None => {
89+
owned = get_current_repository()?;
90+
&owned
91+
}
8892
};
8993
let head = repo.head()?;
9094
if let Some(reference) = head.referent_name() {
@@ -153,19 +157,17 @@ pub fn name_from_url(url: &str) -> Result<String, anyhow::Error> {
153157
/// Convert an `OsString` to a `String`, extracting the name from the path
154158
pub fn name_from_osstring(os_string: std::ffi::OsString) -> Result<String, anyhow::Error> {
155159
osstring_to_string(os_string).and_then(|s| {
156-
if s.is_empty() {
157-
if s.contains('\0') {
158-
Err(anyhow::anyhow!("Name cannot contain null bytes"))
159-
} else {
160-
Ok(s)
161-
}
162-
} else {
163-
let sep = std::path::MAIN_SEPARATOR.to_string();
164-
s.trim().split(&sep)
165-
.last()
166-
.map(|name| name.to_string())
167-
.ok_or_else(|| anyhow::anyhow!("Failed to extract name from OsString"))
160+
if s.contains('\0') {
161+
return Err(anyhow::anyhow!("Name cannot contain null bytes"));
162+
}
163+
if s.trim().is_empty() {
164+
return Err(anyhow::anyhow!("Name cannot be empty"));
168165
}
166+
let sep = std::path::MAIN_SEPARATOR.to_string();
167+
s.trim().split(&sep)
168+
.last()
169+
.map(|name| name.to_string())
170+
.ok_or_else(|| anyhow::anyhow!("Failed to extract name from OsString"))
169171
})
170172
}
171173

tests/config_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,11 @@ active = true
192192
harness
193193
.run_submod_success(&[
194194
"add",
195+
&remote_url,
195196
"--name",
196197
"new-submodule",
197198
"--path",
198199
"lib/new",
199-
&remote_url,
200200
"--sparse-paths",
201201
"src,docs",
202202
])

0 commit comments

Comments
 (0)