Skip to main content

borg/notes/
restore.rs

1//! `borg notes fetch`, the write side: what the mesh handed over (borg-mesh `fetch`, into the
2//! store's `reasoning.jsonl`) becomes notes. An excerpt becomes the note of a commit that has none;
3//! a decision → fix link record (links.rs) goes into the notes on both sides that exist.
4
5use std::path::Path;
6
7use crate::notes::links::{self, Link};
8use crate::notes::{Excerpt, git, has_note, note_of, write_note};
9
10/// The commits to ask the mesh about: the named ones (any form git resolves), or else the last 200
11/// on HEAD, noted ones too: a teammate's later fix of a decision noted here links to it (the mesh
12/// answers a link record by either side).
13///
14/// # Errors
15/// A named commit git cannot resolve.
16pub fn wanted(repo: &Path, named: &[&str]) -> Result<Vec<String>, String> {
17    if !named.is_empty() {
18        return named.iter().map(|c| git(repo, &["rev-parse", "--verify", &format!("{c}^{{commit}}")]).ok_or(format!("{c} is not a commit"))).collect();
19    }
20    Ok(git(repo, &["rev-list", "--max-count=200", "HEAD"]).unwrap_or_default().lines().map(str::to_string).collect())
21}
22
23/// Adds the link records naming `e`'s commit to its note's lists (links.rs `adopt`).
24fn adopt(e: &mut Excerpt, records: &[Link]) -> bool {
25    links::adopt(&e.commit, &e.collective, records, &mut links::Lists { fixed_by: &mut e.fixed_by, fixes: &mut e.fixes })
26}
27
28/// Writes the link records naming `commit` into its existing borg note; whether it changed.
29fn relink(repo: &Path, commit: &str, records: &[Link]) -> Result<bool, String> {
30    let Some(mut e) = note_of(repo, commit) else { return Ok(false) };
31    if !adopt(&mut e, records) {
32        return Ok(false);
33    }
34    write_note(repo, &e).map(|()| true)
35}
36
37/// The commits whose notes the link `records` touching `commits` concern: `commits` and each
38/// link's other side.
39fn link_sides(commits: &[String], records: &[Link]) -> Vec<String> {
40    let mut out: Vec<String> = Vec::new();
41    for l in records.iter().filter(|l| commits.contains(&l.commit) || commits.contains(&l.fix)) {
42        for c in [&l.commit, &l.fix] {
43            if !out.contains(c) {
44                out.push(c.clone());
45            }
46        }
47    }
48    out
49}
50
51/// The excerpts of `text` (reasoning.jsonl) for `commits` written as borg notes with their links,
52/// where the commit exists and has no borg note yet.
53fn restore_excerpts(repo: &Path, text: &str, records: &[Link], commits: &[String]) -> Result<Vec<String>, String> {
54    let mut done: Vec<String> = Vec::new();
55    for mut e in text.lines().filter_map(|l| serde_json::from_str::<Excerpt>(l).ok()) {
56        if e.kind != "reasoning" || !commits.contains(&e.commit) || done.contains(&e.commit) {
57            continue;
58        }
59        if git(repo, &["cat-file", "-e", &format!("{}^{{commit}}", e.commit)]).is_none() || has_note(repo, &e.commit) {
60            continue;
61        }
62        adopt(&mut e, records);
63        write_note(repo, &e)?;
64        done.push(e.commit);
65    }
66    Ok(done)
67}
68
69/// Writes the excerpts `store`'s reasoning.jsonl holds for `commits` of `repo` as borg notes,
70/// where the commit exists and has no borg note yet, with the decision → fix links the store holds
71/// for them (fetched from the mesh beside the excerpts; links.rs). A link also goes into an existing
72/// note on either side. Returns the commits noted or relinked.
73///
74/// # Errors
75/// A note git would not write.
76pub fn restore(repo: &Path, store: &Path, commits: &[String]) -> Result<Vec<String>, String> {
77    let text = std::fs::read_to_string(store.join("reasoning.jsonl")).unwrap_or_default();
78    let records = links::records(&text);
79    let mut done = restore_excerpts(repo, &text, &records, commits)?;
80    let sides: Vec<String> = link_sides(commits, &records).into_iter().filter(|c| !done.contains(c)).collect();
81    for c in sides {
82        if relink(repo, &c, &records)? {
83            done.push(c);
84        }
85    }
86    Ok(done)
87}