Skip to content

Commit 366a0b5

Browse files
authored
Merge pull request #11 from bashandbone/copilot/review-branch-readiness
Fix branch readiness: config loading, dead code removal, sparse checkout, CLI fixes, and documentation
2 parents 87e9ea2 + d1e5a5d commit 366a0b5

16 files changed

Lines changed: 340 additions & 291 deletions
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

README.md

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,34 @@ branch = "develop" # track specific branch
143143
Add a new submodule to your configuration and repository:
144144
145145
```bash
146-
submod add my-lib libs/my-lib https://github.com/example/my-lib.git \
146+
# Basic add
147+
submod add https://github.com/example/my-lib.git --name my-lib --path libs/my-lib
148+
149+
# With sparse checkout paths and extra options
150+
submod add https://github.com/example/my-lib.git \
151+
--name my-lib \
152+
--path libs/my-lib \
147153
--sparse-paths "src/,include/" \
148-
--settings "ignore=all"
154+
--branch main \
155+
--ignore all \
156+
--fetch on-demand
149157
```
150158
159+
**Options:**
160+
161+
| Flag | Short | Description |
162+
|------|-------|-------------|
163+
| `<URL>` | | *(required)* URL or local path of the submodule repository |
164+
| `--name` | `-n` | Nickname for the submodule used in your config and commands |
165+
| `--path` | `-p` | Local directory path where the submodule should be placed |
166+
| `--branch` | `-b` | Branch to track |
167+
| `--ignore` | `-i` | Dirty-state ignore level (`all`, `dirty`, `untracked`, `none`) |
168+
| `--sparse-paths` | `-x` | Comma-separated sparse checkout paths or globs |
169+
| `--fetch` | `-f` | Recursive fetch behavior (`always`, `on-demand`, `never`) |
170+
| `--update` | `-u` | Update strategy (`checkout`, `rebase`, `merge`, `none`) |
171+
| `--shallow` | `-s` | Shallow clone (last commit only) |
172+
| `--no-init` | | Add to config only; do not clone/initialize |
173+
151174
### `submod check`
152175
153176
Check the status of all configured submodules:
@@ -180,8 +203,8 @@ Hard reset submodules (stash changes, reset --hard, clean):
180203
# Reset all submodules
181204
submod reset --all
182205
183-
# Reset specific submodules
184-
submod reset my-lib vendor-utils
206+
# Reset specific submodules (comma-separated)
207+
submod reset my-lib,vendor-utils
185208
```
186209
187210
### `submod sync`
@@ -192,6 +215,79 @@ Run a complete sync (check + init + update):
192215
submod sync
193216
```
194217
218+
### `submod change`
219+
220+
Change the configuration of an existing submodule:
221+
222+
```bash
223+
submod change my-lib --branch main --sparse-paths "src/,include/" --fetch always
224+
```
225+
226+
### `submod change-global`
227+
228+
Change global defaults for all submodules:
229+
230+
```bash
231+
submod change-global --ignore dirty --update checkout
232+
```
233+
234+
### `submod list`
235+
236+
List all configured submodules:
237+
238+
```bash
239+
submod list
240+
submod list --recursive
241+
```
242+
243+
### `submod delete`
244+
245+
Delete a submodule from configuration and filesystem:
246+
247+
```bash
248+
submod delete
249+
```
250+
251+
### `submod disable`
252+
253+
Disable a submodule without deleting files (sets `active = false`):
254+
255+
```bash
256+
submod disable
257+
```
258+
259+
### `submod nuke-it-from-orbit`
260+
261+
Delete all or specific submodules from config and filesystem, with optional reinit:
262+
263+
```bash
264+
# Nuke all submodules (re-initializes by default)
265+
submod nuke-it-from-orbit --all
266+
267+
# Nuke specific submodules permanently
268+
submod nuke-it-from-orbit --kill my-lib,old-dep
269+
```
270+
271+
### `submod generate-config`
272+
273+
Generate a new configuration file:
274+
275+
```bash
276+
# From current git submodule setup
277+
submod generate-config --from-setup .
278+
279+
# As a template with defaults
280+
submod generate-config --template --output my-config.toml
281+
```
282+
283+
### `submod completeme`
284+
285+
Generate shell completion scripts:
286+
287+
```bash
288+
submod completeme bash # or: zsh, fish, powershell, elvish, nushell
289+
```
290+
195291
## 💻 Usage Examples
196292
197293
### Basic Workflow
@@ -214,7 +310,9 @@ submod sync
214310
215311
```bash
216312
# Add a submodule that only checks out specific directories
217-
submod add react-components src/components https://github.com/company/react-components.git \
313+
submod add https://github.com/company/react-components.git \
314+
--name react-components \
315+
--path src/components \
218316
--sparse-paths "src/Button/,src/Input/,README.md"
219317
```
220318
@@ -374,9 +472,13 @@ cargo test --test integration_tests # Integration tests only
374472
submod/
375473
├── src/
376474
│ ├── main.rs # CLI entry point
377-
│ ├── commands.rs # Command definitions
475+
│ ├── commands.rs # Command definitions (clap)
378476
│ ├── config.rs # TOML configuration handling
379-
│ └── git_manager.rs # Core submodule operations
477+
│ ├── git_manager.rs # High-level submodule operations
478+
│ └── git_ops/ # Git backend abstraction
479+
│ ├── mod.rs # GitOpsManager (gix→git2→CLI fallback)
480+
│ ├── gix_ops.rs # gitoxide backend
481+
│ └── git2_ops.rs # libgit2 backend
380482
├── tests/ # Integration tests
381483
├── sample_config/ # Example configurations
382484
├── scripts/ # Development scripts

