AkurAI Build
Menu

AkurAI-Build

public

Latest change fe39ce51fc9c202d5411f39fd9a8a2feeca4a85e - Harden test suite: 16→179 hermetic tests, race-free integration harness by Ólafur Búi Ólafsson

use std::path::{Path, PathBuf};

use anyhow::{Context, Result, ensure};
use serde::Serialize;

use crate::config::validate_ref;

/// Maximum age (seconds) of a cached mirror before a fetch is forced.
const MIRROR_TTL: u64 = 30;
/// Bound the number of tree entries we resolve per-file commits for.
const MAX_ENTRIES: usize = 500;

#[derive(Clone)]
pub struct RepoTree {
    root: PathBuf,
}

#[derive(Serialize)]
pub struct TreeEntry {
    pub name: String,
    pub kind: String,
    pub size: u64,
    pub commit_message: Option<String>,
    pub commit_at: Option<i64>,
}

#[derive(Serialize)]
pub struct LatestCommit {
    pub sha: String,
    pub message: String,
    pub author: String,
    pub committed_at: i64,
}

#[derive(Serialize)]
pub struct RepoCounts {
    pub commits: u64,
    pub branches: u64,
    pub tags: u64,
}

#[derive(Serialize)]
pub struct TreeResponse {
    pub reference: String,
    pub path: String,
    pub entries: Vec<TreeEntry>,
    pub latest_commit: Option<LatestCommit>,
    pub counts: RepoCounts,
    pub readme: Option<String>,
    pub readme_name: Option<String>,
}

impl RepoTree {
    pub fn new(data_root: &Path) -> Result<Self> {
        let root = data_root.join("mirrors");
        std::fs::create_dir_all(&root)?;
        Ok(Self { root })
    }

    fn mirror_path(&self, repository: &str) -> PathBuf {
        self.root.join(format!("{repository}.git"))
    }

    /// Ensure a bare mirror exists and is reasonably fresh.
    fn sync_mirror(&self, repository: &str, url: &str) -> Result<PathBuf> {
        let path = self.mirror_path(repository);
        if path.join("HEAD").is_file() {
            let fresh = std::fs::metadata(path.join("FETCH_HEAD"))
                .ok()
                .and_then(|m| m.modified().ok())
                .and_then(|t| t.elapsed().ok())
                .is_some_and(|age| age.as_secs() < MIRROR_TTL);
            if !fresh {
                let status = git(&path, ["fetch", "--prune", "origin"])?;
                ensure!(status, "git fetch failed for {repository}");
            }
            return Ok(path);
        }
        std::fs::create_dir_all(&path)?;
        let parent = path
            .parent()
            .context("mirror path has no parent")?
            .to_path_buf();
        let name = path
            .file_name()
            .context("mirror path has no file name")?
            .to_string_lossy()
            .to_string();
        let status = git_output(
            &parent,
            ["clone", "--bare", "--filter=blob:none", url, &name],
        )
        .is_ok();
        ensure!(status, "git clone --bare failed for {repository}");
        Ok(path)
    }

    /// Resolve the default branch of the mirror.
    fn default_ref(&self, path: &Path) -> Result<String> {
        if let Ok(head) = git_output(path, ["symbolic-ref", "--short", "HEAD"])
            && !head.is_empty()
        {
            return Ok(head);
        }
        Ok("HEAD".to_string())
    }

    pub fn tree(
        &self,
        repository: &str,
        url: &str,
        reference: Option<&str>,
        sub_path: &str,
    ) -> Result<TreeResponse> {
        if let Some(reference) = reference {
            validate_ref(reference)?;
        }
        ensure!(!sub_path.contains(".."), "invalid tree path");
        let path = self.sync_mirror(repository, url)?;
        let reference = match reference {
            Some(value) => value.to_string(),
            None => self.default_ref(&path)?,
        };

        let spec = if sub_path.is_empty() {
            reference.clone()
        } else {
            format!("{reference}:{sub_path}")
        };
        let raw = git_output(&path, ["ls-tree", "--long", "-z", &spec])
            .with_context(|| format!("ls-tree failed for {repository}@{reference}"))?;
        let mut entries = parse_tree(&raw);
        entries.sort_by(|a, b| {
            (a.kind != "tree")
                .cmp(&(b.kind != "tree"))
                .then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))
        });

        // Per-file latest commit (bounded).
        for entry in entries.iter_mut().take(MAX_ENTRIES) {
            let entry_path = if sub_path.is_empty() {
                entry.name.clone()
            } else {
                format!("{sub_path}/{}", entry.name)
            };
            if let Ok(line) = git_output(
                &path,
                [
                    "log",
                    "-1",
                    "--format=%s%x00%ct",
                    &reference,
                    "--",
                    &entry_path,
                ],
            ) && let Some((message, ts)) = line.split_once('\0')
            {
                entry.commit_message = Some(message.trim().to_string());
                entry.commit_at = ts.trim().parse::<i64>().ok();
            }
        }

        let latest_commit = git_output(
            &path,
            ["log", "-1", "--format=%H%x00%s%x00%an%x00%ct", &reference],
        )
        .ok()
        .and_then(|line| parse_commit(&line));

        let counts = RepoCounts {
            commits: git_output(&path, ["rev-list", "--count", &reference])
                .ok()
                .and_then(|v| v.trim().parse().ok())
                .unwrap_or(0),
            branches: count_lines(&git_output(&path, ["branch", "--list"]).unwrap_or_default()),
            tags: count_lines(&git_output(&path, ["tag", "--list"]).unwrap_or_default()),
        };

        let (readme, readme_name) = read_readme(&path, &reference, sub_path);

        Ok(TreeResponse {
            reference,
            path: sub_path.to_string(),
            entries,
            latest_commit,
            counts,
            readme,
            readme_name,
        })
    }
}

