use std::path::Path;
use serde_json::json;
use crate::branch::{self, Verb};
use crate::error::{Error, Result};
use crate::event::EventKind;
use crate::host::Hosting;
use crate::publish::{PublishOutcome, Subject};
use crate::registry::Registry;
use crate::rules::MergePolicy;
use crate::store;
use crate::stream::Stream;
use crate::workspace::object;
use crate::{guidance, provenance};
pub fn run(
registry: &Registry,
repo: &Path,
branch: &str,
title: Option<Subject>,
body: Option<String>,
hosting: &dyn Hosting,
stream: &mut Stream,
) -> Result<PublishOutcome> {
let landing = branch::prepare(registry, Verb::Recover, repo, branch, None)?;
let unattested = landing.unattested()?;
if unattested.is_empty() {
return Err(Error::Invalid {
reason: nothing_to_recover(&landing)?,
});
}
if let Some(reason) = attests_nothing(&landing) {
return Err(Error::Invalid { reason });
}
landing.check_subject(title.as_ref())?;
landing.sync_change_base(stream)?;
let attested = provenance::attest(
&landing.worktree,
&landing.compared_change_base,
&landing.trailers,
)?;
stream.emit(
EventKind::RecoveryAttested,
object(json!({
"branch": branch,
"markers": unattested,
"attestation": attested,
})),
);
landing.publish(title, body, hosting, stream)
}
fn nothing_to_recover(landing: &branch::Landing) -> Result<String> {
let branch = &landing.branch;
if let Some(prefix) = landing.unrecognized()?.first() {
return Ok(landing.unreadable_prefix(prefix));
}
if landing.ahead()?.is_empty() {
return Ok(format!(
"branch {branch:?} has nothing ahead of {}; there is no preserved work to recover. \
`onevcs recoverable` lists the branches that do carry unpublished work",
landing.change_base
));
}
Ok(format!(
"branch {branch:?} carries no unattested incomplete provenance: it has commits ahead of \
{}, and all of them are complete. `recover` publishes interrupted work; publish a \
completed branch with `{}`",
landing.change_base,
complete_branch_verb(landing),
))
}
fn complete_branch_verb(landing: &branch::Landing) -> String {
if landing.resolved.policy.publication == MergePolicy::LocalDirect {
crate::guidance::command(["onevcs", "integrate", &landing.branch])
} else {
landing.command_for(Verb::PublishBranch)
}
}
fn attests_nothing(landing: &branch::Landing) -> Option<String> {
if landing.resolution.identity.gate != store::NOOP_GATE
|| store::merge_path_coverage(
&landing.resolution,
&landing.source,
landing.resolved.policy.publication,
) != store::Coverage::None
{
return None;
}
Some(format!(
"identity {:?} names no complete bar and nothing on its merge path verifies one, so a \
recovery attestation would attest nothing. Give it one of the two: put an executable \
pre-push hook in {}, which is what judges a publishing push, or resolve this identity \
to a change-request policy in the rules file so a host's required checks judge its \
change requests. Confirm what covers it with `{}`, then re-run `{}`",
landing.resolution.key,
landing.source.display(),
guidance::command(["onevcs", "repos", "--audit-gates"]),
landing.command(),
))
}