use std::collections::BTreeSet;
use std::sync::atomic::{AtomicBool, Ordering};
use super::{AppliedFile, AppliedTree, FileOperation, Patch, TreeError, TreeLimits, TreeOptions};
use crate::index::{Entry, Index, Mode, Stage};
use crate::{EntryMode, Objects, Tree};
#[derive(Clone, Copy, Debug, Default)]
#[non_exhaustive]
pub struct IndexLimits {
pub index: crate::index::Limits,
pub tree: TreeLimits,
}
#[derive(Clone, Debug)]
pub struct AppliedIndex {
index: Index,
tree: AppliedTree,
}
impl AppliedIndex {
pub fn index(&self) -> &Index {
&self.index
}
pub fn into_index(self) -> Index {
self.index
}
pub fn tree(&self) -> &Tree {
self.tree.tree()
}
pub fn files(&self) -> &[AppliedFile] {
self.tree.files()
}
pub fn install_objects(&self, loose: &crate::LooseObjects) -> Result<(), TreeError> {
self.tree.install_objects(loose)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum IndexError {
#[error("preparing index patch")]
Tree(#[from] TreeError),
#[error("validating patch index")]
Index(#[from] crate::index::Error),
#[error("unsupported patch index entry")]
UnsupportedEntry {
path: Vec<u8>,
reason: &'static str,
},
}
impl Patch {
pub fn apply_index(
&self,
objects: &Objects,
index: &Index,
) -> Result<AppliedIndex, IndexError> {
self.apply_index_with(
objects,
index,
TreeOptions::default(),
IndexLimits::default(),
&AtomicBool::new(false),
)
}
pub fn apply_index_with(
&self,
objects: &Objects,
index: &Index,
options: TreeOptions,
limits: IndexLimits,
cancel: &AtomicBool,
) -> Result<AppliedIndex, IndexError> {
if cancel.load(Ordering::Relaxed) {
return Err(TreeError::Cancelled.into());
}
index.encoded_len(limits.index)?;
for entry in index.entries() {
if cancel.load(Ordering::Relaxed) {
return Err(TreeError::Cancelled.into());
}
let reason = if entry.stage != Stage::Normal {
Some("unmerged stage")
} else if entry.intent_to_add {
Some("intent-to-add placeholder")
} else if entry.id.is_null() {
Some("null object identity")
} else if entry.mode == Mode::SparseDirectory {
Some("sparse directory")
} else {
None
};
if let Some(reason) = reason {
return Err(IndexError::UnsupportedEntry {
path: entry.path.clone(),
reason,
});
}
}
let tree =
super::tree::apply_index_tree(self, objects, index, options, limits.tree, cancel)?;
let removals: BTreeSet<_> = self
.files()
.iter()
.zip(tree.files())
.filter(|(patch, _)| patch.operation() != FileOperation::Copy)
.filter_map(|(_, file)| file.source_path.as_deref())
.collect();
let mut entries = Vec::new();
for entry in index.entries() {
if cancel.load(Ordering::Relaxed) {
return Err(TreeError::Cancelled.into());
}
if !removals.contains(entry.path.as_slice()) {
entries.push(entry.clone());
}
}
for file in tree.files() {
if cancel.load(Ordering::Relaxed) {
return Err(TreeError::Cancelled.into());
}
if let Some(value) = file.new {
let mode = match value.mode {
EntryMode::Blob => Mode::Regular,
EntryMode::Executable => Mode::Executable,
EntryMode::Symlink => Mode::Symlink,
_ => return Err(TreeError::Mode.into()),
};
entries.push(Entry::new(file.path.clone(), mode, value.id));
}
}
let mut draft = index.clone();
draft.make_standalone(limits.index)?;
draft.replace_entries(entries, limits.index)?;
Ok(AppliedIndex { index: draft, tree })
}
}