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 {
pub fn get_status<P: AsRef<Path>>(
repo_root: P,
submodule: &Submodule,
) -> Result<SubmoduleStatus> {
let submodule_path = repo_root.as_ref().join(&submodule.path);
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,
})
}
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(())
}
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(())
}
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))
}
})
}
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(())
}
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(())
}
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(())
}
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(())
}
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(())
}
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(())
}
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(())
}
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(())
}
}