use super::super::{Error, checkpoint};
use super::span::Span;
use crate::template::GitRunner;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
pub(super) struct Product {
pub(super) deletions: Vec<String>,
pub(super) summaries: Vec<String>,
}
impl Product {
pub(super) fn is_empty(&self) -> bool {
self.deletions.is_empty() && self.summaries.is_empty()
}
}
pub(super) fn product(
parent_worktree: &Path,
dispatch: &str,
compactor_ref: &str,
git: &dyn GitRunner,
) -> Result<Product, Error> {
Ok(Product {
deletions: diff_class(parent_worktree, dispatch, compactor_ref, "D", None, git)?,
summaries: diff_class(
parent_worktree,
dispatch,
compactor_ref,
"A",
Some("summary"),
git,
)?,
})
}
fn diff_class(
parent_worktree: &Path,
from: &str,
to: &str,
class: &str,
pathspec: Option<&str>,
git: &dyn GitRunner,
) -> Result<Vec<String>, Error> {
let filter = format!("--diff-filter={class}");
let mut args = vec![
"diff",
"--name-only",
"--no-renames",
filter.as_str(),
from,
to,
];
if let Some(spec) = pathspec {
args.extend(["--", spec]);
}
let out = git
.run_capture(parent_worktree, &args)
.map_err(|source| Error::Git {
op: "compaction land product diff",
source,
})?;
Ok(out
.lines()
.map(str::trim)
.filter(|l| !l.is_empty())
.map(str::to_owned)
.collect())
}
pub(super) fn commit(
parent_worktree: &Path,
compactor_id: &str,
compactor_ref: &str,
span: &Span,
product: &Product,
git: &dyn GitRunner,
) -> Result<String, Error> {
let tmp = scratch_worktree(compactor_id);
let tmp_str = tmp.to_string_lossy().into_owned();
git.run(
parent_worktree,
&[
"worktree",
"add",
"--no-checkout",
"--detach",
&tmp_str,
&span.point,
],
)
.map_err(|source| Error::Git {
op: "compaction land scratch worktree",
source,
})?;
let minted = mint(&tmp, compactor_id, compactor_ref, span, product, git);
let _ = git.run(
parent_worktree,
&["worktree", "remove", "--force", &tmp_str],
);
minted
}
fn mint(
tmp: &Path,
compactor_id: &str,
compactor_ref: &str,
span: &Span,
product: &Product,
git: &dyn GitRunner,
) -> Result<String, Error> {
let err = |op| move |source| Error::Git { op, source };
git.run(tmp, &["read-tree", &span.point])
.map_err(err("compaction land read-tree"))?;
if !product.deletions.is_empty() {
let mut args = vec!["rm", "--cached", "-q", "--ignore-unmatch", "--"];
args.extend(product.deletions.iter().map(String::as_str));
git.run(tmp, &args)
.map_err(err("compaction land apply deletions"))?;
}
if !product.summaries.is_empty() {
let source_arg = format!("--source={compactor_ref}");
let mut args = vec!["restore", "--staged", source_arg.as_str(), "--"];
args.extend(product.summaries.iter().map(String::as_str));
git.run(tmp, &args)
.map_err(err("compaction land stage summary"))?;
}
let tree = git
.run_capture(tmp, &["write-tree"])
.map_err(err("compaction land write-tree"))?;
let subject = format!("{}{compactor_id}]", checkpoint::BASE_SUBJECT_PREFIX);
let sha = git
.run_capture(
tmp,
&[
"commit-tree",
tree.trim(),
"-p",
&span.bound,
"-m",
&subject,
],
)
.map_err(err("compaction land commit-tree"))?;
Ok(sha.trim().to_string())
}
fn scratch_worktree(compactor_id: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
std::env::temp_dir().join(format!("lernie-compaction-base-{compactor_id}-{nanos}"))
}