use crate::prelude::*;
use crate::fleet::config::GitModules;
use crate::fleet::error::{Result, SuperGitError};
use crate::fleet::git::{GitOps, SubmoduleOps};
use crate::fleet::output::Formatter;
use crate::git_backend;
use crate::workspace_state::WorkspaceState;
pub fn execute<P: AsRef<Path>>(
repo_root: P,
message: &str,
push: bool,
stage_all: bool,
exclude: &[String],
include: &[String],
dry_run: bool,
verbose: bool,
) -> Result<()> {
let gitmodules = GitModules::parse(&repo_root)?;
Formatter::print_info(&format!(
"Committing changes in superproject + {} submodule(s)",
gitmodules.submodules.len()
));
if dry_run {
Formatter::print_warning("DRY RUN: No changes will be made");
}
let mut successful = 0;
let mut failed = 0;
let mut skipped = 0;
for submodule in &gitmodules.submodules {
if verbose {
Formatter::print_submodule_header(&submodule.name);
}
let submodule_path = repo_root.as_ref().join(&submodule.path);
if !include.is_empty() || !exclude.is_empty() {
let alias = WorkspaceState::load(&submodule_path)
.ok()
.and_then(|s| s.wstemplate_entry().map(|e| e.alias.clone()))
.unwrap_or_else(|| submodule.name.clone());
if !include.is_empty() && !include.iter().any(|inc| inc == &alias) {
if verbose {
Formatter::print_info(&format!("Skipping '{}' (not in --include list)", alias));
}
skipped += 1;
continue;
}
if exclude.iter().any(|ex| ex == &alias) {
if verbose {
Formatter::print_info(&format!("Skipping '{}' (excluded)", alias));
}
skipped += 1;
continue;
}
}
if !submodule_path.exists() {
Formatter::print_warning(&format!(
"Submodule '{}' not initialized, skipping",
submodule.name
));
skipped += 1;
continue;
}
let repo = match GitOps::open_repo(&submodule_path) {
Ok(r) => r,
Err(e) => {
Formatter::print_error(&format!("Failed to open '{}': {}", submodule.name, e));
failed += 1;
continue;
}
};
let is_clean = match GitOps::is_clean(&repo) {
Ok(c) => c,
Err(e) => {
Formatter::print_error(&format!(
"Failed to check status of '{}': {}",
submodule.name, e
));
failed += 1;
continue;
}
};
if is_clean {
if verbose {
Formatter::print_info(&format!("No changes in '{}'", submodule.name));
}
skipped += 1;
continue;
}
if dry_run {
if stage_all {
Formatter::print_info("Would stage all changes (git add -A)");
}
Formatter::print_info("Would commit changes");
successful += 1;
continue;
}
if stage_all {
if let Err(e) = git_backend::stage_all(&repo) {
Formatter::print_error(&format!("Failed to stage in '{}': {}", submodule.name, e));
failed += 1;
continue;
}
}
match SubmoduleOps::commit(&repo_root, submodule, message) {
Ok(_) => {
Formatter::print_success(&format!("Committed changes in '{}'", submodule.name));
successful += 1;
if push {
match SubmoduleOps::push(&repo_root, submodule, false) {
Ok(_) => {
Formatter::print_success(&format!(
"Pushed changes in '{}'",
submodule.name
));
}
Err(e) => {
Formatter::print_error(&format!(
"Failed to push in '{}': {}",
submodule.name, e
));
}
}
}
}
Err(e) => {
Formatter::print_error(&format!("Failed to commit in '{}': {}", submodule.name, e));
failed += 1;
}
}
}
if !dry_run {
Formatter::print_summary(successful, failed, skipped);
if successful > 0 {
Formatter::print_info("Updating superproject...");
let repo = git_backend::open_repo(repo_root.as_ref())?;
if stage_all {
if let Err(e) = git_backend::stage_all(&repo) {
Formatter::print_warning(&format!("Failed to stage superproject: {}", e));
}
}
match git_backend::commit_all(&repo, &format!("Update submodules: {}", message)) {
Ok(Some(_)) => {
Formatter::print_success("Committed in superproject");
if push {
match git_backend::push_current_branch(&repo, Some("origin"), false, false) {
Ok(_) => Formatter::print_success("Pushed superproject"),
Err(err) => {
Formatter::print_error(&format!(
"Failed to push superproject: {}",
err
));
failed += 1;
}
}
}
}
Ok(None) => {}
Err(err) => {
Formatter::print_warning(&format!("Failed to commit in superproject: {}", err));
}
}
}
}
if failed > 0 {
return Err(SuperGitError::Other(format!(
"{} submodule(s) failed to commit",
failed
)));
}
Ok(())
}