fn parse_tree(raw: &str) -> Vec<TreeEntry> {
    // Each record: "<mode> <type> <sha> <size>\t<name>\0"
    raw.split('\0')
        .filter(|record| !record.is_empty())
        .filter_map(|record| {
            let (meta, name) = record.split_once('\t')?;
            let mut fields = meta.split_whitespace();
            let _mode = fields.next()?;
            let kind = fields.next()?;
            let _sha = fields.next()?;
            let size = fields.next().unwrap_or("0");
            Some(TreeEntry {
                name: name.to_string(),
                kind: kind.to_string(),
                size: size.parse().unwrap_or(0),
                commit_message: None,
                commit_at: None,
            })
        })
        .collect()
}

fn parse_commit(line: &str) -> Option<LatestCommit> {
    let mut fields = line.split('\0');
    let sha = fields.next()?.trim().to_string();
    let message = fields.next()?.trim().to_string();
    let author = fields.next()?.trim().to_string();
    let committed_at = fields.next()?.trim().parse().ok()?;
    if sha.is_empty() {
        return None;
    }
    Some(LatestCommit {
        sha,
        message,
        author,
        committed_at,
    })
}

fn read_readme(path: &Path, reference: &str, sub_path: &str) -> (Option<String>, Option<String>) {
    for candidate in ["README.md", "README.markdown", "README", "README.txt"] {
        let spec = if sub_path.is_empty() {
            format!("{reference}:{candidate}")
        } else {
            format!("{reference}:{sub_path}/{candidate}")
        };
        if let Ok(content) = git_output(path, ["show", &spec])
            && !content.is_empty()
        {
            return (Some(content), Some(candidate.to_string()));
        }
    }
    (None, None)
}

fn count_lines(value: &str) -> u64 {
    value.lines().filter(|l| !l.trim().is_empty()).count() as u64
}

fn git<const N: usize>(cwd: &Path, args: [&str; N]) -> Result<bool> {
    let status = crate::git_process::command(cwd).args(args).status()?;
    Ok(status.success())
}

fn git_output<const N: usize>(cwd: &Path, args: [&str; N]) -> Result<String> {
    crate::git_process::output(cwd, args)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_ls_tree_records() {
        let raw = "100644 blob abc123 42\tREADME.md\x0040000 tree def456 -\tsrc\x00";
        let entries = parse_tree(raw);
        assert_eq!(entries.len(), 2);
        assert_eq!(entries[0].name, "README.md");
        assert_eq!(entries[0].kind, "blob");
        assert_eq!(entries[0].size, 42);
        assert_eq!(entries[1].name, "src");
        assert_eq!(entries[1].kind, "tree");
    }

    #[test]
    fn parses_empty_output() {
        let entries = parse_tree("");
        assert!(entries.is_empty(), "empty output -> no entries");

        let entries = parse_tree("\x00");
        assert!(entries.is_empty(), "bare null -> no entries");
    }

    #[test]
    fn skips_malformed_records() {
        let raw = "bad-record\x00100644 blob abc123 42\tok.txt\x00no-tab-here\x00";
        let entries = parse_tree(raw);
        // Only the middle record has a \t separator; first and last should be skipped.
        assert_eq!(entries.len(), 1, "only one well-formed record");
        assert_eq!(entries[0].name, "ok.txt");
        assert_eq!(entries[0].kind, "blob");
    }

    #[test]
    fn handles_unicode_filenames() {
        let raw = b"100644 blob abc123 42\t\xF0\x9F\x93\xA6.md\x00";
        let raw_str = String::from_utf8_lossy(raw);
        let entries = parse_tree(&raw_str);
        assert_eq!(entries.len(), 1);
        assert!(
            entries[0].name.contains('\u{1F4E6}'),
            "unicode filename should be preserved"
        );
    }

    #[test]
    fn handles_large_sizes() {
        let raw = "100644 blob abc123 4294967296\tbig.bin\x00";
        let entries = parse_tree(raw);
        assert_eq!(entries.len(), 1);
        assert_eq!(
            entries[0].size, 4294967296,
            "large size should parse correctly"
        );
    }

    #[test]
    fn size_defaults_to_zero_for_submodules() {
        // Submodules show '-' for size in ls-tree --long.
        let raw = "160000 commit abc123 -\tsubmod\x00";
        let entries = parse_tree(raw);
        assert_eq!(entries.len(), 1);
        assert_eq!(entries[0].kind, "commit");
        assert_eq!(entries[0].size, 0, "submodule size '-' should default to 0");
    }

    #[test]
    fn parses_commit_line() {
        let commit =
            parse_commit("abc\0Initial commit\0Alice\x001700000000").expect("valid commit fixture");
        assert_eq!(commit.sha, "abc");
        assert_eq!(commit.message, "Initial commit");
        assert_eq!(commit.author, "Alice");
        assert_eq!(commit.committed_at, 1700000000);
    }

    #[test]
    fn rejects_empty_commit_sha() {
        assert!(parse_commit("\0msg\0author\x00123").is_none());
    }

    #[test]
    fn rejects_commit_with_missing_fields() {
        // Not enough null-delimited fields.
        assert!(parse_commit("sha\0msg").is_none());
        assert!(parse_commit("sha\x00msg\x00author").is_none());
    }

    #[test]
    fn rejects_commit_with_bad_timestamp() {
        assert!(
            parse_commit("sha\0msg\0author\0not-a-number").is_none(),
            "non-numeric timestamp should fail"
        );
    }
}