Skip to content

Commit ea8ada4

Browse files
committed
feat: Commit::short_id() (#301)
1 parent f16f8e3 commit ea8ada4

File tree

8 files changed

+127
-99
lines changed

8 files changed

+127
-99
lines changed

git-hash/src/owned.rs

+1-81
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::cmp::Ordering;
21
use std::{borrow::Borrow, convert::TryInto, fmt, ops::Deref};
32

43
use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST};
@@ -13,86 +12,7 @@ pub struct Prefix {
1312
}
1413

1514
///
16-
pub mod prefix {
17-
use quick_error::quick_error;
18-
19-
quick_error! {
20-
/// The error returned by [Prefix::try_from_id()][super::Prefix::try_from_id()].
21-
#[derive(Debug)]
22-
#[allow(missing_docs)]
23-
pub enum Error {
24-
TooShort { hex_len: usize } {
25-
display("The minimum hex length of a short object id is 4, got {}", hex_len)
26-
}
27-
TooLong { object_kind: crate::Kind, hex_len: usize } {
28-
display("An object of kind {} cannot be larger than {} in hex, but {} was requested", object_kind, object_kind.len_in_hex(), hex_len)
29-
}
30-
}
31-
}
32-
}
33-
34-
impl Prefix {
35-
/// Create a new instance by taking a full `id` as input and truncating it to `hex_len`.
36-
///
37-
/// For instance, with `hex_len` of 7 the resulting prefix is 3.5 bytes, or 3 bytes and 4 bits
38-
/// wide, with all other bytes and bits set to zero.
39-
pub fn new(id: impl AsRef<oid>, hex_len: usize) -> Result<Self, prefix::Error> {
40-
let id = id.as_ref();
41-
if hex_len > id.kind().len_in_hex() {
42-
Err(prefix::Error::TooLong {
43-
object_kind: id.kind(),
44-
hex_len,
45-
})
46-
} else if hex_len < 4 {
47-
Err(prefix::Error::TooShort { hex_len })
48-
} else {
49-
let mut prefix = ObjectId::null(id.kind());
50-
let b = prefix.as_mut_slice();
51-
let copy_len = (hex_len + 1) / 2;
52-
b[..copy_len].copy_from_slice(&id.as_bytes()[..copy_len]);
53-
if hex_len % 2 == 1 {
54-
b[hex_len / 2] &= 0xf0;
55-
}
56-
57-
Ok(Prefix { bytes: prefix, hex_len })
58-
}
59-
}
60-
61-
/// Returns the prefix as object id.
62-
///
63-
/// Note that it may be deceptive to use given that it looks like a full
64-
/// object id, even though its post-prefix bytes/bits are set to zero.
65-
pub fn as_oid(&self) -> &oid {
66-
&self.bytes
67-
}
68-
69-
/// Return the amount of hexadecimal characters that are set in the prefix.
70-
///
71-
/// This gives the prefix a granularity of 4 bits.
72-
pub fn hex_len(&self) -> usize {
73-
self.hex_len
74-
}
75-
76-
/// Provided with candidate id which is a full hash, determine how this prefix compares to it,
77-
/// only looking at the prefix bytes, ignoring everything behind that.
78-
pub fn cmp_oid(&self, candidate: &oid) -> Ordering {
79-
let common_len = self.hex_len / 2;
80-
81-
self.bytes.as_bytes()[..common_len]
82-
.cmp(&candidate.as_bytes()[..common_len])
83-
.then(if self.hex_len % 2 == 1 {
84-
let half_byte_idx = self.hex_len / 2;
85-
self.bytes.as_bytes()[half_byte_idx].cmp(&(candidate.as_bytes()[half_byte_idx] & 0xf0))
86-
} else {
87-
Ordering::Equal
88-
})
89-
}
90-
91-
/// Create an instance from the given hexadecimal prefix, e.g. `35e77c16` would yield a `Prefix` with `hex_len()` = 8.
92-
pub fn from_hex(_hex: &str) -> Self {
93-
todo!("Prefix::from_hex()")
94-
}
95-
}
15+
pub mod prefix;
9616

9717
/// An owned hash identifying objects, most commonly Sha1
9818
#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]

