use std::path::Path;
use balls::delivery_path::work_branch;
use super::{Attempt, Change, WorkFile, candidates, plan};
use crate::app::Snapshot;
use crate::binding::named_of;
use crate::files_view::{MAX_ENTRIES, Preview, classify};
use crate::git_tree::{file_patch, head_branch, numstat, rev_parse};
pub fn read(
snap: &Snapshot,
workspace: &Path,
entries: &[crate::opslog::OpEntry],
world: &crate::xdg::Env,
) -> Vec<Attempt> {
let Some(name) = named_of(&snap.workspaces, workspace) else {
return Vec::new();
};
let mut out: Vec<Attempt> = plan::plans(&snap.balls_by_project, &name)
.into_iter()
.map(|plan| {
let named = snap.project_name(&plan.project);
resolve(plan, named)
})
.collect();
out.extend(candidates::candidates(
snap, workspace, entries, world, &name,
));
out
}
fn resolve(plan: plan::Plan, project: String) -> Attempt {
let source = work_branch(&plan.ball_id);
let attempt = |change, delivered| Attempt {
project: project.clone(),
ball_id: plan.ball_id.clone(),
handle: None,
delivered,
change,
};
let Ok(Some(head)) = head_branch(&plan.project) else {
return attempt(Change::Unreadable, None);
};
let target = plan.target_ball.as_deref().map_or(head, work_branch);
let mark = crate::fan::delivered_commit(&plan.project, &target, &plan.ball_id);
attempt(diff_change(&plan.project, target, source), mark)
}
pub(super) fn diff_change(project: &Path, target: String, source: String) -> Change {
let resolved = |spec: &str| rev_parse(project, spec).ok().flatten();
let (Some(target_oid), Some(source_oid)) = (resolved(&target), resolved(&source)) else {
let missing = [&target, &source]
.into_iter()
.filter(|spec| resolved(spec).is_none())
.cloned()
.collect();
return Change::Absent {
target,
source,
missing,
};
};
let Ok(out) = numstat(project, &format!("{target}..{source}")) else {
return Change::Unreadable;
};
let mut files = plan::parse_numstat(&out);
let truncated = files.len() > MAX_ENTRIES;
files.truncate(MAX_ENTRIES);
Change::Diff {
target,
source,
target_oid,
source_oid,
files,
truncated,
}
}
pub fn patch(snap: &Snapshot, attempts: &[Attempt], file: &WorkFile) -> Option<Preview> {
let attempt = attempts
.iter()
.find(|a| a.ball_id == file.ball && a.handle == file.handle)?;
let range = attempt.range()?;
if matches!(attempt.change, Change::Absent { .. }) {
return None;
}
let project = snap.project_path(&attempt.project).ok()?;
let bytes = file_patch(&project, &range, &file.path).unwrap_or_default();
let size = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
Some(classify(&bytes, size))
}