wsb 0.164.718

Workspace - A tool suite for file operations, version management, and development workflow automation
use crate::prelude::*;
use crate::fleet::config::{GitModules, Submodule};
use crate::fleet::error::{Result, SuperGitError};
use crate::fleet::git::operations::GitOps;
use crate::git_backend;

#[derive(Debug, Clone)]
pub struct SubmoduleStatus {
    pub name: String,
    pub path: String,
    pub current_branch: Option<String>,
    pub is_clean: bool,
    pub is_detached: bool,
    pub ahead: usize,
    pub behind: usize,
    pub uncommitted_files: Vec<String>,
    pub exists: bool,
}

pub struct SubmoduleOps;

impl SubmoduleOps {
    /// Get status for a single submodule
    pub fn get_status<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
    ) -> Result<SubmoduleStatus> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        // Check if submodule exists
        if !submodule_path.exists() {
            return Ok(SubmoduleStatus {
                name: submodule.name.clone(),
                path: submodule.path.display().to_string(),
                current_branch: None,
                is_clean: true,
                is_detached: false,
                ahead: 0,
                behind: 0,
                uncommitted_files: Vec::new(),
                exists: false,
            });
        }

        let repo = GitOps::open_repo(&submodule_path)?;

        let is_detached = GitOps::is_detached_head(&repo)?;
        let current_branch = if is_detached {
            None
        } else {
            Some(GitOps::current_branch(&repo)?)
        };

        let is_clean = GitOps::is_clean(&repo)?;
        let uncommitted_files = if !is_clean {
            GitOps::get_uncommitted_files(&repo)?
        } else {
            Vec::new()
        };

        let (ahead, behind) = if !is_detached {
            GitOps::get_ahead_behind(&repo).unwrap_or((0, 0))
        } else {
            (0, 0)
        };

        Ok(SubmoduleStatus {
            name: submodule.name.clone(),
            path: submodule.path.display().to_string(),
            current_branch,
            is_clean,
            is_detached,
            ahead,
            behind,
            uncommitted_files,
            exists: true,
        })
    }

    /// Initialize submodule
    pub fn init<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        recursive: bool,
    ) -> Result<()> {
        let repo_root = repo_root.as_ref();
        let submodule_path = repo_root.join(&submodule.path);
        let parent = submodule_path.parent().ok_or_else(|| {
            SuperGitError::Other(format!(
                "Submodule path '{}' does not have a parent directory",
                submodule.path.display()
            ))
        })?;
        std::fs::create_dir_all(parent).map_err(|err| {
            SuperGitError::Other(format!(
                "Failed to create parent directory '{}': {}",
                parent.display(),
                err
            ))
        })?;

        let super_repo = git_backend::open_repo(repo_root)?;
        let gitlink_commit = super_repo
            .head_commit()
            .map_err(|err| SuperGitError::Other(err.to_string()))?
            .tree()
            .map_err(|err| SuperGitError::Other(err.to_string()))?
            .lookup_entry_by_path(&submodule.path)
            .map_err(|err| SuperGitError::Other(err.to_string()))?
            .ok_or_else(|| {
                SuperGitError::Other(format!(
                    "Submodule path '{}' is not present in the current tree",
                    submodule.path.display()
                ))
            })?;

        if !gitlink_commit.mode().is_commit() {
            return Err(SuperGitError::Other(format!(
                "Path '{}' is not a gitlink entry in the superproject",
                submodule.path.display()
            )));
        }

        let repo = git_backend::clone_repository(&submodule.url, &submodule_path)?;
        git_backend::checkout_commit(&repo, gitlink_commit.id().detach())?;

        if recursive {
            match GitModules::parse(&submodule_path) {
                Ok(gitmodules) => {
                    for nested in &gitmodules.submodules {
                        Self::init(&submodule_path, nested, true)?;
                    }
                }
                Err(crate::fleet::error::SuperGitError::NoSubmodules) => {}
                Err(err) => return Err(err),
            }
        }

        Ok(())
    }

    /// Checkout a branch in submodule
    pub fn checkout<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        branch: &str,
        create: bool,
    ) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        if create {
            git_backend::create_branch(&repo, branch, None)?;
        } else {
            git_backend::checkout_branch(&repo, branch)?;
        }

        Ok(())
    }

    /// Pull latest changes in submodule
    pub fn pull<P: AsRef<Path>>(repo_root: P, submodule: &Submodule, rebase: bool) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        git_backend::pull(&repo, rebase).map_err(|err| {
            let message = err.to_string();
            if message.contains("Fast-forward merge is not possible") {
                SuperGitError::MergeConflict(submodule.name.clone())
            } else {
                SuperGitError::Other(format!("Failed to pull in '{}': {}", submodule.name, message))
            }
        })
    }

    /// Push changes in submodule
    pub fn push<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        force_with_lease: bool,
    ) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        git_backend::push_current_branch(&repo, Some("origin"), false, force_with_lease)?;
        Ok(())
    }

    /// Commit changes in submodule
    pub fn commit<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        message: &str,
    ) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        git_backend::commit_all(&repo, message)?;

        Ok(())
    }

    /// Create a new branch in submodule
    pub fn create_branch<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        branch: &str,
        from_branch: Option<&str>,
    ) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        git_backend::create_branch(&repo, branch, from_branch)?;

        Ok(())
    }

    /// Delete a branch in submodule
    pub fn delete_branch<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        branch: &str,
        force: bool,
    ) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        git_backend::delete_local_branch(&repo, branch, force)?;

        Ok(())
    }

    /// Delete remote branch in submodule
    pub fn delete_remote_branch<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        branch: &str,
    ) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        git_backend::delete_remote_branch(&repo, Some("origin"), branch)?;
        Ok(())
    }

    /// Stage all changes in submodule (git add -A)
    pub fn stage<P: AsRef<Path>>(repo_root: P, submodule: &Submodule) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        git_backend::stage_all(&repo)?;

        Ok(())
    }

    /// Merge a branch into the current branch using fast-forward only
    pub fn merge_ff<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        branch: &str,
    ) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        git_backend::merge_ff(&repo, branch)?;

        Ok(())
    }

    /// Fetch in submodule
    pub fn fetch<P: AsRef<Path>>(
        repo_root: P,
        submodule: &Submodule,
        all_remotes: bool,
    ) -> Result<()> {
        let submodule_path = repo_root.as_ref().join(&submodule.path);

        if !submodule_path.exists() {
            return Err(SuperGitError::SubmoduleNotFound(submodule.name.clone()));
        }

        let repo = git_backend::open_repo(&submodule_path)?;
        if all_remotes {
            git_backend::fetch_all(&repo)?;
        } else {
            git_backend::fetch(&repo, Some("origin"))?;
        }

        Ok(())
    }
}