use std::process::Command;
use crate::ui;
pub(super) fn fetch_tracked_files() -> Option<Vec<String>> {
match Command::new("git").args(["ls-files"]).output() {
Ok(o) => Some(
String::from_utf8_lossy(&o.stdout)
.lines()
.map(String::from)
.collect(),
),
Err(_) => None,
}
}
pub(super) fn fetch_staged(filter: &str) -> Vec<String> {
match Command::new("git")
.args([
"diff",
"--cached",
"--name-only",
&format!("--diff-filter={filter}"),
])
.output()
{
Ok(o) => String::from_utf8_lossy(&o.stdout)
.lines()
.map(String::from)
.collect(),
Err(_) => Vec::new(),
}
}
pub(super) fn restage_deletions(files: &[String]) -> bool {
if files.is_empty() {
return true;
}
let mut cmd = Command::new("git");
cmd.args(["rm", "--cached", "--quiet", "--force", "--"]);
for f in files {
cmd.arg(f);
}
match cmd.status() {
Ok(s) if s.success() => true,
Ok(s) => {
let code = s.code().unwrap_or(-1);
ui::error(&format!(
"git rm --cached failed (exit {code}) — staged deletions may be lost"
));
false
}
Err(e) => {
ui::error(&format!(
"git rm --cached failed after fix-mode stash dance: {e}"
));
false
}
}
}
pub(super) fn fetch_unstaged_files() -> Vec<String> {
match Command::new("git").args(["diff", "--name-only"]).output() {
Ok(o) => String::from_utf8_lossy(&o.stdout)
.lines()
.map(String::from)
.collect(),
Err(_) => Vec::new(),
}
}
pub(super) fn has_staged_submodules() -> bool {
let output = Command::new("git")
.args(["diff", "--cached", "--diff-filter=ACMR", "--raw"])
.output();
match output {
Ok(o) => String::from_utf8_lossy(&o.stdout).contains(" 160000 "),
Err(_) => false,
}
}
pub(super) fn stash_push() -> bool {
Command::new("git")
.args(["stash", "push", "--quiet", "--include-untracked"])
.status()
.map(|s| s.success())
.unwrap_or(false)
}
pub(super) fn fetch_staged_rename_targets() -> Vec<String> {
match Command::new("git")
.args(["diff", "--cached", "--diff-filter=R", "--name-only"])
.output()
{
Ok(o) => String::from_utf8_lossy(&o.stdout)
.lines()
.map(String::from)
.collect(),
Err(_) => Vec::new(),
}
}
pub(super) fn unstage_renames(rename_targets: &[String]) -> bool {
if rename_targets.is_empty() {
return true;
}
let mut cmd = Command::new("git");
cmd.args(["restore", "--staged", "--"]);
for f in rename_targets {
cmd.arg(f);
}
matches!(cmd.status(), Ok(s) if s.success())
}
pub(super) fn stash_apply() -> bool {
Command::new("git")
.args(["stash", "apply", "--quiet"])
.status()
.map(|s| s.success())
.unwrap_or(false)
}
pub(super) fn stash_drop() {
let ok = Command::new("git")
.args(["stash", "drop", "--quiet"])
.status()
.map(|s| s.success())
.unwrap_or(false);
if !ok {
ui::warning("git stash drop failed — stash entry may remain");
}
}
pub(super) fn restage_files(files: &[String]) -> bool {
if files.is_empty() {
return true;
}
let mut existing: Vec<&String> = Vec::new();
for f in files {
if std::path::Path::new(f).exists() {
existing.push(f);
} else {
ui::warning(&format!("{f} was deleted by formatter — skipping restage"));
}
}
if existing.is_empty() {
return true;
}
let mut cmd = Command::new("git");
cmd.arg("add").arg("--");
for f in &existing {
cmd.arg(f);
}
match cmd.status() {
Ok(s) if s.success() => true,
Ok(s) => {
let code = s.code().unwrap_or(-1);
ui::error(&format!(
"git add failed (exit {code}) — formatted changes may be lost"
));
false
}
Err(e) => {
ui::error(&format!("git add failed after fix-mode formatting: {e}"));
false
}
}
}