use crate::recovery_files::read_optional;
use crate::shadow_git::shadow_request;
use crate::service_protocol::Cleanup;
use crate::transaction_git::{GitContext, GitRequest, run_git};
use crate::diagnostic_log::{debug, warn};
use std::path::{Path, PathBuf};
const AUTOSTASH_FILE: &str = "MERGE_AUTOSTASH";
const STASH_MESSAGE: &str = "autostash";
fn conflicted_autostash(git: &GitContext, cleanup: &Cleanup) -> Option<String> {
let copy: Vec<u8> = read_optional(cleanup.scratch.join("conclusion").join(AUTOSTASH_FILE).as_path()).ok().flatten()?;
let recorded: String = String::from_utf8_lossy(copy.as_slice()).trim().to_owned();
let shadow: PathBuf = cleanup.scratch.join("shadow");
let stored = run_git(git, &shadow_request(shadow.as_path(), shadow.as_path(), &["rev-parse", "--verify", "--quiet", "refs/stash"])).ok()?;
if !stored.succeeded() {
return None;
}
let kept: String = String::from_utf8_lossy(stored.stdout.as_slice()).trim().to_owned();
if recorded.is_empty() || kept != recorded {
return None;
}
return Some(recorded);
}
fn lists_stash(listing: &str, stash: &str) -> bool {
for line in listing.lines() {
if line == stash {
return true;
}
}
return false;
}
fn is_nul_byte(byte: &u8) -> bool {
return *byte == 0;
}
fn is_space_byte(byte: &u8) -> bool {
return *byte == b' ';
}
fn is_tab_byte(byte: &u8) -> bool {
return *byte == b'\t';
}
pub fn carry_stash(git: &GitContext, repository: &Path, cleanup: &Cleanup) -> bool {
let Some(stash) = conflicted_autostash(git, cleanup) else { return true };
let listed = run_git(git, &GitRequest::new(repository, &["stash", "list", "--format=%H"]));
if let Ok(output) = &listed
&& output.succeeded()
&& lists_stash(String::from_utf8_lossy(output.stdout.as_slice()).as_ref(), stash.as_str())
{
return true;
}
return match run_git(git, &GitRequest::new(repository, &["stash", "store", "-m", STASH_MESSAGE, stash.as_str()])) {
Ok(output) if output.succeeded() => {
debug("autostashCarry", format!("the stash {stash} the apply conflicted on was stored in the repository").as_str());
true
}
other => {
warn("autostashCarry", format!("the stash {stash} could not be stored in the repository: {other:?}").as_str());
false
}
};
}
pub fn carry_stages(git: &GitContext, repository: &Path, cleanup: &Cleanup, built: &Path) -> bool {
if conflicted_autostash(git, cleanup).is_none() || !built.exists() {
return true;
}
let mut listing: GitRequest = GitRequest::new(repository, &["ls-files", "--unmerged", "-z"]);
listing.index = Some(cleanup.scratch.join("commit.index"));
let Ok(stages) = run_git(git, &listing) else { return false };
if !stages.succeeded() {
return false;
}
if stages.stdout.is_empty() {
return true;
}
let mut entries: Vec<&[u8]> = Vec::new();
for entry in stages.stdout.split(is_nul_byte) {
if !entry.is_empty() {
entries.push(entry);
}
}
let length: usize = match entries[0].split(is_space_byte).nth(1) {
Some(object_name) => object_name.len(),
None => 0,
};
let mut paths: Vec<&[u8]> = Vec::new();
for entry in &entries {
if let Some(path) = entry.split(is_tab_byte).nth(1) {
paths.push(path);
}
}
paths.dedup();
let mut input: Vec<u8> = Vec::new();
for path in &paths {
input.extend_from_slice(format!("0 {}\t", "0".repeat(length)).as_bytes());
input.extend_from_slice(path);
input.push(0);
}
for entry in &entries {
input.extend_from_slice(entry);
input.push(0);
}
let mut placing: GitRequest = GitRequest::new(repository, &["update-index", "-z", "--index-info"]);
placing.index = Some(built.to_path_buf());
placing.input = Some(input);
return match run_git(git, &placing) {
Ok(output) if output.succeeded() => true,
other => {
warn("autostashCarry", format!("the conflict stages of the stash could not be put into the index: {other:?}").as_str());
false
}
};
}