use std::collections::HashMap;
use znippy_common::arrow::datatypes::{DataType, Field};
use znippy_common::plugin::{
ArchiveTypePlugin, ExtensionRow, ExtensionValue, HandlerCommand, HandlerMeta,
};
#[cfg(feature = "bench-kernels")]
pub mod bench_kernels;
pub mod archive_write;
pub(crate) mod archive_map;
pub mod arms;
pub mod graph;
pub mod index_layout;
pub mod indexer;
pub mod object;
pub mod oid_index;
pub mod delta;
pub mod pack_walk;
pub mod pushlog;
pub mod read_stack;
pub mod resolve;
pub mod exploded;
pub mod exploded_arrow;
pub mod reach;
pub mod refs;
pub mod replicate;
pub mod secrets;
pub mod store;
pub mod gc;
pub mod git_ops;
pub mod serve;
pub mod git_oracle;
pub mod uring_write;
pub mod sections;
pub use archive_write::{ArchiveWrite, Faults, FastWriter, SafeWriter};
pub use arms::{
ALL_ENV, DEFAULT_REDB_CACHE_BYTES, ENV_BOUNDARY_DELTA, ENV_EMIT_WORKERS, ENV_EXPLODE, ENV_GC,
ENV_INDEX, ENV_REACH_COMMITS, ENV_REDB_CACHE, ENV_WRITER, GcArm, IndexArm, StoreConfig,
WriterArm, env_reads, env_reads_here, redb_cache_bytes,
};
pub use indexer::{AccountIndexer, IndexJob, IndexRow, IndexerPool, Lookup, PushPath};
#[cfg(target_os = "linux")]
pub use uring_write::UringWriter;
pub use graph::{CommitNode, decode_graph, graph_schema, read_graph};
pub use object::{
GitHashKind, GitObject, GitObjectKind, PACKFILE_TYPE, PACK_INDEX_TYPE, PackFileKind, canonical,
is_oid_path, pack_path_kind, parse_canonical, parse_commit, tree_entries,
};
pub use oid_index::{GitOidIndex, OidEntry, OidHit, OidLayout, key_for_oid};
pub use pushlog::{
CompactionPolicy, CompactionReport, Finish, PushLog, PushLogScan, scan_frames,
};
pub use reach::{ReachEntry, ReachPolicy, decode_reach, read_reach, reach_schema};
pub use refs::{RefLog, RefState, RefUpdate, read_refs, refs_schema};
pub use secrets::{SecretState, SecretUpdate, SecretsLog, read_secrets, secrets_schema};
pub use sections::GitIndexBuilder;
pub use git_ops::{
GitOps, GitStore, LookupPath, Oid, RefRow, SelectedStore, Stored, TxId, lookup_path,
open_from_env, open_selected,
};
pub use serve::{Caps, GitServe, PackStats, ReachSet, HEAD};
pub use git_storage_trait::{Observed, RefCas, RefRejection, RefTarget};
pub const GIT_TYPE_ID: i8 = 42;
pub const UNKNOWN_OBJECT_TYPE: &str = "unknown";
pub struct NativeGitPlugin;
impl NativeGitPlugin {
pub fn new() -> Self {
NativeGitPlugin
}
}
impl Default for NativeGitPlugin {
fn default() -> Self {
Self::new()
}
}
impl ArchiveTypePlugin for NativeGitPlugin {
fn name(&self) -> &str {
"git"
}
fn type_id(&self) -> i8 {
GIT_TYPE_ID
}
fn meta(&self) -> HandlerMeta {
HandlerMeta {
name: "git".into(),
aliases: vec!["gunnar".into(), "git-objects".into()],
type_id: GIT_TYPE_ID,
ecosystem: "Git object store (gunnar cold tier — one archive per repository)".into(),
extensions: Vec::new(),
description: "Stores a git repository: either canonical objects keyed by oid hex, \
with the reserved __gunnar_oid__ (stree) / __gunnar_graph__ (commit \
graph) / __gunnar_reach__ (reachability bitmaps) sub-indexes, or the \
pack tier (pack-<id>.pack / .idx) that preserves the client's deflate \
and its delta chains"
.into(),
commands: vec![
HandlerCommand::new(
"inspect",
"Print type/size/sha1/sha256 for a file of canonical git object bytes",
),
HandlerCommand::new(
"lookup",
"Resolve an oid against an archive's __gunnar_oid__ index: `git lookup <archive> <oid>`",
),
HandlerCommand::new(
"graph",
"Print an archive's commit graph (oid, generation, time, parents)",
),
HandlerCommand::new(
"refs",
"Print a live store's ref namespace: `git refs <store-root>`",
),
],
}
}
fn run_command(&self, cmd: &str, args: &[String]) -> anyhow::Result<()> {
match cmd {
"refs" => {
let root = args
.first()
.ok_or_else(|| anyhow::anyhow!("usage: git refs <store-root>"))?;
let store = GitStore::open(std::path::Path::new(root), "cli")?;
for r in store.refs()? {
println!(
"{}\t{}{}",
r.name,
r.oid.as_deref().map(hex::encode).unwrap_or_else(|| "-".into()),
match (&r.peeled, &r.symref_target) {
(Some(p), _) => format!("\t^{}", hex::encode(p)),
(None, Some(t)) => format!("\t-> {t}"),
(None, None) => String::new(),
}
);
}
Ok(())
}
"inspect" => {
let path = args
.first()
.ok_or_else(|| anyhow::anyhow!("usage: git inspect <object-file>"))?;
let data = std::fs::read(path)?;
match parse_canonical(&data) {
Some(obj) => {
println!("type: {}", obj.kind.as_str());
println!("size: {}", obj.payload.len());
println!("sha1: {}", GitHashKind::Sha1.oid_hex_of(&data));
println!("sha256: {}", GitHashKind::Sha256.oid_hex_of(&data));
Ok(())
}
None => anyhow::bail!(
"'{path}' is not canonical git object bytes (`<type> <size>\\0<content>`)"
),
}
}
"lookup" => {
let (archive, oid) = match (args.first(), args.get(1)) {
(Some(a), Some(o)) => (a, o),
_ => anyhow::bail!("usage: git lookup <archive> <oid-hex>"),
};
let index = GitOidIndex::open(std::path::Path::new(archive))?.ok_or_else(|| {
anyhow::anyhow!("'{archive}' carries no __gunnar_oid__ index")
})?;
match index.lookup_hex(oid) {
Some(hit) => {
println!("oid: {oid}");
println!("lookup_row: {}", hit.lookup_row);
println!("ordinal: {}", hit.ordinal);
Ok(())
}
None => anyhow::bail!("oid '{oid}' is not in '{archive}'"),
}
}
"graph" => {
let archive = args
.first()
.ok_or_else(|| anyhow::anyhow!("usage: git graph <archive>"))?;
let nodes = read_graph(std::path::Path::new(archive))?.ok_or_else(|| {
anyhow::anyhow!("'{archive}' carries no __gunnar_graph__ section")
})?;
println!("commits: {}", nodes.len());
for n in &nodes {
println!(
" {} gen={} time={} parents=[{}]",
n.oid,
n.generation,
n.committer_time.map(|t| t.to_string()).unwrap_or_else(|| "-".into()),
n.parents.join(" ")
);
}
Ok(())
}
other => anyhow::bail!("git: unknown subcommand '{other}'"),
}
}
fn matches_path(&self, path: &str) -> bool {
is_oid_path(path) || pack_path_kind(path).is_some()
}
fn schema_fields(&self) -> Vec<Field> {
vec![
Field::new("object_type", DataType::Utf8, true),
Field::new("object_size", DataType::UInt32, true),
]
}
fn extract_metadata(&self, path: &str, data: &[u8]) -> Option<ExtensionRow> {
if !self.matches_path(path) {
return None;
}
let mut f: HashMap<String, ExtensionValue> = HashMap::new();
if let Some(kind) = pack_path_kind(path) {
let typed = if data.starts_with(kind.magic()) {
kind.as_str()
} else {
UNKNOWN_OBJECT_TYPE
};
f.insert("object_type".into(), ExtensionValue::Str(typed.into()));
f.insert(
"object_size".into(),
ExtensionValue::U32(data.len().min(u32::MAX as usize) as u32),
);
return Some(ExtensionRow { fields: f });
}
match parse_canonical(data) {
Some(obj) => {
f.insert("object_type".into(), ExtensionValue::Str(obj.kind.as_str().into()));
f.insert(
"object_size".into(),
ExtensionValue::U32(obj.payload.len().min(u32::MAX as usize) as u32),
);
}
None => {
f.insert(
"object_type".into(),
ExtensionValue::Str(UNKNOWN_OBJECT_TYPE.into()),
);
f.insert(
"object_size".into(),
ExtensionValue::U32(data.len().min(u32::MAX as usize) as u32),
);
}
}
Some(ExtensionRow { fields: f })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::object::canonical;
fn get<'a>(row: &'a ExtensionRow, k: &str) -> Option<&'a ExtensionValue> {
row.fields.get(k)
}
#[test]
fn claims_oid_paths_and_nothing_else() {
let p = NativeGitPlugin::new();
assert!(p.matches_path(&"a1b2c3d4".repeat(5))); assert!(p.matches_path(&"0f".repeat(32))); assert!(!p.matches_path("refs/heads/main"));
assert!(!p.matches_path("objects/pack/pack-abc.pack"));
assert!(!p.matches_path(&"A1B2C3D4".repeat(5)));
}
#[test]
fn claims_the_pack_tier_by_its_whole_name_not_by_a_suffix() {
let p = NativeGitPlugin::new();
let id40 = "a1b2c3d4".repeat(5);
let id64 = "0f".repeat(32);
assert_eq!(pack_path_kind(&format!("pack-{id40}.pack")), Some(PackFileKind::Data));
assert_eq!(pack_path_kind(&format!("pack-{id64}.idx")), Some(PackFileKind::Index));
assert!(p.matches_path(&format!("pack-{id64}.pack")));
assert!(p.matches_path(&format!("objects/pack/pack-{id40}.idx")));
for not_ours in [
"pack-nothex.pack",
"somefile.pack",
"pack-.pack",
&format!("pack-{id40}.bitmap"),
&format!("pack-{}.pack", "A1B2C3D4".repeat(5)),
&format!("{id40}.pack"),
] {
assert_eq!(pack_path_kind(not_ours), None, "wrongly claimed {not_ours}");
}
assert!(!p.matches_path("somefile.pack"));
}
#[test]
fn a_pack_is_typed_by_its_magic_and_a_liar_is_unknown() {
let p = NativeGitPlugin::new();
let id = "0f".repeat(32);
let mut pack = b"PACK".to_vec();
pack.extend_from_slice(&2u32.to_be_bytes());
pack.extend_from_slice(&7u32.to_be_bytes());
let row = p.extract_metadata(&format!("pack-{id}.pack"), &pack).unwrap();
assert_eq!(get(&row, "object_type"), Some(&ExtensionValue::Str(PACKFILE_TYPE.into())));
assert_eq!(get(&row, "object_size"), Some(&ExtensionValue::U32(pack.len() as u32)));
let idx = b"\xfftOc\x00\x00\x00\x02".to_vec();
let row = p.extract_metadata(&format!("pack-{id}.idx"), &idx).unwrap();
assert_eq!(get(&row, "object_type"), Some(&ExtensionValue::Str(PACK_INDEX_TYPE.into())));
for (name, bytes) in [
(format!("pack-{id}.pack"), &b"NOTAPACK"[..]),
(format!("pack-{id}.idx"), &b"PACK\0\0\0\x02"[..]),
(format!("pack-{id}.pack"), &b""[..]),
] {
let row = p.extract_metadata(&name, bytes).expect("a claimed path always yields a row");
assert_eq!(
get(&row, "object_type"),
Some(&ExtensionValue::Str(UNKNOWN_OBJECT_TYPE.into())),
"{name} carries {bytes:?}, which is not that kind of file — it must not be typed \
from its name"
);
}
}
#[test]
fn types_and_sizes_come_from_the_stored_bytes() {
let p = NativeGitPlugin::new();
let body = b"hello world";
let bytes = canonical(GitObjectKind::Blob, body);
let oid = GitHashKind::Sha256.oid_hex_of(&bytes);
let row = p.extract_metadata(&oid, &bytes).unwrap();
assert_eq!(get(&row, "object_type"), Some(&ExtensionValue::Str("blob".into())));
assert_eq!(get(&row, "object_size"), Some(&ExtensionValue::U32(body.len() as u32)));
let commit = canonical(GitObjectKind::Commit, b"tree x\n\nmsg\n");
let coid = GitHashKind::Sha1.oid_hex_of(&commit);
let crow = p.extract_metadata(&coid, &commit).unwrap();
assert_eq!(get(&crow, "object_type"), Some(&ExtensionValue::Str("commit".into())));
}
#[test]
fn garbage_degrades_to_unknown_and_never_panics() {
let p = NativeGitPlugin::new();
let oid = "d".repeat(64);
for bad in [&b""[..], &b"\0\0\0"[..], &[0xffu8; 4096][..], &b"blob 99\0short"[..]] {
let row = p.extract_metadata(&oid, bad).expect("a claimed path always yields a row");
assert_eq!(
get(&row, "object_type"),
Some(&ExtensionValue::Str(UNKNOWN_OBJECT_TYPE.into())),
"unparseable bytes must be typed `unknown`, never guessed"
);
assert_eq!(get(&row, "object_size"), Some(&ExtensionValue::U32(bad.len() as u32)));
}
}
#[test]
fn a_path_that_is_not_an_oid_yields_no_row() {
let p = NativeGitPlugin::new();
assert!(p.extract_metadata("HEAD", b"ref: refs/heads/main\n").is_none());
}
#[test]
fn meta_is_the_discovery_record() {
let m = NativeGitPlugin::new().meta();
assert_eq!(m.name, "git");
assert_eq!(m.type_id, GIT_TYPE_ID);
assert!(m.aliases.contains(&"gunnar".to_string()));
assert_eq!(m.commands.len(), 4);
assert!(
m.commands.iter().any(|c| c.name == "refs"),
"the GitOps-delegating command is not advertised"
);
}
}