use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use mago_database::Database;
use mago_database::DatabaseReader;
use mago_database::error::DatabaseError;
use mago_database::file::FileId;
use crate::error::Error;
pub fn get_staged_file_paths(workspace: &Path) -> Result<Vec<PathBuf>, Error> {
if !is_git_repository(workspace) {
return Err(Error::NotAGitRepository);
}
get_staged_files(workspace)
}
pub fn get_staged_clean_files(workspace: &Path, database: &Database) -> Result<Vec<FileId>, Error> {
if !is_git_repository(workspace) {
return Err(Error::NotAGitRepository);
}
let staged_files = get_staged_files(workspace)?;
if staged_files.is_empty() {
return Ok(Vec::new());
}
let files_with_unstaged = get_files_with_unstaged_changes(workspace)?;
let mut file_ids = Vec::with_capacity(staged_files.len());
for staged_file in staged_files {
if files_with_unstaged.contains(&staged_file) {
return Err(Error::StagedFileHasUnstagedChanges(staged_file.display().to_string()));
}
let absolute_path = workspace.join(&staged_file);
let canonical_path = absolute_path.canonicalize().unwrap_or(absolute_path);
if let Ok(file) = database.get_by_path(&canonical_path) {
file_ids.push(file.id);
}
}
Ok(file_ids)
}
pub fn ensure_staged_files_are_clean(workspace: &Path, staged_files: &[PathBuf]) -> Result<(), Error> {
let files_with_unstaged = get_files_with_unstaged_changes(workspace)?;
for staged_file in staged_files {
if files_with_unstaged.contains(staged_file) {
return Err(Error::StagedFileHasUnstagedChanges(staged_file.display().to_string()));
}
}
Ok(())
}
pub fn stage_files<I>(workspace: &Path, database: &Database, file_ids: I) -> Result<(), Error>
where
I: IntoIterator<Item = FileId>,
{
let paths: Vec<PathBuf> = file_ids
.into_iter()
.filter_map(|id| database.get_ref(&id).ok())
.map(|file| PathBuf::from(&*file.name))
.collect();
if paths.is_empty() {
return Ok(());
}
let mut cmd = Command::new("git");
cmd.args(["add", "--"]);
for path in &paths {
cmd.arg(path);
}
let status = cmd.current_dir(workspace).status().map_err(|e| Error::Database(DatabaseError::IOError(e)))?;
if !status.success() {
return Err(Error::Database(DatabaseError::IOError(std::io::Error::other("git add failed"))));
}
Ok(())
}
fn is_git_repository(workspace: &Path) -> bool {
Command::new("git")
.args(["rev-parse", "--git-dir"])
.current_dir(workspace)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn get_staged_files(workspace: &Path) -> Result<Vec<PathBuf>, Error> {
let output = Command::new("git")
.args(["diff", "--cached", "--name-only", "--diff-filter=ACMR"])
.current_dir(workspace)
.output()
.map_err(|e| Error::Database(DatabaseError::IOError(e)))?;
if !output.status.success() {
return Err(Error::NotAGitRepository);
}
Ok(String::from_utf8_lossy(&output.stdout).lines().filter(|l| !l.is_empty()).map(PathBuf::from).collect())
}
fn get_files_with_unstaged_changes(workspace: &Path) -> Result<HashSet<PathBuf>, Error> {
let output = Command::new("git")
.args(["diff", "--name-only"])
.current_dir(workspace)
.output()
.map_err(|e| Error::Database(DatabaseError::IOError(e)))?;
Ok(String::from_utf8_lossy(&output.stdout).lines().filter(|l| !l.is_empty()).map(PathBuf::from).collect())
}