use crate::candidate_error::CandidateError;
use crate::candidate_object::{CandidateMode, ObjectId};
use crate::candidate_prediction::CandidateRequest;
use crate::candidate_store::CandidateStore;
use crate::candidate_version::{Candidate, CandidateVersion};
use crate::direct_fix_install::InstallChange;
use crate::scanner_selection::is_scannable;
use std::collections::{BTreeMap, HashSet};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ContentBasis {
Nothing,
Baseline,
ParentsOutside(
Vec<ObjectId>,
),
}
impl ContentBasis {
pub fn of(request: &CandidateRequest) -> ContentBasis {
match request {
CandidateRequest::Direct(_) => return ContentBasis::Nothing,
CandidateRequest::Add(_)
| CandidateRequest::Staged(_)
| CandidateRequest::StagedAtBase { .. } => return ContentBasis::Baseline,
CandidateRequest::Landed(commit) => {
return ContentBasis::ParentsOutside(vec![commit.clone()]);
}
CandidateRequest::Pushed(scope) => {
if !scope.trees.is_empty() {
return ContentBasis::Nothing;
}
return ContentBasis::ParentsOutside(scope.commits.clone());
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KnownEntry {
pub object: ObjectId,
pub parent: ObjectId,
}
impl KnownEntry {
pub fn describe(&self, display_path: &str) -> String {
return format!(
"skipped the content of {display_path}: blob {} is already in the tree of commit {}; only the pathname was checked.",
self.object.as_str(),
self.parent.as_str()
);
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct KnownContent {
entries: BTreeMap<usize, KnownEntry>,
}
impl KnownContent {
pub fn entry(&self, index: usize) -> Option<&KnownEntry> {
return self.entries.get(&index);
}
pub fn len(&self) -> usize {
return self.entries.len();
}
pub fn is_empty(&self) -> bool {
return self.entries.is_empty();
}
}
fn is_comparable(
candidate: &Candidate,
rules_paths: &[Vec<u8>],
corrected: &BTreeMap<Vec<u8>, InstallChange>,
) -> bool {
return is_scannable(candidate, rules_paths)
&& candidate.object.is_some()
&& candidate.mode != CandidateMode::Gitlink
&& !corrected.contains_key(&candidate.path);
}
pub fn find_known_content(
store: &mut CandidateStore,
version: &CandidateVersion,
basis: &ContentBasis,
rules_paths: &[Vec<u8>],
corrected: &BTreeMap<Vec<u8>, InstallChange>,
) -> Result<KnownContent, CandidateError> {
let mut pending: Vec<&Candidate> = Vec::new();
for candidate in version.candidates() {
if is_comparable(candidate, rules_paths, corrected) {
pending.push(candidate);
}
}
let mut entries: BTreeMap<usize, KnownEntry> = BTreeMap::new();
if pending.is_empty() {
return Ok(KnownContent { entries });
}
let parents: Vec<ObjectId> = match basis {
ContentBasis::Nothing => Vec::new(),
ContentBasis::Baseline => store.baseline_reference()?.into_iter().collect(),
ContentBasis::ParentsOutside(commits) => store.parents_outside(commits.as_slice())?,
};
for parent in parents {
if pending.is_empty() {
break;
}
let mut blobs: HashSet<ObjectId> = HashSet::new();
for record in store.commit_tree_records(&parent)? {
if record.mode != CandidateMode::Gitlink {
blobs.insert(record.object);
}
}
let mut unplaced: Vec<&Candidate> = Vec::new();
for candidate in pending {
match candidate.object.as_ref() {
Some(object) if blobs.contains(object) => {
entries.insert(
candidate.identity.index,
KnownEntry {
object: object.clone(),
parent: parent.clone(),
},
);
}
Some(_) | None => unplaced.push(candidate),
}
}
pending = unplaced;
}
return Ok(KnownContent { entries });
}
#[cfg(test)]
#[path = "candidate_known_content_tests.rs"]
mod tests;