Skip to content

Commit 4461b17

Browse files
Add binding for git_message_trailers (#749)
* Add binding for `git_message_trailers` * `cargo fmt` * Fix pointer mismatch error Fix error from `cargo run --manifest-path systest/Cargo.toml;`. You can’t just mix and match `*mut` and `*const` like that. * Remove a lot of unnecessary lifetimes Suggested-by: Alex Crichton <[email protected]> See: #749 (comment) * Remove another unnecessary lifetime Suggested-by: Alex Crichton <[email protected]> See: #749 (comment) * Use `Range<usize>` instead of `usize` I love it. Suggested-by: Alex Crichton <[email protected]> See: #749 (comment) * `cargo fmt` * Inline one-off struct Suggested-by: Alex Crichton <[email protected]> See: #749 (comment) * Implement more iterators Also change `to_str_tuple(…)` in order to share more code between two of the iterators. Suggested-by: Alex Crichton <[email protected]> See: #749 (comment) * Undo accidental and unrelated edit * Less explicit lifetimes See: #749 (comment) * Don’t need `std::marker` any more See: #749 (comment) * Correct `len(…)` See: #749 (comment) * Remove unnecessary annotation See: #749 (comment) * Implement `size_hint()` Better than the default implementation. See: #749 (comment) * Split into “bytes” and “string” iterators Support both raw bytes messages as well as normal (UTF-8) messages by making two iterators. See: #749 (comment) * Remove more lifetimes * Docs * `cargo fmt` * Undo accidental and unrelated edit (cherry picked from commit cd4c2db)
1 parent 4348694 commit 4461b17

File tree

3 files changed

+310
-2
lines changed

3 files changed

+310
-2
lines changed

libgit2-sys/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1953,6 +1953,20 @@ git_enum! {
19531953
}
19541954
}
19551955

1956+
#[repr(C)]
1957+
pub struct git_message_trailer {
1958+
pub key: *const c_char,
1959+
pub value: *const c_char,
1960+
}
1961+
1962+
#[repr(C)]
1963+
#[derive(Copy, Clone)]
1964+
pub struct git_message_trailer_array {
1965+
pub trailers: *mut git_message_trailer,
1966+
pub count: size_t,
1967+
pub _trailer_block: *mut c_char,
1968+
}
1969+
19561970
extern "C" {
19571971
// threads
19581972
pub fn git_libgit2_init() -> c_int;
@@ -3678,6 +3692,13 @@ extern "C" {
36783692
comment_char: c_char,
36793693
) -> c_int;
36803694

3695+
pub fn git_message_trailers(
3696+
out: *mut git_message_trailer_array,
3697+
message: *const c_char,
3698+
) -> c_int;
3699+
3700+
pub fn git_message_trailer_array_free(trailer: *mut git_message_trailer_array);
3701+
36813702
// packbuilder
36823703
pub fn git_packbuilder_new(out: *mut *mut git_packbuilder, repo: *mut git_repository) -> c_int;
36833704
pub fn git_packbuilder_set_threads(pb: *mut git_packbuilder, n: c_uint) -> c_uint;

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ pub use crate::indexer::{IndexerProgress, Progress};
101101
pub use crate::mailmap::Mailmap;
102102
pub use crate::mempack::Mempack;
103103
pub use crate::merge::{AnnotatedCommit, MergeOptions};
104-
pub use crate::message::{message_prettify, DEFAULT_COMMENT_CHAR};
104+
pub use crate::message::{
105+
message_prettify, message_trailers_bytes, message_trailers_strs, MessageTrailersBytes,
106+
MessageTrailersBytesIterator, MessageTrailersStrs, MessageTrailersStrsIterator,
107+
DEFAULT_COMMENT_CHAR,
108+
};
105109
pub use crate::note::{Note, Notes};
106110
pub use crate::object::Object;
107111
pub use crate::odb::{Odb, OdbObject, OdbPackwriter, OdbReader, OdbWriter};

src/message.rs

Lines changed: 284 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use core::ops::Range;
2+
use std::ffi::CStr;
13
use std::ffi::CString;
4+
use std::ptr;
25

36
use libc::{c_char, c_int};
47

@@ -31,12 +34,216 @@ fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<Strin
3134
/// The default comment character for `message_prettify` ('#')
3235
pub const DEFAULT_COMMENT_CHAR: Option<u8> = Some(b'#');
3336

37+
/// Get the trailers for the given message.
38+
///
39+
/// Use this function when you are dealing with a UTF-8-encoded message.
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.
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+
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.
68+
pub fn iter(&self) -> MessageTrailersStrsIterator<'_> {
69+
MessageTrailersStrsIterator(self.0.iter())
70+
}
71+
/// The number of trailer key–value pairs.
72+
pub fn len(&self) -> usize {
73+
self.0.len()
74+
}
75+
/// Convert to the “bytes” variant.
76+
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.
88+
pub fn iter(&self) -> MessageTrailersBytesIterator<'_> {
89+
MessageTrailersBytesIterator(self.0.iter())
90+
}
91+
/// The number of trailer key–value pairs.
92+
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 {
102+
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+
}
112+
fn iter(&self) -> MessageTrailersIterator<'_> {
113+
MessageTrailersIterator {
114+
trailers: self,
115+
range: Range {
116+
start: 0,
117+
end: self.raw.count,
118+
},
119+
}
120+
}
121+
fn len(&self) -> usize {
122+
self.raw.count
123+
}
124+
}
125+
126+
impl Drop for MessageTrailers {
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;
136+
unsafe fn from_raw(raw: *mut raw::git_message_trailer_array) -> MessageTrailers {
137+
MessageTrailers { raw: *raw }
138+
}
139+
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+
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+
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+
169+
fn size_hint(&self) -> (usize, Option<usize>) {
170+
self.0.range.size_hint()
171+
}
172+
}
173+
174+
impl ExactSizeIterator for MessageTrailersStrsIterator<'_> {
175+
fn len(&self) -> usize {
176+
self.0.range.len()
177+
}
178+
}
179+
180+
impl DoubleEndedIterator for MessageTrailersStrsIterator<'_> {
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+
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+
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+
211+
fn size_hint(&self) -> (usize, Option<usize>) {
212+
self.0.range.size_hint()
213+
}
214+
}
215+
216+
impl ExactSizeIterator for MessageTrailersBytesIterator<'_> {
217+
fn len(&self) -> usize {
218+
self.0.range.len()
219+
}
220+
}
221+
222+
impl DoubleEndedIterator for MessageTrailersBytesIterator<'_> {
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+
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+
34240
#[cfg(test)]
35241
mod tests {
36-
use crate::{message_prettify, DEFAULT_COMMENT_CHAR};
37242

38243
#[test]
39244
fn prettify() {
245+
use crate::{message_prettify, DEFAULT_COMMENT_CHAR};
246+
40247
// This does not attempt to duplicate the extensive tests for
41248
// git_message_prettify in libgit2, just a few representative values to
42249
// make sure the interface works as expected.
@@ -58,4 +265,80 @@ mod tests {
58265
"1\n"
59266
);
60267
}
268+
269+
#[test]
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+
}
61344
}

0 commit comments

Comments
 (0)