radicle_git_ext/
tree.rs

1use std::{borrow::Cow, collections::BTreeMap, iter::FromIterator};
2
3/// A simplified representation of a git tree, intended mainly to be created
4/// from literals.
5///
6/// # Example
7///
8/// ```
9/// use radicle_git_ext::tree::{Tree, blob};
10///
11/// let my_tree = vec![
12///     ("README", blob(b"awe")),
13///     ("src", vec![("main.rs", blob(b"fn main() {}"))].into_iter().collect()),
14/// ]
15/// .into_iter()
16/// .collect::<Tree>();
17///
18/// assert_eq!(
19///     format!("{:?}", my_tree),
20///     "Tree(\
21///         {\
22///             \"README\": Blob(\
23///                 [\
24///                     97, \
25///                     119, \
26///                     101\
27///                 ]\
28///             ), \
29///             \"src\": Tree(\
30///                 Tree(\
31///                     {\
32///                         \"main.rs\": Blob(\
33///                             [\
34///                                 102, \
35///                                 110, \
36///                                 32, \
37///                                 109, \
38///                                 97, \
39///                                 105, \
40///                                 110, \
41///                                 40, \
42///                                 41, \
43///                                 32, \
44///                                 123, \
45///                                 125\
46///                             ]\
47///                         )\
48///                     }\
49///                 )\
50///             )\
51///         }\
52///     )"
53/// )
54/// ```
55#[derive(Clone, Debug)]
56pub struct Tree<'a>(BTreeMap<Cow<'a, str>, Node<'a>>);
57
58impl Tree<'_> {
59    pub fn write(&self, repo: &git2::Repository) -> Result<git2::Oid, git2::Error> {
60        use Node::*;
61
62        let mut builder = repo.treebuilder(None)?;
63        for (name, node) in &self.0 {
64            match node {
65                Blob(data) => {
66                    let oid = repo.blob(data)?;
67                    builder.insert(name.as_ref(), oid, git2::FileMode::Blob.into())?;
68                }
69                Tree(sub) => {
70                    let oid = sub.write(repo)?;
71                    builder.insert(name.as_ref(), oid, git2::FileMode::Tree.into())?;
72                }
73            }
74        }
75
76        builder.write()
77    }
78}
79
80impl<'a> From<BTreeMap<Cow<'a, str>, Node<'a>>> for Tree<'a> {
81    fn from(map: BTreeMap<Cow<'a, str>, Node<'a>>) -> Self {
82        Self(map)
83    }
84}
85
86impl<'a, K, N> FromIterator<(K, N)> for Tree<'a>
87where
88    K: Into<Cow<'a, str>>,
89    N: Into<Node<'a>>,
90{
91    fn from_iter<T>(iter: T) -> Self
92    where
93        T: IntoIterator<Item = (K, N)>,
94    {
95        Self(
96            iter.into_iter()
97                .map(|(k, v)| (k.into(), v.into()))
98                .collect(),
99        )
100    }
101}
102
103#[derive(Clone, Debug)]
104pub enum Node<'a> {
105    Blob(Cow<'a, [u8]>),
106    Tree(Tree<'a>),
107}
108
109pub fn blob<'a>(slice: &'a [u8]) -> Node<'a> {
110    Node::from(slice)
111}
112
113impl<'a> From<&'a [u8]> for Node<'a> {
114    fn from(slice: &'a [u8]) -> Self {
115        Self::from(Cow::Borrowed(slice))
116    }
117}
118
119impl<'a> From<Cow<'a, [u8]>> for Node<'a> {
120    fn from(bytes: Cow<'a, [u8]>) -> Self {
121        Self::Blob(bytes)
122    }
123}
124
125impl<'a> From<Tree<'a>> for Node<'a> {
126    fn from(tree: Tree<'a>) -> Self {
127        Self::Tree(tree)
128    }
129}
130
131impl<'a, K, N> FromIterator<(K, N)> for Node<'a>
132where
133    K: Into<Cow<'a, str>>,
134    N: Into<Node<'a>>,
135{
136    fn from_iter<T>(iter: T) -> Self
137    where
138        T: IntoIterator<Item = (K, N)>,
139    {
140        Self::Tree(iter.into_iter().collect())
141    }
142}