Skip to main content

gn_sync/
fetch.rs

1use anyhow::Result;
2use chrono::{DateTime, Utc};
3use gn_core::{MergeStrategy, Namespace, NotesEngine};
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6use std::process::Command;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SyncReport {
10    pub fetched: usize,
11    pub merged: usize,
12    pub conflicts: usize,
13    pub timestamp: DateTime<Utc>,
14}
15
16pub fn fetch_notes(
17    repo_path: &Path,
18    remote: &str,
19    namespaces: &[Namespace],
20    strategy: &dyn MergeStrategy,
21) -> Result<SyncReport> {
22    let engine = NotesEngine::new(repo_path);
23    let mut total_fetched = 0;
24    let mut total_merged = 0;
25
26    for ns in namespaces {
27        let ref_path = ns.ref_path();
28
29        // Fetch into a temporary remote ref
30        let remote_ref = format!("refs/notes/{}_{}_remote", remote, ns);
31        let refspec = format!("{}:{}", ref_path, remote_ref);
32
33        let _ = Command::new("git")
34            .current_dir(repo_path)
35            .args(["fetch", remote, &refspec])
36            .status(); // ignore failure if remote ref doesn't exist
37
38        // For merging, we'd ideally instantiate a temporary NotesEngine configured for remote_ref,
39        // but we can just use git commands or temporarily move the ref.
40        // As a simplification, let's assume we can parse remote_ref with ls-tree directly
41        // in our current design we can fetch notes using a custom namespace.
42
43        let local_notes = engine.read_notes(ns).unwrap_or_default();
44
45        // Hack to read from the remote ref namespace
46        let remote_ns = Namespace::Custom(format!("{}_{}_remote", remote, ns));
47        let remote_notes = engine.read_notes(&remote_ns).unwrap_or_default();
48
49        total_fetched += remote_notes.len();
50
51        if !remote_notes.is_empty() {
52            let merged_notes = strategy.merge(&local_notes, &remote_notes);
53
54            // Re-write merged notes locally
55            // First we need to delete existing local ref or overwrite
56            // An easy approach is writing all notes. The LWW strategy guarantees identical notes have same hash
57            for note in &merged_notes {
58                engine.write_note(note)?;
59            }
60            total_merged += merged_notes.len();
61        }
62
63        // Clean up remote ref
64        let _ = Command::new("git")
65            .current_dir(repo_path)
66            .args(["update-ref", "-d", &remote_ref])
67            .status();
68    }
69
70    Ok(SyncReport {
71        fetched: total_fetched,
72        merged: total_merged,
73        conflicts: 0, // simple LWW handles conflicts transparently for now
74        timestamp: Utc::now(),
75    })
76}