use argonauts_core::events::RunEventKind;
use argonauts_core::events::run::RunOutcome;
use argonauts_core::{Note, NoteLevel, RunId};
use crate::engine::host::Host;
use crate::error::EngineResult;
use crate::merge::branch::PREFIX;
use crate::merge::{deliver, final_check, merge_packet};
pub fn kept_ref(run: &RunId) -> String {
format!("refs/argonauts/{run}/kept")
}
pub async fn without_yours(host: &Host) -> EngineResult<Option<String>> {
let Some(snapshot) = deliver::snapshot_of_yours(host) else {
return Ok(None);
};
let Some(branch) = host.read(merge_packet::landing_branch_of) else {
return Ok(None);
};
let git = host.git();
let root = &host.repo().root;
let head = git.resolve(root, &branch).await?;
let target = match final_check::target(host) {
Some(target) => final_check::main_commit(host, &target).await.ok(),
None => None,
};
let main = match target {
Some(main) => main,
None => git
.parents(root, &snapshot)
.await?
.into_iter()
.next()
.unwrap_or(snapshot),
};
let commit = deliver::commit(host, root, &main, &head).await?;
let name = kept_ref(host.run_id());
if let Some(made) = git.ref_commit(root, &name).await?
&& git.tree_of(root, &made).await? == git.tree_of(root, &commit).await?
&& git.parents(root, &made).await? == git.parents(root, &commit).await?
{
return Ok(Some(made));
}
git.set_ref(root, &name, &commit).await?;
Ok(Some(commit))
}
pub async fn body_of(host: &Host, commit: &str) -> EngineResult<String> {
let log = host
.git()
.log_messages(&host.repo().root, &format!("{commit}^!"))
.await?;
let message = log.into_iter().next().map(|(_, message)| message);
let body = message
.as_deref()
.and_then(|message| message.split_once('\n'))
.map(|(_, body)| body.trim().to_owned());
Ok(body.unwrap_or_default())
}
pub async fn at_end(host: &Host, outcome: RunOutcome) -> Option<Kept> {
if !matches!(outcome, RunOutcome::BranchLeft | RunOutcome::PullRequest) {
return None;
}
let branch = host.read(merge_packet::landing_branch_of)?;
match without_yours(host).await {
Ok(commit) => Some(Kept {
branch,
commit: commit?,
}),
Err(err) => {
let note = Note {
level: NoteLevel::Warn,
text: format!(
"{branch} couldn't be rebuilt without what you hadn't committed when the \
run started ({err}), so its history still holds those edits. Look before \
you push it."
),
};
let _ = host.record(None, None, RunEventKind::Note(note));
None
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Kept {
branch: String,
commit: String,
}
impl Kept {
pub async fn leave(&self, host: &Host) {
let git = host.git();
let root = &host.repo().root;
if let Err(err) = git.set_branch(root, &self.branch, &self.commit).await {
tracing::warn!("leaving {} at {}: {err}", self.branch, self.commit);
return;
}
let prefix = format!("{PREFIX}{}/", host.run_id());
let others = git.branches_under(root, &prefix).await.unwrap_or_default();
for other in others.iter().filter(|other| **other != self.branch) {
if let Err(err) = git.delete_branch(root, other).await {
tracing::warn!("deleting {other}: {err}");
}
}
if let Err(err) = git.delete_ref(root, &kept_ref(host.run_id())).await {
tracing::warn!("deleting the run's kept ref: {err}");
}
}
pub async fn leave_in_copy(&self, host: &Host) {
let Ok((_, copy)) = merge_packet::landing_branch(host).await else {
return;
};
if let Err(err) = host.git().reset_hard(©, &self.commit).await {
tracing::warn!("leaving {} at {}: {err}", self.branch, self.commit);
}
}
}