#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecoveryAction {
JjUpdateStale,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RecoveryHint {
pub action: RecoveryAction,
pub key_hint: char,
pub command_label: &'static str,
}
impl RecoveryHint {
pub const fn jj_update_stale() -> Self {
Self {
action: RecoveryAction::JjUpdateStale,
key_hint: 'u',
command_label: "jj workspace update-stale",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorClass {
Transient,
Actionable(RecoveryHint),
Permanent,
}
pub fn classify_error(msg: &str) -> ErrorClass {
if msg.contains("working copy is stale") || msg.contains("workspace update-stale") {
return ErrorClass::Actionable(RecoveryHint::jj_update_stale());
}
if msg.contains(".lock") {
return ErrorClass::Transient;
}
ErrorClass::Permanent
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stale_message_is_actionable_with_update_stale_hint() {
let msg = "Error: The working copy is stale (not updated since operation 26a0bbff5afe). \
Hint: Run `jj workspace update-stale` to update it.";
match classify_error(msg) {
ErrorClass::Actionable(hint) => {
assert_eq!(hint.action, RecoveryAction::JjUpdateStale);
assert_eq!(hint.key_hint, 'u');
}
other => panic!("expected Actionable, got {other:?}"),
}
}
#[test]
fn stale_hint_alone_is_enough_to_trigger_actionable() {
let msg = "Run `jj workspace update-stale` to update it.";
assert!(matches!(classify_error(msg), ErrorClass::Actionable(_)));
}
#[test]
fn lock_message_is_transient() {
let msg = "could not acquire .git/index.lock";
assert_eq!(classify_error(msg), ErrorClass::Transient);
}
#[test]
fn other_errors_are_permanent() {
assert_eq!(classify_error("no such revision"), ErrorClass::Permanent);
assert_eq!(classify_error(""), ErrorClass::Permanent);
assert_eq!(classify_error("Config error: missing setting"), ErrorClass::Permanent);
}
#[test]
fn stale_takes_precedence_over_lock_if_both_mentioned() {
let msg = "working copy is stale (also: .lock present)";
assert!(matches!(classify_error(msg), ErrorClass::Actionable(_)));
}
#[test]
fn recovery_hint_constructor_is_consistent() {
let hint = RecoveryHint::jj_update_stale();
assert_eq!(hint.action, RecoveryAction::JjUpdateStale);
assert_eq!(hint.command_label, "jj workspace update-stale");
assert_eq!(hint.key_hint, 'u');
}
}