diff options
Diffstat (limited to 'vendor/git2/src/message.rs')
-rw-r--r-- | vendor/git2/src/message.rs | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/vendor/git2/src/message.rs b/vendor/git2/src/message.rs new file mode 100644 index 0000000..a7041da --- /dev/null +++ b/vendor/git2/src/message.rs @@ -0,0 +1,349 @@ +use core::ops::Range; +use std::ffi::CStr; +use std::ffi::CString; +use std::iter::FusedIterator; +use std::ptr; + +use libc::{c_char, c_int}; + +use crate::util::Binding; +use crate::{raw, Buf, Error, IntoCString}; + +/// Clean up a message, removing extraneous whitespace, and ensure that the +/// message ends with a newline. If `comment_char` is `Some`, also remove comment +/// lines starting with that character. +pub fn message_prettify<T: IntoCString>( + message: T, + comment_char: Option<u8>, +) -> Result<String, Error> { + _message_prettify(message.into_c_string()?, comment_char) +} + +fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<String, Error> { + let ret = Buf::new(); + unsafe { + try_call!(raw::git_message_prettify( + ret.raw(), + message, + comment_char.is_some() as c_int, + comment_char.unwrap_or(0) as c_char + )); + } + Ok(ret.as_str().unwrap().to_string()) +} + +/// The default comment character for `message_prettify` ('#') +pub const DEFAULT_COMMENT_CHAR: Option<u8> = Some(b'#'); + +/// Get the trailers for the given message. +/// +/// Use this function when you are dealing with a UTF-8-encoded message. +pub fn message_trailers_strs(message: &str) -> Result<MessageTrailersStrs, Error> { + _message_trailers(message.into_c_string()?).map(|res| MessageTrailersStrs(res)) +} + +/// Get the trailers for the given message. +/// +/// Use this function when the message might not be UTF-8-encoded, +/// or if you want to handle the returned trailer key–value pairs +/// as bytes. +pub fn message_trailers_bytes<S: IntoCString>(message: S) -> Result<MessageTrailersBytes, Error> { + _message_trailers(message.into_c_string()?).map(|res| MessageTrailersBytes(res)) +} + +fn _message_trailers(message: CString) -> Result<MessageTrailers, Error> { + let ret = MessageTrailers::new(); + unsafe { + try_call!(raw::git_message_trailers(ret.raw(), message)); + } + Ok(ret) +} + +/// Collection of UTF-8-encoded trailers. +/// +/// Use `iter()` to get access to the values. +pub struct MessageTrailersStrs(MessageTrailers); + +impl MessageTrailersStrs { + /// Create a borrowed iterator. + pub fn iter(&self) -> MessageTrailersStrsIterator<'_> { + MessageTrailersStrsIterator(self.0.iter()) + } + /// The number of trailer key–value pairs. + pub fn len(&self) -> usize { + self.0.len() + } + /// Convert to the “bytes” variant. + pub fn to_bytes(self) -> MessageTrailersBytes { + MessageTrailersBytes(self.0) + } +} + +/// Collection of unencoded (bytes) trailers. +/// +/// Use `iter()` to get access to the values. +pub struct MessageTrailersBytes(MessageTrailers); + +impl MessageTrailersBytes { + /// Create a borrowed iterator. + pub fn iter(&self) -> MessageTrailersBytesIterator<'_> { + MessageTrailersBytesIterator(self.0.iter()) + } + /// The number of trailer key–value pairs. + pub fn len(&self) -> usize { + self.0.len() + } +} + +struct MessageTrailers { + raw: raw::git_message_trailer_array, +} + +impl MessageTrailers { + fn new() -> MessageTrailers { + crate::init(); + unsafe { + Binding::from_raw(&mut raw::git_message_trailer_array { + trailers: ptr::null_mut(), + count: 0, + _trailer_block: ptr::null_mut(), + } as *mut _) + } + } + fn iter(&self) -> MessageTrailersIterator<'_> { + MessageTrailersIterator { + trailers: self, + range: Range { + start: 0, + end: self.raw.count, + }, + } + } + fn len(&self) -> usize { + self.raw.count + } +} + +impl Drop for MessageTrailers { + fn drop(&mut self) { + unsafe { + raw::git_message_trailer_array_free(&mut self.raw); + } + } +} + +impl Binding for MessageTrailers { + type Raw = *mut raw::git_message_trailer_array; + unsafe fn from_raw(raw: *mut raw::git_message_trailer_array) -> MessageTrailers { + MessageTrailers { raw: *raw } + } + fn raw(&self) -> *mut raw::git_message_trailer_array { + &self.raw as *const _ as *mut _ + } +} + +struct MessageTrailersIterator<'a> { + trailers: &'a MessageTrailers, + range: Range<usize>, +} + +fn to_raw_tuple(trailers: &MessageTrailers, index: usize) -> (*const c_char, *const c_char) { + unsafe { + let addr = trailers.raw.trailers.wrapping_add(index); + ((*addr).key, (*addr).value) + } +} + +/// Borrowed iterator over the UTF-8-encoded trailers. +pub struct MessageTrailersStrsIterator<'a>(MessageTrailersIterator<'a>); + +impl<'pair> Iterator for MessageTrailersStrsIterator<'pair> { + type Item = (&'pair str, &'pair str); + + fn next(&mut self) -> Option<Self::Item> { + self.0 + .range + .next() + .map(|index| to_str_tuple(&self.0.trailers, index)) + } + + fn size_hint(&self) -> (usize, Option<usize>) { + self.0.range.size_hint() + } +} + +impl FusedIterator for MessageTrailersStrsIterator<'_> {} + +impl ExactSizeIterator for MessageTrailersStrsIterator<'_> { + fn len(&self) -> usize { + self.0.range.len() + } +} + +impl DoubleEndedIterator for MessageTrailersStrsIterator<'_> { + fn next_back(&mut self) -> Option<Self::Item> { + self.0 + .range + .next_back() + .map(|index| to_str_tuple(&self.0.trailers, index)) + } +} + +fn to_str_tuple(trailers: &MessageTrailers, index: usize) -> (&str, &str) { + unsafe { + let (rkey, rvalue) = to_raw_tuple(&trailers, index); + let key = CStr::from_ptr(rkey).to_str().unwrap(); + let value = CStr::from_ptr(rvalue).to_str().unwrap(); + (key, value) + } +} + +/// Borrowed iterator over the raw (bytes) trailers. +pub struct MessageTrailersBytesIterator<'a>(MessageTrailersIterator<'a>); + +impl<'pair> Iterator for MessageTrailersBytesIterator<'pair> { + type Item = (&'pair [u8], &'pair [u8]); + + fn next(&mut self) -> Option<Self::Item> { + self.0 + .range + .next() + .map(|index| to_bytes_tuple(&self.0.trailers, index)) + } + + fn size_hint(&self) -> (usize, Option<usize>) { + self.0.range.size_hint() + } +} + +impl FusedIterator for MessageTrailersBytesIterator<'_> {} + +impl ExactSizeIterator for MessageTrailersBytesIterator<'_> { + fn len(&self) -> usize { + self.0.range.len() + } +} + +impl DoubleEndedIterator for MessageTrailersBytesIterator<'_> { + fn next_back(&mut self) -> Option<Self::Item> { + self.0 + .range + .next_back() + .map(|index| to_bytes_tuple(&self.0.trailers, index)) + } +} + +fn to_bytes_tuple(trailers: &MessageTrailers, index: usize) -> (&[u8], &[u8]) { + unsafe { + let (rkey, rvalue) = to_raw_tuple(&trailers, index); + let key = CStr::from_ptr(rkey).to_bytes(); + let value = CStr::from_ptr(rvalue).to_bytes(); + (key, value) + } +} + +#[cfg(test)] +mod tests { + + #[test] + fn prettify() { + use crate::{message_prettify, DEFAULT_COMMENT_CHAR}; + + // This does not attempt to duplicate the extensive tests for + // git_message_prettify in libgit2, just a few representative values to + // make sure the interface works as expected. + assert_eq!(message_prettify("1\n\n\n2", None).unwrap(), "1\n\n2\n"); + assert_eq!( + message_prettify("1\n\n\n2\n\n\n3", None).unwrap(), + "1\n\n2\n\n3\n" + ); + assert_eq!( + message_prettify("1\n# comment\n# more", None).unwrap(), + "1\n# comment\n# more\n" + ); + assert_eq!( + message_prettify("1\n# comment\n# more", DEFAULT_COMMENT_CHAR).unwrap(), + "1\n" + ); + assert_eq!( + message_prettify("1\n; comment\n; more", Some(';' as u8)).unwrap(), + "1\n" + ); + } + + #[test] + fn trailers() { + use crate::{message_trailers_bytes, message_trailers_strs, MessageTrailersStrs}; + use std::collections::HashMap; + + // no trailers + let message1 = " +WHAT ARE WE HERE FOR + +What are we here for? + +Just to be eaten? +"; + let expected: HashMap<&str, &str> = HashMap::new(); + assert_eq!(expected, to_map(&message_trailers_strs(message1).unwrap())); + + // standard PSA + let message2 = " +Attention all + +We are out of tomatoes. + +Spoken-by: Major Turnips +Transcribed-by: Seargant Persimmons +Signed-off-by: Colonel Kale +"; + let expected: HashMap<&str, &str> = vec![ + ("Spoken-by", "Major Turnips"), + ("Transcribed-by", "Seargant Persimmons"), + ("Signed-off-by", "Colonel Kale"), + ] + .into_iter() + .collect(); + assert_eq!(expected, to_map(&message_trailers_strs(message2).unwrap())); + + // ignore everything after `---` + let message3 = " +The fate of Seargant Green-Peppers + +Seargant Green-Peppers was killed by Caterpillar Battalion 44. + +Signed-off-by: Colonel Kale +--- +I never liked that guy, anyway. + +Opined-by: Corporal Garlic +"; + let expected: HashMap<&str, &str> = vec![("Signed-off-by", "Colonel Kale")] + .into_iter() + .collect(); + assert_eq!(expected, to_map(&message_trailers_strs(message3).unwrap())); + + // Raw bytes message; not valid UTF-8 + // Source: https://stackoverflow.com/a/3886015/1725151 + let message4 = b" +Be honest guys + +Am I a malformed brussels sprout? + +Signed-off-by: Lieutenant \xe2\x28\xa1prout +"; + + let trailer = message_trailers_bytes(&message4[..]).unwrap(); + let expected = (&b"Signed-off-by"[..], &b"Lieutenant \xe2\x28\xa1prout"[..]); + let actual = trailer.iter().next().unwrap(); + assert_eq!(expected, actual); + + fn to_map(trailers: &MessageTrailersStrs) -> HashMap<&str, &str> { + let mut map = HashMap::with_capacity(trailers.len()); + for (key, value) in trailers.iter() { + map.insert(key, value); + } + map + } + } +} |