use anyhow::{Result, bail};
use ratatui::DefaultTerminal;
use crate::app::{FileEntry, FileMerge, Session};
use crate::git::{ConflictedFile, Git, RepoState};
use crate::i18n::{tr, tr_f};
use crate::ui::{self, Outcome};
pub fn resolve_loop(git: &Git, light: bool) -> Result<()> {
loop {
let files = git.conflicted_files()?;
if files.is_empty() {
let state = git.state()?;
if state == RepoState::Clean {
println!("[git-pincer] {}", tr("resolve.all_done"));
return Ok(());
}
println!("[git-pincer] $ git {} --continue", state.op_name());
let status = git.continue_op(state)?;
if status.success() {
continue;
}
if git.conflicted_files()?.is_empty() {
bail!(
"{}",
tr_f("resolve.continue_failed", &[("op", state.op_name())])
);
}
println!("[git-pincer] {}", tr("resolve.next_round"));
continue;
}
println!(
"[git-pincer] {}",
tr_f("resolve.found", &[("n", &files.len().to_string())])
);
let mut session = build_session(git, &files, false)?;
let outcome = ui::run_session(
&mut session,
&mut |path: &str, bytes: &[u8]| {
git.stage_resolved(path, bytes)?;
Ok(())
},
light,
)?;
if outcome == Outcome::Quit {
println!("[git-pincer] {}", tr("resolve.quit"));
return Ok(());
}
}
}
pub(crate) fn resolve_from_menu(
git: &Git,
light: bool,
terminal: DefaultTerminal,
cmd_line: &str,
captured: &std::process::Output,
) -> Result<()> {
let files = git.conflicted_files()?;
let outcome = if files.is_empty() {
ratatui::restore();
None
} else {
match build_session(git, &files, true) {
Ok(mut session) => Some(ui::run_session_in(
terminal,
&mut session,
&mut |path: &str, bytes: &[u8]| {
git.stage_resolved(path, bytes)?;
Ok(())
},
light,
)?),
Err(e) => {
ratatui::restore();
return Err(e);
}
}
};
println!("[git-pincer] $ git {cmd_line}");
print!("{}", String::from_utf8_lossy(&captured.stdout));
eprint!("{}", String::from_utf8_lossy(&captured.stderr));
if outcome == Some(Outcome::Quit) {
println!("[git-pincer] {}", tr("resolve.quit"));
return Ok(());
}
resolve_loop(git, light)
}
fn build_session(git: &Git, files: &[ConflictedFile], quiet: bool) -> Result<Session> {
let state = git.state()?;
let oids: Vec<&str> = files
.iter()
.flat_map(|f| [f.base.as_deref(), f.ours.as_deref(), f.theirs.as_deref()])
.flatten()
.collect();
let mut blobs = git.read_blobs(&oids)?.into_iter();
let mut take = |oid: &Option<String>| -> Vec<u8> {
if oid.is_some() {
blobs.next().unwrap_or_default()
} else {
Vec::new()
}
};
let mut entries = Vec::new();
for (index, file) in files.iter().enumerate() {
if !quiet {
println!(
"{}",
tr_f(
"resolve.reading",
&[
("i", &(index + 1).to_string()),
("n", &files.len().to_string()),
("path", &file.path),
],
)
);
}
let base = take(&file.base);
let ours = take(&file.ours);
let theirs = take(&file.theirs);
if base.contains(&0) || ours.contains(&0) || theirs.contains(&0) {
entries.push(FileEntry::Binary {
path: file.path.clone(),
ours,
theirs,
choice: None,
});
} else {
entries.push(FileEntry::Text(FileMerge::from_three_way(
file.path.clone(),
&String::from_utf8_lossy(&base),
&String::from_utf8_lossy(&ours),
&String::from_utf8_lossy(&theirs),
)));
}
}
Ok(Session::new(entries, state.op_name().to_owned()))
}