use super::App;
use super::delete::{Confirm, GIT_WORDS, Target};
use crate::git::{git_capture, git_run};
use crate::tui::widgets::Modal;
const STASH_WORDS: usize = 5;
fn failure_lines(words: &str) -> String {
let mut said = 0;
let mut out = Vec::new();
for line in words.lines() {
if line.starts_with("The stash entry is kept") || line == "Aborting" {
continue;
}
if let Some(path) = line.strip_prefix('\t') {
out.push(format!("- {path}"));
} else if said < STASH_WORDS {
said += 1;
out.push(line.to_string());
}
}
out.join("\n")
}
pub(super) fn stash_log() -> Vec<String> {
vec!["-g".to_string(), "refs/stash".to_string()]
}
impl App {
pub(super) fn name_stashes(&mut self, from: usize) {
for (i, c) in self.commits.iter_mut().enumerate().skip(from) {
c.short = format!("stash@{{{i}}}");
}
}
fn stash_here(&mut self) -> Option<(String, String)> {
let i = self.csel.idx()?;
let (name, hash) = (self.commits[i].short.clone(), self.commits[i].hash.clone());
let now = git_capture(&self.repo, &["rev-parse", "--verify", "-q", &name]);
if now.as_deref() != Some(hash.as_str()) {
self.set_failed("the stash list changed since it was read · r reads it again");
return None;
}
Some((name, hash))
}
pub(super) fn apply_stash(&mut self, pop: bool) {
let Some((name, _)) = self.stash_here() else {
return;
};
let verb = if pop { "pop" } else { "apply" };
let (ok, out) = git_run(&self.repo, &["stash", verb, "-q", &name]);
if !ok {
let unmerged = git_capture(&self.repo, &["diff", "--name-only", "--diff-filter=U"])
.unwrap_or_default();
let refused = !failure_lines(&out).is_empty();
let (title, words) = if !refused && !unmerged.is_empty() {
let kept = if pop { "\nthe stash is kept" } else { "" };
(
format!("`{name}` applied with conflicts"),
format!(
"resolve them in:\n\t{}{kept}",
unmerged.replace('\n', "\n\t")
),
)
} else {
(format!("could not {verb} `{name}`"), out)
};
self.modal = Some(Modal::new(title, failure_lines(&words)));
return;
}
if pop {
self.set_status(format!("popped {name} into the working tree"));
self.reread_stashes();
} else {
self.set_status(format!("applied {name}; it is still stashed"));
}
}
pub(super) fn ask_drop_stash(&mut self) {
let Some(i) = self.csel.idx() else {
return;
};
let c = &self.commits[i];
self.confirm = Some(Confirm::new(Target::Stash {
name: c.short.clone(),
subject: c.subject.clone(),
hash: c.hash.clone(),
}));
}
pub(super) fn drop_stash(&mut self, name: &str, hash: &str) {
let now = git_capture(&self.repo, &["rev-parse", "--verify", "-q", name]);
if now.as_deref() != Some(hash) {
self.set_failed("the stash list changed since it was read · r reads it again");
return;
}
let (ok, out) = git_run(&self.repo, &["stash", "drop", "-q", name]);
if !ok {
let words: Vec<&str> = out.lines().take(GIT_WORDS).collect();
self.modal = Some(Modal::new(
format!("could not drop `{name}`"),
words.join("\n"),
));
return;
}
let short: String = hash.chars().take(7).collect();
self.set_status(format!("dropped {name} (was {short})"));
self.reread_stashes();
}
fn reread_stashes(&mut self) {
let at = self.csel.cur;
let next = self
.csel
.visible
.get(at + 1)
.or_else(|| at.checked_sub(1).and_then(|b| self.csel.visible.get(b)));
self.csel.restore = next.map(|&i| self.commits[i].hash.clone());
self.request_commits();
self.pending = true;
}
}