1use std::collections::{HashMap, HashSet};
2
3use crate::object::{Commit, Object, Tree};
4use crate::{Error, WithError as _, bail, error};
5
6#[derive(Debug)]
8pub struct Checkout<'a> {
9 pub commit: &'a Commit,
10 pub entries: HashMap<String, DirEntry<'a>>,
11}
12
13impl<'a> Checkout<'a> {
14 pub fn from_pack(pack: &'a HashMap<String, Object>, oid: &str) -> Result<Self, Error> {
20 let commit_obj = pack
21 .get(oid)
22 .with_err(|| error!("commit {oid} not found"))?;
23
24 let Object::Commit(commit) = commit_obj else {
25 bail!("expected {oid} to be a commit, got {commit_obj:?}")
26 };
27
28 let tree_id = commit.commit.tree.to_string();
29
30 let tree_obj = pack
31 .get(&tree_id)
32 .with_err(|| error!("root tree {tree_id} not found"))?;
33
34 let mut entries = HashMap::new();
35
36 let Object::Tree(tree) = tree_obj else {
37 bail!("expected {tree_id} to be a tree, got {tree_obj:?}")
38 };
39
40 collect_entries(pack, tree, ".", &mut entries);
41
42 Ok(Self { commit, entries })
43 }
44
45 #[must_use]
47 pub fn get_diff(&self, other: &Checkout) -> Vec<BlobDiff> {
48 let all_paths = self
49 .entries
50 .values()
51 .chain(other.entries.values())
52 .filter(|entry| !entry.dir)
53 .map(|entry| entry.path.as_str())
54 .collect::<HashSet<&str>>();
55
56 let mut result = vec![];
57
58 for path in all_paths {
59 let this = self.entries.get(path);
60 let other = other.entries.get(path);
61
62 if this != other {
63 let old = other.map(|a| a.hash.clone());
64 let new = this.map(|a| a.hash.clone());
65
66 result.push(BlobDiff {
67 path: path.to_owned(),
68 old_oid: old,
69 new_oid: new,
70 });
71 }
72 }
73
74 result.sort_by(|a, b| a.path.cmp(&b.path));
75
76 result
77 }
78}
79
80fn collect_entries<'a>(
81 pack: &'a HashMap<String, Object>,
82 tree: &'a Tree,
83 path: &str,
84 entries: &mut HashMap<String, DirEntry<'a>>,
85) {
86 for entry in &tree.entries {
87 let hash = &entry.hash;
88 let path = format!("{path}/{}", entry.name);
89
90 let object = pack.get(hash);
91
92 if let Some(Object::Tree(tree)) = object {
93 collect_entries(pack, tree, &path, entries);
94 }
95
96 entries.insert(
97 path.clone(),
98 DirEntry {
99 path,
100 hash: hash.clone(),
101 dir: entry.is_dir(),
102 object,
103 },
104 );
105 }
106}
107
108#[derive(Debug, Clone)]
109pub struct BlobDiff {
110 pub path: String,
111
112 pub old_oid: Option<String>,
113 pub new_oid: Option<String>,
114}
115
116#[derive(Debug, Clone)]
117pub struct DirEntry<'a> {
118 pub path: String,
119 pub hash: String,
120 pub dir: bool,
121 pub object: Option<&'a Object>,
122}
123
124impl PartialEq<DirEntry<'_>> for DirEntry<'_> {
125 fn eq(&self, other: &DirEntry) -> bool {
126 self.path == other.path && self.hash == other.hash
127 }
128}