Skip to content

Cli improvements#12

Merged
bashandbone merged 69 commits intomainfrom
cli-improvements
Mar 4, 2026
Merged

Cli improvements#12
bashandbone merged 69 commits intomainfrom
cli-improvements

Conversation

@bashandbone
Copy link
Owner

This PR introduces a complete overhaul of submod's fallback trait system, comprehensive CLI improvements, improved testing.

The biggest change: it actually uses its fallbacks. The previous release, v0.1.2 had deferred significant complexity in implementing gitoxide and git2 operations in favor of fallbacks to direct calls to git on the CLI. Put another way: submod was a git cli wrapper.

Not anymore. submod now makes native use of gitoxide when available, and when not, git2. No more use of git CLI as a primary mode. This PR includes more improvements too -- improved/fully implemented sparse handling, better error handling

bashandbone and others added 30 commits June 25, 2025 01:13
…ck strategy

- Add comprehensive GitOperations trait defining all git operations
- Implement GixOperations using gitoxide for read operations
- Implement Git2Operations as complete fallback using libgit2
- Add GitOpsManager with automatic fallback mechanism
- Create detailed status types with bitflags support
- Add configuration types for submodule operations
- Update lib.rs to expose new git_ops module
- Add bitflags dependency to Cargo.toml
- Document implementation details and API capabilities

This provides the foundation for eliminating git CLI calls and using
proper git libraries with a robust fallback strategy.
we're now reuse compliant
- Updated GitOperations trait write_gitmodules to use &mut self
- Fixed borrowing conflicts in git2_ops.rs write_gitmodules implementation
- Fixed borrowing conflicts in gitoxide_manager.rs init_submodule method
- Fixed mutable borrow issue in stash_submodule method
- Fixed unused variable warnings
- Updated method signatures for sync_with_git_config and load_with_git_sync

Still need to fix SubmoduleEntries field visibility issue in gix_ops.rs
- Fixed SerializableBranch value parser issue by changing branch fields from Branch to String
- Fixed path handling by adding proper Option<OsString> to String conversion with fallback
- Fixed name parameter handling with fallback to derived path from URL
- Added type conversions from CLI types to serializable types (Ignore → SerializableIgnore, etc.)
- Added handlers for all missing Commands variants to fix non-exhaustive pattern matching
- All compilation errors resolved, project now builds successfully
- CLI tool is functional and displays help correctly

Phase 2 implementation is now complete with all compilation and linting issues resolved.
- Fixed SubmoduleEntry construction in gix_ops.rs by removing non-existent name field and making fields Optional
- Fixed git2 operations by replacing non-existent methods with proper git config updates
- Fixed Config::load call to use proper three-argument signature
- Fixed sparse checkout configuration to use proper sparse_checkouts() method
- Fixed URL setting through git config instead of non-existent set_url method
- Fixed mutable borrow issues in git2_ops.rs
- Cleaned up unused imports and variables to reduce warnings
- Fixed move errors by using proper Option handling with as_ref()

The code now compiles with only warnings about unused fields and private field access that need to be addressed next.
- Fixed method signatures to use &mut self for write_gitmodules trait methods
- Added proper type conversions from CLI enums to serializable types
- Fixed Option handling for name and path parameters with proper derivation logic
- Simplified sparse_paths processing since CLI already handles comma separation
- Added proper error handling and validation for all parameters
- Fixed SubmoduleEntries constructor usage

The project now compiles successfully with all commands properly connected to the gitoxide_manager backend.
… value manipulation

- Replaced gix::config::tree static keys with direct config file manipulation
- Updated config reading to use mutable content for from_bytes_owned
- Implemented pattern matching for supported config keys (core.bare, user.name, user.email, protocol.file.allow)
- Added proper BStr handling for config values and section names
- Fixed type system issues with config value setting
- Simplified config access logic to use raw value manipulation instead of tree keys
- Added proper error handling for unsupported config keys

This addresses Phase 2.1 of the Git Operations Refactoring Plan by moving away from static tree keys to a more direct config manipulation approach.
This commit implements the core submodule operations for Phase 2.2 of the 
refactoring plan, maximizing the use of gix (gitoxide) APIs:

Key implementations:
- add_submodule(): Implements submodule addition with .gitmodules updates
- init_submodule(): Initializes submodules with config setup  
- update_submodule(): Updates submodules with fetch and checkout logic
- delete_submodule(): Removes submodules from .gitmodules and filesystem
- deinit_submodule(): Deinitializes submodules with safety checks

Technical approach:
- Uses gix::File API for .gitmodules reading/writing (as noted by user)
- Implements proper error handling with fallback to git2 for complex operations
- Handles gix API limitations gracefully (clone, remote, checkout operations)
- Maintains compatibility with existing SubmoduleEntry and options structures

