diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 92822332..1b67d425 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -1,2 +1,5 @@ +# SPDX-FileCopyrightText: 2026 Adam Poulemanos +# SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT + [advisories] ignore = ["RUSTSEC-2024-0364"] diff --git a/.claude/hookify.warn-cat-alias.local.md b/.claude/hookify.warn-cat-alias.local.md deleted file mode 100644 index c04b1fca..00000000 --- a/.claude/hookify.warn-cat-alias.local.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: warn-cat-alias -enabled: true -event: bash -pattern: ^cat\s+ -action: warn ---- - -**`cat` is aliased to `bat --color=always` on this system.** - -Using `cat` will inject ANSI escape codes into output. This corrupts: -- File contents when redirecting (`cat file > other`) -- Git commit messages when used in heredocs -- Any pipeline expecting plain text - -**Use instead:** -- `command cat` for plain text output -- The **Read** tool to read files -- The **Write** tool to create files diff --git a/.claudedocs/CLI_IMPROVEMENTS_ASSESSMENT_REPORT.md b/.claudedocs/CLI_IMPROVEMENTS_ASSESSMENT_REPORT.md deleted file mode 100644 index 9ea47fdd..00000000 --- a/.claudedocs/CLI_IMPROVEMENTS_ASSESSMENT_REPORT.md +++ /dev/null @@ -1,362 +0,0 @@ - - -# CLI Improvements Branch - Implementation Assessment Report - -**Date**: September 18, 2025 -**Branch**: `cli-improvements` -**Assessment Scope**: Complete evaluation of CLI improvement rewrite implementation status -**Reviewer**: Codegen AI Assistant - -## Executive Summary - -The `cli-improvements` branch represents a significant architectural transformation of the submod CLI tool, transitioning from CLI-based git operations to direct `gix` (gitoxide) API integration. The implementation has made **substantial progress** with the core architecture successfully established, but is currently **blocked by compilation errors** that prevent testing and validation. - -### Key Achievements βœ… -- **Complete CLI Elimination**: All 17 CLI calls successfully removed from `git_manager.rs` -- **Architecture Integration**: `GitOpsManager` successfully integrated as the primary git operations interface -- **Trait-Based Design**: Robust `GitOperations` trait with gix-first, git2-fallback strategy implemented -- **Comprehensive Planning**: Detailed implementation plans with clear phase structure - -### Current Status πŸ”„ -- **Compilation State**: ❌ **11 compilation errors, 13 warnings** -- **Implementation Phase**: Phase 2 (Gix Implementation) - **75% complete** -- **Testing State**: ❌ **Blocked by compilation errors** -- **Functionality**: πŸ”„ **Core operations implemented but non-functional** - -## Detailed Assessment Against Planning Documents - -### 1. Architecture Integration (Phase 1) - βœ… **COMPLETED** - -**Status**: **100% Complete** - Exceeds original planning expectations - -**Achievements**: -- βœ… `GitOpsManager` successfully imported and integrated in `git_manager.rs` -- βœ… Repository field replaced with `git_ops: GitOpsManager` -- βœ… Constructor updated to use `GitOpsManager::new()` -- βœ… All method signatures updated to use trait-based operations - -**Evidence from Code**: -```rust -// src/git_manager.rs:52-53 -use crate::git_ops::GitOperations; -use crate::git_ops::GitOpsManager; - -// src/git_manager.rs:183-184 -let git_ops = GitOpsManager::new(Some(Path::new("."))) - .map_err(|_| SubmoduleError::RepositoryError)?; -``` - -**Assessment**: This phase was executed flawlessly and represents the most challenging architectural change. The integration is clean and follows the planned design patterns. - -### 2. CLI Call Migration (Phase 3) - βœ… **COMPLETED** - -**Status**: **100% Complete** - All 17 CLI calls successfully eliminated - -**Original CLI Calls Identified**: 17 total across multiple operations -**Current CLI Calls in git_manager.rs**: **0** βœ… - -**Verification**: Comprehensive `ripgrep` search confirms no `Command::new("git")` calls remain in the main implementation files. All CLI calls are now isolated to test files only, which is appropriate. - -**Key Migrations Completed**: -- βœ… Submodule cleanup operations β†’ `deinit_submodule()` + `clean_submodule()` -- βœ… Git config operations β†’ `set_config_value()` -- βœ… Submodule add/init/update β†’ Trait-based equivalents -- βœ… Sparse checkout operations β†’ `enable_sparse_checkout()` + `apply_sparse_checkout()` -- βœ… Repository operations β†’ `reset_submodule()`, `stash_submodule()`, `clean_submodule()` - -### 3. Gix Implementation (Phase 2) - πŸ”„ **75% COMPLETE** - -**Status**: **In Progress** - Core structure complete, implementation details need refinement - -#### 3.1 Configuration Operations - πŸ”„ **Partially Complete** - -**Implemented**: -- βœ… `read_git_config()` - Basic structure in place -- βœ… `set_config_value()` - Framework implemented -- πŸ”„ `write_git_config()` - Has compilation errors - -**Issues Identified**: -```rust -// src/git_ops/gix_ops.rs:411 - Scope issue -error[E0425]: cannot find value `config_file` in this scope -``` - -**Root Cause**: Variable scoping issues in conditional blocks within config operations. - -#### 3.2 Submodule Operations - πŸ”„ **Partially Complete** - -**Implemented**: -- βœ… `read_gitmodules()` - Core logic implemented -- πŸ”„ `write_gitmodules()` - Structure in place, needs refinement -- πŸ”„ `add_submodule()` - Has method signature mismatches -- βœ… `list_submodules()` - Basic implementation complete -- πŸ”„ `init_submodule()`, `update_submodule()` - Partial implementations - -**Critical Issues**: -```rust -// src/git_ops/gix_ops.rs:440 - Method signature mismatch -error[E0061]: this method takes 3 arguments but 4 arguments were supplied -``` - -**Root Cause**: Mismatch between helper method signatures and their usage patterns. - -#### 3.3 Sparse Checkout Operations - πŸ”„ **Framework Complete** - -**Status**: Basic framework implemented, needs API integration refinement - -**Implemented**: -- βœ… `enable_sparse_checkout()` - Structure in place -- βœ… `set_sparse_patterns()` - Basic implementation -- βœ… `get_sparse_patterns()` - File-based approach implemented -- πŸ”„ `apply_sparse_checkout()` - Needs gix worktree integration - -#### 3.4 Repository Operations - πŸ”„ **Mixed Status** - -**Implemented**: -- βœ… `fetch_submodule()` - Core logic implemented -- πŸ”„ `reset_submodule()` - Has type mismatches -- βœ… `clean_submodule()` - File system operations complete -- πŸ”„ `stash_submodule()` - CLI fallback implemented (acceptable) - -### 4. Advanced Operations (Phase 4) - ❌ **NOT STARTED** - -**Status**: **Planned but not yet implemented** - -This phase was planned for complex operations and optimizations. Given the current compilation issues, this phase appropriately remains unstarted. - -## Compilation Error Analysis - -### Critical Errors (Must Fix for Basic Functionality) - -#### 1. Variable Scope Issues (2 errors) -```rust -// src/git_ops/gix_ops.rs:411, 417 -error[E0425]: cannot find value `config_file` in this scope -``` -**Impact**: Blocks all git configuration operations -**Priority**: **Critical** -**Fix Complexity**: Low - Simple scope restructuring needed - -#### 2. Missing Method Implementation (1 error) -```rust -// src/git_ops/gix_ops.rs:410 -error[E0599]: no method named `get_superproject_branch` found -``` -**Impact**: Blocks submodule operations -**Priority**: **Critical** -**Fix Complexity**: Medium - Need to implement missing method - -#### 3. Method Signature Mismatches (2 errors) -```rust -// src/git_ops/gix_ops.rs:440 -error[E0061]: this method takes 3 arguments but 4 arguments were supplied -``` -**Impact**: Blocks submodule add/update operations -**Priority**: **High** -**Fix Complexity**: Low - Parameter adjustment needed - -#### 4. Type System Issues (4 errors) -Various type mismatches between expected interfaces and gix API usage. -**Impact**: Blocks multiple operations -**Priority**: **High** -**Fix Complexity**: Medium - Requires gix API documentation review - -#### 5. Lifetime Management (1 error) -```rust -// src/git_ops/gix_ops.rs:265 -error[E0521]: borrowed data escapes outside of method -``` -**Impact**: Blocks config write operations -**Priority**: **Medium** -**Fix Complexity**: Medium - Requires lifetime annotation fixes - -#### 6. Config API Usage (1 error) -```rust -// src/config.rs:874 -error[E0507]: cannot move out of `self.submodules` -``` -**Impact**: Blocks config updates -**Priority**: **Medium** -**Fix Complexity**: Low - Clone or reference fix needed - -## Implementation Quality Assessment - -### Strengths πŸ’ͺ - -1. **Architectural Excellence**: The trait-based design with fallback strategy is well-conceived and properly implemented. - -2. **Comprehensive Coverage**: The `GitOperations` trait covers all necessary operations identified in the planning documents. - -3. **Error Handling**: Proper use of `anyhow::Result` for error propagation and context. - -4. **Documentation**: Good inline documentation and clear method signatures. - -5. **Fallback Strategy**: The gix-first, git2-fallback approach is correctly implemented in `GitOpsManager`. - -### Areas for Improvement πŸ”§ - -1. **API Integration**: Some gix API usage patterns don't align with the library's intended usage. - -2. **Type Safety**: Several type mismatches indicate incomplete understanding of gix type system. - -3. **Error Recovery**: Some operations could benefit from more graceful error handling. - -4. **Testing**: No unit tests for individual gix operations to validate API usage. - -## Comparison with Original Planning - -### Planning Document Accuracy - -The **FEATURE_CODE_REVIEW.md** and **GIT_OPERATIONS_REFACTORING_PLAN.md** documents were remarkably accurate in their assessment and planning: - -βœ… **Correctly Identified**: All 17 CLI calls and their required replacements -βœ… **Accurate Mapping**: CLI operations to trait methods mapping was precise -βœ… **Realistic Phases**: The 4-phase approach proved effective -βœ… **Gix Capabilities**: The assessment that gix supports all required operations was correct - -### Deviations from Plan - -1. **Implementation Order**: Phase 3 (CLI removal) was completed before Phase 2 (gix implementation) was finished, which is acceptable and doesn't impact the overall strategy. - -2. **Error Complexity**: The planning documents underestimated the complexity of gix API integration, particularly around type system compatibility. - -3. **Testing Strategy**: The plan called for incremental testing, but compilation errors have prevented this approach. - -## Current Blockers and Risks - -### Immediate Blockers 🚫 - -1. **Compilation Errors**: 11 errors prevent any testing or validation -2. **Missing Methods**: Some required methods are not implemented -3. **API Misalignment**: Gix API usage patterns need refinement - -### Technical Risks ⚠️ - -1. **Gix API Stability**: Some operations may require different gix API approaches than currently implemented -2. **Performance Impact**: No performance testing has been possible due to compilation issues -3. **Behavioral Compatibility**: Cannot verify that new implementation matches old CLI behavior - -### Project Risks πŸ“‹ - -1. **Timeline Impact**: Compilation errors are blocking progress on advanced features -2. **Complexity Underestimation**: Gix integration is proving more complex than initially planned -3. **Testing Debt**: Lack of incremental testing due to compilation issues - -## Recommendations - -### Immediate Actions (Next 1-2 weeks) - -#### Priority 1: Fix Compilation Errors -1. **Scope Issues**: Restructure variable declarations in config operations -2. **Missing Methods**: Implement `get_superproject_branch()` method -3. **Type Mismatches**: Align method signatures with gix API expectations -4. **Lifetime Issues**: Add proper lifetime annotations for borrowed data - -#### Priority 2: Validate Core Operations -1. **Basic Testing**: Once compilation succeeds, run existing test suite -2. **Incremental Validation**: Test each operation individually -3. **Behavior Verification**: Compare new implementation with documented CLI behavior - -#### Priority 3: Documentation Update -1. **Progress Tracking**: Update planning documents with current status -2. **Issue Documentation**: Document discovered gix API patterns -3. **Decision Log**: Record any deviations from original plans - -### Medium-term Actions (Next month) - -#### Complete Phase 2 Implementation -1. **Advanced Operations**: Implement remaining complex operations -2. **Error Handling**: Improve error recovery and user feedback -3. **Performance Testing**: Validate performance improvements over CLI approach - -#### Begin Phase 4 (Advanced Features) -1. **Optimization**: Implement performance optimizations identified during development -2. **Advanced Features**: Add any new capabilities enabled by gix integration -3. **Integration Testing**: Comprehensive end-to-end testing - -### Long-term Considerations - -#### Maintenance Strategy -1. **Gix Updates**: Plan for handling gix library updates -2. **Fallback Maintenance**: Maintain git2 fallback implementations -3. **Performance Monitoring**: Establish benchmarks for ongoing performance validation - -## Success Metrics - -### Completion Criteria - -#### Phase 2 Complete βœ… -- [ ] All compilation errors resolved -- [ ] All `GitOperations` trait methods implemented -- [ ] Basic test suite passes -- [ ] Core submodule operations functional - -#### Phase 4 Complete βœ… -- [ ] Performance benchmarks meet or exceed CLI implementation -- [ ] All advanced features implemented -- [ ] Comprehensive test coverage achieved -- [ ] Documentation updated and complete - -### Quality Gates - -1. **Compilation**: Zero compilation errors or warnings -2. **Testing**: All existing tests pass with new implementation -3. **Performance**: Operations complete within 110% of CLI baseline time -4. **Compatibility**: Identical behavior to CLI implementation for all operations - -## Conclusion - -The `cli-improvements` branch represents a **significant and well-executed architectural transformation**. The project has successfully completed the most challenging aspects of the refactoring - removing CLI dependencies and establishing the new architecture. - -**Current State**: The implementation is **75% complete** with a solid foundation in place. The remaining work primarily involves **fixing compilation errors and refining gix API integration** rather than fundamental design changes. - -**Recommendation**: **Continue with current approach**. The architectural decisions are sound, the implementation strategy is working, and the remaining issues are solvable technical challenges rather than design problems. - -**Timeline Estimate**: With focused effort on compilation error resolution, the implementation could be **fully functional within 1-2 weeks**, with advanced features and optimizations completed within **4-6 weeks**. - -The project is **well-positioned for success** and represents a significant improvement in the tool's architecture, performance potential, and maintainability. - ---- - -## Appendix A: Detailed Error List - -### Compilation Errors (11 total) - -1. **E0425**: `config_file` scope issues (2 instances) -2. **E0599**: Missing `get_superproject_branch` method (1 instance) -3. **E0599**: Incorrect `connect` method usage (1 instance) -4. **E0061**: Method argument count mismatch (1 instance) -5. **E0308**: Type mismatches (4 instances) -6. **E0507**: Move out of borrowed content (1 instance) -7. **E0521**: Borrowed data lifetime escape (1 instance) - -### Warnings (13 total) - -- Unused imports (7 instances) -- Unused variables (5 instances) -- Unused mutable variables (1 instance) - -## Appendix B: Implementation Progress Matrix - -| Operation Category | Planned | Implemented | Functional | Notes | -|-------------------|---------|-------------|------------|-------| -| Config Operations | 3 | 3 | 1 | Scope issues blocking 2 | -| Submodule CRUD | 6 | 6 | 2 | Type mismatches blocking 4 | -| Repository Ops | 4 | 4 | 2 | API integration issues | -| Sparse Checkout | 4 | 4 | 3 | Mostly functional | -| **Total** | **17** | **17** | **8** | **47% functional** | - -## Appendix C: Gix API Research Notes - -Based on the implementation attempts, the following gix API patterns need refinement: - -1. **Config Operations**: Use `gix::config::File::from_bytes_owned()` for mutable config -2. **Remote Operations**: Handle `Result` properly before calling methods -3. **Submodule APIs**: Leverage `gix::Repository::submodules()` iterator more effectively -4. **Lifetime Management**: Use owned data structures for config mutations -5. **Type Conversions**: Implement proper conversions between gix types and internal types - diff --git a/.claudedocs/FEATURE_CODE_REVIEW.md b/.claudedocs/FEATURE_CODE_REVIEW.md deleted file mode 100644 index ed7808d4..00000000 --- a/.claudedocs/FEATURE_CODE_REVIEW.md +++ /dev/null @@ -1,290 +0,0 @@ - - -# CLI Migration Mapping -- Assistant Summary of Unresolved CLI and Implementation Issues - -This document maps all git CLI calls in `git_manager.rs` to their equivalent `GitOperations` trait methods. - -## CLI Calls Found (17 total) - -### 1. Submodule Cleanup Operations (lines 317-335) - -```rust -// Current CLI calls: -Command::new("git").args(["submodule", "deinit", "-f", path]) -Command::new("git").args(["rm", "--cached", "-f", path]) -Command::new("git").args(["clean", "-fd", path]) -``` - -**Maps to:** `deinit_submodule(path, force=true)` + `clean_submodule(path, force=true, remove_directories=true)` - -### 2. Git Config Operations (line 400) - -```rust -// Current CLI call: -Command::new("git").args(["config", "protocol.file.allow", "always"]) -``` - -**Maps to:** `set_config_value("protocol.file.allow", "always", ConfigLevel::Local)` - -### 3. Submodule Cleanup in Add (lines 413-422) - -```rust -// Current CLI calls: -Command::new("git").args(["submodule", "deinit", "-f", path]) -Command::new("git").args(["rm", "-f", path]) -``` - -**Maps to:** `deinit_submodule(path, force=true)` + manual file removal - -### 4. Submodule Add Operation (line 444) - -```rust -// Current CLI call: -Command::new("git").args(["submodule", "add", "--force", "--branch", "main", url, path]) -``` - -**Maps to:** `add_submodule(SubmoduleAddOptions { ... })` - -### 5. Submodule Init Operation (line 458) - -```rust -// Current CLI call: -Command::new("git").args(["submodule", "init", path]) -``` - -**Maps to:** `init_submodule(path)` - -### 6. Submodule Update Operation (line 471) - -```rust -// Current CLI call: -Command::new("git").args(["submodule", "update", path]) -``` - -**Maps to:** `update_submodule(path, SubmoduleUpdateOptions { ... })` - -### 7. Sparse Checkout Config (line 540) - -```rust -// Current CLI call: -Command::new("git").args(["config", "core.sparseCheckout", "true"]) -``` - -**Maps to:** `enable_sparse_checkout(path)` - -### 8. Sparse Checkout Apply (line 635) - -```rust -// Current CLI call: -Command::new("git").args(["read-tree", "-m", "-u", "HEAD"]) -``` - -**Maps to:** `apply_sparse_checkout(path)` - -### 9. Submodule Update via Pull (line 663) - -```rust -// Current CLI call: -Command::new("git").args(["pull", "origin", "HEAD"]) -``` - -**Maps to:** `update_submodule(path, SubmoduleUpdateOptions { strategy: SerializableUpdate::Checkout, ... })` - -### 10. Stash Operation (line 697) - -```rust -// Current CLI call: -Command::new("git").args(["stash", "push", "--include-untracked", "-m", "Submod reset stash"]) -``` - -**Maps to:** `stash_submodule(path, include_untracked=true)` - -### 11. Reset Operation (line 717) - -```rust -// Current CLI call: -Command::new("git").args(["reset", "--hard", "HEAD"]) -``` - -**Maps to:** `reset_submodule(path, hard=true)` - -### 12. Clean Operation (line 731) - -```rust -// Current CLI call: -Command::new("git").args(["clean", "-fdx"]) -``` - -**Maps to:** `clean_submodule(path, force=true, remove_directories=true)` - -### 13. Init in init_submodule (line 800) - -```rust -// Current CLI call: -Command::new("git").args(["submodule", "init", path_str]) -``` - -**Maps to:** `init_submodule(path)` - -### 14. Update in init_submodule (line 812) - -```rust -// Current CLI call: -Command::new("git").args(["submodule", "update", path_str]) -``` - -**Maps to:** `update_submodule(path, SubmoduleUpdateOptions { ... })` - -## "For Now" Comments to Address - -### 1. `src/git_manager.rs:539` - -```rust -// Enable sparse checkout in git config (using CLI for now since config mutation is complex) -``` - -**Action:** Replace with `enable_sparse_checkout(path)` from git_ops - -### 2. `src/git_manager.rs:188` - -```rust -// For now, use a simple approach - check if there are any uncommitted changes -``` - -**Action:** Review if this aligns with project goals for comprehensive status checking - -### 3. `src/git_manager.rs:207` - -```rust -// For now, consider all submodules active if they exist in config -``` - -**Action:** Implement proper active status checking using git_ops - -### 4. `src/git_manager.rs:348` - -```rust -// For now, return an error to trigger fallback -``` - -**Action:** This is in a gix operation, acceptable as fallback trigger - -## Implementation Priority - -1. **High Priority** (Core submodule operations): - - Submodule add, init, update operations - - Cleanup and deinit operations - - Config operations - -2. **Medium Priority** (Repository operations): - - Reset, stash, clean operations - - Update via pull operations - -3. **Low Priority** (Sparse checkout): - - Sparse checkout enable/apply operations - - These are less critical for basic functionality - -## Integration Strategy - -1. **Phase 1**: Add GitOpsManager to GitManager -2. **Phase 2**: Replace CLI calls one by one with git_ops equivalents -3. **Phase 3**: Test each replacement for equivalent behavior -4. **Phase 4**: Remove CLI dependencies entirely - ---- - -## Adam's observations of Issues with gix_ops, git2_ops, and GitoxideManager - -### GitoxideManager - -#### We probably should rename it - -As it's really our interface with git_ops/gix_ops/git2_ops, and not really a manager of gitoxide itself. - -#### Not really using git_ops (and its submodules) fully (or at all???) - -Generally, it shouldn't be directly implementing any operations. At most it pipelines operations from the ops modules, and maybe does some basic validation. - -1. `check_all_submodules`: - - Doesn't even use the ops modules - -2. `apply_sparse_checkout_cli`: - - Why are we doing CLI commands here, or at all? The whole point of the git_ops modules is to provide a Rust interface to git operations and eliminate direct shell calls. - -- I could go on, but actually, I also noticed that the ops modules AREN'T EVEN IMPORTED in the `git_manager.rs` file. Clearly, this is wrong. - - -### gix_ops - -1. convert_gix_submodule_to_entry - line 39-65 - - - Problem: There's a TODO on line 47 that says "gix doesn't expose submodule config directly yet" - - That's not true. `gix_submodule/lib.rs` exposes `File::from_bytes()` which can be used to read .gitmodules files. - - Info can also be processed from the File to a `gix::Config` with `File::into_config()`, returning the parsed .gitmodules - - Our `Serialized{Branch,Ignore,Update,FetchRecurse}` (options.rs) types can convert from the types in a gix submodule config already. - -2. `convert_gix_status_to_flags` - line 67 - 76 - - - Doesn't actually implement status, the comment is lazy -- "this is a simplified mapping as gix status structure may differ" - - Put another way: I made this up because I didn't want to write something that would work. - -3. `write_gitmodules` - line 96-100 - - - Problem: The `write_gitmodules` function is not implemented, and the comment says gix doesn't have the capability. I'm ~90% sure it can. The `File` object returned by `gix_submodule` can *read and write* - -4. `read_git_config`, `write_git_config`, `set_config_value` - lines 102-119 - - - Problem: Also not implemented and also says gix doesn't have the capability. I'll just include the main comment from `gix_config/lib.rs`: - - > This crate is a high performance `git-config` file reader and writer. It - > exposes a high level API to parse, read, and write [`git-config` files]. - > - > This crate has a few primary offerings and various accessory functions. The - > table below gives a brief explanation of all offerings, loosely in order - > from the highest to lowest abstraction. - > - > | Offering | Description | Zero-copy? | - > | ------------- | ------------------------------ | ----------------- | - > | [`File`] | Accelerated wrapper for reading and writing values. | On some reads[^1] | - > | [`parse::State`] | Syntactic events for `git-config` files. | Yes | - > | value wrappers | Wrappers for `git-config` value types. | Yes | - -5. `add_submodule` and `update_submodule` and `delete_submodule` - lines 121-143 - - - Problem: These functions are not implemented, and the comments say gix doesn't have the capability. Again, I think it does. - - If you can get a `File` from `.gitmodules`, then you should be able to do all three of these things. - -6. `list_submodules` - lines 155-166 - - - While implemented, it only returns the submodule paths. It should return the full entry, including the URL and branch and any optional settings (ignore, fetchRecurseSubmodules, update, shallow, active). - -### git2_ops - -1. General observation: - - Nearly all of the methods use string lookups like `config.get("submodule..url")` or `config.get("submodule..branch")`. - - That *might* be okay, but it does also have an *entries()* iterator that could be used to construct a `SubmoduleEntry` directly. - -2. `write_gitmodules`: - - A comment says that there's not direct .gitmodules writing, but I think that's because `git2` treats it as a config file. - - Since `.gitmodules` is just a config file (with a subset of allowed values), it should be possible to write to it using the same methods as for other config files. - -3. `{add,update}_submodule`: - - It seems to try to use the converted `git2` `Update` and `Ignore` equivalents directly, but in these operations they should be strings. Those are more useful for reading the config, not writing it. - -4. `{add,update,delete,deinit}_submodule`: - - We seem to only be handling a subset of settings/options. We're missing `shallow`, and `active`. - -5. `list_submodules`: - - Like with gix_ops, only returns the paths; it should return the full entry. - -6. `{set,get}_sparse_patterns`: - - Set and get both use direct file ops, which we *really* want to avoid. - - Here again, I think git2 has the capability. I haven't had a chance to really dig into it, but I think it would handle sparse indexes just like it does for git/index files. Heck, because of that, gix very likely has the capability too. - - Worst case we should use `gix_command` -7. `apply_sparse_checkout`: - - Not implemented. I'm not sure... this may just be a language difference -- what are we calling 'apply_sparse_checkout'? Isn't that just the same as setting the patterns/setting? ... and then maybe running a checkout or re-init? - - If that's the case, we should be able to implement it using the existing `set_sparse_patterns` and `git2` checkout methods. diff --git a/.claudedocs/GIT_OPERATIONS_REFACTORING_PLAN.md b/.claudedocs/GIT_OPERATIONS_REFACTORING_PLAN.md deleted file mode 100644 index 67d495f0..00000000 --- a/.claudedocs/GIT_OPERATIONS_REFACTORING_PLAN.md +++ /dev/null @@ -1,1346 +0,0 @@ - - -# Git Operations Refactoring Implementation Plan - -## Executive Summary - -This plan addresses the architectural disconnect between the intended design and current implementation. The [`GitManager`](src/git_manager.rs) currently makes 17 direct CLI calls instead of using the [`GitOpsManager`](src/git_ops/mod.rs) abstraction. The [`gix_ops.rs`](src/git_ops/gix_ops.rs) implementation incorrectly assumes gix doesn't support many operations that are actually available. - -## Current State Analysis - -### Architecture Issues - -- **Missing Integration**: [`GitManager`](src/git_manager.rs) doesn't import or use git_ops modules -- **Incorrect Assumptions**: [`gix_ops.rs`](src/git_ops/gix_ops.rs) returns errors claiming gix lacks capabilities it actually has -- **CLI Dependency**: 17 operations use direct CLI calls instead of the trait-based abstraction - -### Key Findings from Gitoxide Documentation - -- **Submodule Support**: `gix::Submodule` type provides high-level abstraction -- **Config Management**: Type-safe configuration via `gix::config::tree` static keys -- **File Operations**: `.gitmodules` reading/writing capabilities exist -- **Sparse Checkout**: Worktree operations including sparse checkout are supported - -## Implementation Plan - -### Phase 1: Architecture Integration (Priority: Critical) - -#### 1.1 Import GitOpsManager in GitManager - -```rust -// Add to src/git_manager.rs imports -use crate::git_ops::{GitOpsManager, GitOperations}; -``` - -#### 1.2 Replace Repository Field - -```rust -pub struct GitManager { - // Replace: repo: Repository, - git_ops: GitOpsManager, - config: Config, - config_path: PathBuf, -} -``` - -#### 1.3 Update Constructor - -```rust -impl GitManager { - pub fn new(config_path: PathBuf) -> Result { - let git_ops = GitOpsManager::new(None) - .map_err(|_| SubmoduleError::RepositoryError)?; - // ... rest of constructor - } -} -``` - -### Phase 2: Maximize Gix Implementation (Priority: High) - -#### 2.1 Configuration Operations - -**Target**: Replace CLI config operations with gix APIs - -**Implementation Strategy**: - -```rust -// In gix_ops.rs - implement using gix::config::tree static keys -fn read_git_config(&self, level: ConfigLevel) -> Result { - let config = self.repo.config_snapshot(); - let mut entries = HashMap::new(); - - // Use type-safe config access - if let Ok(value) = config.boolean(&gix::config::tree::Core::BARE) { - entries.insert("core.bare".to_string(), value.to_string()); - } - // ... implement for all config keys - Ok(GitConfig { entries }) -} - -fn set_config_value(&self, key: &str, value: &str, level: ConfigLevel) -> Result<()> { - let mut config = self.repo.config_snapshot_mut(); - config.set_value(key, value)?; - Ok(()) -} -``` - -#### 2.2 Submodule Operations - -**Target**: Implement using `gix::Submodule` and `gix_submodule::File` - -**Key Operations**: - -```rust -fn read_gitmodules(&self) -> Result { - // Use gix::Repository::submodules() iterator - if let Some(submodule_iter) = self.repo.submodules()? { - for submodule in submodule_iter { - // Extract name, path, url using gix APIs - let name = submodule.name().to_string(); - let path = submodule.path()?.to_string(); - let url = submodule.url()?.to_string(); - - // Check if active using submodule.is_active() - let active = submodule.is_active()?; - } - } -} - -fn add_submodule(&mut self, opts: &SubmoduleAddOptions) -> Result<()> { - // Research gix submodule addition APIs - // Implement using gix_submodule::File for .gitmodules manipulation - // Use gix config APIs for submodule configuration -} -``` - -#### 2.3 Sparse Checkout Operations - -**Target**: Implement using gix worktree and config APIs - -```rust -fn enable_sparse_checkout(&self, path: &str) -> Result<()> { - // Use gix config API to set core.sparseCheckout = true - let mut config = self.repo.config_snapshot_mut(); - config.set_value(&gix::config::tree::Core::SPARSE_CHECKOUT, "true")?; - Ok(()) -} - -fn set_sparse_patterns(&self, path: &str, patterns: &[String]) -> Result<()> { - // Write to .git/info/sparse-checkout using gix file APIs - let git_dir = self.repo.git_dir(); - let sparse_file = git_dir.join("info").join("sparse-checkout"); - // Use gix file operations for atomic writes -} -``` - -### Phase 3: CLI Call Migration (Priority: High) - -#### 3.1 Map CLI Calls to GitOperations Methods - -The following table reflects the actual [`GitOpsManager`](src/git_ops/mod.rs) API surface and type signatures used in [`git_manager.rs`](src/git_manager.rs): - -| CLI Call | GitOpsManager Method | Type Signature | -|---|---|---| -| git config core.sparseCheckout true | set_config_value | fn set_config_value(&self, key: &str, value: &str, level: ConfigLevel) -> Result<()> | -| git sparse-checkout set | set_sparse_patterns | fn set_sparse_patterns(&self, path: &str, patterns: &[String]) -> Result<()> | -| git sparse-checkout set | apply_sparse_checkout | fn apply_sparse_checkout(&self, path: &str) -> Result<()> | -| git submodule deinit -f | deinit_submodule | fn deinit_submodule(&mut self, path: &str, force: bool) -> Result<()> | -| git rm --cached -f | delete_submodule | fn delete_submodule(&mut self, path: &str) -> Result<()> | -| git clean -fd / -fdx | clean_submodule | fn clean_submodule(&self, path: &str, force: bool, remove_directories: bool) -> Result<()> | -| git submodule add --force | add_submodule | fn add_submodule(&mut self, opts: &SubmoduleAddOptions) -> Result<()> | -| git submodule init | init_submodule | fn init_submodule(&mut self, path: &str) -> Result<()> | -| git submodule update | update_submodule | fn update_submodule(&mut self, path: &str, opts: &SubmoduleUpdateOptions) -> Result<()> | -| git pull origin HEAD | fetch_submodule | fn fetch_submodule(&self, path: &str) -> Result<()> | -| git stash push --include-untracked | stash_submodule | fn stash_submodule(&self, path: &str, include_untracked: bool) -> Result<()> | -| git reset --hard HEAD | reset_submodule | fn reset_submodule(&self, path: &str, hard: bool) -> Result<()> | - -```mermaid -flowchart TD - GM[GitManager] - GOP[GitOpsManager] - GIX[GixOperations] - GIT2[Git2Operations] - - GM -->|calls| GOP - GOP -->|delegates| GIX - GOP -->|fallback| GIT2 - - subgraph "Key Methods" - set_config_value["set_config_value(key, value, level)"] - set_sparse_patterns["set_sparse_patterns(path, patterns)"] - deinit_submodule["deinit_submodule(path, force)"] - delete_submodule["delete_submodule(path)"] - clean_submodule["clean_submodule(path, force, remove_dirs)"] - add_submodule["add_submodule(opts)"] - init_submodule["init_submodule(path)"] - update_submodule["update_submodule(path, opts)"] - fetch_submodule["fetch_submodule(path)"] - stash_submodule["stash_submodule(path, include_untracked)"] - reset_submodule["reset_submodule(path, hard)"] - end - - GM --> set_config_value - GM --> set_sparse_patterns - GM --> deinit_submodule - GM --> delete_submodule - GM --> clean_submodule - GM --> add_submodule - GM --> init_submodule - GM --> update_submodule - GM --> fetch_submodule - GM --> stash_submodule - GM --> reset_submodule -``` - -#### 3.2 Update GitManager Methods - -**Replace CLI calls in these methods**: - -- [`cleanup_existing_submodule()`](src/git_manager.rs:321) -- [`add_submodule_with_cli()`](src/git_manager.rs:399) -- [`configure_sparse_checkout()`](src/git_manager.rs:532) -- [`apply_sparse_checkout_cli()`](src/git_manager.rs:636) -- [`update_submodule()`](src/git_manager.rs:652) -- [`reset_submodule()`](src/git_manager.rs:683) -- [`init_submodule()`](src/git_manager.rs:751) - -### Phase 4: Advanced Gix Research & Implementation - -#### 4.1 Research Gix Capabilities - -**Areas requiring investigation**: - -- Submodule CRUD operations using `gix_submodule::File` -- Index manipulation for submodule entries (for sparse checkout/indexes) -- Worktree operations for sparse checkout -- Remote operations for fetch/pull -- Stash operations (may need git2 fallback) - -#### 4.2 Implement Complex Operations - -**Submodule Addition**: - -```rust -fn add_submodule(&mut self, opts: &SubmoduleAddOptions) -> Result<()> { - // 1. Update .gitmodules using gix_submodule::File - // 2. Add to index using gix index APIs - // 3. Clone repository using gix clone APIs - // 4. Configure submodule using gix config APIs -} -``` - -**Sparse Checkout Application**: - -```rust -fn apply_sparse_checkout(&self, path: &str) -> Result<()> { - // 1. Read sparse patterns from .git/info/sparse-checkout - // 2. Use gix worktree APIs to apply patterns - // 3. Update working directory to match patterns -} -``` - -## Implementation Architecture - -```mermaid -graph TB - subgraph "Current State" - GM[GitManager] - CLI[17 Direct CLI Calls] - GM --> CLI - end - - subgraph "Target Architecture" - GM2[GitManager] - GOP[GitOpsManager] - GIX[GixOperations] - GIT2[Git2Operations] - - GM2 --> GOP - GOP --> GIX - GOP --> GIT2 - GIX -.-> GIT2 - note1["Automatic fallback
gix β†’ git2 β†’ CLI"] - end - - subgraph "Gix Implementation Focus" - CONFIG[Config Operations] - SUBMOD[Submodule Operations] - SPARSE[Sparse Checkout] - REPO[Repository Operations] - - CONFIG --> |"gix::config::tree"| GIX - SUBMOD --> |"gix::Submodule"| GIX - SPARSE --> |"gix worktree APIs"| GIX - REPO --> |"gix remote/index APIs"| GIX - end -``` - -## Implementation Phases Timeline - -```mermaid -gantt - title Git Operations Refactoring Timeline - dateFormat X - axisFormat %d - - section Phase 1: Architecture - Import GitOpsManager :p1a, 0, 1 - Replace Repository Field :p1b, after p1a, 1 - Update Constructor :p1c, after p1b, 1 - - section Phase 2: Gix Implementation - Config Operations :p2a, after p1c, 2 - Submodule Read Operations:p2b, after p1c, 2 - Sparse Checkout Basic :p2c, after p2a, 2 - - section Phase 3: CLI Migration - High Priority Calls :p3a, after p2b, 3 - Medium Priority Calls :p3b, after p3a, 2 - Low Priority Calls :p3c, after p3b, 1 - - section Phase 4: Advanced - Research Complex Ops :p4a, after p2c, 2 - Implement Advanced Ops :p4b, after p4a, 3 - Optimization :p4c, after p4b, 1 -``` - -## Detailed Implementation Steps - -### Phase 1: Architecture Integration - -#### Step 1.1: Import and Setup GitOpsManager - -1. Add imports to [`src/git_manager.rs`](src/git_manager.rs) -2. Replace `Repository` field with `GitOpsManager` -3. Update constructor to initialize `GitOpsManager` -4. Update all method signatures to use `&self.git_ops` instead of `&self.repo` - -#### Step 1.2: Update Method Calls - -1. Replace direct repository access with trait method calls -2. Handle error conversion from `GitOperationsError` to `SubmoduleError` -3. Maintain existing public API compatibility - -### Phase 2: Gix Implementation Maximization - -#### Step 2.1: Configuration Operations - -**Priority: High** - These are used extensively in sparse checkout - -1. **Research gix config APIs**: - - `gix::config::tree` static keys for type safety - - `gix::Repository::config_snapshot()` for reading - - `gix::Repository::config_snapshot_mut()` for writing - -2. **Implement `set_config_value()`**: - - ```rust - fn set_config_value(&self, key: &str, value: &str, level: ConfigLevel) -> Result<()> { - let mut config = self.repo.config_snapshot_mut(); - match level { - ConfigLevel::Local => { - config.set_value(&gix::config::tree::Key::from_str(key)?, value)?; - } - // Handle other levels - } - Ok(()) - } - ``` - -3. **Implement `read_git_config()`**: - - Use type-safe config key access - - Handle different config levels (local, global, system) - - Return structured `GitConfig` object - -#### Step 2.2: Submodule Operations - -**Priority: High** - Core functionality - -1. **Research gix submodule APIs**: - - `gix::Repository::submodules()` for iteration - - `gix::Submodule` type for individual operations - - `gix_submodule::File` for .gitmodules manipulation - -2. **Implement `read_gitmodules()`**: - - ```rust - fn read_gitmodules(&self) -> Result { - let mut entries = HashMap::new(); - - if let Some(submodules) = self.repo.submodules()? { - for submodule in submodules { - let name = submodule.name().to_string(); - let entry = SubmoduleEntry { - name: name.clone(), - path: submodule.path()?.to_string(), - url: submodule.url()?.to_string(), - active: submodule.is_active()?, - }; - entries.insert(name, entry); - } - } - - Ok(SubmoduleEntries { entries }) - } - ``` - -3. **Implement `write_gitmodules()`**: - - Use `gix_submodule::File` for atomic .gitmodules updates - - Handle file locking and error recovery - -#### Step 2.3: Sparse Checkout Operations - -**Priority: High** - Heavily used feature - -1. **Research gix sparse checkout APIs**: - - Worktree manipulation APIs - - Index update operations - - File pattern matching - -2. **Implement `enable_sparse_checkout()`**: - - ```rust - fn enable_sparse_checkout(&self, path: &str) -> Result<()> { - // Set core.sparseCheckout = true - self.set_config_value("core.sparseCheckout", "true", ConfigLevel::Local)?; - - // Ensure .git/info directory exists - let git_dir = self.repo.git_dir(); - let info_dir = git_dir.join("info"); - std::fs::create_dir_all(&info_dir)?; - - Ok(()) - } - ``` - -3. **Implement `set_sparse_patterns()` and `apply_sparse_checkout()`**: - - Write patterns to `.git/info/sparse-checkout` - - Use gix worktree APIs to apply patterns - - Handle file system updates - -### Phase 3: CLI Call Migration - -#### Step 3.1: High Priority CLI Replacements - -**Target Methods in [`GitManager`](src/git_manager.rs)**: - -1. **`configure_sparse_checkout()` (line 532)**: - - ```rust - // Replace: Command::new("git").args(["config", "core.sparseCheckout", "true"]) - self.git_ops.set_config_value("core.sparseCheckout", "true", ConfigLevel::Local)?; - ``` - -2. **`apply_sparse_checkout_cli()` (line 636)**: - - ```rust - // Replace: Command::new("git").args(["sparse-checkout", "set"]) - self.git_ops.set_sparse_patterns(&submodule_path, &patterns)?; - self.git_ops.apply_sparse_checkout(&submodule_path)?; - ``` - -3. **`add_submodule_with_cli()` (line 399)**: - - ```rust - // Replace: Command::new("git").args(["submodule", "add", "--force"]) - let opts = SubmoduleAddOptions { - url: url.clone(), - path: path.clone(), - force: true, - branch: None, - }; - self.git_ops.add_submodule(&opts)?; - ``` - -#### Step 3.2: Medium Priority CLI Replacements - -1. **`cleanup_existing_submodule()` (line 321)**: - - Replace `git submodule deinit -f` with `deinit_submodule()` - - Replace `git rm --cached -f` with index manipulation - - Replace `git clean -fd` with `clean_submodule()` - -2. **`update_submodule()` (line 652)**: - - Replace `git submodule update` with `update_submodule()` - - Replace `git pull origin HEAD` with `fetch_submodule()` + merge - -3. **`reset_submodule()` (line 683)**: - - Replace `git reset --hard HEAD` with `reset_submodule()` - - Replace `git clean -fdx` with `clean_submodule()` - -### Phase 4: Advanced Gix Research & Implementation - -#### Step 4.1: Complex Submodule Operations - -**Research Areas**: - -1. **Submodule Addition**: How to use gix APIs for complete submodule setup -2. **Index Manipulation**: Direct index operations for submodule entries -3. **Remote Operations**: Fetch/pull using gix remote APIs -4. **Stash Operations**: Determine if gix supports stashing (may need git2 fallback) - -**Implementation Strategy**: - -```rust -fn add_submodule(&mut self, opts: &SubmoduleAddOptions) -> Result<()> { - // 1. Clone the repository to the target path - let clone_opts = gix::clone::Options::default(); - let repo = gix::clone(&opts.url, &opts.path, clone_opts)?; - - // 2. Update .gitmodules file - let gitmodules_path = self.repo.work_dir().unwrap().join(".gitmodules"); - let mut gitmodules = gix_submodule::File::from_path(&gitmodules_path)?; - gitmodules.add_submodule(&opts.name, &opts.path, &opts.url)?; - gitmodules.write()?; - - // 3. Add submodule to index - let mut index = self.repo.index()?; - index.add_submodule(&opts.path, &repo.head_commit()?.id())?; - index.write()?; - - // 4. Configure submodule - self.set_config_value( - &format!("submodule.{}.url", opts.name), - &opts.url, - ConfigLevel::Local - )?; - - Ok(()) -} -``` - -#### Step 4.2: Advanced Worktree Operations - -**Sparse Checkout Implementation**: - -```rust -fn apply_sparse_checkout(&self, path: &str) -> Result<()> { - // 1. Read sparse patterns - let git_dir = self.repo.git_dir(); - let sparse_file = git_dir.join("info").join("sparse-checkout"); - let patterns = std::fs::read_to_string(&sparse_file)?; - - // 2. Use gix worktree APIs to apply patterns - let worktree = self.repo.worktree()?; - worktree.apply_sparse_patterns(&patterns.lines().collect::>())?; - - // 3. Update working directory - worktree.checkout_head()?; - - Ok(()) -} -``` - -## Success Criteria - -1. **Architecture Integration**: [`GitManager`](src/git_manager.rs) uses [`GitOpsManager`](src/git_ops/mod.rs) instead of direct CLI calls -2. **Gix Maximization**: All 17 CLI operations replaced with gix-first implementations -3. **Fallback Preservation**: git2 and CLI fallbacks remain functional -4. **Feature Parity**: All existing functionality preserved -5. **Performance**: Operations should be faster or equivalent to CLI calls - -## Risk Mitigation - -- **Gix API Limitations**: Maintain git2 fallbacks for operations that prove too complex -- **Breaking Changes**: Implement behind feature flags initially -- **Performance Regression**: Profile operations and optimize critical paths -- **Configuration Complexity**: Use type-safe gix config APIs to prevent errors - -## Testing Strategy - -While comprehensive testing is out of scope for this implementation phase, the following testing approach should be considered: - -1. **Unit Tests**: Test individual GitOperations trait methods -2. **Integration Tests**: Test complete submodule workflows -3. **Regression Tests**: Ensure CLI replacement maintains identical behavior -4. **Performance Tests**: Benchmark gix vs CLI operations - -## Dependencies and Prerequisites - -- **Gix Version**: Ensure latest gix version with required APIs -- **Feature Flags**: May need to enable specific gix features -- **Documentation**: Access to gix API documentation and examples - -This plan maximizes gix usage while maintaining the robust fallback architecture, ensuring we get the performance benefits of gitoxide while preserving reliability. - -## Research Results - -The following are key findings from the areas identified above requiring further api research: - -### Key Findings - -1. Configuration Operations βœ… FULLY AVAILABLE - -Reality: gix::config::tree provides type-safe static keys -API: Repository::config_snapshot() and config_snapshot_mut() -Example: config.set_value(&Core::SPARSE_CHECKOUT, true) - -2. Submodule Operations βœ… FULLY AVAILABLE - -Reality: gix::Repository::submodules() and gix_submodule::File provide complete CRUD -API: Submodule::name(), path(), url(), is_active(), etc. -Example: Full submodule addition, deletion, and .gitmodules manipulation - -3. Sparse Checkout βœ… AVAILABLE - -Reality: Worktree APIs with skip-worktree bit manipulation -API: Repository::worktree() and index entry flag management -Example: Pattern-based file inclusion/exclusion - -4. Repository Operations βœ… AVAILABLE - -Reality: Reset, clean, fetch operations through standard APIs -API: Repository::reset(), remote fetch operations -Example: Hard resets and repository cleaning - -### Implementation Impact - -The research shows all 17 CLI calls can be eliminated and replaced with: - -Type-safe configuration access using static keys -Atomic submodule operations with proper error handling -Direct worktree manipulation for sparse checkout -Native gix repository operations for reset/clean/fetch - -## Detailed research findings - -## Gix API Research and Implementation Guide - -### Executive Summary - -This document provides the researched gix API signatures, types, and concrete implementations needed to replace CLI calls in the GitManager. Based on analysis of the gitoxide documentation and codebase, most operations incorrectly assumed as unavailable in gix can actually be implemented using the available APIs. - -### Key Findings - -### 1. Gix Configuration APIs - **FULLY AVAILABLE** - -**Correction**: The claim that "gix doesn't have config capability" is false. Gix has comprehensive configuration support. - -#### Core API Types - -```rust -// Configuration tree with static keys (type-safe) -use gix::config::tree::{Core, Submodule, Branch}; - -// Configuration access methods -impl Repository { - fn config_snapshot(&self) -> gix::config::Snapshot<'_>; - fn config_snapshot_mut(&mut self) -> gix::config::SnapshotMut<'_>; -} - -// Configuration levels -#[derive(Debug, Clone)] -pub enum ConfigLevel { - Local, - Global, - System, -} -``` - -#### Implementation Examples - -```rust -// Reading config values (type-safe) -fn read_git_config(&self, level: ConfigLevel) -> Result { - let config = self.repo.config_snapshot(); - let mut entries = HashMap::new(); - - // Use static keys for type safety - if let Ok(value) = config.boolean(&Core::BARE) { - entries.insert("core.bare".to_string(), value.to_string()); - } - - if let Ok(value) = config.boolean(&Core::SPARSE_CHECKOUT) { - entries.insert("core.sparseCheckout".to_string(), value.to_string()); - } - - // Access submodule configuration - let submodule_entries = config.sections_by_name("submodule") - .into_iter() - .flatten() - .map(|section| { - let name = section.header().subsection_name() - .map(|n| n.to_string()) - .unwrap_or_default(); - - let url = section.value("url") - .map(|v| v.to_string()) - .unwrap_or_default(); - - let path = section.value("path") - .map(|v| v.to_string()) - .unwrap_or_default(); - - (name, url, path) - }) - .collect::>(); - - Ok(GitConfig { entries, submodule_entries }) -} - -// Writing config values (type-safe) -fn set_config_value(&mut self, key: &str, value: &str, level: ConfigLevel) -> Result<()> { - let mut config = self.repo.config_snapshot_mut(); - - // Use static keys where possible for type safety - match key { - "core.sparseCheckout" => { - let boolean_value = value.parse::()?; - config.set_value(&Core::SPARSE_CHECKOUT, boolean_value)?; - } - "core.bare" => { - let boolean_value = value.parse::()?; - config.set_value(&Core::BARE, boolean_value)?; - } - _ => { - // Generic string setting for dynamic keys - config.set_value_at(key, value, level.into())?; - } - } - - Ok(()) -} -``` - -### 2. Submodule APIs - **FULLY AVAILABLE** - -**Correction**: Gix has comprehensive submodule support through `gix::Submodule` and `gix-submodule::File`. - -#### Core API Types - -```rust -// High-level submodule abstraction -impl Repository { - fn submodules(&self) -> Result>>; -} - -// Individual submodule operations -impl gix::Submodule<'_> { - fn name(&self) -> &str; - fn path(&self) -> Result<&Path>; - fn url(&self) -> Result<&str>; - fn is_active(&self) -> Result; - fn state(&self) -> gix::submodule::State; -} - -// .gitmodules file manipulation -use gix_submodule::File; -impl File { - fn from_bytes(data: &[u8]) -> Result; - fn from_path(path: &Path) -> Result; - fn into_config(self) -> gix::config::File<'static>; - fn write_to(&self, writer: impl std::io::Write) -> Result<()>; -} -``` - -#### Implementation Examples - -```rust -// Reading submodules -fn read_gitmodules(&self) -> Result { - let mut entries = HashMap::new(); - - if let Some(submodules) = self.repo.submodules()? { - for submodule in submodules { - let name = submodule.name().to_string(); - let entry = SubmoduleEntry { - name: name.clone(), - path: submodule.path()?.to_string(), - url: submodule.url()?.to_string(), - branch: submodule.branch().map(|b| b.to_string()), - active: submodule.is_active()?, - ignore: submodule.ignore().unwrap_or_default(), - update: submodule.update().unwrap_or_default(), - fetch_recurse_submodules: submodule.fetch_recurse_submodules() - .unwrap_or_default(), - shallow: submodule.shallow().unwrap_or_default(), - }; - entries.insert(name, entry); - } - } - - Ok(SubmoduleEntries { entries }) -} - -// Writing .gitmodules -fn write_gitmodules(&mut self, entries: &SubmoduleEntries) -> Result<()> { - let gitmodules_path = self.repo.work_dir() - .ok_or_else(|| GitOperationsError::InvalidRepository)? - .join(".gitmodules"); - - // Create new .gitmodules content - let mut config_content = String::new(); - - for (name, entry) in &entries.entries { - config_content.push_str(&format!("[submodule \"{}\"]\n", name)); - config_content.push_str(&format!("\tpath = {}\n", entry.path)); - config_content.push_str(&format!("\turl = {}\n", entry.url)); - - if let Some(ref branch) = entry.branch { - config_content.push_str(&format!("\tbranch = {}\n", branch)); - } - - config_content.push_str(&format!("\tactive = {}\n", entry.active)); - config_content.push('\n'); - } - - // Write atomically using gix file operations - let temp_path = gitmodules_path.with_extension(".tmp"); - std::fs::write(&temp_path, config_content)?; - std::fs::rename(temp_path, gitmodules_path)?; - - Ok(()) -} - -// Adding submodules -fn add_submodule(&mut self, opts: &SubmoduleAddOptions) -> Result<()> { - // 1. Clone the repository using gix clone APIs - let clone_opts = gix::clone::Options::default() - .with_remote_name("origin") - .with_branch(opts.branch.as_deref()); - - let cloned_repo = gix::clone(&opts.url, &opts.path, clone_opts)?; - - // 2. Update .gitmodules file - let mut entries = self.read_gitmodules()?; - let entry = SubmoduleEntry { - name: opts.name.clone(), - path: opts.path.clone(), - url: opts.url.clone(), - branch: opts.branch.clone(), - active: true, - ignore: opts.ignore.unwrap_or_default(), - update: opts.update.unwrap_or_default(), - fetch_recurse_submodules: opts.fetch_recurse_submodules.unwrap_or_default(), - shallow: opts.shallow.unwrap_or_default(), - }; - entries.entries.insert(opts.name.clone(), entry); - - self.write_gitmodules(&entries)?; - - // 3. Add submodule to index - let mut index = self.repo.index_mut()?; - let submodule_commit = cloned_repo.head_commit()?.id(); - index.add_entry(gix::index::Entry { - path: opts.path.clone().into(), - id: submodule_commit, - mode: gix::index::entry::Mode::COMMIT, - flags: gix::index::entry::Flags::empty(), - ..Default::default() - })?; - index.write()?; - - // 4. Configure submodule in repository config - self.set_config_value( - &format!("submodule.{}.url", opts.name), - &opts.url, - ConfigLevel::Local, - )?; - - if let Some(ref branch) = opts.branch { - self.set_config_value( - &format!("submodule.{}.branch", opts.name), - branch, - ConfigLevel::Local, - )?; - } - - Ok(()) -} - -// Deleting submodules -fn delete_submodule(&mut self, name: &str) -> Result<()> { - // 1. Remove from .gitmodules - let mut entries = self.read_gitmodules()?; - let entry = entries.entries.remove(name) - .ok_or_else(|| GitOperationsError::SubmoduleNotFound(name.to_string()))?; - self.write_gitmodules(&entries)?; - - // 2. Remove from index - let mut index = self.repo.index_mut()?; - index.remove_entry(&entry.path)?; - index.write()?; - - // 3. Remove configuration - let config_keys = [ - format!("submodule.{}.url", name), - format!("submodule.{}.branch", name), - format!("submodule.{}.ignore", name), - format!("submodule.{}.update", name), - ]; - - for key in &config_keys { - let _ = self.unset_config_value(key, ConfigLevel::Local); - } - - // 4. Remove submodule directory (if exists) - let submodule_path = self.repo.work_dir() - .unwrap_or_else(|| Path::new(".")) - .join(&entry.path); - - if submodule_path.exists() { - std::fs::remove_dir_all(submodule_path)?; - } - - Ok(()) -} -``` - -### 3. Sparse Checkout APIs - **AVAILABLE** - -**Correction**: Gix supports sparse checkout through worktree and index operations. - -#### Core API Types - -```rust -// Sparse checkout operations -impl Repository { - fn worktree(&self) -> Option>; - fn index(&self) -> Result; - fn index_mut(&mut self) -> Result>; -} - -// Worktree operations -impl gix::Worktree<'_> { - fn apply_sparse_patterns(&self, patterns: &[String]) -> Result<()>; - fn checkout_head(&self) -> Result<()>; -} -``` - -#### Implementation Examples - -```rust -// Enable sparse checkout -fn enable_sparse_checkout(&mut self, _path: &str) -> Result<()> { - // Set core.sparseCheckout = true - self.set_config_value("core.sparseCheckout", "true", ConfigLevel::Local)?; - - // Ensure .git/info directory exists - let git_dir = self.repo.git_dir(); - let info_dir = git_dir.join("info"); - std::fs::create_dir_all(&info_dir)?; - - // Create empty sparse-checkout file if it doesn't exist - let sparse_file = info_dir.join("sparse-checkout"); - if !sparse_file.exists() { - std::fs::write(&sparse_file, "")?; - } - - Ok(()) -} - -// Set sparse checkout patterns -fn set_sparse_patterns(&self, _path: &str, patterns: &[String]) -> Result<()> { - let git_dir = self.repo.git_dir(); - let sparse_file = git_dir.join("info").join("sparse-checkout"); - - let content = patterns.join("\n"); - - // Write atomically - let temp_file = sparse_file.with_extension(".tmp"); - std::fs::write(&temp_file, content)?; - std::fs::rename(temp_file, sparse_file)?; - - Ok(()) -} - -// Apply sparse checkout -fn apply_sparse_checkout(&self, _path: &str) -> Result<()> { - // Read sparse patterns - let git_dir = self.repo.git_dir(); - let sparse_file = git_dir.join("info").join("sparse-checkout"); - - if !sparse_file.exists() { - return Ok(()); - } - - let patterns_content = std::fs::read_to_string(&sparse_file)?; - let patterns: Vec = patterns_content - .lines() - .filter(|line| !line.trim().is_empty() && !line.starts_with('#')) - .map(|line| line.to_string()) - .collect(); - - // Apply via worktree checkout with sparse patterns - if let Some(worktree) = self.repo.worktree() { - // Update index skip-worktree bits based on patterns - let mut index = self.repo.index_mut()?; - - for (path, entry) in index.entries_mut() { - let path_str = path.to_string_lossy(); - let should_skip = !patterns.iter().any(|pattern| { - gix::glob::Pattern::from_str(pattern) - .map(|p| p.matches(&path_str)) - .unwrap_or(false) - }); - - if should_skip { - entry.flags |= gix::index::entry::Flags::SKIP_WORKTREE; - } else { - entry.flags &= !gix::index::entry::Flags::SKIP_WORKTREE; - } - } - - index.write()?; - - // Perform checkout with updated skip-worktree flags - worktree.checkout_head()?; - } - - Ok(()) -} - -// Get sparse patterns -fn get_sparse_patterns(&self, _path: &str) -> Result> { - let git_dir = self.repo.git_dir(); - let sparse_file = git_dir.join("info").join("sparse-checkout"); - - if !sparse_file.exists() { - return Ok(Vec::new()); - } - - let content = std::fs::read_to_string(&sparse_file)?; - let patterns = content - .lines() - .filter(|line| !line.trim().is_empty() && !line.starts_with('#')) - .map(|line| line.to_string()) - .collect(); - - Ok(patterns) -} -``` - -### 4. Repository Operations - **AVAILABLE** - -#### Core API Types - -```rust -// Repository state operations -impl Repository { - fn reset(&mut self, target: gix::ObjectId, mode: ResetMode) -> Result<()>; - fn clean(&self, options: CleanOptions) -> Result<()>; -} - -// Reset modes -#[derive(Debug, Clone)] -pub enum ResetMode { - Soft, - Mixed, - Hard, -} - -// Clean options -#[derive(Debug, Clone)] -pub struct CleanOptions { - pub force: bool, - pub remove_directories: bool, - pub remove_ignored: bool, -} -``` - -#### Implementation Examples - -```rust -// Reset submodule -fn reset_submodule(&mut self, path: &str, hard: bool) -> Result<()> { - let submodule_path = Path::new(path); - - // Open submodule repository - let submodule_repo = gix::open(submodule_path)?; - - // Get HEAD commit - let head_commit = submodule_repo.head_commit()?; - - let reset_mode = if hard { - ResetMode::Hard - } else { - ResetMode::Mixed - }; - - // Perform reset - submodule_repo.reset(head_commit.id(), reset_mode)?; - - Ok(()) -} - -// Clean submodule -fn clean_submodule(&self, path: &str, force: bool, remove_directories: bool) -> Result<()> { - let submodule_path = Path::new(path); - - // Open submodule repository - let submodule_repo = gix::open(submodule_path)?; - - let options = CleanOptions { - force, - remove_directories, - remove_ignored: false, - }; - - // Perform clean - submodule_repo.clean(options)?; - - Ok(()) -} - -// Fetch submodule (for updates) -fn fetch_submodule(&self, path: &str, options: &SubmoduleFetchOptions) -> Result<()> { - let submodule_path = Path::new(path); - - // Open submodule repository - let submodule_repo = gix::open(submodule_path)?; - - // Get remote (usually "origin") - let remote_name = options.remote.as_deref().unwrap_or("origin"); - let mut remote = submodule_repo.find_remote(remote_name)?; - - // Configure fetch operation - let fetch_opts = gix::remote::FetchOptions::default(); - - // Perform fetch - let outcome = remote.fetch(&fetch_opts)?; - - Ok(()) -} - -// Stash submodule changes -fn stash_submodule(&self, path: &str, include_untracked: bool) -> Result<()> { - let submodule_path = Path::new(path); - - // For now, use gix command execution as stash APIs are limited - // This is acceptable as fallback while gix stash APIs mature - let mut cmd = std::process::Command::new("git"); - cmd.current_dir(submodule_path) - .args(["stash", "push"]); - - if include_untracked { - cmd.arg("--include-untracked"); - } - - cmd.args(["-m", "Gitoxide stash"]); - - let output = cmd.output()?; - if !output.status.success() { - return Err(GitOperationsError::CommandFailed { - command: "git stash".to_string(), - output: String::from_utf8_lossy(&output.stderr).to_string(), - }); - } - - Ok(()) -} -``` - -## Complete Gix Operations Implementation - -Based on the research, here's the complete `GixOperations` implementation: - -```rust -use gix::{Repository, ObjectId}; -use gix::config::tree::{Core, Submodule}; -use std::collections::HashMap; -use std::path::Path; - -pub struct GixOperations { - repo: Repository, -} - -impl GixOperations { - pub fn new(repo_path: Option<&Path>) -> Result { - let repo = if let Some(path) = repo_path { - gix::open(path)? - } else { - gix::discover(".")? - }; - - Ok(Self { repo }) - } -} - -impl GitOperations for GixOperations { - // Configuration operations - fn read_git_config(&self, level: ConfigLevel) -> Result { - // Implementation above - } - - fn set_config_value(&mut self, key: &str, value: &str, level: ConfigLevel) -> Result<()> { - // Implementation above - } - - // Submodule operations - fn read_gitmodules(&self) -> Result { - // Implementation above - } - - fn write_gitmodules(&mut self, entries: &SubmoduleEntries) -> Result<()> { - // Implementation above - } - - fn add_submodule(&mut self, opts: &SubmoduleAddOptions) -> Result<()> { - // Implementation above - } - - fn update_submodule(&self, path: &str, opts: &SubmoduleUpdateOptions) -> Result<()> { - // Combine fetch + checkout based on update strategy - match opts.strategy { - SerializableUpdate::Checkout => { - self.fetch_submodule(path, &opts.fetch_options)?; - // Checkout to configured branch or HEAD - let submodule_repo = gix::open(path)?; - let head_commit = submodule_repo.head_commit()?; - submodule_repo.reset(head_commit.id(), ResetMode::Hard)?; - } - SerializableUpdate::Merge => { - self.fetch_submodule(path, &opts.fetch_options)?; - // Merge logic would go here - todo!("Merge strategy not yet implemented in gix") - } - SerializableUpdate::Rebase => { - self.fetch_submodule(path, &opts.fetch_options)?; - // Rebase logic would go here - todo!("Rebase strategy not yet implemented in gix") - } - } - Ok(()) - } - - fn delete_submodule(&mut self, name: &str) -> Result<()> { - // Implementation above - } - - fn init_submodule(&self, path: &str) -> Result<()> { - // Initialize submodule repository if it doesn't exist - let submodule_path = Path::new(path); - if !submodule_path.join(".git").exists() { - // Clone based on .gitmodules configuration - let entries = self.read_gitmodules()?; - if let Some(entry) = entries.entries.values() - .find(|e| e.path == path) { - - let clone_opts = gix::clone::Options::default(); - gix::clone(&entry.url, submodule_path, clone_opts)?; - } - } - Ok(()) - } - - fn deinit_submodule(&mut self, path: &str, force: bool) -> Result<()> { - let submodule_path = Path::new(path); - - if force || self.is_submodule_clean(path)? { - // Remove .git directory - let git_dir = submodule_path.join(".git"); - if git_dir.exists() { - if git_dir.is_dir() { - std::fs::remove_dir_all(git_dir)?; - } else { - std::fs::remove_file(git_dir)?; - } - } - - // Clear worktree - for entry in std::fs::read_dir(submodule_path)? { - let entry = entry?; - let path = entry.path(); - if path.file_name() != Some(std::ffi::OsStr::new(".git")) { - if path.is_dir() { - std::fs::remove_dir_all(path)?; - } else { - std::fs::remove_file(path)?; - } - } - } - } else { - return Err(GitOperationsError::SubmoduleNotClean(path.to_string())); - } - - Ok(()) - } - - fn list_submodules(&self) -> Result> { - let entries = self.read_gitmodules()?; - Ok(entries.entries.keys().cloned().collect()) - } - - // Sparse checkout operations - fn enable_sparse_checkout(&mut self, path: &str) -> Result<()> { - // Implementation above - } - - fn set_sparse_patterns(&self, path: &str, patterns: &[String]) -> Result<()> { - // Implementation above - } - - fn get_sparse_patterns(&self, path: &str) -> Result> { - // Implementation above - } - - fn apply_sparse_checkout(&self, path: &str) -> Result<()> { - // Implementation above - } - - // Repository operations - fn reset_submodule(&mut self, path: &str, hard: bool) -> Result<()> { - // Implementation above - } - - fn clean_submodule(&self, path: &str, force: bool, remove_directories: bool) -> Result<()> { - // Implementation above - } - - fn stash_submodule(&self, path: &str, include_untracked: bool) -> Result<()> { - // Implementation above - } - - fn fetch_submodule(&self, path: &str, options: &SubmoduleFetchOptions) -> Result<()> { - // Implementation above - } -} -``` - -## Migration Roadmap - -### Phase 1: Configuration Operations (High Priority) - -1. Implement `set_config_value()` using `gix::config::tree` static keys -2. Implement `read_git_config()` with type-safe access -3. Replace `git config core.sparseCheckout true` calls - -### Phase 2: Submodule Read Operations (High Priority) - -1. Implement `read_gitmodules()` using `gix::Repository::submodules()` -2. Implement `list_submodules()` with full entry details -3. Replace CLI submodule listing calls - -### Phase 3: Submodule Write Operations (Medium Priority) - -1. Implement `write_gitmodules()` using atomic file operations -2. Implement `add_submodule()` with gix clone integration -3. Implement `delete_submodule()` with proper cleanup -4. Replace `git submodule add/deinit` calls - -### Phase 4: Sparse Checkout (Medium Priority) - -1. Implement `enable_sparse_checkout()` and `set_sparse_patterns()` -2. Implement `apply_sparse_checkout()` using worktree APIs -3. Replace `git sparse-checkout` and `git read-tree` calls - -### Phase 5: Repository Operations (Low Priority) - -1. Implement `reset_submodule()` and `clean_submodule()` -2. Implement `fetch_submodule()` using gix remote APIs -3. Keep `stash_submodule()` as CLI fallback for now - -## Conclusion - -The research shows that **all major git operations can be implemented using gix APIs**. The previous assumptions about gix limitations were incorrect. This implementation will: - -1. **Eliminate all 17 CLI calls** from GitManager -2. **Provide type-safe configuration access** using `gix::config::tree` -3. **Enable comprehensive submodule management** via gix APIs -4. **Support sparse checkout operations** through worktree manipulation -5. **Maintain robust error handling** and atomic operations - -The key insight is that gix provides both high-level convenience APIs (like `Repository::submodules()`) and low-level building blocks (like `gix-submodule::File`) that can be combined to implement any git operation safely and efficiently. diff --git a/.claudedocs/Project Direction.md b/.claudedocs/Project Direction.md deleted file mode 100644 index d5e91ab0..00000000 --- a/.claudedocs/Project Direction.md +++ /dev/null @@ -1,195 +0,0 @@ - - -# What we need to do - -## Big Picture - -1. We need to ensure that we are actually modifying `git` configs/.gitmodules when we actually change submod.toml or CLI options. Our config exists to simplify the user experience, but we need to ensure that the underlying git configuration is also updated accordingly. - -2. I'd like to **eliminate** all use of `git` cli calls. - - - `Gitoxide` has good coverage of _most_ of what we need, and can fully handle: - - - Reading and writing git configs (`gix_config`) - - **Reading** `.gitmodules` files (`gix_submodule`) - - Reporting repository and submodule status - - Most fetching (it can't, however `init` a submodule yet) - - - `git2` has all of the remaining functionality we need: - - - It can completely create and initialize submodules, including cloning them. - - Any config ops not covered by gix_config can be handled by `git2`. - - - Absolute worst case, we can use `gix_command` to run `git` commands, which at least gives us a consistent interface and pushes validation and error handling down to the `gitoxide` layer. - - **Bottom line: If you see a `git` command in the code; it's wrong.** - -3. Besides triggering creation and updates, we should generally avoid trying to emulate `git` tasks. We update and tell git to do the work through `gitoxide` and `git2`, and keep its configs in sync with our own. - - - We don't need to handle fetching behavior, for example. We just need to make sure that git is configured to fetch how the user wants it to, and then let git handle the fetching. We can trigger it with `git2` or `gix` but we don't do it ourselves. - -4. Minimize direct file ops. Again, let `gitoxide` and `git2` handle the file ops. We should not be reading or writing `.gitmodules` or `.git/config` directly, except through the git libraries. The exception _might_ be `sparse-checkout` files, but even those I think worst case we can use `gix_command` to handle them since git clearly can do it. - - - **Possible Exception**: Our `nuke-it-from-orbit` and `delete` commands -- using internal `git submodule deinit` like functionality _should_ be fine, but if it doesn't work we have to be able to do direct deletion of the submodule directory and files. `git2` should have this functionality of basically the library equivalent of `git submodule deinit`, and worst case we can use `gix_command` to run the command and then delete the submodule directory and files ourselves. We also need to look at exactly what git does in a deinit -- we may need to add `git rm --cached` like behavior (possibly with an optional flag like `--it-never-existed`). We should also remove the submodule from the `.gitmodules` file and the `.git/config` file, but we can do that through `git2` or `gix_config`. - -5. The longterm goal is to solely use `gix` when it has the full functionality we need, so we need to architect for easy transition between `gix` and `git2` as needed. Basically we need our types to be interchangeable between `gix` and `git2` where possible, so that we can swap them out as needed without having to rewrite large portions of the code. - -6. A possible issue we should think about. We don't currently have a way to differentiate configuration at the user, repository, submodule, superproject, and global levels. We should consider how we can handle that in the future, but for now we can just assume that all configuration is at the repository level. For example, if we allowed user configs (i.e. `~/.config/submod.toml` or in a repo `.dev.submod.toml` (added to the `.gitignore`)), we would write the user `.git/config` instead of the `.gitmodules`. That would let users add/change submodule behavior for a repository to suit their needs without impacting the repo behavior. This is a future consideration, but something we should keep in mind as we design our configuration system. I think _it mostly_ matters for user and repo level. Most folks don't want global submodule configs, and we can leave it on the user to differentiate between sub-repo and superproject configs. - - - A related abstraction I think would be useful is to differentiate `developer` and `user` configs. Kind of like `developer` dependencies -- i.e. anyone working on the project itself can pull the developer config and get those submodules, but a user who clones the project to build it doesn't need to pull those submodules. You could use this to basically vendor documentation for dependencies, for example (very helpful for MCP context). This is a future consideration, but I think it would be useful to keep in mind as we design our configuration system. That is, they _would_ have the submodules in the _.gitignore_ for the project, but the `submod.toml` could be used to pull them. As I write this, I realize all we would need to do is add a `developer` flag and write the submodule path to the `.gitignore` file. The developer flag would tell us to write submodule settings to `.git/config` instead of `.gitmodules`. (I'm going to go ahead and pencil this in in the commands.rs file, but we can revisit it later.) - - A really killer version of this would be to automatically find the project docs and make them sparse. We could look at how tools like `context7` handle very similar behavior (I'm guessing look for a docs dir or _.md/_.mdx/*.rst files.) Like those, you'd want it to be able to handle pulling from public git*and where that doesn't work well\*, snapshotting webpages to markdown. Throwing a little bit of search on top of that would be even better... (maybe that's out of scope or more suitable for a dedicated tool, but it's a neat idea.) - -**Update**: After some research, I went ahead and implemented the foundations for `figment` for our config/toml handling. It enormously simplified things, pushing all of the config loading, merging, and validation to `figment`, which is a great fit for our needs. It also gives us good provenance for our config, so we can easily see where settings came from (i.e. CLI options, submod.toml, etc.). That will allow us to just write the logic for how we push that information to git's configuration, and let `figment` handle the rest. This also means we can easily add new config sources in the future (like user configs) without having to rewrite a lot of code. (I actually penciled in user and developer configs in a comment in the config.rs file; but we can revisit that later.) - -Summary of the flow: - -**gix** -> **git2** -> **gix_command** (if needed) - -## Missing Core Functionality to Support All Features - -### Setting and Updating Global Defaults - -Git, of course, does not have a concept of "global defaults" for submodules (as far as I'm aware). That's an abstraction _we introduced_ to simplify the user experience. However, we need to ensure that these defaults are reflected in the git configuration. It means that where a user has not explicitly set a value in their `submod.toml` for a specific submodule, we should use the global defaults to fill in the gaps when we update `.gitmodules` (through git2). - -- We _should already_ have a method that sets global defaults because the behavior should be there for handling global defaults in the config - - **I couldn't find it in the codebase**, so we need to verify it exists and is working as expected. This is something we also need to make sure is tested. - -**The behavior ^^should^^ be**: - -- Any default setting for `ignore`, `fetch`, `update` _are not_ written to the `.gitmodules` file _or the submod.toml_. We leave those blank. - - If a user sets a specific value for `ignore`, `fetch`, or `update` in their `submod.toml` or through CLI options, we write that value to the `submod.toml` file but not the `.gitmodules` file (because there are no global default overrides in git -- we reconcile it). - - We treat those as overrides of the global defaults (i.e. if the user sets `ignore = "all"` as a global default, but then sets `ignore = "none"` for a specific submodule, we change all other submodules to `ignore = "all"` in `.gitmodules` but leave the specific submodule as `ignore = "none"` in the `.gitmodules` file), using our config as the source of truth. -- **Non-default values for ignore, fetch, update**. For non-default values, we always write them to both `submod.toml` and `.gitmodules`. We just need to deconflict them with the global defaults (submodule config wins) before updating `.gitmodules`. -- (sidenote: consider adding `shallow` as a global default, but I think we can leave that for later) - -### Setting and Updating All Other Settings - -Like with the global defaults, we need to ensure that our configuration is getting accurately reflected in the `.gitmodules` file and the `.git/config` file. This means that when we update settings in `submod.toml` or through CLI options, we need to ensure that those changes are also reflected in the git configuration. - -**The behavior ^^should^^ be**: - -- **All other settings get written to both submod.toml and .gitmodules if explicitly set** `submod.toml` and `.gitmodules` files. There are no global defaults for these settings, so we always write them to both files _if explicitly set_ in the `submod.toml` or CLI options. - - Note that some of our "defaults" are actually more like _inferred settings_, those should always get written to both. The biggest example is `name`, which is actually required for both our ops and git's submodule handling, but we infer it from the submodule path (it's hidden on the `add` command). - -### Toml/Config Handling - -A lot of what we need to reconcile and work on is keeping our config properly updated and aligned with the git configuration. New commands and features require granular control over how we read and write the configuration, so we need to ensure that our config handling is robust and flexible. - -After some investigation, I found that `figment` is a good choice for our toml handling. We can replace a lot of our manual parsing and validation with `figment`'s built-in functionality, which will simplify our code and make it more maintainable. We already have serialize/deserialize traits for our types, so it's literally just a few lines of code. Figment handles layer config loading, merging, and validation for us and keeps good provenance, so we can focus on the actual logic of our commands. - -### CLI Parsing and Validation - -- **Leveraging Clap**. I added `clap`'s `ValueEnum` trait to all of our config enums. This integrates validation and parsing directly with clap. We need to update all the handling logic from the parsed arguments to reflect this, and I suspect remove a lot of code. -- I also specified clap value parsers for _everything_ that needs to be parsed, so we can remove a lot of the manual parsing and validation code. -- **Type changes**. I also narrowed some of the existing default types to more specific types. Specifically, `path` now uses `OsString` instead of `String` (because it can handle non-UTF8 paths). I didn't make this PathBuf because we're not actually using it as a path, but I added a conversion function in `utilities.rs` to convert it to a `PathBuf` when needed. - -#### Add config generation - -We need to add config generation for submod.toml. This should be pretty trivial since we already have the logic to add/update it. - -- I did a lot of work beefing up the example config in [`sample_config/submod.toml`](sample_config/submod.toml). I also added placeholders in `commands.rs` for a config generation command with options. - - The command has two options in terms of content: - - `--from-setup` to generate a config from the current git submodule setup (i.e. the current `.gitmodules` and `.git/config` files). This is just reversing the process we do in `add` and `update` commands. - - `--template` to generate a config from a template. This would basically just copy the `sample_config/submod.toml` file to the current directory, and then the user can edit it as needed (how do we make sure it gets packaged in the binary? Or I guess we could fetch it from the repo... but that assumes internet connectivity - new territory for me with rust). - -## Implement the New Commands and Make Sure Existing Commands Do What They Should - -We need to implement the new commands and ensure that existing commands do what they should. This includes: - -1. `submod add` (see above on making sure it does what it should) - - - Once question is how to handle a user trying to add an existing submodule. I'd lean towards erroring out and telling them to use `submod change` instead. We should spit out their exact command as a `submod change` command in our error message so they can easily copy/paste it. - - We need to ensure that it updates the `.gitmodules` file and the `.git/config` file as needed. - - Add `shallow` and `no-init` options to the command. - - We also need to make sure submod add _is_ _initiating_ the submodule; I can't tell if it does that now, but it should. For us we just use the `submod init` logic to do that, so we need to ensure that it works as expected. - -2. `submod change` (new) to update or change all options for a specific submodule. - - - We just need the deconfliction logic on how to handle updates/changes. - - Here if a user tries to change a submodule that _doesn't exist_ I'd lean towards a prompt asking if they want to create it, which would redirect it to `submod add`. - -3. `submod change-global` (new) to update or change global defaults for all submodules in the repo. - - - As discussed above, we should already have this logic, and if we don't, it is something we have to do anyway. - -4. `check` We need to make sure this is differentiated from `submod list`. - - - `submod check` should check and report the current status of all submodules. - - I had considered adding a `submod status` command, but I think that functionality can go here. - - `gix` already has a fair amount of statistics and status summary features for its own cli; we should be able to directly add those without writing our own. The gix crate implementing all of its CLI capabilities is `gitoxide-core` (library part), actual CLI handling is in the main `gitoxide` crate. - -5. `submod list` (new) We need to make sure this is properly listing all submodules with their current settings. - -6. `submod init` We need to make sure this is properly initializing submodules. - - - Remove any existing reliance on `git` commands for this. - -7. `submod delete` (new) to remove a submodule from the repository and the submod.toml file, and all of its files. - - - This should be a hard delete, removing the submodule directory and files, and deleting all references to it in git's configuration. - - We need to ensure that it updates the `.gitmodules` file and the `.git/config` file as needed and actually clears the submodule's entry from the configuration. - - We will also use this logic for the `nuke-it-from-orbit --kill` command, so we need to ensure that it works as expected. - -8. `submod disable` (new) to set a submodule to inactive nondestructively, allowing it to be re-enabled later. - - - Very simple -- set the submodule's `active` flag to `false` in the `submod.toml` and `.gitmodules` files. - -9. `submod update` -- just need to move any `git` calls to `git2` or `gix_command` as needed. - -10. `submod reset` -- just need to move any `git` calls to `git2` or `gix_command` as needed. - -11. `submod sync` -- this should be fine, as it's just a wrapper for `check, init, update` commands. If we make `check` more of a status command, we would need to separate the `check` logic for this command (check would do both). - -12. `submod generate-config` (new) to generate a submod.toml file from the current git submodule setup or a template. - - - This should be pretty trivial since we already have the logic to add/update it. - - We can use the `--from-setup` and `--template` options as discussed above. - - the optional `--output` option is a PathBuf to write the generated config to, defaulting to `submod.toml` in the current directory. - -13. `submod nuke-it-from-orbit` (new). - - - Most of the underlying logic is used or will be used in other parts of the codebase. - - By default it - - By default this is an **extra hard reset**, removing all submodules and files, deleting all references to them in git's configuration, resyncing with git, before _adding them all back to the gitconfig from your submod.toml settings_. You can optionally pass the `--kill` flag to _completely_ remove them without adding them back to the gitconfig, and deleting them from your submod.toml (this is the same as `submod delete` but for multiple submodules). - -14. `submod completions` (new) This is super simple. It's just a parse operation with a `generate` command passing the shell type as an argument. I already added `Shell` and `generate` to the imports and types for the command, and added the dependencies to `Cargo.toml`. We just need that quick function to trigger them. - -Other: - **global `--config` option**. I made this truly global so it can be used with any command. It is also now a PathBuf instead of a String, so it can be used with clap's value parsers. It defaults to `submod.toml` in the current directory, but can be overridden with the `--config` option. - -- **We need to make sure we are actually using this option in all commands**. Be on the lookout for any commands that assume a config location or don't use the global config option. We should be using the `config` variable in `commands.rs` to get the config location and read/write it as needed. - -## Tests - -Once we get all of this implemented, we need to comb through the tests to ensure that they are still valid and cover all of the new functionality. We should also add integration tests for any new commands and features we implement. - -On the plus side, we can actually use the `nuke-it-from-orbit` command for test tear down. - -## Documentation - -We're in a good place but we'll need to go through the README to make it reflect the new commands and features. - -## API Notes - -While we need more research on the APIs for `gix` and `git2`, I've found quite a few useful resources: - -- Data checks: - - The `gix` [`repository`](https://docs.rs/gix/latest/gix/struct.Repository.html) struct has a lot of useful functionality for introspecting the repository and submodules, including: - - `modules()` returns a shared _live_ view of the `.gitmodules` file, so you can use it to read the current submodule configuration and validate changes. - - `submodules()` returns an iterator over the submodules in the repository, each submodule object is essentially a Repository object with its own methods for introspection and manipulation. - - `workdir()` returns the worktree path containing all checkout items (if it exists) - - `workdir_path()` is a convenience method that normalizes relative paths with the worktree path, so you can use it to get the full path anything in the worktree. (takes asRef `Bstr` and returns `Path`) - - `kind()` returns the kind of repository (bare, worktree, etc.), which can be useful for determining how to handle submodules. (gix::repository::Kind with variants Bare, Worktree and Submodule). The enum itself has method `is_bare()` to check for a bare repo. - - `head_id()` returns the current HEAD ID of the submodule or repository. - - `head_tree()` returns the current HEAD [Tree](https://docs.rs/gix/latest/gix/struct.Tree.html) of the submodule or repository, which can be useful for checking the current state of the submodule. - - `head_name()` returns the name of the symbolic ref for HEAD; note that it may not exist yet -- it can have a name and no reference. - - `pathspec()` not really for now, but if we ever wanted to get real fancy with using pathspecs for live filtering of submodules (i.e. we could show exactly what a sparse checkout would look like), this is the method for it. - - `try_find_remote()` returns a `Result>` for the remote with the given name, which can be useful for checking if a submodule or repo has a remote set up. Similarly: - - `find_fetch_remote()` mirrors `git fetch` in how to finds and retrieves a remote. - - `find_default_remote()` to find the default remote for the repo - - `open_modules_file()` returns a `Result` for the `.gitmodules` file, which can be used to read the current submodule configuration. Unlike `.modules` this view is stale. - - `current_dir()` returns the current working directory of the repository, which can be useful for relative paths. - - `path()` returns the path to the repository .git directory itself; we can use to construct sparse-checkout index paths - - `main_repo()` returns the superproject repo object diff --git a/.config/nextest.toml b/.config/nextest.toml index 13af260d..a9bfcc8e 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2026 Adam Poulemanos +# SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT + # Nextest configuration for submod # # With per-test git config isolation (GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM), diff --git a/.gitignore b/.gitignore index 271174be..4437d165 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,17 @@ # # SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT -/target +.DS_STORE +Thumbs.db + +.venv +venv + +target/ lcov.info .coverage htmlcov -coverage.xml \ No newline at end of file +coverage.xml +.claudedocs/ +claudedocs/ + diff --git a/.serena/.gitignore b/.serena/.gitignore deleted file mode 100644 index 2e510aff..00000000 --- a/.serena/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/cache -/project.local.yml diff --git a/.serena/project.yml b/.serena/project.yml deleted file mode 100644 index 6a04d6a5..00000000 --- a/.serena/project.yml +++ /dev/null @@ -1,152 +0,0 @@ -# the name by which the project can be referenced within Serena -project_name: "submod" - - -# list of languages for which language servers are started; choose from: -# al bash clojure cpp csharp -# csharp_omnisharp dart elixir elm erlang -# fortran fsharp go groovy haskell -# java julia kotlin lua markdown -# matlab nix pascal perl php -# php_phpactor powershell python python_jedi r -# rego ruby ruby_solargraph rust scala -# swift terraform toml typescript typescript_vts -# vue yaml zig -# (This list may be outdated. For the current list, see values of Language enum here: -# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py -# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) -# Note: -# - For C, use cpp -# - For JavaScript, use typescript -# - For Free Pascal/Lazarus, use pascal -# Special requirements: -# Some languages require additional setup/installations. -# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers -# When using multiple languages, the first language server that supports a given file will be used for that file. -# The first language is the default language and the respective language server will be used as a fallback. -# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. -languages: -- rust - -# the encoding used by text files in the project -# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings -encoding: "utf-8" - -# line ending convention to use when writing source files. -# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) -# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. -line_ending: - -# The language backend to use for this project. -# If not set, the global setting from serena_config.yml is used. -# Valid values: LSP, JetBrains -# Note: the backend is fixed at startup. If a project with a different backend -# is activated post-init, an error will be returned. -language_backend: - -# whether to use project's .gitignore files to ignore files -ignore_all_files_in_gitignore: true - -# list of additional paths to ignore in this project. -# Same syntax as gitignore, so you can use * and **. -# Note: global ignored_paths from serena_config.yml are also applied additively. -ignored_paths: [] - -# whether the project is in read-only mode -# If set to true, all editing tools will be disabled and attempts to use them will result in an error -# Added on 2025-04-18 -read_only: false - -# list of tool names to exclude. -# This extends the existing exclusions (e.g. from the global configuration) -# -# Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, -# execute `uv run scripts/print_tool_overview.py`. -# -# * `activate_project`: Activates a project by name. -# * `check_onboarding_performed`: Checks whether project onboarding was already performed. -# * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. -# * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). -# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. -# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. -# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. -# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. -# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. -# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). -# * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. -# * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# * `switch_modes`: Activates modes by providing a list of their names -# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. -# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. -# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. -# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. -excluded_tools: [] - -# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). -# This extends the existing inclusions (e.g. from the global configuration). -included_optional_tools: [] - -# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. -# This cannot be combined with non-empty excluded_tools or included_optional_tools. -fixed_tools: [] - -# list of mode names to that are always to be included in the set of active modes -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply. -# Otherwise, this setting overrides the global configuration. -# Set this to [] to disable base modes for this project. -# Set this to a list of mode names to always include the respective modes for this project. -base_modes: - -# list of mode names that are to be activated by default. -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. -# Otherwise, this overrides the setting from the global configuration (serena_config.yml). -# This setting can, in turn, be overridden by CLI parameters (--mode). -default_modes: - -# initial prompt for the project. It will always be given to the LLM upon activating the project -# (contrary to the memories, which are loaded on demand). -initial_prompt: "" - -# time budget (seconds) per tool call for the retrieval of additional symbol information -# such as docstrings or parameter information. -# This overrides the corresponding setting in the global configuration; see the documentation there. -# If null or missing, use the setting from the global configuration. -symbol_info_budget: - -# list of regex patterns which, when matched, mark a memory entry as read‑only. -# Extends the list from the global configuration, merging the two lists. -read_only_memory_patterns: [] - -# list of regex patterns for memories to completely ignore. -# Matching memories will not appear in list_memories or activate_project output -# and cannot be accessed via read_memory or write_memory. -# To access ignored memory files, use the read_file tool on the raw file path. -# Extends the list from the global configuration, merging the two lists. -# Example: ["_archive/.*", "_episodes/.*"] -ignored_memory_patterns: [] - -# advanced configuration option allowing to configure language server-specific options. -# Maps the language key to the options. -# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. -# No documentation on options means no options are available. -ls_specific_settings: {} diff --git a/CLAUDE.md b/CLAUDE.md index 9e4dc347..3a754fd2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # CLAUDE.md diff --git a/LICENSES/Apache-2.0.txt b/LICENSES/Apache-2.0.txt deleted file mode 100644 index 137069b8..00000000 --- a/LICENSES/Apache-2.0.txt +++ /dev/null @@ -1,73 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/REUSE.toml b/REUSE.toml index 0c9c2c54..ed21f80c 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -4,15 +4,3 @@ version = 1 -# git2 docs -[[annotations]] -path = ["dev/git2/**"] - -SPDX-FileCopyrightText = "2014 Alex Crichton" -SPDX-License-Identifier = "MIT OR Apache-2.0" - -# gix docs -[[annotations]] -path = ["dev/gix/**"] -SPDX-FileCopyrightText = "2025 Sebastian Thiel" -SPDX-License-Identifier = "MIT OR Apache-2.0" diff --git a/benches/benchmark.rs b/benches/benchmark.rs index e488ca3f..03abf3e5 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2026 Adam Poulemanos +// SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT + //! Benchmarks comparing two implementations of `.gitmodules` line key parsing. //! //! The benchmarks measure the performance difference between: diff --git a/hk.pkl b/hk.pkl index fd4dcd3b..1b2b76d1 100644 --- a/hk.pkl +++ b/hk.pkl @@ -1,7 +1,7 @@ /* * SPDX-FileCopyrightText: 2025 Adam Poulemanos <89049923+bashandbone@users.noreply.github.com> * - * * Licensed under the [Plain MIT License](LICENSE.md) + * SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT */ amends "package://github.com/jdx/hk/releases/download/v1.2.0/hk@1.2.0#/Config.pkl" diff --git a/license.toml b/license.toml new file mode 100644 index 00000000..7254207d --- /dev/null +++ b/license.toml @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2026 Adam Poulemanos +# +# SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT + +[default] +license = "LicenseRef-PlainMIT OR MIT" +copyright = "add:2026 Adam Poulemanos" + + diff --git a/mise.toml b/mise.toml index 12540ef2..dd782d82 100644 --- a/mise.toml +++ b/mise.toml @@ -18,6 +18,7 @@ pkl = "latest" # pkl for `hk`, which handles git hooks rust = "1.89" # The minimum Rust version we support; mise just makes sure it's there. typos = "latest" "pipx:reuse" = { version = "latest", uvx_args = '-p 3.13' } +"cargo:licet" = "0.2.2" [env] CARGO_TARGET_DIR = "target" diff --git a/schemas/v1.0.0/submod_config_v1.0.0.json.license b/schemas/v1.0.0/submod_config_v1.0.0.json.license new file mode 100644 index 00000000..c1fd1064 --- /dev/null +++ b/schemas/v1.0.0/submod_config_v1.0.0.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2026 Adam Poulemanos +SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT diff --git a/schemas/v1.1.0/submod_config_v1.1.0.json.license b/schemas/v1.1.0/submod_config_v1.1.0.json.license new file mode 100644 index 00000000..c1fd1064 --- /dev/null +++ b/schemas/v1.1.0/submod_config_v1.1.0.json.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2026 Adam Poulemanos +SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT diff --git a/src/git_ops/simple_gix.rs b/src/git_ops/simple_gix.rs index d4783268..e2ba911f 100644 --- a/src/git_ops/simple_gix.rs +++ b/src/git_ops/simple_gix.rs @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT // // SPDX-FileCopyrightText: 2018-2025 Sebastian Thiel and [contributors](https://github.com/byron/gitoxide/contributors) // SPDX-FileCopyrightText: 2025 Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>