use super::super::{Error, checkpoint};
use crate::prompt::role;
use crate::template::GitRunner;
use std::path::Path;
pub(super) struct Span {
pub(super) dispatch: String,
pub(super) point: String,
pub(super) bound: String,
}
pub(super) fn of(
parent_worktree: &Path,
parent_id: &str,
compactor_id: &str,
compactor_ref: &str,
git: &dyn GitRunner,
) -> Result<Option<Span>, Error> {
let dispatch = role::founding_sha(parent_worktree, compactor_ref, compactor_id, git)?.ok_or(
Error::Git {
op: "compaction land dispatch commit",
source: std::io::Error::other(format!(
"no dispatch commit names [{compactor_id}] on {compactor_ref}"
)),
},
)?;
let parent_rev = format!("{dispatch}^");
let point = git
.run_capture(parent_worktree, &["rev-parse", &parent_rev])
.map_err(|source| Error::Git {
op: "compaction land point",
source,
})?
.trim()
.to_string();
if !reaches(parent_worktree, &point, git) || landed_since(parent_worktree, &point, git)? {
return Ok(None);
}
let bound = match checkpoint::origin(parent_worktree, &point, parent_id, git)? {
Some(sha) => sha,
None => checkpoint::root_of(parent_worktree, &point, git)?,
};
Ok(Some(Span {
dispatch,
point,
bound,
}))
}
fn reaches(parent_worktree: &Path, point: &str, git: &dyn GitRunner) -> bool {
git.run(
parent_worktree,
&["merge-base", "--is-ancestor", point, "HEAD"],
)
.is_ok()
}
fn landed_since(parent_worktree: &Path, point: &str, git: &dyn GitRunner) -> Result<bool, Error> {
let range = format!("{point}..HEAD");
let err = |source| Error::Git {
op: "compaction land span rev-list",
source,
};
let landed = checkpoint::landing_subject_pattern();
let bases = git
.run_capture(
parent_worktree,
&["rev-list", "-E", "--grep", landed.as_str(), &range],
)
.map_err(err)?;
if !bases.trim().is_empty() {
return Ok(true);
}
let merges = git
.run_capture(parent_worktree, &["rev-list", "--merges", &range])
.map_err(err)?;
Ok(!merges.trim().is_empty())
}