radicle_git_ext/
tree.rs

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