diff --git a/git-hash/src/owned.rs b/git-hash/src/owned.rs index 88bf44c9880..5de0c7b6158 100644 --- a/git-hash/src/owned.rs +++ b/git-hash/src/owned.rs @@ -1,4 +1,3 @@ -use std::cmp::Ordering; use std::{borrow::Borrow, convert::TryInto, fmt, ops::Deref}; use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST}; @@ -13,86 +12,7 @@ pub struct Prefix { } /// -pub mod prefix { - use quick_error::quick_error; - - quick_error! { - /// The error returned by [Prefix::try_from_id()][super::Prefix::try_from_id()]. - #[derive(Debug)] - #[allow(missing_docs)] - pub enum Error { - TooShort { hex_len: usize } { - display("The minimum hex length of a short object id is 4, got {}", hex_len) - } - TooLong { object_kind: crate::Kind, hex_len: usize } { - display("An object of kind {} cannot be larger than {} in hex, but {} was requested", object_kind, object_kind.len_in_hex(), hex_len) - } - } - } -} - -impl Prefix { - /// Create a new instance by taking a full `id` as input and truncating it to `hex_len`. - /// - /// For instance, with `hex_len` of 7 the resulting prefix is 3.5 bytes, or 3 bytes and 4 bits - /// wide, with all other bytes and bits set to zero. - pub fn new(id: impl AsRef, hex_len: usize) -> Result { - let id = id.as_ref(); - if hex_len > id.kind().len_in_hex() { - Err(prefix::Error::TooLong { - object_kind: id.kind(), - hex_len, - }) - } else if hex_len < 4 { - Err(prefix::Error::TooShort { hex_len }) - } else { - let mut prefix = ObjectId::null(id.kind()); - let b = prefix.as_mut_slice(); - let copy_len = (hex_len + 1) / 2; - b[..copy_len].copy_from_slice(&id.as_bytes()[..copy_len]); - if hex_len % 2 == 1 { - b[hex_len / 2] &= 0xf0; - } - - Ok(Prefix { bytes: prefix, hex_len }) - } - } - - /// Returns the prefix as object id. - /// - /// Note that it may be deceptive to use given that it looks like a full - /// object id, even though its post-prefix bytes/bits are set to zero. - pub fn as_oid(&self) -> &oid { - &self.bytes - } - - /// Return the amount of hexadecimal characters that are set in the prefix. - /// - /// This gives the prefix a granularity of 4 bits. - pub fn hex_len(&self) -> usize { - self.hex_len - } - - /// Provided with candidate id which is a full hash, determine how this prefix compares to it, - /// only looking at the prefix bytes, ignoring everything behind that. - pub fn cmp_oid(&self, candidate: &oid) -> Ordering { - let common_len = self.hex_len / 2; - - self.bytes.as_bytes()[..common_len] - .cmp(&candidate.as_bytes()[..common_len]) - .then(if self.hex_len % 2 == 1 { - let half_byte_idx = self.hex_len / 2; - self.bytes.as_bytes()[half_byte_idx].cmp(&(candidate.as_bytes()[half_byte_idx] & 0xf0)) - } else { - Ordering::Equal - }) - } - - /// Create an instance from the given hexadecimal prefix, e.g. `35e77c16` would yield a `Prefix` with `hex_len()` = 8. - pub fn from_hex(_hex: &str) -> Self { - todo!("Prefix::from_hex()") - } -} +pub mod prefix; /// An owned hash identifying objects, most commonly Sha1 #[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)] diff --git a/git-hash/src/owned/prefix.rs b/git-hash/src/owned/prefix.rs new file mode 100644 index 00000000000..f1568b961e6 --- /dev/null +++ b/git-hash/src/owned/prefix.rs @@ -0,0 +1,86 @@ +use crate::{oid, ObjectId, Prefix}; +use quick_error::quick_error; +use std::cmp::Ordering; + +quick_error! { + /// The error returned by [Prefix::try_from_id()][super::Prefix::try_from_id()]. + #[derive(Debug)] + #[allow(missing_docs)] + pub enum Error { + TooShort { hex_len: usize } { + display("The minimum hex length of a short object id is 4, got {}", hex_len) + } + TooLong { object_kind: crate::Kind, hex_len: usize } { + display("An object of kind {} cannot be larger than {} in hex, but {} was requested", object_kind, object_kind.len_in_hex(), hex_len) + } + } +} + +impl Prefix { + /// Create a new instance by taking a full `id` as input and truncating it to `hex_len`. + /// + /// For instance, with `hex_len` of 7 the resulting prefix is 3.5 bytes, or 3 bytes and 4 bits + /// wide, with all other bytes and bits set to zero. + pub fn new(id: impl AsRef, hex_len: usize) -> Result { + let id = id.as_ref(); + if hex_len > id.kind().len_in_hex() { + Err(Error::TooLong { + object_kind: id.kind(), + hex_len, + }) + } else if hex_len < 4 { + Err(Error::TooShort { hex_len }) + } else { + let mut prefix = ObjectId::null(id.kind()); + let b = prefix.as_mut_slice(); + let copy_len = (hex_len + 1) / 2; + b[..copy_len].copy_from_slice(&id.as_bytes()[..copy_len]); + if hex_len % 2 == 1 { + b[hex_len / 2] &= 0xf0; + } + + Ok(Prefix { bytes: prefix, hex_len }) + } + } + + /// Returns the prefix as object id. + /// + /// Note that it may be deceptive to use given that it looks like a full + /// object id, even though its post-prefix bytes/bits are set to zero. + pub fn as_oid(&self) -> &oid { + &self.bytes + } + + /// Return the amount of hexadecimal characters that are set in the prefix. + /// + /// This gives the prefix a granularity of 4 bits. + pub fn hex_len(&self) -> usize { + self.hex_len + } + + /// Provided with candidate id which is a full hash, determine how this prefix compares to it, + /// only looking at the prefix bytes, ignoring everything behind that. + pub fn cmp_oid(&self, candidate: &oid) -> Ordering { + let common_len = self.hex_len / 2; + + self.bytes.as_bytes()[..common_len] + .cmp(&candidate.as_bytes()[..common_len]) + .then(if self.hex_len % 2 == 1 { + let half_byte_idx = self.hex_len / 2; + self.bytes.as_bytes()[half_byte_idx].cmp(&(candidate.as_bytes()[half_byte_idx] & 0xf0)) + } else { + Ordering::Equal + }) + } + + /// Create an instance from the given hexadecimal prefix, e.g. `35e77c16` would yield a `Prefix` with `hex_len()` = 8. + pub fn from_hex(_hex: &str) -> Self { + todo!("Prefix::from_hex()") + } +} + +impl std::fmt::Display for Prefix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.bytes.to_hex_with_len(self.hex_len).fmt(f) + } +} diff --git a/git-hash/tests/oid/mod.rs b/git-hash/tests/oid/mod.rs index c0205a59089..acc6bbaee85 100644 --- a/git-hash/tests/oid/mod.rs +++ b/git-hash/tests/oid/mod.rs @@ -14,17 +14,19 @@ mod prefix { prefix.cmp_oid(&hex_to_id("b920bbf055e1efb9080592a409d3975738b6efb3")), Ordering::Less ); + assert_eq!(prefix.to_string(), "b920bbb"); } #[test] fn it_detects_equality() { - let id = hex_to_id("b920bbb055e1efb9080592a409d3975738b6efb3"); - let prefix = git_hash::Prefix::new(id, 7).unwrap(); + let id = hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb3"); + let prefix = git_hash::Prefix::new(id, 6).unwrap(); assert_eq!(prefix.cmp_oid(&id), Ordering::Equal); assert_eq!( - prefix.cmp_oid(&hex_to_id("b920bbbfffffffffffffffffffffffffffffffff")), + prefix.cmp_oid(&hex_to_id("a920bbffffffffffffffffffffffffffffffffff")), Ordering::Equal ); + assert_eq!(prefix.to_string(), "a920bb"); } } mod new { diff --git a/git-repository/src/id.rs b/git-repository/src/id.rs index 2ca48a936cf..bd27ab26497 100644 --- a/git-repository/src/id.rs +++ b/git-repository/src/id.rs @@ -51,8 +51,8 @@ impl<'repo> Id<'repo> { } /// -mod prefix { - /// Returned by [`Oid::prefix()`][super::Oid::prefix()]. +pub mod prefix { + /// Returned by [`Id::prefix()`][super::Id::prefix()]. #[derive(thiserror::Error, Debug)] #[allow(missing_docs)] pub enum Error { diff --git a/git-repository/src/object/commit.rs b/git-repository/src/object/commit.rs index f7fa487e471..53e8183c042 100644 --- a/git-repository/src/object/commit.rs +++ b/git-repository/src/object/commit.rs @@ -22,6 +22,12 @@ mod error { pub use error::Error; impl<'repo> Commit<'repo> { + /// Turn this objects id into a shortened id with a length in hex as configured by `core.abbrev`. + pub fn short_id(&self) -> Result { + use crate::ext::ObjectIdExt; + self.id.attach(self.repo).prefix() + } + /// Parse the commits message into a [`MessageRef`][git_object::commit::MessageRef], after decoding the entire commit object. pub fn message(&self) -> Result, git_object::decode::Error> { Ok(self.decode()?.message()) diff --git a/git-repository/src/object/impls.rs b/git-repository/src/object/impls.rs index 72a10a749d3..980627a3c01 100644 --- a/git-repository/src/object/impls.rs +++ b/git-repository/src/object/impls.rs @@ -15,6 +15,17 @@ impl<'repo> From> for DetachedObject { } } +impl<'repo> From> for Object<'repo> { + fn from(mut r: Commit<'repo>) -> Self { + Object { + id: r.id, + kind: git_object::Kind::Commit, + data: steal_from_freelist(&mut r.data), + repo: r.repo, + } + } +} + impl<'repo> AsRef<[u8]> for Object<'repo> { fn as_ref(&self) -> &[u8] { &self.data diff --git a/git-repository/tests/easy/id.rs b/git-repository/tests/easy/id.rs index 5621a6c13bb..b4ae3dae68e 100644 --- a/git-repository/tests/easy/id.rs +++ b/git-repository/tests/easy/id.rs @@ -10,6 +10,7 @@ fn prefix() -> crate::Result { assert_eq!(prefix.cmp_oid(&id), Ordering::Equal); assert_eq!(prefix.hex_len(), 8, "preconfigured via core.abbrev default value"); + // TODO: do this in-memory (with or without writing to disk) assert!( std::process::Command::new("git") .current_dir(worktree_dir.path()) diff --git a/git-repository/tests/easy/object.rs b/git-repository/tests/easy/object.rs index 41b80d72085..b6f094d16c5 100644 --- a/git-repository/tests/easy/object.rs +++ b/git-repository/tests/easy/object.rs @@ -1,19 +1,29 @@ mod commit { use git_repository::{Commit, Repository}; use git_testtools::hex_to_id; + use std::cmp::Ordering; use crate::basic_repo; #[test] - fn tree() { - let handle = basic_repo().unwrap(); + fn short_id() -> crate::Result { + let handle = basic_repo()?; + let commit = head_commit(&handle); + assert_eq!(commit.short_id()?.cmp_oid(&commit.id), Ordering::Equal); + Ok(()) + } + + #[test] + fn tree() -> crate::Result { + let handle = basic_repo()?; let commit = head_commit(&handle); - assert_eq!(commit.tree().unwrap().id, commit.tree_id().expect("id present")); + assert_eq!(commit.tree()?.id, commit.tree_id().expect("id present")); assert_eq!( commit.tree_id(), Some(hex_to_id("21d3ba9a26b790a4858d67754ae05d04dfce4d0c")) - ) + ); + Ok(()) } #[test] @@ -27,15 +37,7 @@ mod commit { } fn head_commit(repo: &Repository) -> Commit<'_> { - repo.head() - .unwrap() - // TODO: Add something like peel_to_commit() to cut the chain, deal with unborn as Error - .into_fully_peeled_id() - .expect("born") - .unwrap() - .object() - .unwrap() - .into_commit() + repo.head().unwrap().peel_to_commit_in_place().unwrap() } }