gix_diff/tree/
visit.rs

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use gix_hash::ObjectId;
use gix_object::{tree, tree::EntryMode};

/// A way to recognize and associate different [`Change`] instances.
///
/// These are unique only within one diff operation.
pub type ChangeId = u32;

/// Identifies a relationship between this instance and another one.
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)]
pub enum Relation {
    /// This is a parent with the given ID, which will always have at least one child
    /// assuming that empty directories are not allowed in valid trees.
    /// It's also always a tree which is the start of a recursive deletion or addition.
    ///
    /// The change with this relation is always emitted first.
    Parent(ChangeId),
    /// This is a direct or indirect child, tree or not tree, of the parent with the given ID.
    ChildOfParent(ChangeId),
}

/// 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,
        /// Possibly associate this change with another for hierarchical rename tracking.
        relation: Option<Relation>,
    },
    /// 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,
        /// Possibly associate this change with another for hierarchical rename tracking.
        relation: Option<Relation>,
    },
    /// 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,
                relation: _,
            }
            | Change::Deletion {
                oid,
                entry_mode,
                relation: _,
            }
            | Change::Modification { oid, entry_mode, .. } => (oid, *entry_mode),
        }
    }
}

/// What to do after a [Change] was [recorded](super::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(…)](super::Visit::visit()).
    Cancel,
}

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

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

    use crate::tree::visit::Relation;
    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 relation(&self) -> Option<Relation> {
            match self {
                Change::Addition { relation, .. } | Change::Deletion { relation, .. } => *relation,
                Change::Modification { .. } => None,
            }
        }

        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 <= 48,
            "{actual} <= 48: this type shouldn't grow without us knowing"
        );
    }
}