use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::path::Path;
use std::str;
use crate::{raw, Error, IntoCString, ObjectType};
use crate::util::{c_cmp_to_ordering, Binding};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(not(feature = "unstable-sha256"), non_exhaustive)]
pub enum ObjectFormat {
Sha1,
#[cfg(feature = "unstable-sha256")]
Sha256,
}
impl ObjectFormat {
pub fn str(&self) -> &'static str {
match self {
ObjectFormat::Sha1 => "sha1",
#[cfg(feature = "unstable-sha256")]
ObjectFormat::Sha256 => "sha256",
}
}
}
impl fmt::Display for ObjectFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.str().fmt(f)
}
}
impl Binding for ObjectFormat {
type Raw = raw::git_oid_t;
unsafe fn from_raw(raw: raw::git_oid_t) -> Self {
match raw {
raw::GIT_OID_SHA1 => ObjectFormat::Sha1,
#[cfg(feature = "unstable-sha256")]
raw::GIT_OID_SHA256 => ObjectFormat::Sha256,
_ => panic!("Unknown git oid type"),
}
}
fn raw(&self) -> Self::Raw {
match self {
ObjectFormat::Sha1 => raw::GIT_OID_SHA1,
#[cfg(feature = "unstable-sha256")]
ObjectFormat::Sha256 => raw::GIT_OID_SHA256,
}
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct Oid {
raw: raw::git_oid,
}
impl Oid {
pub const ZERO_SHA1: Oid = Oid {
raw: raw::git_oid {
#[cfg(feature = "unstable-sha256")]
kind: raw::GIT_OID_SHA1 as libc::c_uchar,
id: [0; raw::GIT_OID_MAX_SIZE],
},
};
#[cfg(feature = "unstable-sha256")]
pub const ZERO_SHA256: Oid = Oid {
raw: raw::git_oid {
kind: raw::GIT_OID_SHA256 as libc::c_uchar,
id: [0; raw::GIT_OID_MAX_SIZE],
},
};
pub fn from_str(s: &str) -> Result<Oid, Error> {
Self::from_str_ext(s, ObjectFormat::Sha1)
}
pub fn from_str_ext(s: &str, format: ObjectFormat) -> Result<Oid, Error> {
crate::init();
let mut raw = crate::util::zeroed_raw_oid();
let data = s.as_bytes().as_ptr() as *const libc::c_char;
let len = s.len() as libc::size_t;
unsafe {
#[cfg(not(feature = "unstable-sha256"))]
{
let _ = format;
try_call!(raw::git_oid_fromstrn(&mut raw, data, len));
}
#[cfg(feature = "unstable-sha256")]
try_call!(raw::git_oid_fromstrn(&mut raw, data, len, format.raw()));
}
Ok(Oid { raw })
}
pub fn from_bytes(bytes: &[u8]) -> Result<Oid, Error> {
crate::init();
let mut raw = crate::util::zeroed_raw_oid();
#[cfg(not(feature = "unstable-sha256"))]
{
if bytes.len() != raw::GIT_OID_SHA1_SIZE {
return Err(Error::from_str(&format!(
"raw byte array must be 20 bytes, but got {}",
bytes.len()
)));
}
unsafe {
try_call!(raw::git_oid_fromraw(&mut raw, bytes.as_ptr()));
}
}
#[cfg(feature = "unstable-sha256")]
{
let oid_type = match bytes.len() {
raw::GIT_OID_SHA1_SIZE => raw::GIT_OID_SHA1,
raw::GIT_OID_SHA256_SIZE => raw::GIT_OID_SHA256,
_ => {
return Err(Error::from_str(&format!(
"raw byte array must be 20 bytes (SHA1) or 32 bytes (SHA256), but got {}",
bytes.len()
)));
}
};
unsafe {
try_call!(raw::git_oid_fromraw(&mut raw, bytes.as_ptr(), oid_type));
}
}
Ok(Oid { raw })
}
#[deprecated(since = "0.21.0", note = "use `Oid::ZERO_SHA1` instead")]
pub fn zero() -> Oid {
Self::ZERO_SHA1
}
pub fn hash_object(kind: ObjectType, bytes: &[u8]) -> Result<Oid, Error> {
Self::hash_object_ext(kind, bytes, ObjectFormat::Sha1)
}
pub fn hash_object_ext(
kind: ObjectType,
bytes: &[u8],
format: ObjectFormat,
) -> Result<Oid, Error> {
crate::init();
let mut out = crate::util::zeroed_raw_oid();
let data = bytes.as_ptr() as *const libc::c_void;
unsafe {
#[cfg(not(feature = "unstable-sha256"))]
{
let _ = format;
try_call!(raw::git_odb_hash(&mut out, data, bytes.len(), kind.raw()));
}
#[cfg(feature = "unstable-sha256")]
try_call!(raw::git_odb_hash(
&mut out,
data,
bytes.len(),
kind.raw(),
format.raw()
));
}
Ok(Oid { raw: out })
}
pub fn hash_file<P: AsRef<Path>>(kind: ObjectType, path: P) -> Result<Oid, Error> {
Self::hash_file_ext(kind, path, ObjectFormat::Sha1)
}
pub fn hash_file_ext<P: AsRef<Path>>(
kind: ObjectType,
path: P,
format: ObjectFormat,
) -> Result<Oid, Error> {
crate::init();
let rpath = path.as_ref().into_c_string()?;
let mut out = crate::util::zeroed_raw_oid();
unsafe {
#[cfg(not(feature = "unstable-sha256"))]
{
let _ = format;
try_call!(raw::git_odb_hashfile(&mut out, rpath, kind.raw()));
}
#[cfg(feature = "unstable-sha256")]
try_call!(raw::git_odb_hashfile(
&mut out,
rpath,
kind.raw(),
format.raw()
));
}
Ok(Oid { raw: out })
}
pub fn as_bytes(&self) -> &[u8] {
#[cfg(not(feature = "unstable-sha256"))]
{
&self.raw.id
}
#[cfg(feature = "unstable-sha256")]
{
let size = match self.raw.kind as raw::git_oid_t {
raw::GIT_OID_SHA1 => raw::GIT_OID_SHA1_SIZE,
raw::GIT_OID_SHA256 => raw::GIT_OID_SHA256_SIZE,
_ => panic!("Unknown git oid type"),
};
&self.raw.id[..size]
}
}
pub fn is_zero(&self) -> bool {
unsafe { raw::git_oid_is_zero(&self.raw) == 1 }
}
pub fn object_format(&self) -> ObjectFormat {
#[cfg(not(feature = "unstable-sha256"))]
{
ObjectFormat::Sha1
}
#[cfg(feature = "unstable-sha256")]
{
unsafe { Binding::from_raw(self.raw.kind as raw::git_oid_t) }
}
}
}
impl Binding for Oid {
type Raw = *const raw::git_oid;
unsafe fn from_raw(oid: *const raw::git_oid) -> Oid {
Oid { raw: *oid }
}
fn raw(&self) -> *const raw::git_oid {
&self.raw as *const _
}
}
impl fmt::Debug for Oid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for Oid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut dst = [0u8; raw::GIT_OID_MAX_HEXSIZE + 1];
unsafe {
raw::git_oid_tostr(
dst.as_mut_ptr() as *mut libc::c_char,
dst.len() as libc::size_t,
&self.raw,
);
}
let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()];
str::from_utf8(s).unwrap().fmt(f)
}
}
impl str::FromStr for Oid {
type Err = Error;
fn from_str(s: &str) -> Result<Oid, Error> {
Oid::from_str(s)
}
}
impl PartialEq for Oid {
fn eq(&self, other: &Oid) -> bool {
unsafe { raw::git_oid_equal(&self.raw, &other.raw) != 0 }
}
}
impl Eq for Oid {}
impl PartialOrd for Oid {
fn partial_cmp(&self, other: &Oid) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Oid {
fn cmp(&self, other: &Oid) -> Ordering {
c_cmp_to_ordering(unsafe { raw::git_oid_cmp(&self.raw, &other.raw) })
}
}
impl Hash for Oid {
fn hash<H: Hasher>(&self, into: &mut H) {
#[cfg(feature = "unstable-sha256")]
self.raw.kind.hash(into);
self.raw.id.hash(into)
}
}
impl AsRef<[u8]> for Oid {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
#[cfg(not(feature = "unstable-sha256"))]
const _: () = assert!(raw::GIT_OID_MAX_SIZE == 20);
#[cfg(feature = "unstable-sha256")]
const _: () = {
assert!(raw::GIT_OID_SHA1_SIZE == 20);
assert!(raw::GIT_OID_SHA256_SIZE == 32);
assert!(raw::GIT_OID_MAX_SIZE == 32);
};
#[cfg(test)]
mod tests {
use std::fs::File;
use std::io::prelude::*;
use libgit2_sys as raw;
use super::Error;
use super::Oid;
use crate::ObjectFormat;
use crate::ObjectType;
use tempfile::TempDir;
#[test]
fn conversions() {
assert!(Oid::from_str("foo").is_err());
assert!(Oid::from_str("decbf2be529ab6557d5429922251e5ee36519817").is_ok());
assert!(Oid::from_bytes(b"foo").is_err());
assert!(Oid::from_bytes(b"00000000000000000000").is_ok());
}
#[test]
fn conversions_ext_sha1() {
assert!(Oid::from_str_ext("foo", ObjectFormat::Sha1).is_err());
assert!(Oid::from_str_ext(
"decbf2be529ab6557d5429922251e5ee36519817",
ObjectFormat::Sha1
)
.is_ok());
let sha1 = Oid::ZERO_SHA1;
assert_eq!(sha1.as_bytes().len(), raw::GIT_OID_SHA1_SIZE);
assert_eq!(sha1.to_string().len(), raw::GIT_OID_SHA1_HEXSIZE);
}
#[test]
#[cfg(feature = "unstable-sha256")]
fn conversions_ext_sha256() {
assert!(Oid::from_str_ext("foo", ObjectFormat::Sha256).is_err());
assert!(Oid::from_str_ext(
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
ObjectFormat::Sha256
)
.is_ok());
let sha256 = Oid::ZERO_SHA256;
assert_eq!(sha256.as_bytes().len(), raw::GIT_OID_SHA256_SIZE);
assert_eq!(sha256.to_string().len(), raw::GIT_OID_SHA256_HEXSIZE);
assert_ne!(Oid::ZERO_SHA1, sha256);
}
#[test]
fn object_format_always_sha1() {
let oid = Oid::ZERO_SHA1;
assert_eq!(oid.object_format(), ObjectFormat::Sha1);
}
#[test]
fn object_format_from_oid_ext_sha1() {
let sha1 = Oid::from_str_ext(
"decbf2be529ab6557d5429922251e5ee36519817",
ObjectFormat::Sha1,
)
.unwrap();
assert_eq!(sha1.object_format(), ObjectFormat::Sha1);
}
#[test]
#[cfg(feature = "unstable-sha256")]
fn object_format_from_oid_ext_sha256() {
assert_eq!(Oid::ZERO_SHA256.object_format(), ObjectFormat::Sha256);
let sha256 = Oid::from_str_ext(
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
ObjectFormat::Sha256,
)
.unwrap();
assert_eq!(sha256.object_format(), ObjectFormat::Sha256);
}
#[test]
fn comparisons() -> Result<(), Error> {
assert_eq!(Oid::from_str("decbf2b")?, Oid::from_str("decbf2b")?);
assert!(Oid::from_str("decbf2b")? <= Oid::from_str("decbf2b")?);
assert!(Oid::from_str("decbf2b")? >= Oid::from_str("decbf2b")?);
{
let o = Oid::from_str("decbf2b")?;
assert_eq!(o, o);
assert!(o <= o);
assert!(o >= o);
}
assert_eq!(
Oid::from_str("decbf2b")?,
Oid::from_str("decbf2b000000000000000000000000000000000")?
);
assert!(
Oid::from_bytes(b"00000000000000000000")? < Oid::from_bytes(b"00000000000000000001")?
);
assert!(Oid::from_bytes(b"00000000000000000000")? < Oid::from_str("decbf2b")?);
assert_eq!(
Oid::from_bytes(b"00000000000000000000")?,
Oid::from_str("3030303030303030303030303030303030303030")?
);
Ok(())
}
#[test]
fn comparisons_ext_sha1() -> Result<(), Error> {
let a = Oid::from_str_ext("decbf2b", ObjectFormat::Sha1)?;
let b = Oid::from_str_ext(
"decbf2b000000000000000000000000000000000",
ObjectFormat::Sha1,
)?;
assert_eq!(a, b);
assert!(a <= b);
assert!(a >= b);
assert!(Oid::from_bytes(b"00000000000000000000")? < a);
Ok(())
}
#[test]
#[cfg(feature = "unstable-sha256")]
fn comparisons_ext_sha256() -> Result<(), Error> {
const HEX: &str = "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
let a = Oid::from_str_ext(HEX, ObjectFormat::Sha256)?;
let b = Oid::from_str_ext(HEX, ObjectFormat::Sha256)?;
assert_eq!(a, b);
assert!(a <= b);
assert!(a >= b);
assert_eq!(
Oid::from_str_ext("abcdef12", ObjectFormat::Sha256)?,
Oid::from_str_ext(
"abcdef1200000000000000000000000000000000000000000000000000000000",
ObjectFormat::Sha256
)?
);
assert!(
Oid::from_bytes(b"00000000000000000000000000000000")?
< Oid::from_bytes(b"00000000000000000000000000000001")?
);
assert!(Oid::from_bytes(b"00000000000000000000000000000000")? < a);
Ok(())
}
#[test]
fn zero_is_zero() {
assert!(Oid::ZERO_SHA1.is_zero());
assert_eq!(Oid::ZERO_SHA1.object_format(), ObjectFormat::Sha1);
}
#[test]
#[cfg(feature = "unstable-sha256")]
fn zero_sha256_is_zero() {
assert!(Oid::ZERO_SHA256.is_zero());
assert_eq!(Oid::ZERO_SHA256.object_format(), ObjectFormat::Sha256);
}
#[test]
fn hash_object() {
let bytes = "Hello".as_bytes();
let oid = Oid::hash_object(ObjectType::Blob, bytes).unwrap();
assert_eq!(oid.to_string().len(), raw::GIT_OID_SHA1_HEXSIZE);
assert_eq!(oid.as_bytes().len(), raw::GIT_OID_SHA1_SIZE);
}
#[test]
fn hash_object_ext_sha1() -> Result<(), Error> {
let oid = Oid::hash_object_ext(ObjectType::Blob, b"hello world", ObjectFormat::Sha1)?;
assert_eq!(oid.to_string().len(), raw::GIT_OID_SHA1_HEXSIZE);
assert_eq!(oid.as_bytes().len(), raw::GIT_OID_SHA1_SIZE);
Ok(())
}
#[test]
#[cfg(feature = "unstable-sha256")]
fn hash_object_ext_sha256() -> Result<(), Error> {
let bytes = b"hello world";
let sha1 = Oid::hash_object_ext(ObjectType::Blob, bytes, ObjectFormat::Sha1)?;
let sha256 = Oid::hash_object_ext(ObjectType::Blob, bytes, ObjectFormat::Sha256)?;
assert_eq!(sha256.to_string().len(), raw::GIT_OID_SHA256_HEXSIZE);
assert_eq!(sha256.as_bytes().len(), raw::GIT_OID_SHA256_SIZE);
assert_ne!(sha1, sha256);
Ok(())
}
#[test]
fn hash_file() {
let td = TempDir::new().unwrap();
let path = td.path().join("hello.txt");
let mut file = File::create(&path).unwrap();
file.write_all("Hello".as_bytes()).unwrap();
let oid = Oid::hash_file(ObjectType::Blob, &path).unwrap();
assert_eq!(oid.to_string().len(), raw::GIT_OID_SHA1_HEXSIZE);
assert_eq!(oid.as_bytes().len(), raw::GIT_OID_SHA1_SIZE);
}
#[test]
fn hash_file_ext_sha1() -> Result<(), Error> {
let td = TempDir::new().unwrap();
let path = td.path().join("test.txt");
let mut file = File::create(&path).unwrap();
file.write_all(b"test content").unwrap();
let oid = Oid::hash_file_ext(ObjectType::Blob, &path, ObjectFormat::Sha1)?;
assert_eq!(oid.to_string().len(), raw::GIT_OID_SHA1_HEXSIZE);
assert_eq!(oid.as_bytes().len(), raw::GIT_OID_SHA1_SIZE);
Ok(())
}
#[test]
#[cfg(feature = "unstable-sha256")]
fn hash_file_ext_sha256() -> Result<(), Error> {
let td = TempDir::new().unwrap();
let path = td.path().join("test.txt");
let mut file = File::create(&path).unwrap();
file.write_all(b"test content").unwrap();
let sha1 = Oid::hash_file_ext(ObjectType::Blob, &path, ObjectFormat::Sha1)?;
let sha256 = Oid::hash_file_ext(ObjectType::Blob, &path, ObjectFormat::Sha256)?;
assert_eq!(sha256.to_string().len(), raw::GIT_OID_SHA256_HEXSIZE);
assert_eq!(sha256.as_bytes().len(), raw::GIT_OID_SHA256_SIZE);
assert_ne!(sha1, sha256);
Ok(())
}
}