diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0259736..9801043 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: hk install --mise - name: Run hk ci workflow - run: hk ci + run: hk run ci security_audit: name: Security Audit diff --git a/src/config.rs b/src/config.rs index c4fe27b..8a6d894 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -#![doc = r#" +#![doc = r" Configuration types and utilities for submod. Defines serializable wrappers for git submodule options, project-level defaults, and submodule @@ -18,7 +18,7 @@ Features: TODO: - Add validation for config values when loading from file. -"#] +"] use anyhow::{Context, Result}; use bstr::BStr; diff --git a/src/gitoxide_manager.rs b/src/gitoxide_manager.rs index abcf47c..a48f32b 100644 --- a/src/gitoxide_manager.rs +++ b/src/gitoxide_manager.rs @@ -1,4 +1,4 @@ -#![doc = r#" +#![doc = r" # Gitoxide-Based Submodule Manager Provides core logic for managing git submodules using the [`gitoxide`](https://github.com/Byron/gitoxide) library, with fallbacks to `git2` and the Git CLI when needed. Supports advanced features like sparse checkout and TOML-based configuration. @@ -42,7 +42,7 @@ All operations return [`SubmoduleError`](src/gitoxide_manager.rs:14) for consist ## Usage Use this module as the backend for CLI commands to manage submodules in a repository. See the project [README](README.md) for usage examples and configuration details. -"#] +"] use crate::config::{Config, SubmoduleConfig, SubmoduleGitOptions}; use gix::Repository; @@ -385,9 +385,46 @@ impl GitoxideSubmoduleManager { .output() .map_err(SubmoduleError::IoError)?; + // Clean up any existing broken submodule state + let target_path = std::path::Path::new(workdir).join(path); + + // Always try to clean up, even if the directory doesn't exist + // because there might be git metadata left behind + + // Try to deinitialize the submodule first + let _ = Command::new("git") + .args(["submodule", "deinit", "-f", path]) + .current_dir(workdir) + .output(); + + // Remove the submodule from .gitmodules and .git/config + let _ = Command::new("git") + .args(["rm", "-f", path]) + .current_dir(workdir) + .output(); + + // Remove the directory if it exists + if target_path.exists() { + let _ = std::fs::remove_dir_all(&target_path); + } + + // Clean up git modules directory + let git_modules_path = std::path::Path::new(workdir) + .join(".git/modules") + .join(path); + if git_modules_path.exists() { + let _ = std::fs::remove_dir_all(&git_modules_path); + } + + // Also try to clean up parent directories in git modules if they're empty + if let Some(parent) = git_modules_path.parent() { + let _ = std::fs::remove_dir(parent); // This will only succeed if empty + } + // Use --force to ensure git overwrites any stale state + // Explicitly specify the main branch to avoid default branch issues let output = Command::new("git") - .args(["submodule", "add", "--force", url, path]) + .args(["submodule", "add", "--force", "--branch", "main", url, path]) .current_dir(workdir) .output() .map_err(SubmoduleError::IoError)?; diff --git a/src/main.rs b/src/main.rs index 110ef75..f04e017 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -#![doc = r#" +#![doc = r" Main entry point for the submod CLI tool. Parses command-line arguments and dispatches submodule management commands using the @@ -15,7 +15,7 @@ and syncing submodules with advanced features like sparse checkout. - `sync`: Run check, init, and update in sequence. Exits with an error if any operation fails. -"#] +"] mod commands; mod config; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index db58d4b..809fddc 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -80,6 +80,12 @@ impl TestHarness { return Err(format!("Failed to init git repo: {stderr}").into()); } + // Ensure we're on the main branch + Command::new("git") + .args(["checkout", "-b", "main"]) + .current_dir(&self.work_dir) + .output()?; + // Configure git user for tests Command::new("git") .args(["config", "user.name", "Test User"]) @@ -128,6 +134,12 @@ impl TestHarness { .arg(&remote_dir) .output()?; + // Set the default branch to main for the bare repository + Command::new("git") + .args(["symbolic-ref", "HEAD", "refs/heads/main"]) + .current_dir(&remote_dir) + .output()?; + // Create a working copy to add content let work_copy = self.temp_dir.path().join(format!("{name}_work")); Command::new("git") @@ -135,6 +147,12 @@ impl TestHarness { .arg(&work_copy) .output()?; + // Set the default branch to main for the working copy + Command::new("git") + .args(["checkout", "-b", "main"]) + .current_dir(&work_copy) + .output()?; + // Set up remote Command::new("git") .args(["remote", "add", "origin", remote_dir.to_str().unwrap()]) @@ -181,11 +199,17 @@ impl TestHarness { .current_dir(&work_copy) .output()?; - Command::new("git") - .args(["push", "origin", "main"]) + let push_output = Command::new("git") + .args(["push", "--no-verify", "origin", "main"]) .current_dir(&work_copy) .output()?; + // Check if push was successful + if !push_output.status.success() { + let stderr = String::from_utf8_lossy(&push_output.stderr); + return Err(format!("Failed to push to remote: {stderr}").into()); + } + Ok(remote_dir) }