use crate::candidate_error::{CandidateError, CandidateFailure};
use crate::candidate_object::{ObjectId, parse_object_id};
use crate::candidate_pathspec_chunk_size::PATHSPEC_CHUNK_SIZE;
use crate::candidate_record::{CandidateRecord, parse_raw_records};
use crate::candidate_store::{CandidateStore, listing_failure};
use crate::candidate_tree::{TreeRecord, parse_tree_records};
use std::collections::HashSet;
use std::ffi::OsString;
static LISTED: std::sync::Mutex<Option<(ObjectId, std::sync::Arc<Vec<TreeRecord>>)>> = std::sync::Mutex::new(None);
const COMMIT_CHUNK_SIZE: usize = PATHSPEC_CHUNK_SIZE;
impl CandidateStore {
pub fn head_tree_records(&mut self) -> Result<Vec<TreeRecord>, CandidateError> {
let Some(head) = self.baseline_commit()? else {
return Ok(Vec::new());
};
return self.commit_tree_records(&head);
}
pub fn commit_tree_records(
&mut self,
commit: &ObjectId,
) -> Result<Vec<TreeRecord>, CandidateError> {
let kept: Option<(ObjectId, std::sync::Arc<Vec<TreeRecord>>)> = match LISTED.lock() {
Ok(held) => held.clone(),
Err(_) => None,
};
if let Some((listed, records)) = kept
&& listed == *commit
{
return Ok(records.as_ref().clone());
}
let records: Vec<TreeRecord> = self.list_commit_tree(commit)?;
if let Ok(mut held) = LISTED.lock() {
*held = Some((commit.clone(), std::sync::Arc::new(records.clone())));
}
return Ok(records);
}
fn list_commit_tree(&mut self, commit: &ObjectId) -> Result<Vec<TreeRecord>, CandidateError> {
let output: Vec<u8> = self.run_listing(
"ls-tree",
&[
OsString::from("ls-tree"),
OsString::from("-r"),
OsString::from("-z"),
OsString::from("--full-tree"),
OsString::from(commit.as_str()),
],
)?;
return parse_tree_records(output.as_slice());
}
pub fn parents_outside(
&mut self,
commits: &[ObjectId],
) -> Result<Vec<ObjectId>, CandidateError> {
let given: HashSet<&ObjectId> = commits.iter().collect();
let mut seen: HashSet<ObjectId> = HashSet::new();
let mut outside: Vec<ObjectId> = Vec::new();
for chunk in commits.chunks(COMMIT_CHUNK_SIZE) {
let mut command: Vec<OsString> = vec![
OsString::from("rev-list"),
OsString::from("--no-walk=unsorted"),
OsString::from("--parents"),
];
for commit in chunk {
command.push(OsString::from(commit.as_str()));
}
let output: Vec<u8> = self.run_listing("rev-list", command.as_slice())?;
for line in output.split(is_line_feed_byte) {
for name in line.split(is_space_byte).skip(1) {
let Some(parent) = parse_object_id(name) else {
return Err(listing_failure(
CandidateFailure::ListingMalformed,
"rev-list",
"printed a parent that is not an object name.",
));
};
if !given.contains(&parent) && seen.insert(parent.clone()) {
outside.push(parent);
}
}
}
}
return Ok(outside);
}
pub fn committed_records(
&mut self,
commit: &ObjectId,
) -> Result<Vec<CandidateRecord>, CandidateError> {
let output: Vec<u8> = self.list_committed(commit)?;
return parse_raw_records(output.as_slice());
}
pub fn tree_records(
&mut self,
tree: &ObjectId,
) -> Result<Vec<CandidateRecord>, CandidateError> {
let listing: Vec<u8> = self.run_listing(
"ls-tree",
&[
OsString::from("ls-tree"),
OsString::from("--full-tree"),
OsString::from("-r"),
OsString::from("-z"),
OsString::from(tree.as_str()),
],
)?;
let raw: Vec<u8> = added_raw_from_ls_tree(listing.as_slice())?;
return parse_raw_records(raw.as_slice());
}
}
fn is_line_feed_byte(byte: &u8) -> bool {
return *byte == b'\n';
}
fn is_space_byte(byte: &u8) -> bool {
return *byte == b' ';
}
fn is_nul_byte(byte: &u8) -> bool {
return *byte == 0;
}
fn is_tab_byte(byte: &u8) -> bool {
return *byte == b'\t';
}
fn added_raw_from_ls_tree(output: &[u8]) -> Result<Vec<u8>, CandidateError> {
let mut raw: Vec<u8> = Vec::new();
for record in output.split(is_nul_byte) {
if record.is_empty() {
continue;
}
let Some(tab) = record.iter().position(is_tab_byte) else {
return Err(CandidateError::new(
crate::candidate_error::CandidateFailure::ListingMalformed,
"monochromatic-git-policy-cli could not list the pushed tree: an entry lacks its path separator.",
));
};
let metadata = String::from_utf8_lossy(&record[..tab]);
let path: &[u8] = &record[tab + 1..];
let fields: Vec<&str> = metadata.split(' ').collect();
if fields.len() != 3 {
return Err(CandidateError::new(
crate::candidate_error::CandidateFailure::ListingMalformed,
"monochromatic-git-policy-cli could not list the pushed tree: an entry lacks its mode, type or object name.",
));
}
if fields[1] != "blob" {
continue;
}
let zeros = "0".repeat(fields[2].len());
raw.extend_from_slice(format!(":000000 {} {zeros} {} A", fields[0], fields[2]).as_bytes());
raw.push(0);
raw.extend_from_slice(path);
raw.push(0);
}
return Ok(raw);
}