jacquardSnapshot

← snapshot

2871 bytes
//! The git-commit-shaped image a snapshot exports to.

use core::fmt;

use jac_core::{BlobId, RepoPath, SnapshotId};

/// Trailer keys jacquard writes onto exported commits.
///
/// The vocabulary a knowledge layer greps for: because these are ordinary
/// git trailers on ordinary commits, anything that already crawls the repo —
/// blame tooling, enterprise search, an AI reviewer citing prior decisions —
/// indexes jacquard's provenance for free.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum TrailerKey {
    /// `Jacquard-Provenance`: human | agent | mixed.
    Provenance,
    /// `Jacquard-Author`: `human:hum-…` or `agent:agt-…`.
    Author,
    /// `Jacquard-Timestamp`: milliseconds since the epoch.
    Timestamp,
    /// `Jacquard-Decision`: a decision id this change was made under.
    Decision,
}

impl TrailerKey {
    /// The literal trailer key.
    #[must_use]
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Provenance => "Jacquard-Provenance",
            Self::Author => "Jacquard-Author",
            Self::Timestamp => "Jacquard-Timestamp",
            Self::Decision => "Jacquard-Decision",
        }
    }
}

impl fmt::Display for TrailerKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

/// What a snapshot looks like as a git commit.
///
/// The manifest flattens the tree to `(path, blob)` pairs — how a real
/// adapter would drive `git fast-import` — and the trailers carry everything
/// git's own commit format has no field for. If a datum is not in this
/// struct, the bridge does not preserve it; that is the point of modelling
/// the image explicitly.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GitCommitImage {
    /// Every file in the snapshot, as full path plus blob id, sorted.
    pub manifest: Vec<(RepoPath, BlobId)>,
    /// Parent snapshots.
    pub parents: Vec<SnapshotId>,
    /// The commit message, without trailers.
    pub message: String,
    /// The jacquard trailers.
    pub trailers: Vec<(TrailerKey, String)>,
}

impl GitCommitImage {
    /// The first value for a trailer key, if present.
    #[must_use]
    pub fn trailer(&self, key: TrailerKey) -> Option<&str> {
        self.trailers
            .iter()
            .find(|(k, _)| *k == key)
            .map(|(_, v)| v.as_str())
    }

    /// Renders the full commit message with trailers appended, as a real
    /// adapter would write it.
    #[must_use]
    pub fn full_message(&self) -> String {
        use core::fmt::Write as _;
        let mut out = self.message.clone();
        if !self.trailers.is_empty() {
            out.push_str("\n\n");
            for (key, value) in &self.trailers {
                let _ = writeln!(out, "{key}: {value}");
            }
        }
        out
    }
}