git-hash/src/owned/prefix.rs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use crate::{oid, ObjectId, Prefix};
2+
use quick_error::quick_error;
3+
use std::cmp::Ordering;
4+
5+
quick_error! {
6+
/// The error returned by [Prefix::try_from_id()][super::Prefix::try_from_id()].
7+
#[derive(Debug)]
8+
#[allow(missing_docs)]
9+
pub enum Error {
10+
TooShort { hex_len: usize } {
11+
display("The minimum hex length of a short object id is 4, got {}", hex_len)
12+
}
13+
TooLong { object_kind: crate::Kind, hex_len: usize } {
14+
display("An object of kind {} cannot be larger than {} in hex, but {} was requested", object_kind, object_kind.len_in_hex(), hex_len)
15+
}
16+
}
17+
}
18+
19+
impl Prefix {
20+
/// Create a new instance by taking a full `id` as input and truncating it to `hex_len`.
21+
///
22+
/// For instance, with `hex_len` of 7 the resulting prefix is 3.5 bytes, or 3 bytes and 4 bits
23+
/// wide, with all other bytes and bits set to zero.
24+
pub fn new(id: impl AsRef<oid>, hex_len: usize) -> Result<Self, Error> {
25+
let id = id.as_ref();
26+
if hex_len > id.kind().len_in_hex() {
27+
Err(Error::TooLong {
28+
object_kind: id.kind(),
29+
hex_len,
30+
})
31+
} else if hex_len < 4 {
32+
Err(Error::TooShort { hex_len })
33+
} else {
34+
let mut prefix = ObjectId::null(id.kind());
35+
let b = prefix.as_mut_slice();
36+
let copy_len = (hex_len + 1) / 2;
37+
b[..copy_len].copy_from_slice(&id.as_bytes()[..copy_len]);
38+
if hex_len % 2 == 1 {
39+
b[hex_len / 2] &= 0xf0;
40+
}
41+
42+
Ok(Prefix { bytes: prefix, hex_len })
43+
}
44+
}
45+
46+
/// Returns the prefix as object id.
47+
///
48+
/// Note that it may be deceptive to use given that it looks like a full
49+
/// object id, even though its post-prefix bytes/bits are set to zero.
50+
pub fn as_oid(&self) -> &oid {
51+
&self.bytes
52+
}
53+
54+
/// Return the amount of hexadecimal characters that are set in the prefix.
55+
///
56+
/// This gives the prefix a granularity of 4 bits.
57+
pub fn hex_len(&self) -> usize {
58+
self.hex_len
59+
}
60+
61+
/// Provided with candidate id which is a full hash, determine how this prefix compares to it,
62+
/// only looking at the prefix bytes, ignoring everything behind that.
63+
pub fn cmp_oid(&self, candidate: &oid) -> Ordering {
64+
let common_len = self.hex_len / 2;
65+
66+
self.bytes.as_bytes()[..common_len]
67+
.cmp(&candidate.as_bytes()[..common_len])
68+
.then(if self.hex_len % 2 == 1 {
69+
let half_byte_idx = self.hex_len / 2;
70+
self.bytes.as_bytes()[half_byte_idx].cmp(&(candidate.as_bytes()[half_byte_idx] & 0xf0))
71+
} else {
72+
Ordering::Equal
73+
})
74+
}
75+
76+
/// Create an instance from the given hexadecimal prefix, e.g. `35e77c16` would yield a `Prefix` with `hex_len()` = 8.
77+
pub fn from_hex(_hex: &str) -> Self {
78+
todo!("Prefix::from_hex()")
79+
}
80+
}
81+
82+
impl std::fmt::Display for Prefix {
83+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84+
self.bytes.to_hex_with_len(self.hex_len).fmt(f)
85+
}
86+
}

git-hash/tests/oid/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@ mod prefix {
1414
prefix.cmp_oid(&hex_to_id("b920bbf055e1efb9080592a409d3975738b6efb3")),
1515
Ordering::Less
1616
);
17+
assert_eq!(prefix.to_string(), "b920bbb");
1718
}
1819