src/commands.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ pub enum Commands {
139139
#[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.")]
140140
shallow: bool,
141141

142-
#[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.")]
142+
#[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.")]
143143
url: Option<String>,
144144

145145
#[arg(long = "active", default_value = "true", value_parser = clap::value_parser!(bool), default_missing_value = "true", help = "If true, the submodule will be considered active and included in operations. If false, will disable the submodule. For a shorter version of this command, use `submod disable <name>` instead.")]

src/config.rs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Features:
2222

2323
use std::path::PathBuf;
2424
use anyhow::Result;
25-
use serde::{Deserialize, Serialize};
25+
use serde::{Deserialize, Deserializer, Serialize};
2626
use std::{collections::HashMap, path::Path};
2727
use crate::options::{
2828
ConfigLevel, GitmodulesConvert, SerializableFetchRecurse, SerializableIgnore, SerializableUpdate
@@ -631,12 +631,34 @@ impl From<OtherSubmoduleSettings> for SubmoduleEntry {
631631
/// A collection of submodule entries, including sparse checkouts
632632
///
633633
/// Revamped to better reflect git's structure so we can use the SubmoduleEntry types directly with gix/git2
634-
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
634+
#[derive(Debug, Default, Clone, Serialize, PartialEq, Eq)]
635635
pub struct SubmoduleEntries {
636636
submodules: Option<HashMap<SubmoduleName, SubmoduleEntry>>,
637637
sparse_checkouts: Option<HashMap<SubmoduleName, Vec<String>>>,
638638
}
639639

640+
impl<'de> Deserialize<'de> for SubmoduleEntries {
641+
/// Deserialize from the flat TOML format where each top-level key is a submodule name.
642+
/// Accepts a map where each key maps to a [`SubmoduleEntry`], building both the
643+
/// `submodules` map and the `sparse_checkouts` map from each entry's `sparse_paths`.
644+
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
645+
let map: HashMap<SubmoduleName, SubmoduleEntry> =
646+
HashMap::deserialize(deserializer)?;
647+
let mut sparse_checkouts: HashMap<SubmoduleName, Vec<String>> = HashMap::new();
648+
for (name, entry) in &map {
649+
if let Some(paths) = &entry.sparse_paths {
650+
if !paths.is_empty() {
651+
sparse_checkouts.insert(name.clone(), paths.clone());
652+
}
653+
}
654+
}
655+
Ok(SubmoduleEntries {
656+
submodules: Some(map),
657+
sparse_checkouts: Some(sparse_checkouts),
658+
})
659+
}
660+
}
661+
640662
impl SubmoduleEntries {
641663
/// Create a new empty SubmoduleEntries
642664
pub fn new(submodules: Option<HashMap<SubmoduleName, SubmoduleEntry>>, sparse_checkouts: Option<HashMap<SubmoduleName, Vec<String>>>) -> Self {
@@ -928,12 +950,13 @@ impl Config {
928950

929951
/// Load configuration from a file, merging with CLI options
930952
pub fn load(&self, path: impl AsRef<Path>, cli_options: Config) -> anyhow::Result<Self> {
931-
let fig = Figment::from(Self::default()) // 1) start from Rust-side defaults
932-
.merge(Toml::file(path).nested()) // 2) file-based overrides
933-
.merge(cli_options); // 3) CLI overrides file
934-
935-
// 4) extract into Config, then post-process submodules
936-
let cfg: Config = fig.extract()?;
953+
let cfg: Config = Figment::new()
954+
.merge(Toml::file(path))
955+
// Merge only the defaults sub-field from CLI options so that the flat submodule
956+
// entries (written as top-level TOML sections) are not corrupted by the
957+
// SubmoduleEntries serialized field names ("submodules", "sparse_checkouts").
958+
.merge(figment::providers::Serialized::defaults(cli_options.defaults).key("defaults"))
959+
.extract()?;
937960
Ok(cfg.apply_defaults())
938961
}
939962

@@ -943,10 +966,9 @@ impl Config {
943966
Some(ref p) => p,
944967
None => &".",
945968
};
946-
let fig = Figment::from(Self::default())
947-
.merge(Toml::file(p).nested());
948-
// Extract the configuration from Figment
949-
let cfg: Config = fig.extract()?;
969+
let cfg: Config = Figment::new()
970+
.merge(Toml::file(p))
971+
.extract()?;
950972
Ok(cfg.apply_defaults())
951973
}
952974

0 commit comments

Comments
 (0)