1 use core::ops::Range;
2 use std::ffi::CStr;
3 use std::ffi::CString;
4 use std::ptr;
5 
6 use libc::{c_char, c_int};
7 
8 use crate::util::Binding;
9 use crate::{raw, Buf, Error, IntoCString};
10 
11 /// Clean up a message, removing extraneous whitespace, and ensure that the
12 /// message ends with a newline. If `comment_char` is `Some`, also remove comment
13 /// lines starting with that character.
message_prettify<T: IntoCString>( message: T, comment_char: Option<u8>, ) -> Result<String, Error>14 pub fn message_prettify<T: IntoCString>(
15     message: T,
16     comment_char: Option<u8>,
17 ) -> Result<String, Error> {
18     _message_prettify(message.into_c_string()?, comment_char)
19 }
20 
_message_prettify(message: CString, comment_char: Option<u8>) -> Result<String, Error>21 fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<String, Error> {
22     let ret = Buf::new();
23     unsafe {
24         try_call!(raw::git_message_prettify(
25             ret.raw(),
26             message,
27             comment_char.is_some() as c_int,
28             comment_char.unwrap_or(0) as c_char
29         ));
30     }
31     Ok(ret.as_str().unwrap().to_string())
32 }
33 
34 /// The default comment character for `message_prettify` ('#')
35 pub const DEFAULT_COMMENT_CHAR: Option<u8> = Some(b'#');
36 
37 /// Get the trailers for the given message.
38 ///
39 /// Use this function when you are dealing with a UTF-8-encoded message.
message_trailers_strs(message: &str) -> Result<MessageTrailersStrs, Error>40 pub fn message_trailers_strs(message: &str) -> Result<MessageTrailersStrs, Error> {
41     _message_trailers(message.into_c_string()?).map(|res| MessageTrailersStrs(res))
42 }
43 
44 /// Get the trailers for the given message.
45 ///
46 /// Use this function when the message might not be UTF-8-encoded,
47 /// or if you want to handle the returned trailer key–value pairs
48 /// as bytes.
message_trailers_bytes<S: IntoCString>(message: S) -> Result<MessageTrailersBytes, Error>49 pub fn message_trailers_bytes<S: IntoCString>(message: S) -> Result<MessageTrailersBytes, Error> {
50     _message_trailers(message.into_c_string()?).map(|res| MessageTrailersBytes(res))
51 }
52 
_message_trailers(message: CString) -> Result<MessageTrailers, Error>53 fn _message_trailers(message: CString) -> Result<MessageTrailers, Error> {
54     let ret = MessageTrailers::new();
55     unsafe {
56         try_call!(raw::git_message_trailers(ret.raw(), message));
57     }
58     Ok(ret)
59 }
60 
61 /// Collection of UTF-8-encoded trailers.
62 ///
63 /// Use `iter()` to get access to the values.
64 pub struct MessageTrailersStrs(MessageTrailers);
65 
66 impl MessageTrailersStrs {
67     /// Create a borrowed iterator.
iter(&self) -> MessageTrailersStrsIterator<'_>68     pub fn iter(&self) -> MessageTrailersStrsIterator<'_> {
69         MessageTrailersStrsIterator(self.0.iter())
70     }
71     /// The number of trailer key–value pairs.
len(&self) -> usize72     pub fn len(&self) -> usize {
73         self.0.len()
74     }
75     /// Convert to the “bytes” variant.
to_bytes(self) -> MessageTrailersBytes76     pub fn to_bytes(self) -> MessageTrailersBytes {
77         MessageTrailersBytes(self.0)
78     }
79 }
80 
81 /// Collection of unencoded (bytes) trailers.
82 ///
83 /// Use `iter()` to get access to the values.
84 pub struct MessageTrailersBytes(MessageTrailers);
85 
86 impl MessageTrailersBytes {
87     /// Create a borrowed iterator.
iter(&self) -> MessageTrailersBytesIterator<'_>88     pub fn iter(&self) -> MessageTrailersBytesIterator<'_> {
89         MessageTrailersBytesIterator(self.0.iter())
90     }
91     /// The number of trailer key–value pairs.
len(&self) -> usize92     pub fn len(&self) -> usize {
93         self.0.len()
94     }
95 }
96 
97 struct MessageTrailers {
98     raw: raw::git_message_trailer_array,
99 }
100 
101 impl MessageTrailers {
new() -> MessageTrailers102     fn new() -> MessageTrailers {
103         crate::init();
104         unsafe {
105             Binding::from_raw(&mut raw::git_message_trailer_array {
106                 trailers: ptr::null_mut(),
107                 count: 0,
108                 _trailer_block: ptr::null_mut(),
109             } as *mut _)
110         }
111     }
iter(&self) -> MessageTrailersIterator<'_>112     fn iter(&self) -> MessageTrailersIterator<'_> {
113         MessageTrailersIterator {
114             trailers: self,
115             range: Range {
116                 start: 0,
117                 end: self.raw.count,
118             },
119         }
120     }
len(&self) -> usize121     fn len(&self) -> usize {
122         self.raw.count
123     }
124 }
125 
126 impl Drop for MessageTrailers {
drop(&mut self)127     fn drop(&mut self) {
128         unsafe {
129             raw::git_message_trailer_array_free(&mut self.raw);
130         }
131     }
132 }
133 
134 impl Binding for MessageTrailers {
135     type Raw = *mut raw::git_message_trailer_array;
from_raw(raw: *mut raw::git_message_trailer_array) -> MessageTrailers136     unsafe fn from_raw(raw: *mut raw::git_message_trailer_array) -> MessageTrailers {
137         MessageTrailers { raw: *raw }
138     }
raw(&self) -> *mut raw::git_message_trailer_array139     fn raw(&self) -> *mut raw::git_message_trailer_array {
140         &self.raw as *const _ as *mut _
141     }
142 }
143 
144 struct MessageTrailersIterator<'a> {
145     trailers: &'a MessageTrailers,
146     range: Range<usize>,
147 }
148 
to_raw_tuple(trailers: &MessageTrailers, index: usize) -> (*const c_char, *const c_char)149 fn to_raw_tuple(trailers: &MessageTrailers, index: usize) -> (*const c_char, *const c_char) {
150     unsafe {
151         let addr = trailers.raw.trailers.wrapping_add(index);
152         ((*addr).key, (*addr).value)
153     }
154 }
155 
156 /// Borrowed iterator over the UTF-8-encoded trailers.
157 pub struct MessageTrailersStrsIterator<'a>(MessageTrailersIterator<'a>);
158 
159 impl<'pair> Iterator for MessageTrailersStrsIterator<'pair> {
160     type Item = (&'pair str, &'pair str);
161 
next(&mut self) -> Option<Self::Item>162     fn next(&mut self) -> Option<Self::Item> {
163         self.0
164             .range
165             .next()
166             .map(|index| to_str_tuple(&self.0.trailers, index))
167     }
168 
size_hint(&self) -> (usize, Option<usize>)169     fn size_hint(&self) -> (usize, Option<usize>) {
170         self.0.range.size_hint()
171     }
172 }
173 
174 impl ExactSizeIterator for MessageTrailersStrsIterator<'_> {
len(&self) -> usize175     fn len(&self) -> usize {
176         self.0.range.len()
177     }
178 }
179 
180 impl DoubleEndedIterator for MessageTrailersStrsIterator<'_> {
next_back(&mut self) -> Option<Self::Item>181     fn next_back(&mut self) -> Option<Self::Item> {
182         self.0
183             .range
184             .next_back()
185             .map(|index| to_str_tuple(&self.0.trailers, index))
186     }
187 }
188 
to_str_tuple(trailers: &MessageTrailers, index: usize) -> (&str, &str)189 fn to_str_tuple(trailers: &MessageTrailers, index: usize) -> (&str, &str) {
190     unsafe {
191         let (rkey, rvalue) = to_raw_tuple(&trailers, index);
192         let key = CStr::from_ptr(rkey).to_str().unwrap();
193         let value = CStr::from_ptr(rvalue).to_str().unwrap();
194         (key, value)
195     }
196 }
197 
198 /// Borrowed iterator over the raw (bytes) trailers.
199 pub struct MessageTrailersBytesIterator<'a>(MessageTrailersIterator<'a>);
200 
201 impl<'pair> Iterator for MessageTrailersBytesIterator<'pair> {
202     type Item = (&'pair [u8], &'pair [u8]);
203 
next(&mut self) -> Option<Self::Item>204     fn next(&mut self) -> Option<Self::Item> {
205         self.0
206             .range
207             .next()
208             .map(|index| to_bytes_tuple(&self.0.trailers, index))
209     }
210 
size_hint(&self) -> (usize, Option<usize>)211     fn size_hint(&self) -> (usize, Option<usize>) {
212         self.0.range.size_hint()
213     }
214 }
215 
216 impl ExactSizeIterator for MessageTrailersBytesIterator<'_> {
len(&self) -> usize217     fn len(&self) -> usize {
218         self.0.range.len()
219     }
220 }
221 
222 impl DoubleEndedIterator for MessageTrailersBytesIterator<'_> {
next_back(&mut self) -> Option<Self::Item>223     fn next_back(&mut self) -> Option<Self::Item> {
224         self.0
225             .range
226             .next_back()
227             .map(|index| to_bytes_tuple(&self.0.trailers, index))
228     }
229 }
230 
to_bytes_tuple(trailers: &MessageTrailers, index: usize) -> (&[u8], &[u8])231 fn to_bytes_tuple(trailers: &MessageTrailers, index: usize) -> (&[u8], &[u8]) {
232     unsafe {
233         let (rkey, rvalue) = to_raw_tuple(&trailers, index);
234         let key = CStr::from_ptr(rkey).to_bytes();
235         let value = CStr::from_ptr(rvalue).to_bytes();
236         (key, value)
237     }
238 }
239 
240 #[cfg(test)]
241 mod tests {
242 
243     #[test]
prettify()244     fn prettify() {
245         use crate::{message_prettify, DEFAULT_COMMENT_CHAR};
246 
247         // This does not attempt to duplicate the extensive tests for
248         // git_message_prettify in libgit2, just a few representative values to
249         // make sure the interface works as expected.
250         assert_eq!(message_prettify("1\n\n\n2", None).unwrap(), "1\n\n2\n");
251         assert_eq!(
252             message_prettify("1\n\n\n2\n\n\n3", None).unwrap(),
253             "1\n\n2\n\n3\n"
254         );
255         assert_eq!(
256             message_prettify("1\n# comment\n# more", None).unwrap(),
257             "1\n# comment\n# more\n"
258         );
259         assert_eq!(
260             message_prettify("1\n# comment\n# more", DEFAULT_COMMENT_CHAR).unwrap(),
261             "1\n"
262         );
263         assert_eq!(
264             message_prettify("1\n; comment\n; more", Some(';' as u8)).unwrap(),
265             "1\n"
266         );
267     }
268 
269     #[test]
trailers()270     fn trailers() {
271         use crate::{message_trailers_bytes, message_trailers_strs, MessageTrailersStrs};
272         use std::collections::HashMap;
273 
274         // no trailers
275         let message1 = "
276 WHAT ARE WE HERE FOR
277 
278 What are we here for?
279 
280 Just to be eaten?
281 ";
282         let expected: HashMap<&str, &str> = HashMap::new();
283         assert_eq!(expected, to_map(&message_trailers_strs(message1).unwrap()));
284 
285         // standard PSA
286         let message2 = "
287 Attention all
288 
289 We are out of tomatoes.
290 
291 Spoken-by: Major Turnips
292 Transcribed-by: Seargant Persimmons
293 Signed-off-by: Colonel Kale
294 ";
295         let expected: HashMap<&str, &str> = vec![
296             ("Spoken-by", "Major Turnips"),
297             ("Transcribed-by", "Seargant Persimmons"),
298             ("Signed-off-by", "Colonel Kale"),
299         ]
300         .into_iter()
301         .collect();
302         assert_eq!(expected, to_map(&message_trailers_strs(message2).unwrap()));
303 
304         // ignore everything after `---`
305         let message3 = "
306 The fate of Seargant Green-Peppers
307 
308 Seargant Green-Peppers was killed by Caterpillar Battalion 44.
309 
310 Signed-off-by: Colonel Kale
311 ---
312 I never liked that guy, anyway.
313 
314 Opined-by: Corporal Garlic
315 ";
316         let expected: HashMap<&str, &str> = vec![("Signed-off-by", "Colonel Kale")]
317             .into_iter()
318             .collect();
319         assert_eq!(expected, to_map(&message_trailers_strs(message3).unwrap()));
320 
321         // Raw bytes message; not valid UTF-8
322         // Source: https://stackoverflow.com/a/3886015/1725151
323         let message4 = b"
324 Be honest guys
325 
326 Am I a malformed brussels sprout?
327 
328 Signed-off-by: Lieutenant \xe2\x28\xa1prout
329 ";
330 
331         let trailer = message_trailers_bytes(&message4[..]).unwrap();
332         let expected = (&b"Signed-off-by"[..], &b"Lieutenant \xe2\x28\xa1prout"[..]);
333         let actual = trailer.iter().next().unwrap();
334         assert_eq!(expected, actual);
335 
336         fn to_map(trailers: &MessageTrailersStrs) -> HashMap<&str, &str> {
337             let mut map = HashMap::with_capacity(trailers.len());
338             for (key, value) in trailers.iter() {
339                 map.insert(key, value);
340             }
341             map
342         }
343     }
344 }
345