git2/
signature.rs

1use std::ffi::CString;
2use std::fmt;
3use std::marker;
4use std::mem;
5use std::ptr;
6use std::str;
7
8use crate::util::Binding;
9use crate::{raw, Error, Time};
10
11/// A Signature is used to indicate authorship of various actions throughout the
12/// library.
13///
14/// Signatures contain a name, email, and timestamp. All fields can be specified
15/// with `new` while the `now` constructor omits the timestamp. The
16/// [`Repository::signature`] method can be used to create a default signature
17/// with name and email values read from the configuration.
18///
19/// [`Repository::signature`]: struct.Repository.html#method.signature
20pub struct Signature<'a> {
21    raw: *mut raw::git_signature,
22    _marker: marker::PhantomData<&'a str>,
23    owned: bool,
24}
25
26impl<'a> Signature<'a> {
27    /// Create a new action signature with a timestamp of 'now'.
28    ///
29    /// See `new` for more information
30    pub fn now(name: &str, email: &str) -> Result<Signature<'static>, Error> {
31        crate::init();
32        let mut ret = ptr::null_mut();
33        let name = CString::new(name)?;
34        let email = CString::new(email)?;
35        unsafe {
36            try_call!(raw::git_signature_now(&mut ret, name, email));
37            Ok(Binding::from_raw(ret))
38        }
39    }
40
41    /// Create a new action signature.
42    ///
43    /// The `time` specified is in seconds since the epoch, and the `offset` is
44    /// the time zone offset in minutes.
45    ///
46    /// Returns error if either `name` or `email` contain angle brackets.
47    pub fn new(name: &str, email: &str, time: &Time) -> Result<Signature<'static>, Error> {
48        crate::init();
49        let mut ret = ptr::null_mut();
50        let name = CString::new(name)?;
51        let email = CString::new(email)?;
52        unsafe {
53            try_call!(raw::git_signature_new(
54                &mut ret,
55                name,
56                email,
57                time.seconds() as raw::git_time_t,
58                time.offset_minutes() as libc::c_int
59            ));
60            Ok(Binding::from_raw(ret))
61        }
62    }
63
64    /// Gets the name on the signature.
65    ///
66    /// Returns `None` if the name is not valid utf-8
67    pub fn name(&self) -> Option<&str> {
68        str::from_utf8(self.name_bytes()).ok()
69    }
70
71    /// Gets the name on the signature as a byte slice.
72    pub fn name_bytes(&self) -> &[u8] {
73        unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() }
74    }
75
76    /// Gets the email on the signature.
77    ///
78    /// Returns `None` if the email is not valid utf-8
79    pub fn email(&self) -> Option<&str> {
80        str::from_utf8(self.email_bytes()).ok()
81    }
82
83    /// Gets the email on the signature as a byte slice.
84    pub fn email_bytes(&self) -> &[u8] {
85        unsafe { crate::opt_bytes(self, (*self.raw).email).unwrap() }
86    }
87
88    /// Get the `when` of this signature.
89    pub fn when(&self) -> Time {
90        unsafe { Binding::from_raw((*self.raw).when) }
91    }
92
93    /// Convert a signature of any lifetime into an owned signature with a
94    /// static lifetime.
95    pub fn to_owned(&self) -> Signature<'static> {
96        unsafe {
97            let me = mem::transmute::<&Signature<'a>, &Signature<'static>>(self);
98            me.clone()
99        }
100    }
101}
102
103impl<'a> Binding for Signature<'a> {
104    type Raw = *mut raw::git_signature;
105    unsafe fn from_raw(raw: *mut raw::git_signature) -> Signature<'a> {
106        Signature {
107            raw,
108            _marker: marker::PhantomData,
109            owned: true,
110        }
111    }
112    fn raw(&self) -> *mut raw::git_signature {
113        self.raw
114    }
115}
116
117/// Creates a new signature from the give raw pointer, tied to the lifetime
118/// of the given object.
119///
120/// This function is unsafe as there is no guarantee that `raw` is valid for
121/// `'a` nor if it's a valid pointer.
122pub unsafe fn from_raw_const<'b, T>(_lt: &'b T, raw: *const raw::git_signature) -> Signature<'b> {
123    Signature {
124        raw: raw as *mut raw::git_signature,
125        _marker: marker::PhantomData,
126        owned: false,
127    }
128}
129
130impl Clone for Signature<'static> {
131    fn clone(&self) -> Signature<'static> {
132        // TODO: can this be defined for 'a and just do a plain old copy if the
133        //       lifetime isn't static?
134        let mut raw = ptr::null_mut();
135        let rc = unsafe { raw::git_signature_dup(&mut raw, &*self.raw) };
136        assert_eq!(rc, 0);
137        unsafe { Binding::from_raw(raw) }
138    }
139}
140
141impl<'a> Drop for Signature<'a> {
142    fn drop(&mut self) {
143        if self.owned {
144            unsafe { raw::git_signature_free(self.raw) }
145        }
146    }
147}
148
149impl<'a> fmt::Display for Signature<'a> {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(
152            f,
153            "{} <{}>",
154            String::from_utf8_lossy(self.name_bytes()),
155            String::from_utf8_lossy(self.email_bytes())
156        )
157    }
158}
159
160impl PartialEq for Signature<'_> {
161    fn eq(&self, other: &Self) -> bool {
162        self.when() == other.when()
163            && self.email_bytes() == other.email_bytes()
164            && self.name_bytes() == other.name_bytes()
165    }
166}
167
168impl Eq for Signature<'_> {}
169
170#[cfg(test)]
171mod tests {
172    use crate::{Signature, Time};
173
174    #[test]
175    fn smoke() {
176        Signature::new("foo", "bar", &Time::new(89, 0)).unwrap();
177        Signature::now("foo", "bar").unwrap();
178        assert!(Signature::new("<foo>", "bar", &Time::new(89, 0)).is_err());
179        assert!(Signature::now("<foo>", "bar").is_err());
180
181        let s = Signature::now("foo", "bar").unwrap();
182        assert_eq!(s.name(), Some("foo"));
183        assert_eq!(s.email(), Some("bar"));
184
185        drop(s.clone());
186        drop(s.to_owned());
187    }
188}