blob: 5677b7b57749d0f5a9d06a59f3e4130ccb2d86e1 [file] [log] [blame]
use libc;
use std::ffi::CString;
use std::fmt;
use std::marker;
use std::mem;
use std::ptr;
use std::str;
use crate::util::Binding;
use crate::{raw, Error, Time};
/// A Signature is used to indicate authorship of various actions throughout the
/// library.
///
/// Signatures contain a name, email, and timestamp. All fields can be specified
/// with `new` while the `now` constructor omits the timestamp. The
/// [`Repository::signature`] method can be used to create a default signature
/// with name and email values read from the configuration.
///
/// [`Repository::signature`]: struct.Repository.html#method.signature
pub struct Signature<'a> {
raw: *mut raw::git_signature,
_marker: marker::PhantomData<&'a str>,
owned: bool,
}
impl<'a> Signature<'a> {
/// Create a new action signature with a timestamp of 'now'.
///
/// See `new` for more information
pub fn now(name: &str, email: &str) -> Result<Signature<'static>, Error> {
crate::init();
let mut ret = ptr::null_mut();
let name = CString::new(name)?;
let email = CString::new(email)?;
unsafe {
try_call!(raw::git_signature_now(&mut ret, name, email));
Ok(Binding::from_raw(ret))
}
}
/// Create a new action signature.
///
/// The `time` specified is in seconds since the epoch, and the `offset` is
/// the time zone offset in minutes.
///
/// Returns error if either `name` or `email` contain angle brackets.
pub fn new(name: &str, email: &str, time: &Time) -> Result<Signature<'static>, Error> {
crate::init();
let mut ret = ptr::null_mut();
let name = CString::new(name)?;
let email = CString::new(email)?;
unsafe {
try_call!(raw::git_signature_new(
&mut ret,
name,
email,
time.seconds() as raw::git_time_t,
time.offset_minutes() as libc::c_int
));
Ok(Binding::from_raw(ret))
}
}
/// Gets the name on the signature.
///
/// Returns `None` if the name is not valid utf-8
pub fn name(&self) -> Option<&str> {
str::from_utf8(self.name_bytes()).ok()
}
/// Gets the name on the signature as a byte slice.
pub fn name_bytes(&self) -> &[u8] {
unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() }
}
/// Gets the email on the signature.
///
/// Returns `None` if the email is not valid utf-8
pub fn email(&self) -> Option<&str> {
str::from_utf8(self.email_bytes()).ok()
}
/// Gets the email on the signature as a byte slice.
pub fn email_bytes(&self) -> &[u8] {
unsafe { crate::opt_bytes(self, (*self.raw).email).unwrap() }
}
/// Get the `when` of this signature.
pub fn when(&self) -> Time {
unsafe { Binding::from_raw((*self.raw).when) }
}
/// Convert a signature of any lifetime into an owned signature with a
/// static lifetime.
pub fn to_owned(&self) -> Signature<'static> {
unsafe {
let me = mem::transmute::<&Signature<'a>, &Signature<'static>>(self);
me.clone()
}
}
}
impl<'a> Binding for Signature<'a> {
type Raw = *mut raw::git_signature;
unsafe fn from_raw(raw: *mut raw::git_signature) -> Signature<'a> {
Signature {
raw: raw,
_marker: marker::PhantomData,
owned: true,
}
}
fn raw(&self) -> *mut raw::git_signature {
self.raw
}
}
/// Creates a new signature from the give raw pointer, tied to the lifetime
/// of the given object.
///
/// This function is unsafe as there is no guarantee that `raw` is valid for
/// `'a` nor if it's a valid pointer.
pub unsafe fn from_raw_const<'b, T>(_lt: &'b T, raw: *const raw::git_signature) -> Signature<'b> {
Signature {
raw: raw as *mut raw::git_signature,
_marker: marker::PhantomData,
owned: false,
}
}
impl Clone for Signature<'static> {
fn clone(&self) -> Signature<'static> {
// TODO: can this be defined for 'a and just do a plain old copy if the
// lifetime isn't static?
let mut raw = ptr::null_mut();
let rc = unsafe { raw::git_signature_dup(&mut raw, &*self.raw) };
assert_eq!(rc, 0);
unsafe { Binding::from_raw(raw) }
}
}
impl<'a> Drop for Signature<'a> {
fn drop(&mut self) {
if self.owned {
unsafe { raw::git_signature_free(self.raw) }
}
}
}
impl<'a> fmt::Display for Signature<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} <{}>",
String::from_utf8_lossy(self.name_bytes()),
String::from_utf8_lossy(self.email_bytes())
)
}
}
#[cfg(test)]
mod tests {
use crate::{Signature, Time};
#[test]
fn smoke() {
Signature::new("foo", "bar", &Time::new(89, 0)).unwrap();
Signature::now("foo", "bar").unwrap();
assert!(Signature::new("<foo>", "bar", &Time::new(89, 0)).is_err());
assert!(Signature::now("<foo>", "bar").is_err());
let s = Signature::now("foo", "bar").unwrap();
assert_eq!(s.name(), Some("foo"));
assert_eq!(s.email(), Some("bar"));
drop(s.clone());
drop(s.to_owned());
}
}