rag-rat-core 0.4.0

Repository evidence engine for source chunks, symbols, graph edges, Git history, GitHub rationale, and source-bound memories.
Documentation
use super::*;

pub(crate) fn discover_and_store_refs(
    conn: &Connection,
    root: &Path,
    ctx: &GitHubContext,
) -> anyhow::Result<Vec<GitHubRef>> {
    let default_repo = ctx.default_repo().map(str::to_string);
    let mut refs = Vec::new();
    discover_commit_refs(conn, default_repo.as_deref(), &mut refs)?;
    discover_file_refs(conn, root, default_repo.as_deref(), &mut refs)?;
    let branch = git_output(root, &["branch", "--show-current"]).unwrap_or_default();
    for parsed in parse_refs(&branch, default_repo.as_deref()) {
        refs.push(GitHubRef {
            owner: parsed.owner,
            repo: parsed.repo,
            number: parsed.number,
            ref_kind: parsed.kind,
            source_kind: "branch".to_string(),
            source_path: None,
            source_commit: None,
            source_text: branch.clone(),
        });
    }
    let mut unique = BTreeSet::new();
    refs.retain(|r| {
        unique.insert((
            r.owner.clone(),
            r.repo.clone(),
            r.number,
            r.source_kind.clone(),
            r.source_path.clone(),
            r.source_commit.clone(),
            r.source_text.clone(),
        ))
    });
    for reference in &refs {
        store_ref(conn, reference)?;
    }
    Ok(refs)
}
pub(crate) fn sync_refs<'a, C: GitHubClient>(
    conn: &Connection,
    client: &C,
    refs: impl Iterator<Item = &'a GitHubRef>,
    progress: &mut impl FnMut(GitHubSyncProgress),
) -> anyhow::Result<SyncRefsReport> {
    let refs = refs.collect::<Vec<_>>();
    let total = refs
        .iter()
        .map(|reference| (reference.owner.clone(), reference.repo.clone(), reference.number))
        .collect::<BTreeSet<_>>()
        .len();
    let mut report = SyncRefsReport::default();
    let mut seen = BTreeSet::new();
    for reference in refs {
        if !seen.insert((reference.owner.clone(), reference.repo.clone(), reference.number)) {
            continue;
        }
        let current = seen.len();
        if github_ref_synced(conn, reference)? {
            report.skipped_refs += 1;
            progress(sync_progress(reference, current, total, GitHubSyncAction::Skipped, None));
            continue;
        }
        progress(sync_progress(reference, current, total, GitHubSyncAction::Syncing, None));
        match sync_one_ref(conn, client, reference) {
            Ok(items) => {
                report.synced_items += items;
                mark_ref_sync(conn, reference, "synced", None)?;
                progress(sync_progress(reference, current, total, GitHubSyncAction::Synced, None));
            },
            Err(err) => {
                let message = err.to_string();
                let status = if is_not_found_error(&message) { "not_found" } else { "failed" };
                mark_ref_sync(conn, reference, status, Some(&message))?;
                report.failed_refs += 1;
                report.errors.push(GitHubSyncError {
                    owner: reference.owner.clone(),
                    repo: reference.repo.clone(),
                    number: reference.number,
                    status: status.to_string(),
                    error: message.clone(),
                });
                progress(sync_progress(
                    reference,
                    current,
                    total,
                    GitHubSyncAction::Failed,
                    Some(message),
                ));
            },
        }
    }
    progress(GitHubSyncProgress {
        current: total,
        total,
        owner: String::new(),
        repo: String::new(),
        number: 0,
        action: GitHubSyncAction::RebuildingFts,
        message: None,
    });
    rebuild_fts(conn)?;
    Ok(report)
}
pub(crate) fn sync_one_ref<C: GitHubClient>(
    conn: &Connection,
    client: &C,
    reference: &GitHubRef,
) -> anyhow::Result<usize> {
    let mut synced = 0;
    let issue = client.issue(&reference.owner, &reference.repo, reference.number)?;
    store_issue(conn, &issue)?;
    synced += 1;
    for comment in client.issue_comments(&reference.owner, &reference.repo, reference.number)? {
        store_comment(conn, &comment)?;
        synced += 1;
    }
    if let Some(pull) = client.pull(&reference.owner, &reference.repo, reference.number)? {
        store_pull(conn, &pull)?;
        synced += 1;
        for review in client.pull_reviews(&reference.owner, &reference.repo, reference.number)? {
            store_review(conn, &review)?;
            synced += 1;
        }
        for comment in
            client.pull_review_comments(&reference.owner, &reference.repo, reference.number)?
        {
            store_review_comment(conn, &comment)?;
            synced += 1;
        }
    }
    Ok(synced)
}
pub(crate) fn sync_progress(
    reference: &GitHubRef,
    current: usize,
    total: usize,
    action: GitHubSyncAction,
    message: Option<String>,
) -> GitHubSyncProgress {
    GitHubSyncProgress {
        current,
        total,
        owner: reference.owner.clone(),
        repo: reference.repo.clone(),
        number: reference.number,
        action,
        message,
    }
}
pub(crate) fn github_ref_synced(conn: &Connection, reference: &GitHubRef) -> anyhow::Result<bool> {
    let status = conn
        .query_row(
            "
            SELECT status
            FROM github_ref_sync
            WHERE owner = ?1 AND repo = ?2 AND number = ?3
            ",
            params![reference.owner, reference.repo, reference.number],
            |row| row.get::<_, String>(0),
        )
        .optional()?;
    if matches!(status.as_deref(), Some("synced" | "not_found")) {
        return Ok(true);
    }
    let cached_issue = conn.query_row(
        "
        SELECT EXISTS(
            SELECT 1 FROM github_issues
            WHERE owner = ?1 AND repo = ?2 AND number = ?3
        )
        ",
        params![reference.owner, reference.repo, reference.number],
        |row| row.get::<_, bool>(0),
    )?;
    Ok(cached_issue)
}
pub(crate) fn mark_ref_sync(
    conn: &Connection,
    reference: &GitHubRef,
    status: &str,
    error: Option<&str>,
) -> anyhow::Result<()> {
    conn.execute(
        "
        INSERT INTO github_ref_sync(owner, repo, number, status, synced_at_ms, last_error)
        VALUES (?1, ?2, ?3, ?4, ?5, ?6)
        ON CONFLICT(owner, repo, number) DO UPDATE SET
            status = excluded.status,
            synced_at_ms = excluded.synced_at_ms,
            last_error = excluded.last_error
        ",
        params![reference.owner, reference.repo, reference.number, status, now_ms(), error],
    )?;
    Ok(())
}
pub(crate) fn is_not_found_error(message: &str) -> bool {
    message.contains("HTTP 404") || message.to_ascii_lowercase().contains("not found")
}
pub(crate) fn discover_commit_refs(
    conn: &Connection,
    default_repo: Option<&str>,
    out: &mut Vec<GitHubRef>,
) -> anyhow::Result<()> {
    let mut stmt = conn.prepare("SELECT hash, subject, body FROM git_commits")?;
    let rows = stmt.query_map([], |row| {
        Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?))
    })?;
    for row in rows {
        let (hash, subject, body) = row?;
        for text in [subject, body] {
            for parsed in parse_refs(&text, default_repo) {
                out.push(GitHubRef {
                    owner: parsed.owner,
                    repo: parsed.repo,
                    number: parsed.number,
                    ref_kind: parsed.kind,
                    source_kind: "commit".to_string(),
                    source_path: None,
                    source_commit: Some(hash.clone()),
                    source_text: text.clone(),
                });
            }
        }
    }
    Ok(())
}
pub(crate) fn discover_file_refs(
    conn: &Connection,
    root: &Path,
    default_repo: Option<&str>,
    out: &mut Vec<GitHubRef>,
) -> anyhow::Result<()> {
    let mut stmt = conn.prepare("SELECT path FROM files ORDER BY path")?;
    let rows = stmt.query_map([], |row| row.get::<_, String>(0))?;
    for row in rows {
        let path = row?;
        let Ok(text) = std::fs::read_to_string(root.join(&path)) else {
            continue;
        };
        for line in text.lines() {
            for parsed in parse_refs(line, default_repo) {
                out.push(GitHubRef {
                    owner: parsed.owner,
                    repo: parsed.repo,
                    number: parsed.number,
                    ref_kind: parsed.kind,
                    source_kind: "file".to_string(),
                    source_path: Some(path.clone()),
                    source_commit: None,
                    source_text: line.trim().to_string(),
                });
            }
        }
    }
    Ok(())
}