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