Skip to main content

radicle_git_metadata/
commit.rs

1pub mod headers;
2pub mod trailers;
3
4use core::fmt;
5use std::str;
6
7use headers::{Headers, Signature};
8use trailers::{OwnedTrailer, Trailer};
9
10use crate::author::Author;
11
12/// A git commit in its object description form, i.e. the output of
13/// `git cat-file` for a commit object.
14#[derive(Debug)]
15pub struct CommitData<Tree, Parent> {
16    tree: Tree,
17    parents: Vec<Parent>,
18    author: Author,
19    committer: Author,
20    headers: Headers,
21    message: String,
22    trailers: Vec<OwnedTrailer>,
23}
24
25impl<Tree, Parent> CommitData<Tree, Parent> {
26    pub fn new<P, I, T>(
27        tree: Tree,
28        parents: P,
29        author: Author,
30        committer: Author,
31        headers: Headers,
32        message: String,
33        trailers: I,
34    ) -> Self
35    where
36        P: IntoIterator<Item = Parent>,
37        I: IntoIterator<Item = T>,
38        OwnedTrailer: From<T>,
39    {
40        let trailers = trailers.into_iter().map(OwnedTrailer::from).collect();
41        let parents = parents.into_iter().collect();
42        Self {
43            tree,
44            parents,
45            author,
46            committer,
47            headers,
48            message,
49            trailers,
50        }
51    }
52
53    /// The tree this commit points to.
54    pub fn tree(&self) -> &Tree {
55        &self.tree
56    }
57
58    /// The parents of this commit.
59    pub fn parents(&self) -> impl Iterator<Item = Parent> + '_
60    where
61        Parent: Clone,
62    {
63        self.parents.iter().cloned()
64    }
65
66    /// The author of this commit, i.e. the header corresponding to `author`.
67    pub fn author(&self) -> &Author {
68        &self.author
69    }
70
71    /// The committer of this commit, i.e. the header corresponding to
72    /// `committer`.
73    pub fn committer(&self) -> &Author {
74        &self.committer
75    }
76
77    /// The message body of this commit.
78    pub fn message(&self) -> &str {
79        &self.message
80    }
81
82    /// The [`Signature`]s found in this commit, i.e. the headers corresponding
83    /// to `gpgsig`.
84    pub fn signatures(&self) -> impl Iterator<Item = Signature<'_>> + '_ {
85        self.headers.signatures()
86    }
87
88    /// The [`Headers`] found in this commit.
89    ///
90    /// Note: these do not include `tree`, `parent`, `author`, and `committer`.
91    pub fn headers(&self) -> impl Iterator<Item = (&str, &str)> {
92        self.headers.iter()
93    }
94
95    /// Iterate over the [`Headers`] values that match the provided `name`.
96    pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a str> + 'a {
97        self.headers.values(name)
98    }
99
100    /// Push a header to the end of the headers section.
101    pub fn push_header(&mut self, name: &str, value: &str) {
102        self.headers.push(name, value.trim());
103    }
104
105    pub fn trailers(&self) -> impl Iterator<Item = &OwnedTrailer> {
106        self.trailers.iter()
107    }
108
109    /// Convert the `CommitData::tree` into a value of type `U`. The
110    /// conversion function `f` can be fallible.
111    ///
112    /// For example, `map_tree` can be used to turn raw tree data into
113    /// an `Oid` by writing it to a repository.
114    pub fn map_tree<U, E, F>(self, f: F) -> Result<CommitData<U, Parent>, E>
115    where
116        F: FnOnce(Tree) -> Result<U, E>,
117    {
118        Ok(CommitData {
119            tree: f(self.tree)?,
120            parents: self.parents,
121            author: self.author,
122            committer: self.committer,
123            headers: self.headers,
124            message: self.message,
125            trailers: self.trailers,
126        })
127    }
128
129    /// Convert the [`CommitData::parents`] into a vector containing
130    /// values of type `U`. The conversion function `f` can be
131    /// fallible.
132    ///
133    /// For example, this can be used to resolve the object identifiers
134    /// to their respective full commits.
135    pub fn map_parents<U, E, F>(self, f: F) -> Result<CommitData<Tree, U>, E>
136    where
137        F: FnMut(Parent) -> Result<U, E>,
138    {
139        Ok(CommitData {
140            tree: self.tree,
141            parents: self
142                .parents
143                .into_iter()
144                .map(f)
145                .collect::<Result<Vec<_>, _>>()?,
146            author: self.author,
147            committer: self.committer,
148            headers: self.headers,
149            message: self.message,
150            trailers: self.trailers,
151        })
152    }
153}
154
155impl<Tree: fmt::Display, Parent: fmt::Display> fmt::Display for CommitData<Tree, Parent> {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        writeln!(f, "tree {}", self.tree)?;
158        for parent in self.parents.iter() {
159            writeln!(f, "parent {parent}")?;
160        }
161        writeln!(f, "author {}", self.author)?;
162        writeln!(f, "committer {}", self.committer)?;
163
164        for (name, value) in self.headers.iter() {
165            writeln!(f, "{name} {}", value.replace('\n', "\n "))?;
166        }
167        writeln!(f)?;
168        write!(f, "{}", self.message.trim())?;
169        writeln!(f)?;
170
171        if !self.trailers.is_empty() {
172            writeln!(f)?;
173        }
174        for trailer in self.trailers.iter() {
175            writeln!(f, "{}", Trailer::from(trailer).display(": "))?;
176        }
177        Ok(())
178    }
179}