use super::{Index, Mode, Stage};
use crate::{EntryMode, ObjectId, Tree, TreeEntry};
impl Index {
pub fn tree_id(&self) -> Option<ObjectId> {
let mut directories = vec![Directory {
name: &[],
entries: Vec::new(),
}];
for entry in &self.entries {
if entry.stage != Stage::Normal {
return None;
}
if entry.intent_to_add {
continue;
}
if entry.id.is_null() {
return None;
}
let path = entry.path.strip_suffix(b"/").unwrap_or(&entry.path);
let mut components: Vec<_> = path.split(|&b| b == b'/').collect();
let name = components.pop()?;
let common = directories
.iter()
.skip(1)
.zip(&components)
.take_while(|(directory, component)| directory.name == **component)
.count();
while directories.len() > common + 1 {
let tree = directories.pop()?.finish(self.format)?;
directories.last_mut()?.entries.push(tree);
}
for &name in &components[common..] {
directories.push(Directory {
name,
entries: Vec::new(),
});
}
let mode = match entry.mode {
Mode::Regular => EntryMode::Blob,
Mode::Executable => EntryMode::Executable,
Mode::Symlink => EntryMode::Symlink,
Mode::Gitlink => EntryMode::Gitlink,
Mode::SparseDirectory => EntryMode::Tree,
};
directories.last_mut()?.entries.push(TreeEntry {
name: name.to_vec(),
mode,
id: entry.id,
});
}
while directories.len() > 1 {
let tree = directories.pop()?.finish(self.format)?;
directories.last_mut()?.entries.push(tree);
}
Some(directories.pop()?.finish(self.format)?.id)
}
}
struct Directory<'a> {
name: &'a [u8],
entries: Vec<TreeEntry>,
}
impl Directory<'_> {
fn finish(self, format: crate::ObjectFormat) -> Option<TreeEntry> {
let tree = Tree::new(format, self.entries).ok()?;
Some(TreeEntry {
name: self.name.to_vec(),
mode: EntryMode::Tree,
id: tree.id(),
})
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::ObjectFormat;
use crate::index::{Entry, Limits};
#[rstest]
#[case::unmerged(Stage::Ours, ObjectId::for_blob(ObjectFormat::Sha1, b"x"))]
#[case::null(Stage::Normal, ObjectId::null(ObjectFormat::Sha1))]
fn incomplete_index_has_no_tree_identity(#[case] stage: Stage, #[case] id: ObjectId) {
let index = Index::new(
ObjectFormat::Sha1,
vec![Entry::new(b"file".to_vec(), Mode::Regular, id).with_stage(stage)],
Limits::default(),
)
.unwrap();
assert_eq!(index.tree_id(), None);
}
#[test]
fn null_intent_to_add_is_omitted() {
let format = ObjectFormat::Sha1;
let index = Index::new(
format,
vec![
Entry::new(
b"future/file".to_vec(),
Mode::Regular,
ObjectId::null(format),
)
.with_intent_to_add(true),
],
Limits::default(),
)
.unwrap();
assert_eq!(index.tree_id(), Index::empty(format).tree_id());
}
}