1920
#[test]
2021
fn it_detects_equality() {
21-
let id = hex_to_id("b920bbb055e1efb9080592a409d3975738b6efb3");
22-
let prefix = git_hash::Prefix::new(id, 7).unwrap();
22+
let id = hex_to_id("a920bbb055e1efb9080592a409d3975738b6efb3");
23+
let prefix = git_hash::Prefix::new(id, 6).unwrap();
2324
assert_eq!(prefix.cmp_oid(&id), Ordering::Equal);
2425
assert_eq!(
25-
prefix.cmp_oid(&hex_to_id("b920bbbfffffffffffffffffffffffffffffffff")),
26+
prefix.cmp_oid(&hex_to_id("a920bbffffffffffffffffffffffffffffffffff")),
2627
Ordering::Equal
2728
);
29+
assert_eq!(prefix.to_string(), "a920bb");
2830
}
2931
}
3032
mod new {

git-repository/src/id.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ impl<'repo> Id<'repo> {
5151
}
5252

5353
///
54-
mod prefix {
55-
/// Returned by [`Oid::prefix()`][super::Oid::prefix()].
54+
pub mod prefix {
55+
/// Returned by [`Id::prefix()`][super::Id::prefix()].
5656
#[derive(thiserror::Error, Debug)]
5757
#[allow(missing_docs)]
5858
pub enum Error {

git-repository/src/object/commit.rs

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ mod error {
2222
pub use error::Error;
2323

2424
impl<'repo> Commit<'repo> {
25+
/// Turn this objects id into a shortened id with a length in hex as configured by `core.abbrev`.
26+
pub fn short_id(&self) -> Result<git_hash::Prefix, crate::id::prefix::Error> {
27+
use crate::ext::ObjectIdExt;
28+
self.id.attach(self.repo).prefix()
29+
}
30+
2531
/// Parse the commits message into a [`MessageRef`][git_object::commit::MessageRef], after decoding the entire commit object.
2632
pub fn message(&self) -> Result<git_object::commit::MessageRef<'_>, git_object::decode::Error> {
2733
Ok(self.decode()?.message())

git-repository/src/object/impls.rs

+11
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ impl<'repo> From<Object<'repo>> for DetachedObject {
1515
}
1616
}
1717

18+
impl<'repo> From<Commit<'repo>> for Object<'repo> {
19+
fn from(mut r: Commit<'repo>) -> Self {
20+
Object {
21+
id: r.id,
22+
kind: git_object::Kind::Commit,
23+
data: steal_from_freelist(&mut r.data),
24+
repo: r.repo,
25+
}
26+
}
27+
}
28+
1829
impl<'repo> AsRef<[u8]> for Object<'repo> {
1930
fn as_ref(&self) -> &[u8] {
2031
&self.data

git-repository/tests/easy/id.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ fn prefix() -> crate::Result {
1010
assert_eq!(prefix.cmp_oid(&id), Ordering::Equal);
1111
assert_eq!(prefix.hex_len(), 8, "preconfigured via core.abbrev default value");
1212

13+
// TODO: do this in-memory (with or without writing to disk)
1314
assert!(
1415
std::process::Command::new("git")
1516
.current_dir(worktree_dir.path())

git-repository/tests/easy/object.rs

+15-13
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
mod commit {
22
use git_repository::{Commit, Repository};
33
use git_testtools::hex_to_id;
4+
use std::cmp::Ordering;
45

56
use crate::basic_repo;
67

78
#[test]
8-
fn tree() {
9-
let handle = basic_repo().unwrap();
9+
fn short_id() -> crate::Result {
10+
let handle = basic_repo()?;
11+
let commit = head_commit(&handle);
12+
assert_eq!(commit.short_id()?.cmp_oid(&commit.id), Ordering::Equal);
13+
Ok(())
14+
}
15+
16+
#[test]
17+
fn tree() -> crate::Result {
18+
let handle = basic_repo()?;
1019
let commit = head_commit(&handle);
1120

12-
assert_eq!(commit.tree().unwrap().id, commit.tree_id().expect("id present"));
21+
assert_eq!(commit.tree()?.id, commit.tree_id().expect("id present"));
1322
assert_eq!(
1423
commit.tree_id(),
1524
Some(hex_to_id("21d3ba9a26b790a4858d67754ae05d04dfce4d0c"))
16-
)
25+
);
26+
Ok(())
1727
}
1828

1929
#[test]
@@ -27,15 +37,7 @@ mod commit {
2737
}
2838

2939
fn head_commit(repo: &Repository) -> Commit<'_> {
30-
repo.head()
31-
.unwrap()
32-
// TODO: Add something like peel_to_commit() to cut the chain, deal with unborn as Error
33-
.into_fully_peeled_id()
34-
.expect("born")
35-
.unwrap()
36-
.object()
37-
.unwrap()
38-
.into_commit()
40+
repo.head().unwrap().peel_to_commit_in_place().unwrap()
3941
}
4042
}
4143

0 commit comments

Comments
 (0)