use std::collections::{HashMap, HashSet};
use crate::object::{Commit, Object, Tree};
use crate::{Error, WithError as _, bail, error};
#[derive(Debug)]
pub struct Checkout<'a> {
pub commit: &'a Commit,
pub entries: HashMap<String, DirEntry<'a>>,
}
impl<'a> Checkout<'a> {
pub fn from_pack(pack: &'a HashMap<String, Object>, oid: &str) -> Result<Self, Error> {
let commit_obj = pack
.get(oid)
.with_err(|| error!("commit {oid} not found"))?;
let Object::Commit(commit) = commit_obj else {
bail!("expected {oid} to be a commit, got {commit_obj:?}")
};
let tree_id = commit.commit.tree.to_string();
let tree_obj = pack
.get(&tree_id)
.with_err(|| error!("root tree {tree_id} not found"))?;
let mut entries = HashMap::new();
let Object::Tree(tree) = tree_obj else {
bail!("expected {tree_id} to be a tree, got {tree_obj:?}")
};
collect_entries(pack, tree, ".", &mut entries);
Ok(Self { commit, entries })
}
#[must_use]
pub fn get_diff(&self, other: &Checkout) -> Vec<BlobDiff> {
let all_paths = self
.entries
.values()
.chain(other.entries.values())
.filter(|entry| !entry.dir)
.map(|entry| entry.path.as_str())
.collect::<HashSet<&str>>();
let mut result = vec![];
for path in all_paths {
let this = self.entries.get(path);
let other = other.entries.get(path);
if this != other {
let old = other.map(|a| a.hash.clone());
let new = this.map(|a| a.hash.clone());
result.push(BlobDiff {
path: path.to_owned(),
old_oid: old,
new_oid: new,
});
}
}
result.sort_by(|a, b| a.path.cmp(&b.path));
result
}
}
fn collect_entries<'a>(
pack: &'a HashMap<String, Object>,
tree: &'a Tree,
path: &str,
entries: &mut HashMap<String, DirEntry<'a>>,
) {
for entry in &tree.entries {
let hash = &entry.hash;
let path = format!("{path}/{}", entry.name);
let object = pack.get(hash);
if let Some(Object::Tree(tree)) = object {
collect_entries(pack, tree, &path, entries);
}
entries.insert(
path.clone(),
DirEntry {
path,
hash: hash.clone(),
dir: entry.is_dir(),
object,
},
);
}
}
#[derive(Debug, Clone)]
pub struct BlobDiff {
pub path: String,
pub old_oid: Option<String>,
pub new_oid: Option<String>,
}
#[derive(Debug, Clone)]
pub struct DirEntry<'a> {
pub path: String,
pub hash: String,
pub dir: bool,
pub object: Option<&'a Object>,
}
impl PartialEq<DirEntry<'_>> for DirEntry<'_> {
fn eq(&self, other: &DirEntry) -> bool {
self.path == other.path && self.hash == other.hash
}
}