The implementation follows the refactoring plan's goal of maximizing gix usage
while preserving the fallback architecture for operations that are too complex
or unstable in the current gix API.

Related to: GIT_OPERATIONS_REFACTORING_PLAN.md Phase 2.2
- Replace final 2 CLI calls in git_manager.rs with GitOperations trait methods
- git submodule init → self.git_ops.init_submodule(path_str)  
- git submodule update → self.git_ops.update_submodule(path_str, &update_opts)
- All 17 CLI calls from original code review now eliminated
- CLI commands in main.rs properly connected to GitManager → GitOpsManager
- Phase 3 objectives fully achieved: architectural integration complete

Note: Compilation errors in gix_ops.rs are pre-existing issues for Phase 4
- Detailed analysis of cli-improvements branch implementation status
- Assessment against original planning documents (FEATURE_CODE_REVIEW.md and GIT_OPERATIONS_REFACTORING_PLAN.md)
- Identified 75% completion with 11 compilation errors blocking progress
- Architecture integration (Phase 1) and CLI removal (Phase 3) completed successfully
- Gix implementation (Phase 2) partially complete with specific technical issues documented
- Provided actionable recommendations for completing the implementation
- Includes detailed error analysis, success metrics, and timeline estimates
Copy link
Contributor

Copilot AI commented Mar 4, 2026

@bashandbone I've opened a new pull request, #17, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 6 commits March 4, 2026 13:20
…ommands

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Fix build failures, config loading, and wire remaining CLI commands
@bashandbone bashandbone added enhancement New feature or request dependencies Pull requests that update a dependency file rust Pull requests that update rust code labels Mar 4, 2026
@bashandbone
Copy link
Owner Author

@copilot it looks like we accidentally introduced a duplicate entry in Cargo.toml on the merge commit. Please address and ensure everything is passing before submitting your PR

Copy link
Contributor

Copilot AI commented Mar 4, 2026

@bashandbone I've opened a new pull request, #18, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 2 commits March 4, 2026 21:29
…ust)

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Fix duplicate entries in Cargo.toml introduced by merge commit
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 60 out of 68 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

publish = true
description = "A headache-free submodule management tool, built on top of gitoxide. Manage sparse checkouts, submodule updates, and adding/removing submodules with ease."
license = "MIT" # Plain MIT license: plainlicense.org/licenses/permissive/mit/
license-file = "LICENSE.md"
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cargo manifest metadata: license and license-file are mutually exclusive in Cargo.toml. Having both will cause a manifest validation/publish failure; keep only one (typically license = "MIT" or license-file = "LICENSE.md").

Suggested change
license-file = "LICENSE.md"

Copilot uses AI. Check for mistakes.
.roo/mcp.json Outdated
"-y",
"@modelcontextprotocol/server-filesystem",
"~/submod"
"~/submod", "~/.cargo"
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding ~/.cargo to the filesystem MCP server allowlist is risky because it can contain sensitive material (e.g., registry credentials/tokens in ~/.cargo/credentials). Please avoid granting access to the entire directory; if needed, whitelist only the minimal subpaths required (or omit entirely).

Suggested change
"~/submod", "~/.cargo"
"~/submod"

