{"id":28996762,"url":"https://github.com/bashandbone/submod","last_synced_at":"2026-04-01T23:56:23.899Z","repository":{"id":300508090,"uuid":"1006247541","full_name":"bashandbone/submod","owner":"bashandbone","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-22T05:03:08.000Z","size":1068,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-22T05:20:08.615Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bashandbone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-21T20:29:28.000Z","updated_at":"2025-06-22T04:38:24.000Z","dependencies_parsed_at":"2025-06-22T05:32:18.150Z","dependency_job_id":null,"html_url":"https://github.com/bashandbone/submod","commit_stats":null,"previous_names":["bashandbone/submod"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bashandbone/submod","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashandbone%2Fsubmod","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashandbone%2Fsubmod/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashandbone%2Fsubmod/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashandbone%2Fsubmod/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bashandbone","download_url":"https://codeload.github.com/bashandbone/submod/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bashandbone%2Fsubmod/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261808072,"owners_count":23212694,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-25T05:11:26.420Z","updated_at":"2026-04-01T23:56:23.886Z","avatar_url":"https://github.com/bashandbone.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nSPDX-FileCopyrightText: 2025 Adam Poulemanos \u003c89049923+bashandbone@users.noreply.github.com\u003e\n\nSPDX-License-Identifier: LicenseRef-PlainMIT OR MIT\n--\u003e\n\n# `submod`\n\n[![Crates.io](https://img.shields.io/crates/v/submod.svg)](https://crates.io/crates/submod)\n[![Documentation](https://docs.rs/submod/badge.svg)](https://docs.rs/submod)\n[![Static Badge](https://img.shields.io/badge/Plain-MIT-15db95?style=flat-square\u0026labelColor=0d19a3\u0026cacheSeconds=86400\u0026link=https%3A%2F%2Fplainlicense.org%2Flicenses%2Fpermissive%2Fmit%2Fmit%2F)](https://plainlicense.org/licenses/permissive/mit/)\n[![Rust](https://img.shields.io/badge/rust-1.87%2B-blue.svg)](https://www.rust-lang.org)\n[![codecov](https://codecov.io/gh/bashandbone/submod/branch/main/graph/badge.svg?token=MOW92KKK0G)](https://codecov.io/gh/bashandbone/submod)\n![Crates.io Downloads (latest version)](https://img.shields.io/crates/dv/submod)\n\nGit submodules solve a real problem. **Managing submodules is a pain.** You use them infrequently enough that you always forget which command does what — and when something breaks, the recovery steps are a small nightmare. New contributors hit this especially hard: onboarding onto a project that uses submodules is its own obstacle course.\n\n`submod` wraps the whole lifecycle in one consistent CLI. Sixteen commands, including nuke-it-from-orbit for when you're done being reasonable. Built on gitoxide and git2, with automatic fallback so operations don't fail silently.[^1] It's actively used across @knitli and @plainlicense, where submodules handle shared functionality between repos.\n\n## :rocket: Features\n\n- **TOML config** — define submodules, sparse-checkout paths, and defaults in one file\n- **Sparse checkout** — clone only the parts of a submodule you actually need\n- **Global defaults with per-submodule overrides** — set it once, customize where it matters\n- **Fallback chain** — tries gitoxide first, falls back to git2, then CLI\n- **Clear status and errors** — you'll know what broke and why\n\n[^1]: The fallback architecture is more a reflection of the status of `gitoxide` and `git2` submodule support than a stability concern. Their features do not consistently provide the full lifecycle of submodule operations. Together they cover \u003e90%, but sometimes immaturely. The fallbacks architecture handles that gracefully and lets native operations grow with those libraries. \n\n## 📋 Table of Contents\n\n- [Installation](#-installation)\n- [Quick Start](#-quick-start)\n- [Configuration](#-configuration)\n- [Commands](#-commands)\n- [Usage Examples](#-usage-examples)\n- [Development](#-development)\n- [Contributing](#-contributing)\n- [License](#-license)\n\n## 🔧 Installation\n\n### Using Cargo\n\n```bash\ncargo install submod\n```\n\n### Using Mise\n\n[Mise](https://mise.jdx.dev/) is a project management tool and package manager that can manage your development environment.\n\n```bash\n# Global installation\nmise use -g cargo:submod@latest\n\n# Project-specific installation\nmise use cargo:submod@latest\n```\n\n### From Source\n\n```bash\ngit clone https://github.com/yourusername/submod.git\ncd submod\ncargo install --path .\n```\n\n## 🚀 Quick Start\n\n1. **Initialize a config file** in your git repository:\n\n    ```bash\n    # Create a basic submod.toml configuration\n    cat \u003e submod.toml \u003c\u003c EOF\n    [defaults]\n    ignore = \"dirty\"\n\n    [my-submodule]\n    path = \"vendor/my-lib\"\n    url = \"https://github.com/example/my-lib.git\"\n    sparse_paths = [\"src/\", \"include/\", \"*.md\"]\n    EOF\n    ```\n\n2. **Initialize your submodules**:\n\n    ```bash\n    submod init\n    ```\n\n3. **Check status**:\n\n    ```bash\n    submod check\n    ```\n\n## ⚙️ Configuration\n\nCreate a `submod.toml` file in your repository root:\n\n```toml\n# Global defaults applied to all submodules\n[defaults]\nignore = \"dirty\"          # ignore dirty state in status\nupdate = \"checkout\"       # update method\nbranch = \"main\"          # default branch to track\n\n# Individual submodule configuration\n[vendor-utils]\npath = \"vendor/utils\"\nurl = \"https://github.com/example/utils.git\"\nsparse_paths = [\"src/\", \"include/\", \"*.md\"]\nignore = \"all\"           # override default ignore setting\nactive = true            # whether submodule is active\n\n[my-submodule]\npath = \"libs/my-submodule\"\nurl = \"https://github.com/example/my-submodule.git\"\nsparse_paths = [\"src/core/\", \"docs/\"]\nbranch = \"develop\"       # track specific branch\n```\n\n### Configuration Options\n\n#### Global Defaults\n\n- `ignore`: How to handle dirty submodules (`all`, `dirty`, `untracked`, `none`)\n- `update`: Update strategy (`checkout`, `rebase`, `merge`, `none`, `!command`)\n- `branch`: Default branch to track (`.` for current superproject branch)\n- `fetchRecurse`: Fetch recursion (`always`, `on-demand`, `never`)\n\n#### Per-Submodule Settings\n\n- `path`: Local path where submodule should be placed\n- `url`: Git repository URL\n- `sparse_paths`: Array of paths to include in sparse checkout\n- `active`: Whether the submodule is active (default: `true`)\n- All global defaults can be overridden per submodule\n\n## 📖 Commands\n\n### `submod add`\n\nAdd a new submodule to your configuration and repository:\n\n```bash\n# Basic add\nsubmod add https://github.com/example/my-lib.git --name my-lib --path libs/my-lib\n\n# With sparse checkout paths and extra options\nsubmod add https://github.com/example/my-lib.git \\\n  --name my-lib \\\n  --path libs/my-lib \\\n  --sparse-paths \"src/,include/\" \\\n  --branch main \\\n  --ignore all \\\n  --fetch on-demand\n```\n\n**Options:**\n\n| Flag | Short | Description |\n|------|-------|-------------|\n| `\u003cURL\u003e` | | *(required)* URL or local path of the submodule repository |\n| `--name` | `-n` | Nickname for the submodule used in your config and commands |\n| `--path` | `-p` | Local directory path where the submodule should be placed |\n| `--branch` | `-b` | Branch to track |\n| `--ignore` | `-i` | Dirty-state ignore level (`all`, `dirty`, `untracked`, `none`) |\n| `--sparse-paths` | `-x` | Comma-separated sparse checkout paths or globs |\n| `--fetch` | `-f` | Recursive fetch behavior (`always`, `on-demand`, `never`) |\n| `--update` | `-u` | Update strategy (`checkout`, `rebase`, `merge`, `none`) |\n| `--shallow` | `-s` | Shallow clone (last commit only) |\n| `--no-init` | | Add to config only; do not clone/initialize |\n\n### `submod check`\n\nCheck the status of all configured submodules:\n\n```bash\nsubmod check\n```\n\n*alias*: `submod c`\n\n### `submod init`\n\nInitialize all missing submodules:\n\n```bash\nsubmod init\n```\n\n*alias*: `submid i`\n\n### `submod update`\n\nUpdate all submodules to their latest commits:\n\n```bash\nsubmod update\n```\n*alias*: `submod u`\n\n### `submod reset`\n\nHard reset submodules (stash changes, reset --hard, clean):\n\n```bash\n# Reset all submodules\nsubmod reset --all\n\n# Reset specific submodules (comma-separated)\nsubmod reset my-lib,vendor-utils\n```\n*alias*: `submod r`\n\n### `submod sync`\n\nRun a complete sync (check + init + update):\n\n```bash\nsubmod sync\n```\n*alias*: `submod s`\n\n### `submod change`\n\nChange the configuration of an existing submodule:\n\n```bash\nsubmod change my-lib --branch main --sparse-paths \"src/,include/\" --fetch always\n```\n\n### `submod change-global`\n\nChange global defaults for all submodules:\n\n```bash\nsubmod change-global --ignore dirty --update checkout\n```\n*aliases*: `submod cg`, `submod chgl`, `submod global`\n\n### `submod list`\n\nList all configured submodules:\n\n```bash\nsubmod list\nsubmod list --recursive\n```\n\n*aliases*: `submod ls`, `submod l`\n\n### `submod delete`\n\nDelete a submodule from configuration and filesystem:\n\n```bash\nsubmod delete my-lib\n```\n\n*alias*: `submod del`\n\n### `submod disable`\n\nDisable a submodule without deleting files (sets `active = false`):\n\n```bash\nsubmod disable my-lib\n```\n\n*alias*: `submod del`\n\n### `submod nuke-it-from-orbit`\n\nDelete all or specific submodules from config and filesystem, with optional reinit:\n\n```bash\n# Nuke all submodules (re-initializes by default)\nsubmod nuke-it-from-orbit --all\n\n# Nuke specific submodules permanently\nsubmod nuke-it-from-orbit --kill my-lib,old-dep\n```\n*aliases*: `submod nuke-em`, `submod nuke-it`, `submod nuke-them`\n\nUse `nuke-it-from-orbit` was created because sometimes submodule just... don't cooperate. You're done being nice and just want to get back to work. Nuke it.\n\n### `submod generate-config`\n\nGenerate a new configuration file:\n\n```bash\n# From current git submodule setup\nsubmod generate-config --from-setup .\n\n# As a template with defaults\nsubmod generate-config --template --output my-config.toml\n```\n*aliases*: `submod gc`, `submod genconf`\n\n### `submod completeme`\n\nGenerate shell completion scripts:\n\n#### bash\n\n```bash \"bash\"\nmkdir -p ~/.bash_completion.d\nsubmod completeme bash \u003e ~/.bash_completion.d/submod\n```\n*aliases*: `submod comp`, `submod complete`, `submod comp-me`, `submod complete-me`\n\n\u003cdetails\u003e\n\u003csummary\u003eAdd completions to other shells\u003c/summary\u003e\n\n#### zsh\n\n```zsh \"zsh\"\n# zsh has an fpath array with possible function directories. You can\n# put your competions in any of these; we use the first one here:\nZSH_DEFAULT=\"${XDG_DATA_HOME:-~/.local/share}/zsh/site-functions\"\nZFUNCDIR=\"\"${fpath[1]:-$ZSH_DEFAULT}\"\nmkdir -p \"$ZFUNCDIR\"\nsubmod completeme zsh \u003e \"${ZFUNCDIR}/_submod\"\n```\n\n#### fish\n\n```fish\nmkdir -p \"${XDG_CONFIG_HOME:-~/.config}/fish/completions\"\nsubmod completeme fish \u003e \"{XDG_CONFIG_HOME:-~/.config}/fish/completions/submod.fish\n```\n\n#### powershell\n\n```pwsh\nmkdir -p $Home\\Documents\\PowerShell\\completions\nsubmod completeme powershell \u003e $Home\\Documents\\PowerShell\\completions\\submod.completion.ps1\n```\n\n#### elvish\n\n```elvish\nmkdir -p ~/.config/elvish/completions\nsubmod completeme elvish \u003e ~/.config/elvish/completions/submod.elv\n```\n\n#### nushell\n\n```nushell\nsubmod completeme nu \u003e \"$NUSHELL_CONFIG_DIR/scripts/completions/submod.nu\"\necho 'use completions/submod.nu' \u003e\u003e \"$NU_CONFIG_PATH\"\n```\n\n\u003c/details\u003e\n\n## 💻 Usage Examples\n\n### Basic Workflow\n\n```bash\n# Start with checking current state\nsubmod check\n\n# Initialize any missing submodules\nsubmod init\n\n# Update everything to latest\nsubmod update\n\n# Or do it all at once\nsubmod sync\n```\n\n### Adding Submodules with Sparse Checkout\n\n```bash\n# Add a submodule that only checks out specific directories\nsubmod add https://github.com/company/react-components.git \\\n  --name react-components \\\n  --path src/components \\\n  --sparse-paths \"src/Button/,src/Input/,README.md\"\n```\n\n### Working with Different Configurations\n\n```bash\n# Use a custom config file\nsubmod --config my-custom.toml check\n\n# Check status with custom config\nsubmod --config production.toml sync\n```\n\n### Handling Problematic Submodules\n\n```bash\n# Reset a problematic submodule\nsubmod reset my-problematic-submodule\n\n# Check what's wrong\nsubmod check\n\n# Re-sync everything\nsubmod sync\n```\n\nIf that doesn't work, try [nuke-it-from-orbit](https://github.com/bashandbone/submod/edit/main/README.md#submod-nuke-it-from-orbit)\n\n## 🛠️ Development\n\n### Prerequisites\n\n- Rust 1.87 or later\n- Git\n- [Mise](https://mise.jdx.dev/) (recommended) - for tool management and task running\n\n### Quick Setup with Mise (Recommended)\n\n```bash\n# Clone the repository\ngit clone https://github.com/bashandbone/submod.git\ncd submod\n\n# Install mise if you haven't already\ncurl https://mise.run | sh\n\n# Install all development tools and dependencies\nmise install\n\n# Build the project\nmise run build\n# or: mise run b (alias)\n\n# Run tests\nmise run test\n\n# Run the full CI suite (build + lint + test)\nmise run ci\n```\n\n### Available Mise Tasks\n\n```bash\n# Build the project\nmise run build          # or: mise run b\n\n# Run tests\nmise run test\n\n# Lint with clippy\nmise run lint\n\n# Run full CI pipeline\nmise run ci\n\n# Clean build artifacts\nmise run clean\n\n# Cut a new release (maintainers only)\nmise run release\n```\n\n### Git Hooks with hk\n\nThis project uses [hk](https://github.com/jdx/hk) for automated git hooks that ensure code quality:\n\n```bash\n# Install git hooks (done automatically with mise install)\nhk install\n\n# Run pre-commit checks manually\nhk run pre-commit\n\n# Run all linters and checks\nhk check\n\n# Auto-fix issues where possible\nhk fix\n\n# Run CI checks locally\nhk run ci\n```\n\nThe pre-commit hooks automatically run:\n- **cargo fmt** - Code formatting\n- **cargo clippy** - Linting\n- **cargo test** - Test suite\n- **typos** - Spell checking\n- **prettier** - TOML/YAML formatting\n- **cargo deny** - Security and license auditing\n\n### Manual Setup (Alternative)\n\nIf you prefer not to use mise:\n\n```bash\n# Clone the repository\ngit clone https://github.com/bashandbone/submod.git\ncd submod\n\n# Install Rust if needed\ncurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh\n\n# Build the project\ncargo build\n\n# Run tests\ncargo test\n# or hk run test\n\n# Or use the comprehensive test runner\n./scripts/run-tests.sh --verbose\n```\n\n### Running Tests\n\n```bash\n# Using mise (recommended)\nmise run test           # Run all tests\nmise run ci             # Run full CI suite\n\n# Using hk\nhk run test                 # Run tests only\nhk run ci                   # Run CI checks\n\n# Using cargo directly\ncargo test              # Run all tests\ncargo test --test integration_tests  # Integration tests only\n\n# Using the test script\n./scripts/run-tests.sh --verbose     # Comprehensive reporting\n./scripts/run-tests.sh --performance # Include performance tests\n./scripts/run-tests.sh --filter sparse_checkout  # Filter tests\n```\n\n### Project Structure\n\n```plaintext\nsubmod/\n├── src/\n│   ├── main.rs              # CLI entry point\n│   ├── commands.rs          # Command definitions (clap)\n│   ├── config.rs            # TOML configuration handling\n│   ├── git_manager.rs       # High-level submodule operations\n│   └── git_ops/             # Git backend abstraction\n│       ├── mod.rs           # GitOpsManager (gix→git2→CLI fallback)\n│       ├── gix_ops.rs       # gitoxide backend\n│       └── git2_ops.rs      # libgit2 backend\n├── tests/                   # Integration tests\n├── sample_config/           # Example configurations\n├── scripts/                 # Development scripts\n└── docs/                    # Documentation\n```\n\n## 🤝 Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n### Quick Contributing Steps\n\n1. **Fork the repository**\n2. **Create a feature branch**: `git checkout -b feature/amazing-feature`\n3. **Set up development environment**: `mise install` (installs all tools and git hooks)\n4. **Make your changes** and add tests if applicable\n5. **Commit your changes**: `git commit -m 'Add amazing feature'` (hooks run automatically)\n6. **Push to your branch**: `git push origin feature/amazing-feature` (they'll actually run again in check mode, so they need to pass)\n7. **Open a Pull Request**\n\n### Development Guidelines\n\n- Follow Rust best practices and idioms\n- Add tests for new functionality. I'm not big on unit tests, but integration tests are essential.\n- Update documentation for user-facing changes\n- Use conventional commit messages\n- Run `mise run ci` or `hk run ci` before submitting PR\n- Pre-commit hooks will automatically format code and run basic checks\n- All automated checks must pass before PR can be merged\n\n## 🔍 Troubleshooting\n\n### Common Issues\n\n**Submodule not initializing:**\n\n```bash\n# Check if the URL is accessible\ngit ls-remote \u003csubmodule-url\u003e\n\n# Verify your configuration\nsubmod check\n```\n\n**Sparse checkout not working:**\n\n- Ensure paths in `sparse_paths` are relative to the submodule root\n- Check that the submodule repository contains the specified paths\n- Verify sparse checkout is enabled: `git config core.sparseCheckout` in the submodule\n\n**Permission issues:**\n\n- Ensure you have proper SSH keys set up for private repositories\n- Check if your Git credentials are configured correctly\n\n## 📋 Motivation\n\nManaging git submodules, especially with sparse checkouts, can be complex and error-prone. Traditional git submodule commands require multiple steps and careful attention to configuration details.\n\nThis tool was created to:\n\n- **Reduce barriers to contribution** - Make it easier for new developers to work with projects using submodules\n- **Simplify complex workflows** - Handle initialization, updates, and sparse checkout configuration automatically\n- **Provide better tooling** - Clear status reporting and error messages\n- **Leverage modern Git libraries** - Use `gitoxide` for better performance and reliability\n\nThe tool is actively used in multiple projects at [@knitli](https://github.com/knitli) and [@plainlicense](https://github.com/plainlicense), where submodules are essential for sharing core functionality across repositories.\n\n## 📄 License\n\nThis project is licensed under the [Plain MIT License](https://plainlicense.org/licenses/permissive/mit/).\n\n## 🙏 Acknowledgments\n\n- [gitoxide](https://github.com/Byron/gitoxide) - Fast and safe pure Rust implementation of Git\n- [git2-rs](https://github.com/rust-lang/git2-rs) - Rust bindings to libgit2\n- [clap](https://github.com/clap-rs/clap) - Command line argument parser\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**[Homepage](https://github.com/bashandbone/submod)** • **[Documentation](https://docs.rs/submod)** • **[Crate](https://crates.io/crates/submod)**\n\nMade with ❤️ for the Rust and Git communities\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbashandbone%2Fsubmod","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbashandbone%2Fsubmod","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbashandbone%2Fsubmod/lists"}