gix_object/tree/
mod.rs

1use crate::{
2    bstr::{BStr, BString},
3    tree, Tree, TreeRef,
4};
5use std::cell::RefCell;
6use std::cmp::Ordering;
7
8///
9pub mod editor;
10
11mod ref_iter;
12///
13pub mod write;
14
15/// The state needed to apply edits instantly to in-memory trees.
16///
17/// It's made so that each tree is looked at in the object database at most once, and held in memory for
18/// all edits until everything is flushed to write all changed trees.
19///
20/// The editor is optimized to edit existing trees, but can deal with building entirely new trees as well
21/// with some penalties.
22#[doc(alias = "TreeUpdateBuilder", alias = "git2")]
23#[derive(Clone)]
24pub struct Editor<'a> {
25    /// A way to lookup trees.
26    find: &'a dyn crate::FindExt,
27    /// The kind of hashes to produce
28    object_hash: gix_hash::Kind,
29    /// All trees we currently hold in memory. Each of these may change while adding and removing entries.
30    /// null-object-ids mark tree-entries whose value we don't know yet, they are placeholders that will be
31    /// dropped when writing at the latest.
32    trees: std::collections::HashMap<BString, Tree>,
33    /// A buffer to build up paths when finding the tree to edit.
34    path_buf: RefCell<BString>,
35    /// Our buffer for storing tree-data in, right before decoding it.
36    tree_buf: Vec<u8>,
37}
38
39/// The mode of items storable in a tree, similar to the file mode on a unix file system.
40///
41/// Used in [`mutable::Entry`][crate::tree::Entry] and [`EntryRef`].
42///
43/// Note that even though it can be created from any `u16`, it should be preferable to
44/// create it by converting [`EntryKind`] into `EntryMode`.
45#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct EntryMode(pub u16);
48
49impl std::fmt::Debug for EntryMode {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(f, "EntryMode({:#o})", self.0)
52    }
53}
54
55/// A discretized version of ideal and valid values for entry modes.
56///
57/// Note that even though it can represent every valid [mode](EntryMode), it might
58/// loose information due to that as well.
59#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
60#[repr(u16)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62pub enum EntryKind {
63    /// A tree, or directory
64    Tree = 0o040000u16,
65    /// A file that is not executable
66    Blob = 0o100644,
67    /// A file that is executable
68    BlobExecutable = 0o100755,
69    /// A symbolic link
70    Link = 0o120000,
71    /// A commit of a git submodule
72    Commit = 0o160000,
73}
74
75impl From<EntryKind> for EntryMode {
76    fn from(value: EntryKind) -> Self {
77        EntryMode(value as u16)
78    }
79}
80
81impl From<EntryMode> for EntryKind {
82    fn from(value: EntryMode) -> Self {
83        value.kind()
84    }
85}
86
87/// Serialization
88impl EntryKind {
89    /// Return the representation as used in the git internal format.
90    pub fn as_octal_str(&self) -> &'static BStr {
91        use EntryKind::*;
92        let bytes: &[u8] = match self {
93            Tree => b"40000",
94            Blob => b"100644",
95            BlobExecutable => b"100755",
96            Link => b"120000",
97            Commit => b"160000",
98        };
99        bytes.into()
100    }
101}
102
103impl std::ops::Deref for EntryMode {
104    type Target = u16;
105
106    fn deref(&self) -> &Self::Target {
107        &self.0
108    }
109}
110
111const IFMT: u16 = 0o170000;
112
113impl EntryMode {
114    /// Discretize the raw mode into an enum with well-known state while dropping unnecessary details.
115    pub const fn kind(&self) -> EntryKind {
116        let etype = self.0 & IFMT;
117        if etype == 0o100000 {
118            if self.0 & 0o000100 == 0o000100 {
119                EntryKind::BlobExecutable
120            } else {
121                EntryKind::Blob
122            }
123        } else if etype == EntryKind::Link as u16 {
124            EntryKind::Link
125        } else if etype == EntryKind::Tree as u16 {
126            EntryKind::Tree
127        } else {
128            EntryKind::Commit
129        }
130    }
131
132    /// Return true if this entry mode represents a Tree/directory
133    pub const fn is_tree(&self) -> bool {
134        self.0 & IFMT == EntryKind::Tree as u16
135    }
136
137    /// Return true if this entry mode represents the commit of a submodule.
138    pub const fn is_commit(&self) -> bool {
139        self.0 & IFMT == EntryKind::Commit as u16
140    }
141
142    /// Return true if this entry mode represents a symbolic link
143    pub const fn is_link(&self) -> bool {
144        self.0 & IFMT == EntryKind::Link as u16
145    }
146
147    /// Return true if this entry mode represents anything BUT Tree/directory
148    pub const fn is_no_tree(&self) -> bool {
149        self.0 & IFMT != EntryKind::Tree as u16
150    }
151
152    /// Return true if the entry is any kind of blob.
153    pub const fn is_blob(&self) -> bool {
154        self.0 & IFMT == 0o100000
155    }
156
157    /// Return true if the entry is an executable blob.
158    pub const fn is_executable(&self) -> bool {
159        matches!(self.kind(), EntryKind::BlobExecutable)
160    }
161
162    /// Return true if the entry is any kind of blob or symlink.
163    pub const fn is_blob_or_symlink(&self) -> bool {
164        matches!(
165            self.kind(),
166            EntryKind::Blob | EntryKind::BlobExecutable | EntryKind::Link
167        )
168    }
169
170    /// Represent the mode as descriptive string.
171    pub const fn as_str(&self) -> &'static str {
172        use EntryKind::*;
173        match self.kind() {
174            Tree => "tree",
175            Blob => "blob",
176            BlobExecutable => "exe",
177            Link => "link",
178            Commit => "commit",
179        }
180    }
181
182    /// Return the representation as used in the git internal format, which is octal and written
183    /// to the `backing` buffer. The respective sub-slice that was written to is returned.
184    pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr {
185        if self.0 == 0 {
186            std::slice::from_ref(&b'0')
187        } else {
188            let mut nb = 0;
189            let mut n = self.0;
190            while n > 0 {
191                let remainder = (n % 8) as u8;
192                backing[nb] = b'0' + remainder;
193                n /= 8;
194                nb += 1;
195            }
196            let res = &mut backing[..nb];
197            res.reverse();
198            res
199        }
200        .into()
201    }
202}
203
204impl TreeRef<'_> {
205    /// Convert this instance into its own version, creating a copy of all data.
206    ///
207    /// This will temporarily allocate an extra copy in memory, so at worst three copies of the tree exist
208    /// at some intermediate point in time. Use [`Self::into_owned()`] to avoid this.
209    pub fn to_owned(&self) -> Tree {
210        self.clone().into()
211    }
212
213    /// Convert this instance into its own version, creating a copy of all data.
214    pub fn into_owned(self) -> Tree {
215        self.into()
216    }
217}
218
219/// An element of a [`TreeRef`][crate::TreeRef::entries].
220#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
221#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
222pub struct EntryRef<'a> {
223    /// The kind of object to which `oid` is pointing.
224    pub mode: tree::EntryMode,
225    /// The name of the file in the parent tree.
226    pub filename: &'a BStr,
227    /// The id of the object representing the entry.
228    // TODO: figure out how these should be called. id or oid? It's inconsistent around the codebase.
229    //       Answer: make it 'id', as in `git2`
230    #[cfg_attr(feature = "serde", serde(borrow))]
231    pub oid: &'a gix_hash::oid,
232}
233
234impl PartialOrd for EntryRef<'_> {
235    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
236        Some(self.cmp(other))
237    }
238}
239
240impl Ord for EntryRef<'_> {
241    fn cmp(&self, b: &Self) -> Ordering {
242        let a = self;
243        let common = a.filename.len().min(b.filename.len());
244        a.filename[..common].cmp(&b.filename[..common]).then_with(|| {
245            let a = a.filename.get(common).or_else(|| a.mode.is_tree().then_some(&b'/'));
246            let b = b.filename.get(common).or_else(|| b.mode.is_tree().then_some(&b'/'));
247            a.cmp(&b)
248        })
249    }
250}
251
252/// An entry in a [`Tree`], similar to an entry in a directory.
253#[derive(PartialEq, Eq, Debug, Hash, Clone)]
254#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
255pub struct Entry {
256    /// The kind of object to which `oid` is pointing to.
257    pub mode: EntryMode,
258    /// The name of the file in the parent tree.
259    pub filename: BString,
260    /// The id of the object representing the entry.
261    pub oid: gix_hash::ObjectId,
262}
263
264impl PartialOrd for Entry {
265    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
266        Some(self.cmp(other))
267    }
268}
269
270impl Ord for Entry {
271    fn cmp(&self, b: &Self) -> Ordering {
272        let a = self;
273        let common = a.filename.len().min(b.filename.len());
274        a.filename[..common].cmp(&b.filename[..common]).then_with(|| {
275            let a = a.filename.get(common).or_else(|| a.mode.is_tree().then_some(&b'/'));
276            let b = b.filename.get(common).or_else(|| b.mode.is_tree().then_some(&b'/'));
277            a.cmp(&b)
278        })
279    }
280}