You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
programming-rust-examples/libgit2-rs-safe/src/git/mod.rs

247 lines
6.0 KiB
Rust

mod raw;
use std::error;
use std::fmt;
use std::result;
#[derive(Debug)]
pub struct Error {
code: i32,
message: String,
class: i32
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
// Displaying an `Error` simply displays the message from libgit2.
self.message.fmt(f)
}
}
impl error::Error for Error { }
pub type Result<T> = result::Result<T, Error>;
use std::os::raw::c_int;
use std::ffi::CStr;
fn check(code: c_int) -> Result<c_int> {
if code >= 0 {
return Ok(code);
}
unsafe {
let error = raw::giterr_last();
// libgit2 ensures that (*error).message is always non-null and null
// terminated, so this call is safe.
let message = CStr::from_ptr((*error).message)
.to_string_lossy()
.into_owned();
Err(Error {
code: code as i32,
message,
class: (*error).klass as i32
})
}
}
/// A Git repository.
pub struct Repository {
// This must always be a pointer to a live `git_repository` structure.
// No other `Repository` may point to it.
raw: *mut raw::git_repository
}
use std::path::Path;
use std::ptr;
impl Repository {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Repository> {
ensure_initialized();
let path = path_to_cstring(path.as_ref())?;
let mut repo = ptr::null_mut();
unsafe {
check(raw::git_repository_open(&mut repo, path.as_ptr()))?;
}
Ok(Repository { raw: repo })
}
}
fn ensure_initialized() {
static ONCE: std::sync::Once = std::sync::Once::new();
ONCE.call_once(|| {
unsafe {
check(raw::git_libgit2_init())
.expect("initializing libgit2 failed");
assert_eq!(libc::atexit(shutdown), 0);
}
});
}
extern fn shutdown() {
unsafe {
if let Err(e) = check(raw::git_libgit2_shutdown()) {
eprintln!("shutting down libgit2 failed: {}", e);
std::process::abort();
}
}
}
impl Drop for Repository {
fn drop(&mut self) {
unsafe {
raw::git_repository_free(self.raw);
}
}
}
use std::ffi::CString;
#[cfg(unix)]
fn path_to_cstring(path: &Path) -> Result<CString> {
// The `as_bytes` method exists only on Unix-like systems.
use std::os::unix::ffi::OsStrExt;
Ok(CString::new(path.as_os_str().as_bytes())?)
}
#[cfg(windows)]
fn path_to_cstring(path: &Path) -> Result<CString> {
// Try to convert to UTF-8. If this fails, libgit2 can't handle the path
// anyway.
match path.to_str() {
Some(s) => Ok(CString::new(s)?),
None => {
let message = format!("Couldn't convert path '{}' to UTF-8",
path.display());
Err(message.into())
}
}
}
impl From<String> for Error {
fn from(message: String) -> Error {
Error { code: -1, message, class: 0 }
}
}
// NulError is what `CString::new` returns if a string
// has embedded zero bytes.
impl From<std::ffi::NulError> for Error {
fn from(e: std::ffi::NulError) -> Error {
Error { code: -1, message: e.to_string(), class: 0 }
}
}
/// The identifier of some sort of object stored in the Git object
/// database: a commit, tree, blob, tag, etc. This is a wide hash of the
/// object's contents.
pub struct Oid {
pub raw: raw::git_oid
}
use std::mem;
use std::os::raw::c_char;
impl Repository {
pub fn reference_name_to_id(&self, name: &str) -> Result<Oid> {
let name = CString::new(name)?;
unsafe {
let oid = {
let mut oid = mem::MaybeUninit::uninit();
check(raw::git_reference_name_to_id(
oid.as_mut_ptr(), self.raw,
name.as_ptr() as *const c_char))?;
oid.assume_init()
};
Ok(Oid { raw: oid })
}
}
}
use std::marker::PhantomData;
pub struct Commit<'repo> {
// This must always be a pointer to a usable `git_commit` structure.
raw: *mut raw::git_commit,
_marker: PhantomData<&'repo Repository>
}
impl Repository {
pub fn find_commit(&self, oid: &Oid) -> Result<Commit> {
let mut commit = ptr::null_mut();
unsafe {
check(raw::git_commit_lookup(&mut commit, self.raw, &oid.raw))?;
}
Ok(Commit { raw: commit, _marker: PhantomData })
}
}
impl<'repo> Drop for Commit<'repo> {
fn drop(&mut self) {
unsafe {
raw::git_commit_free(self.raw);
}
}
}
impl<'repo> Commit<'repo> {
pub fn author(&self) -> Signature {
unsafe {
Signature {
raw: raw::git_commit_author(self.raw),
_marker: PhantomData
}
}
}
pub fn message(&self) -> Option<&str> {
unsafe {
let message = raw::git_commit_message(self.raw);
char_ptr_to_str(self, message)
}
}
}
pub struct Signature<'text> {
raw: *const raw::git_signature,
_marker: PhantomData<&'text str>
}
impl<'text> Signature<'text> {
/// Return the author's name as a `&str`,
/// or `None` if it is not well-formed UTF-8.
pub fn name(&self) -> Option<&str> {
unsafe {
char_ptr_to_str(self, (*self.raw).name)
}
}
/// Return the author's email as a `&str`,
/// or `None` if it is not well-formed UTF-8.
pub fn email(&self) -> Option<&str> {
unsafe {
char_ptr_to_str(self, (*self.raw).email)
}
}
}
/// Try to borrow a `&str` from `ptr`, given that `ptr` may be null or
/// refer to ill-formed UTF-8. Give the result a lifetime as if it were
/// borrowed from `_owner`.
///
/// Safety: if `ptr` is non-null, it must point to a null-terminated C
/// string that is safe to access for at least as long as the lifetime of
/// `_owner`.
unsafe fn char_ptr_to_str<T>(_owner: &T, ptr: *const c_char) -> Option<&str> {
if ptr.is_null() {
return None;
} else {
CStr::from_ptr(ptr).to_str().ok()
}
}