relay-knowledge 1.1.6

Graph-database-based knowledge graph project.
Documentation
use std::{collections::BTreeMap, path::Path};

#[cfg(test)]
use std::{fs, path::PathBuf, sync::Mutex};

use crate::domain::{CodeIndexSnapshot, CodeRepositoryRegistration, CodeRepositorySelector};

use super::{
    CodeIndexError,
    parser::parse_indexed_file,
    scope::{ScopedSourceSnapshot, scoped_source_snapshot},
    snapshot::{SnapshotBuild, SnapshotScopeFilters},
    source::{
        ensure_filesystem_blobs_match_content_hashes, filesystem_content_hashes_for_paths,
        filesystem_tree_hash_from_path_hashes, source_snapshot_bytes,
    },
};

#[cfg(test)]
struct FullSnapshotReadMutation {
    root: PathBuf,
    path: String,
    content: Vec<u8>,
}

#[cfg(test)]
static FULL_SNAPSHOT_READ_MUTATION: Mutex<Option<FullSnapshotReadMutation>> = Mutex::new(None);

#[cfg(test)]
pub(crate) fn mutate_next_filesystem_full_snapshot_read(root: PathBuf, path: &str, content: &[u8]) {
    *FULL_SNAPSHOT_READ_MUTATION
        .lock()
        .expect("full snapshot mutation should lock") = Some(FullSnapshotReadMutation {
        root,
        path: path.to_owned(),
        content: content.to_vec(),
    });
}

pub(super) fn build_full_snapshot(
    registration: &CodeRepositoryRegistration,
    selector: &CodeRepositorySelector,
    root: &Path,
) -> Result<CodeIndexSnapshot, CodeIndexError> {
    let snapshot = scoped_source_snapshot(registration, selector, root, &selector.ref_selector)?;
    let filesystem_path_hashes = filesystem_full_snapshot_path_hashes(&snapshot)?;
    let mut build = SnapshotBuild::new_with_scope_filters(
        registration,
        snapshot.resolved_commit_sha.clone(),
        snapshot.tree_hash.clone(),
        SnapshotScopeFilters {
            path_filters: snapshot.path_filters.clone(),
            language_filters: snapshot.language_filters.clone(),
        },
        true,
        snapshot.entries.len(),
        0,
    );

    #[cfg(test)]
    apply_filesystem_full_snapshot_read_mutation(&snapshot)?;

    for entry in snapshot.entries {
        let bytes =
            source_snapshot_bytes(&snapshot.root, snapshot.kind, &build.commit, &entry.path)?;
        ensure_filesystem_blobs_match_content_hashes(
            &build.commit,
            std::slice::from_ref(&entry.path),
            std::slice::from_ref(&bytes),
            &filesystem_path_hashes,
        )?;
        parse_indexed_file(&mut build, &entry.path, &bytes)?;
    }

    Ok(build.finish())
}

fn filesystem_full_snapshot_path_hashes(
    snapshot: &ScopedSourceSnapshot,
) -> Result<BTreeMap<String, String>, CodeIndexError> {
    if !snapshot.kind.is_filesystem() {
        return Ok(BTreeMap::new());
    }
    let paths = snapshot
        .entries
        .iter()
        .map(|entry| entry.path.clone())
        .collect::<Vec<_>>();
    let path_hashes = filesystem_content_hashes_for_paths(&snapshot.root, &paths)?;
    let tree_hash = filesystem_tree_hash_from_path_hashes(&path_hashes);
    if tree_hash != snapshot.tree_hash {
        return Err(CodeIndexError::InvalidInput(format!(
            "filesystem source snapshot {} no longer matches planned filesystem content {tree_hash}",
            snapshot.resolved_commit_sha
        )));
    }

    Ok(path_hashes)
}

#[cfg(test)]
fn apply_filesystem_full_snapshot_read_mutation(
    snapshot: &ScopedSourceSnapshot,
) -> Result<(), CodeIndexError> {
    if !snapshot.kind.is_filesystem() {
        return Ok(());
    }
    let mut mutation = FULL_SNAPSHOT_READ_MUTATION
        .lock()
        .expect("full snapshot mutation should lock");
    let Some(next) = mutation.take() else {
        return Ok(());
    };
    if next.root != snapshot.root {
        *mutation = Some(next);
        return Ok(());
    }
    fs::write(snapshot.root.join(next.path), next.content).map_err(CodeIndexError::Io)
}