use std::collections::{HashSet, VecDeque};
use std::fs;
use std::path::Path;
use crate::config::ConfigSet;
use crate::error::Result;
use crate::objects::{parse_commit, parse_tag, parse_tree, ObjectId, ObjectKind};
use crate::pack;
use crate::repo::Repository;
pub const PROMISOR_MISSING_FILE: &str = "grit-promisor-missing";
#[must_use]
pub fn read_promisor_missing_oids(git_dir: &Path) -> Vec<ObjectId> {
let path = git_dir.join(PROMISOR_MISSING_FILE);
let content = match fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => return Vec::new(),
};
let mut seen = HashSet::new();
let mut out = Vec::new();
for line in content.lines() {
let t = line.trim();
if t.len() != 40 || !t.chars().all(|c| c.is_ascii_hexdigit()) {
continue;
}
if let Ok(oid) = ObjectId::from_hex(t) {
if seen.insert(oid) {
out.push(oid);
}
}
}
out
}
pub fn write_promisor_marker(git_dir: &Path, oids: &HashSet<ObjectId>) -> Result<()> {
let path = git_dir.join(PROMISOR_MISSING_FILE);
let mut v: Vec<String> = oids.iter().map(|o| o.to_hex()).collect();
v.sort();
if v.is_empty() {
fs::write(&path, b"")?;
} else {
fs::write(&path, format!("{}\n", v.join("\n")))?;
}
Ok(())
}
#[must_use]
pub fn repo_treats_promisor_packs(_git_dir: &Path, config: &ConfigSet) -> bool {
if config.get("extensions.partialclone").is_some() {
return true;
}
config
.entries()
.iter()
.any(|e| e.key.ends_with(".promisor") && e.value.as_deref() == Some("true"))
}
#[must_use]
pub fn promisor_pack_object_ids(objects_dir: &Path) -> HashSet<ObjectId> {
let Ok(indexes) = pack::read_local_pack_indexes(objects_dir) else {
return HashSet::new();
};
let mut ids = HashSet::new();
for idx in indexes {
let marker = idx.pack_path.with_extension("promisor");
if !marker.is_file() {
continue;
}
for e in idx.entries {
ids.insert(e.oid);
}
}
ids
}
pub fn promisor_expanded_object_ids(repo: &Repository) -> Result<HashSet<ObjectId>> {
let objects_dir = repo.git_dir.join("objects");
let seeds = promisor_pack_object_ids(&objects_dir);
let mut set: HashSet<ObjectId> = HashSet::new();
let mut queue: VecDeque<ObjectId> = seeds.iter().copied().collect();
while let Some(oid) = queue.pop_front() {
if !set.insert(oid) {
continue;
}
let obj = match repo.odb.read(&oid) {
Ok(o) => o,
Err(_) => continue,
};
match obj.kind {
ObjectKind::Commit => {
let c = parse_commit(&obj.data)?;
queue.push_back(c.tree);
for p in c.parents {
queue.push_back(p);
}
}
ObjectKind::Tag => {
let t = parse_tag(&obj.data)?;
queue.push_back(t.object);
}
ObjectKind::Tree => {
let entries = parse_tree(&obj.data)?;
for e in entries {
if e.mode != 0o160000 {
queue.push_back(e.oid);
}
}
}
ObjectKind::Blob => {}
}
}
for oid in read_promisor_missing_oids(&repo.git_dir) {
set.insert(oid);
}
Ok(set)
}