1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use gix_hash::ObjectId;
use gix_object::{bstr::BStr, tree, tree::EntryMode};

/// Represents any possible change in order to turn one tree into another.
#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
pub enum Change {
    /// An entry was added, like the addition of a file or directory.
    Addition {
        /// The mode of the added entry.
        entry_mode: tree::EntryMode,
        /// The object id of the added entry.
        oid: ObjectId,
    },
    /// An entry was deleted, like the deletion of a file or directory.
    Deletion {
        /// The mode of the deleted entry.
        entry_mode: tree::EntryMode,
        /// The object id of the deleted entry.
        oid: ObjectId,
    },
    /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning
    /// a file into a symbolic link adjusts its mode.
    Modification {
        /// The mode of the entry before the modification.
        previous_entry_mode: tree::EntryMode,
        /// The object id of the entry before the modification.
        previous_oid: ObjectId,

        /// The mode of the entry after the modification.
        entry_mode: tree::EntryMode,
        /// The object id after the modification.
        oid: ObjectId,
    },
}

impl Change {
    /// Return the current object id.
    pub fn oid(&self) -> &gix_hash::oid {
        match self {
            Change::Addition { oid, .. } | Change::Deletion { oid, .. } | Change::Modification { oid, .. } => oid,
        }
    }
    /// Return the current tree entry mode.
    pub fn entry_mode(&self) -> EntryMode {
        match self {
            Change::Addition { entry_mode, .. }
            | Change::Deletion { entry_mode, .. }
            | Change::Modification { entry_mode, .. } => *entry_mode,
        }
    }
    /// Return the current object id and tree entry mode of a change.
    pub fn oid_and_entry_mode(&self) -> (&gix_hash::oid, EntryMode) {
        match self {
            Change::Addition { oid, entry_mode }
            | Change::Deletion { oid, entry_mode }
            | Change::Modification { oid, entry_mode, .. } => (oid, *entry_mode),
        }
    }
}

/// What to do after a [Change] was [recorded][Visit::visit()].
#[derive(Default, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
pub enum Action {
    /// Continue the traversal of changes.
    #[default]
    Continue,
    /// Stop the traversal of changes, making this the last call to [visit(…)][Visit::visit()].
    Cancel,
}

impl Action {
    /// Returns true if this action means to stop the traversal.
    pub fn cancelled(&self) -> bool {
        matches!(self, Action::Cancel)
    }
}

/// A trait to allow responding to a traversal designed to figure out the [changes][Change]
/// to turn tree A into tree B.
pub trait Visit {
    /// Sets the full path path in front of the queue so future calls to push and pop components affect it instead.
    fn pop_front_tracked_path_and_set_current(&mut self);
    /// Append a `component` to the end of a path, which may be empty.
    fn push_back_tracked_path_component(&mut self, component: &BStr);
    /// Append a `component` to the end of a path, which may be empty.
    fn push_path_component(&mut self, component: &BStr);
    /// Removes the last component from the path, which may leave it empty.
    fn pop_path_component(&mut self);
    /// Record a `change` and return an instruction whether to continue or not.
    ///
    /// The implementation may use the current path to lean where in the tree the change is located.
    fn visit(&mut self, change: Change) -> Action;
}

#[cfg(feature = "blob")]
mod change_impls {
    use gix_hash::oid;
    use gix_object::tree::EntryMode;

    use crate::{rewrites::tracker::ChangeKind, tree::visit::Change};

    impl crate::rewrites::tracker::Change for crate::tree::visit::Change {
        fn id(&self) -> &oid {
            match self {
                Change::Addition { oid, .. } | Change::Deletion { oid, .. } | Change::Modification { oid, .. } => oid,
            }
        }

        fn kind(&self) -> ChangeKind {
            match self {
                Change::Addition { .. } => ChangeKind::Addition,
                Change::Deletion { .. } => ChangeKind::Deletion,
                Change::Modification { .. } => ChangeKind::Modification,
            }
        }

        fn entry_mode(&self) -> EntryMode {
            match self {
                Change::Addition { entry_mode, .. }
                | Change::Deletion { entry_mode, .. }
                | Change::Modification { entry_mode, .. } => *entry_mode,
            }
        }

        fn id_and_entry_mode(&self) -> (&oid, EntryMode) {
            match self {
                Change::Addition { entry_mode, oid, .. }
                | Change::Deletion { entry_mode, oid, .. }
                | Change::Modification { entry_mode, oid, .. } => (oid, *entry_mode),
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn size_of_change() {
        let actual = std::mem::size_of::<Change>();
        assert!(
            actual <= 46,
            "{actual} <= 46: this type shouldn't grow without us knowing"
        )
    }
}