1use std::collections::BTreeMap;
2
3use crate::{EntryKind, ObjectId, Repository, Result, TreeEntry};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum ChangeKind {
7 Added,
8 Deleted,
9 Modified,
10 TypeChanged,
11}
12
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct TreeChange {
15 pub path: Vec<u8>,
16 pub kind: ChangeKind,
17 pub old: Option<TreeEntry>,
18 pub new: Option<TreeEntry>,
19}
20
21pub(crate) fn between(
22 repository: &Repository,
23 old: ObjectId,
24 new: ObjectId,
25) -> Result<Vec<TreeChange>> {
26 across(repository, old, repository, new)
27}
28
29pub(crate) fn across(
30 old_repository: &Repository,
31 old: ObjectId,
32 new_repository: &Repository,
33 new: ObjectId,
34) -> Result<Vec<TreeChange>> {
35 if old == new {
36 return Ok(Vec::new());
37 }
38 let mut old_entries = BTreeMap::new();
39 let mut new_entries = BTreeMap::new();
40 flatten(old_repository, old, &[], 0, &mut old_entries)?;
41 flatten(new_repository, new, &[], 0, &mut new_entries)?;
42 let mut paths = old_entries
43 .keys()
44 .chain(new_entries.keys())
45 .collect::<Vec<_>>();
46 paths.sort_unstable();
47 paths.dedup();
48 let mut changes = Vec::new();
49 for path in paths {
50 let old_entry = old_entries.get(path);
51 let new_entry = new_entries.get(path);
52 let kind = match (old_entry, new_entry) {
53 (None, Some(_)) => ChangeKind::Added,
54 (Some(_), None) => ChangeKind::Deleted,
55 (Some(old), Some(new)) if old.kind != new.kind => ChangeKind::TypeChanged,
56 (Some(old), Some(new)) if old.id != new.id || old.mode != new.mode => {
57 ChangeKind::Modified
58 }
59 _ => continue,
60 };
61 changes.push(TreeChange {
62 path: path.clone(),
63 kind,
64 old: old_entry.cloned(),
65 new: new_entry.cloned(),
66 });
67 }
68 Ok(changes)
69}
70
71fn flatten(
72 repository: &Repository,
73 tree_id: ObjectId,
74 prefix: &[u8],
75 depth: usize,
76 output: &mut BTreeMap<Vec<u8>, TreeEntry>,
77) -> Result<()> {
78 if depth >= repository.limits().max_tree_depth {
79 return Err(crate::GitError::LimitExceeded {
80 resource: "tree depth",
81 limit: repository.limits().max_tree_depth,
82 });
83 }
84 for mut entry in repository.tree(tree_id)?.entries {
85 let mut path = Vec::with_capacity(prefix.len() + entry.name.len() + 1);
86 if !prefix.is_empty() {
87 path.extend_from_slice(prefix);
88 path.push(b'/');
89 }
90 path.extend_from_slice(&entry.name);
91 if entry.kind == EntryKind::Tree {
92 flatten(repository, entry.id, &path, depth + 1, output)?;
93 } else {
94 entry.name.clone_from(&path);
95 output.insert(path, entry);
96 }
97 }
98 Ok(())
99}