use heddle_core::status::next_action::canonical_adopt_ref_command;
use anyhow::{Result, anyhow};
use objects::{
error::HeddleError,
object::{ChangeId, State},
store::ObjectStore,
};
use repo::{
ResolvePolicy, StateResolveFailure, resolve_state_for_command, Repository,
};
use super::advice::RecoveryAdvice;
pub(crate) fn state_resolve_failure_to_error(failure: StateResolveFailure) -> anyhow::Error {
match failure {
StateResolveFailure::GitBranchHistoryNotImported { branch } => {
anyhow!(tip_only_branch_history_advice(&branch))
}
StateResolveFailure::GitTagHistoryNotImported { tag } => {
anyhow!(tip_only_tag_history_advice(&tag))
}
StateResolveFailure::NotFound { spec } => anyhow!(state_not_found_advice(&spec)),
}
}
pub(crate) fn resolve_state_id(repo: &Repository, spec: &str) -> Result<ChangeId> {
resolve_state_id_with_policy(repo, spec, ResolvePolicy::with_git_overlay_hints())
}
pub(crate) fn resolve_state_id_with_policy(
repo: &Repository,
spec: &str,
policy: ResolvePolicy<'_>,
) -> Result<ChangeId> {
resolve_state_for_command(repo, spec, policy)
.map(|resolved| resolved.change_id)
.map_err(state_resolve_error_to_anyhow)
}
fn state_resolve_error_to_anyhow(error: repo::StateResolveError) -> anyhow::Error {
match error {
repo::StateResolveError::Repository(err) => err.into(),
repo::StateResolveError::Failure(failure) => state_resolve_failure_to_error(failure),
}
}
pub(crate) fn require_resolved_state(repo: &Repository, id: &ChangeId) -> Result<State> {
repo.store().get_state(id)?.ok_or_else(|| {
anyhow::Error::new(HeddleError::MissingObject {
object_type: "state".to_string(),
id: id.to_string_full(),
})
})
}
fn state_not_found_advice(spec: &str) -> RecoveryAdvice {
RecoveryAdvice::safety_refusal(
"state_not_found",
format!("State not found: {spec}"),
"Inspect available states with `heddle log`, then retry with an existing state, marker, thread, or HEAD expression.",
format!("no Heddle state, marker, thread, or HEAD expression matched '{spec}'"),
"the command cannot move refs, inspect content, or write worktree files until the target state is resolved",
"repository state and worktree files were left unchanged",
"heddle log",
vec!["heddle log".to_string()],
)
}
fn tip_only_branch_history_advice(branch: &str) -> RecoveryAdvice {
let import_command = canonical_adopt_ref_command(branch);
RecoveryAdvice::safety_refusal(
"git_branch_history_not_imported",
format!("Heddle has not imported Git branch '{branch}' history yet"),
format!("Import its history first with `{import_command}`."),
format!("branch '{branch}' has a Git tip but no imported Heddle history"),
"history-sensitive commands cannot safely resolve this branch until Heddle imports its Git history",
"repository state and worktree files were left unchanged",
import_command.clone(),
vec![import_command],
)
}
fn tip_only_tag_history_advice(tag: &str) -> RecoveryAdvice {
let import_command = canonical_adopt_ref_command(tag);
RecoveryAdvice::safety_refusal(
"git_tag_history_not_imported",
format!("Git tag '{tag}' is visible but its history is not imported yet"),
format!("Import it first with `{import_command}`."),
format!("tag '{tag}' has a Git tip but no imported Heddle history"),
"history-sensitive commands cannot safely resolve this tag to a Heddle state yet",
"repository state and worktree files were left unchanged",
import_command.clone(),
vec![import_command],
)
}
pub(crate) fn resolve_state_id_bytes(repo: &Repository, spec: &str) -> Result<Vec<u8>> {
Ok(resolve_state_id(repo, spec)?.as_bytes().to_vec())
}