Decide what to do with a [[repo-file]] destination. Unlike [[file]] and [[skill]], a repo-file that exists but isn't ours can be *merged* — the canonical content is appended with a separator, preserving the user's existing content. If the file already contains the Atomic marker, it's treated as a refresh (replace the whole file).
(
dst: &Path,
previous: Option<&Receipt>,
force: bool,
)
| 518 | /// existing content. If the file already contains the Atomic marker, it's |
| 519 | /// treated as a refresh (replace the whole file). |
| 520 | fn repo_file_decision( |
| 521 | dst: &Path, |
| 522 | previous: Option<&Receipt>, |
| 523 | force: bool, |
| 524 | ) -> AgentResult<Decision> { |
| 525 | if !dst.exists() { |
| 526 | return Ok(Decision::Install); |
| 527 | } |
| 528 | let recorded = previous.and_then(|r| r.recorded_hash(dst)); |
| 529 | match recorded { |
| 530 | // Ours and untouched since install → safe to refresh. |
| 531 | Some(hash) if hash == hash_file(dst)? => Ok(Decision::Refresh), |
| 532 | // Ours but the user changed it → hands off unless forced. |
| 533 | Some(_) if !force => Ok(Decision::Skip(SkipReason::UserModified)), |
| 534 | // Not ours. If it already has the Atomic marker, treat as refresh |
| 535 | // (replace the whole file — it's Atomic content, just not from this |
| 536 | // receipt). Otherwise merge (append with separator). |
| 537 | None if !force => { |
| 538 | let existing = std::fs::read_to_string(dst).unwrap_or_default(); |
| 539 | if existing.contains(ATOMIC_MARKER) { |
| 540 | Ok(Decision::Refresh) |
| 541 | } else { |
| 542 | Ok(Decision::Merge) |
| 543 | } |
| 544 | } |
| 545 | // --force: clobber everything. |
| 546 | _ => Ok(Decision::Refresh), |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | /// Expand a leading `~` in a destination path. |
| 551 | fn expand_dst(dst: &str) -> AgentResult<PathBuf> { |
no test coverage detected