use crate::candidate_error::CandidateError;
use crate::candidate_object::{CandidateMode, ObjectId};
use crate::candidate_record::{CandidateChange, CandidateRecord};
use crate::candidate_store::CandidateStore;
use crate::candidate_version::{Candidate, CandidateVersion};
use crate::dependent_version_content::{
ContentUnavailable, TrackedMode, TrackedPath, WorkspaceContent,
};
use crate::direct_fix_install::InstallChange;
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::rc::Rc;
const UNMERGED_REASON: &str =
"is an unmerged (conflicted) index entry; resolve the conflict and stage the result";
const UNLISTED_REASON: &str = "is a candidate that the listing of tracked files does not hold, so the listing cannot be trusted";
const UNTRACKED_REASON: &str = "is not a tracked file of the candidate state";
pub struct LifecycleWorkspace<'a> {
store: &'a mut CandidateStore,
corrected: &'a BTreeMap<Vec<u8>, InstallChange>,
candidates: &'a [Vec<u8>],
index: Option<Rc<CandidateVersion>>,
head: Option<Rc<CandidateVersion>>,
}
pub fn lifecycle_workspace<'a>(
store: &'a mut CandidateStore,
corrected: &'a BTreeMap<Vec<u8>, InstallChange>,
candidates: &'a [Vec<u8>],
) -> LifecycleWorkspace<'a> {
return LifecycleWorkspace {
store,
corrected,
candidates,
index: None,
head: None,
};
}
fn unavailable(path: Option<&[u8]>, error: &CandidateError) -> ContentUnavailable {
return ContentUnavailable {
path: path.map(<[u8]>::to_vec),
reason: error.message.clone(),
};
}
pub fn tracked_mode(mode: CandidateMode) -> TrackedMode {
match mode {
CandidateMode::Regular => return TrackedMode::Regular,
CandidateMode::Executable => return TrackedMode::Executable,
CandidateMode::Symlink => return TrackedMode::Symlink,
CandidateMode::Gitlink => return TrackedMode::Submodule,
}
}
fn entry_record(path: &[u8], mode: CandidateMode, object: &ObjectId) -> CandidateRecord {
return CandidateRecord {
path: path.to_vec(),
mode,
change: CandidateChange::Modified,
object: Some(object.clone()),
};
}
impl LifecycleWorkspace<'_> {
fn index(&mut self) -> Result<Rc<CandidateVersion>, ContentUnavailable> {
if let Some(listed) = &self.index {
return Ok(Rc::clone(listed));
}
let entries = match self.store.stage_records(&[OsString::from(":/")]) {
Ok(found) => found,
Err(error) => return Err(unavailable(None, &error)),
};
let mut records: Vec<CandidateRecord> = Vec::new();
for entry in &entries {
if entry.stage != 0 {
return Err(ContentUnavailable {
path: Some(entry.path.clone()),
reason: String::from(UNMERGED_REASON),
});
}
records.push(entry_record(&entry.path, entry.mode, &entry.object));
}
let version: Rc<CandidateVersion> = self.store.derived_version(records);
for path in self.candidates {
if version.candidate_at_path(path).is_none() {
return Err(ContentUnavailable {
path: Some(path.clone()),
reason: String::from(UNLISTED_REASON),
});
}
}
self.index = Some(Rc::clone(&version));
return Ok(version);
}
fn head(&mut self) -> Result<Rc<CandidateVersion>, ContentUnavailable> {
if let Some(listed) = &self.head {
return Ok(Rc::clone(listed));
}
let entries = match self.store.head_tree_records() {
Ok(found) => found,
Err(error) => return Err(unavailable(None, &error)),
};
let mut records: Vec<CandidateRecord> = Vec::new();
for entry in &entries {
records.push(entry_record(&entry.path, entry.mode, &entry.object));
}
let version: Rc<CandidateVersion> = self.store.derived_version(records);
self.head = Some(Rc::clone(&version));
return Ok(version);
}
pub fn index_entry(
&mut self,
path: &[u8],
) -> Result<Option<(CandidateMode, ObjectId)>, ContentUnavailable> {
let version: Rc<CandidateVersion> = self.index()?;
return Ok(entry_of(&version, path));
}
pub fn head_entry(
&mut self,
path: &[u8],
) -> Result<Option<(CandidateMode, ObjectId)>, ContentUnavailable> {
let version: Rc<CandidateVersion> = self.head()?;
return Ok(entry_of(&version, path));
}
fn entry_bytes(&mut self, candidate: &Candidate) -> Result<Vec<u8>, ContentUnavailable> {
match self.store.bytes(candidate) {
Ok(read) => return Ok(read.to_vec()),
Err(error) => return Err(unavailable(Some(candidate.path.as_slice()), &error)),
}
}
}
fn entry_of(version: &CandidateVersion, path: &[u8]) -> Option<(CandidateMode, ObjectId)> {
let candidate: &Candidate = version.candidate_at_path(path)?;
let object: &ObjectId = candidate.object.as_ref()?;
return Some((candidate.mode, object.clone()));
}
impl WorkspaceContent for LifecycleWorkspace<'_> {
fn tracked_paths(&mut self) -> Result<Vec<TrackedPath>, ContentUnavailable> {
let version: Rc<CandidateVersion> = self.index()?;
let mut paths: Vec<TrackedPath> = Vec::new();
for candidate in version.candidates() {
paths.push(TrackedPath {
path: candidate.path.clone(),
mode: tracked_mode(candidate.mode),
});
}
return Ok(paths);
}
fn candidate_bytes(&mut self, path: &[u8]) -> Result<Vec<u8>, ContentUnavailable> {
if let Some(found) = self.corrected.get(path) {
return Ok(found.replacement.to_vec());
}
let version: Rc<CandidateVersion> = self.index()?;
let Some(candidate) = version.candidate_at_path(path) else {
return Err(ContentUnavailable {
path: Some(path.to_vec()),
reason: String::from(UNTRACKED_REASON),
});
};
return self.entry_bytes(candidate);
}
fn base_bytes(&mut self, path: &[u8]) -> Result<Option<Vec<u8>>, ContentUnavailable> {
let version: Rc<CandidateVersion> = self.head()?;
let Some(candidate) = version.candidate_at_path(path) else {
return Ok(None);
};
return Ok(Some(self.entry_bytes(candidate)?));
}
}
#[cfg(test)]
#[path = "dependent_version_lifecycle_tests.rs"]
mod tests;