Skip to main content

weavatrix_git/
diff.rs

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}