Copilot uses AI. Check for mistakes.
src/utilities.rs Outdated
Comment on lines +165 to +172
pub(crate) fn name_from_osstring(os_string: std::ffi::OsString) -> Result<String, anyhow::Error> {
osstring_to_string(os_string).and_then(|s| {
if s.is_empty() {
if s.contains('\0') {
Err(anyhow::anyhow!("Name cannot contain null bytes"))
} else {
Ok(s)
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name_from_osstring() currently treats an empty string as valid and the null-byte check is unreachable (s.is_empty() implies it can't contain \0). Consider validating null bytes before emptiness, and returning an error for empty names (or deriving the name from the path components instead).

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +268
fn add_submodule(&mut self, opts: &SubmoduleAddOptions) -> Result<()> {
// Try gix first (not yet implemented → falls through), then git2 which now uses
// the correct `submodule.clone() + add_finalize()` sequence.
// CLI is kept as a last-resort safety net and sets current_dir to the superproject
// workdir so it works regardless of the process's CWD.
self.try_with_fallback_mut(
|gix| gix.add_submodule(opts),
|git2| git2.add_submodule(opts),
)
.or_else(|_| {
let workdir = self.git2_ops.workdir()
.ok_or_else(|| anyhow::anyhow!("Repository has no working directory"))?;
let output = std::process::Command::new("git")
.current_dir(workdir)
.arg("submodule")
.arg("add")
.arg("--name")
.arg(&opts.name)
.arg("--")
.arg(&opts.url)
.arg(&opts.path)
.output()
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitOpsManager::add_submodule() falls back to git submodule add and only passes --name, url, and path, dropping options like branch, shallow, no_init, and any git settings. If CLI fallback is kept, it should honor SubmoduleAddOptions (or the fallback will behave differently from gix/git2 paths and the CLI contract).

Copilot uses AI. Check for mistakes.
Cargo.toml Outdated
Comment on lines +23 to +31
"git2",
"gitmodules",
"repository",
"management",
"command-line",
"tool",
"development",
"utilities",
"development-tools",
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keywords contains more than the crates.io limit (max 5). This will be rejected on publish; reduce to the most relevant 5 keywords.

Suggested change
"git2",
"gitmodules",
"repository",
"management",
"command-line",
"tool",
"development",
"utilities",
"development-tools",

Copilot uses AI. Check for mistakes.
Comment on lines +243 to +257
### `submod delete`

Delete a submodule from configuration and filesystem:

```bash
submod delete
```

### `submod disable`

Disable a submodule without deleting files (sets `active = false`):

```bash
submod disable
```
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The examples for submod delete and submod disable omit the required submodule name argument. Update the snippets to include an example name (e.g., submod delete my-lib, submod disable my-lib) so users can copy/paste successfully.

Copilot uses AI. Check for mistakes.
#
# ## `shallow`
#
# If `true`, performs a shallow clone of the submodule, which means it only fetchest the most recent commit. Defaults to `false`. This is useful for large repositories where you only need the latest commit.
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "fetchest" → "fetches".

Copilot uses AI. Check for mistakes.
src/utilities.rs Outdated
Comment on lines +6 to +10
use anyhow::Ok;
use git2::Repository as Git2Repository;
use gix::open::Options;
use std::path::PathBuf;
use std::result::Result;
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use anyhow::Ok; doesn't compile (anyhow doesn't export an Ok item). Also importing std::result::Result here is confusing since the file returns Result<_, anyhow::Error> everywhere; prefer importing anyhow::Result (or just use std::result::Result without the anyhow Ok import).

Copilot uses AI. Check for mistakes.
src/commands.rs Outdated
Comment on lines +104 to +119
#[arg(short = 'i', long = "ignore", default_value = "unspecified", help = "What changes in the submodule git should ignore.")]
ignore: 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(short = 'f', long = "fetch", default_value = "unspecified", help = "Sets the recursive fetch behavior for the submodule (like, if we should fetch its submodules).")]
fetch: FetchRecurse,

#[arg(short = 'u', long = "update", default_value = "unspecified", help = "How git should update the submodule when you run `git submodule update`.")]
update: Update,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI exposes internal sentinel values by defaulting --ignore/--fetch/--update to "unspecified" (which the help text elsewhere says users shouldn't set). Prefer making these flags Option<...> and treating None as unspecified, or hide the Unspecified variant from clap (e.g., #[value(skip)]) and use a real default.

Copilot uses AI. Check for mistakes.
Comment on lines +290 to +297
fn add_submodule(&mut self, opts: &SubmoduleAddOptions) -> Result<()> {
// git2 submodule cloning requires remote callbacks that are complex to configure.
// Fall through to the CLI fallback which handles this reliably.
Err(anyhow::anyhow!(
"Unable to add submodule '{}' using the library API; it will be added using the Git CLI instead",
opts.name
))
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Git2Operations::add_submodule() always returns an error, which makes the Git CLI fallback in GitOpsManager::add_submodule() effectively the primary implementation whenever gix add isn't implemented. This contradicts the gix→git2 fallback design and the PR goal of not relying on the git CLI as a primary mode.

Copilot uses AI. Check for mistakes.
@bashandbone
Copy link
Owner Author

@copilot Please implement the suggestions in this thread: #12 (review)

For the license/license-file issue, please retain 'license-file' vice license.

Copy link
Contributor

Copilot AI commented Mar 4, 2026

@bashandbone I've opened a new pull request, #19, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 3 commits March 4, 2026 22:01
…ity, utilities fixes, options Unspecified skip, CLI fallback improvements, git2 add_submodule implementation

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
…update after cloning

Co-authored-by: bashandbone <89049923+bashandbone@users.noreply.github.com>
Address PR review feedback: security, CLI ergonomics, git2 add_submodule, and correctness fixes
@bashandbone bashandbone merged commit 562e9b5 into main Mar 4, 2026
6 of 8 checks passed
@bashandbone bashandbone deleted the cli-improvements branch March 4, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file enhancement New feature or request rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants