use recall_hooks::{backfill, exit, Disposition, Entry};
use crate::project;
pub async fn run() -> anyhow::Result<i32> {
let ctx = project::resolve().hook_context()?;
let outcome = match backfill(&ctx).await {
Ok(outcome) => outcome,
Err(err) => {
eprintln!("recall: {err}");
return Ok(match err {
recall_hooks::Error::Pull { .. } => exit::SERVER,
_ => exit::CONFIG,
});
}
};
println!(
"recall: {} sent, {} already on the server, {} held back, {} deleted on the \
server, {} skipped ({})",
outcome.count(Disposition::Sent),
outcome.count(Disposition::Matches),
outcome.count(Disposition::Held),
outcome.count(Disposition::Deleted),
outcome.count(Disposition::Unroutable)
+ outcome.count(Disposition::NotUtf8)
+ outcome.count(Disposition::Refused)
+ outcome.count(Disposition::Ignored)
+ outcome.count(Disposition::Internal),
ctx.project_key()
);
list(
&outcome.entries,
Disposition::Held,
"held back: the server has a different version of these, so they were \
not overwritten. Edit one through Claude and the server merges the two. \
Deleting your copy deletes the server's too:",
);
list(
&outcome.entries,
Disposition::Deleted,
"deleted on the server: removed on another machine. 'recall pull' \
removes them here:",
);
list(
&outcome.entries,
Disposition::Refused,
"refused by the server, so they were skipped:",
);
list(
&outcome.entries,
Disposition::Unroutable,
"not in any scope, so these belong nowhere. Each line says what would \
change that:",
);
list(
&outcome.entries,
Disposition::NotUtf8,
"not text, so they cannot be synced at all:",
);
list(
&outcome.entries,
Disposition::Ignored,
"on disk but unreadable:",
);
if let Some(reason) = &outcome.baseline {
eprintln!("\nrecall: {reason}");
}
if let Some(reason) = &outcome.stopped {
eprintln!(
"\nrecall: stopped {reason}\n {} file(s) after it were not reached.",
outcome.not_reached
);
return Ok(exit::SERVER);
}
Ok(exit::OK)
}
fn list(entries: &[Entry], what: Disposition, heading: &str) {
let named: Vec<&Entry> = entries.iter().filter(|e| e.disposition == what).collect();
if named.is_empty() {
return;
}
println!("\n {heading}");
for entry in named {
match &entry.detail {
Some(why) => println!(" {}: {why}", entry.path),
None => println!(" {}", entry.path),
}
}
}