Skip to content

Commit 0866e89

Browse files
committed
Merge branch 'kalkin-improve-prefix'
2 parents e058bda + 521c894 commit 0866e89

File tree

7 files changed

+146
-7
lines changed

7 files changed

+146
-7
lines changed

etc/check-package-size.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ echo "in root: gitoxide CLI"
3030
(enter git-tempfile && indent cargo diet -n --package-size-limit 25KB)
3131
(enter git-lock && indent cargo diet -n --package-size-limit 15KB)
3232
(enter git-config && indent cargo diet -n --package-size-limit 70KB)
33-
(enter git-hash && indent cargo diet -n --package-size-limit 15KB)
33+
(enter git-hash && indent cargo diet -n --package-size-limit 20KB)
3434
(enter git-chunk && indent cargo diet -n --package-size-limit 10KB)
3535
(enter git-rebase && indent cargo diet -n --package-size-limit 5KB)
3636
(enter git-sequencer && indent cargo diet -n --package-size-limit 5KB)

git-hash/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ impl Kind {
144144
}
145145
}
146146

147+
/// Returns the kind of hash that would fit the given `hex_len`, or `None` if there is no fitting hash.
148+
/// Note that 0 as `hex_len` fits always yields Sha1.
149+
#[inline]
150+
pub const fn from_hex_len(hex_len: usize) -> Option<Self> {
151+
Some(match hex_len {
152+
0..=40 => Kind::Sha1,
153+
_ => return None,
154+
})
155+
}
156+
147157
/// Converts a size in bytes as obtained by `Kind::len_in_bytes()` into the corresponding hash kind, if possible.
148158
///
149159
/// **Panics** if the hash length doesn't match a known hash.

git-hash/src/owned.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{borrowed::oid, Kind, SIZE_OF_SHA1_DIGEST};
44

55
/// An partial owned hash possibly identifying an object uniquely,
66
/// whose non-prefix bytes are zeroed.
7-
#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
7+
#[derive(PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy, Debug)]
88
#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
99
pub struct Prefix {
1010
bytes: ObjectId,

git-hash/src/owned/prefix.rs

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,47 @@
11
use std::cmp::Ordering;
2+
use std::convert::TryFrom;
23

34
use quick_error::quick_error;
45

56
use crate::{oid, ObjectId, Prefix};
67

8+
const MIN_HEX_LEN: usize = 4;
9+
710
quick_error! {
811
/// The error returned by [Prefix::try_from_id()][super::Prefix::try_from_id()].
912
#[derive(Debug)]
1013
#[allow(missing_docs)]
1114
pub enum Error {
1215
TooShort { hex_len: usize } {
13-
display("The minimum hex length of a short object id is 4, got {}", hex_len)
16+
display("The minimum hex length of a short object id is {}, got {}", MIN_HEX_LEN, hex_len)
1417
}
1518
TooLong { object_kind: crate::Kind, hex_len: usize } {
1619
display("An object of kind {} cannot be larger than {} in hex, but {} was requested", object_kind, object_kind.len_in_hex(), hex_len)
1720
}
1821
}
1922
}
2023

24+
///
25+
pub mod from_hex {
26+
use quick_error::quick_error;
27+
quick_error! {
28+
/// The error returned by [Prefix::from_hex][super::Prefix::from_hex()].
29+
#[derive(Debug, PartialEq)]
30+
#[allow(missing_docs)]
31+
pub enum Error {
32+
TooShort { hex_len: usize } {
33+
display("The minimum hex length of a short object id is {}, got {}", super::MIN_HEX_LEN, hex_len)
34+
}
35+
TooLong { hex_len: usize } {
36+
display("An id cannot be larger than {} chars in hex, but {} was requested", crate::Kind::longest().len_in_hex(), hex_len)
37+
}
38+
Invalid { c: char, index: usize } {
39+
display("Invalid character {} at position {}", c, index)
40+
}
41+
}
42+
}
43+
}
44+
2145
impl Prefix {
2246
/// Create a new instance by taking a full `id` as input and truncating it to `hex_len`.
2347
///
@@ -30,7 +54,7 @@ impl Prefix {
3054
object_kind: id.kind(),
3155
hex_len,
3256
})
33-
} else if hex_len < 4 {
57+
} else if hex_len < MIN_HEX_LEN {
3458
Err(Error::TooShort { hex_len })
3559
} else {
3660
let mut prefix = ObjectId::null(id.kind());
@@ -75,9 +99,46 @@ impl Prefix {
7599
})
76100
}
77101

78-
/// Create an instance from the given hexadecimal prefix, e.g. `35e77c16` would yield a `Prefix` with `hex_len()` = 8.
79-
pub fn from_hex(_hex: &str) -> Self {
80-
todo!("Prefix::from_hex()")
102+
/// Create an instance from the given hexadecimal prefix `value`, e.g. `35e77c16` would yield a `Prefix` with `hex_len()` = 8.
103+
pub fn from_hex(value: &str) -> Result<Self, from_hex::Error> {
104+
use hex::FromHex;
105+
let hex_len = value.len();
106+
107+
if hex_len > crate::Kind::longest().len_in_hex() {
108+
return Err(from_hex::Error::TooLong { hex_len });
109+
} else if hex_len < MIN_HEX_LEN {
110+
return Err(from_hex::Error::TooShort { hex_len });
111+
};
112+
113+
let src = if value.len() % 2 == 0 {
114+
Vec::from_hex(value)
115+
} else {
116+
let mut buf = [0u8; crate::Kind::longest().len_in_hex()];
117+
buf[..value.len()].copy_from_slice(value.as_bytes());
118+
buf[value.len()] = b'0';
119+
Vec::from_hex(&buf[..value.len() + 1])
120+
}
121+
.map_err(|e| match e {
122+
hex::FromHexError::InvalidHexCharacter { c, index } => from_hex::Error::Invalid { c, index },
123+
hex::FromHexError::OddLength | hex::FromHexError::InvalidStringLength => panic!("This is already checked"),
124+
})?;
125+
126+
let mut bytes = ObjectId::null(crate::Kind::from_hex_len(value.len()).expect("hex-len is already checked"));
127+
let dst = bytes.as_mut_slice();
128+
let copy_len = src.len();
129+
dst[..copy_len].copy_from_slice(&src);
130+
131+
Ok(Prefix { bytes, hex_len })
132+
}
133+
}
134+
135+
/// Create an instance from the given hexadecimal prefix, e.g. `35e77c16` would yield a `Prefix`
136+
/// with `hex_len()` = 8.
137+
impl TryFrom<&str> for Prefix {
138+
type Error = from_hex::Error;
139+
140+
fn try_from(value: &str) -> Result<Self, Self::Error> {
141+
Prefix::from_hex(value)
81142
}
82143
}
83144

git-hash/tests/integration.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
mod kind;
12
mod oid;

git-hash/tests/kind/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
mod from_hex_len {
2+
use git_hash::Kind;
3+
4+
#[test]
5+
fn some_sha1() {
6+
assert_eq!(Kind::from_hex_len(0), Some(Kind::Sha1));
7+
assert_eq!(Kind::from_hex_len(10), Some(Kind::Sha1));
8+
assert_eq!(Kind::from_hex_len(20), Some(Kind::Sha1));
9+
assert_eq!(Kind::from_hex_len(40), Some(Kind::Sha1));
10+
}
11+
12+
#[test]
13+
fn none_if_there_is_no_fit() {
14+
assert_eq!(Kind::from_hex_len(65), None);
15+
}
16+
}

git-hash/tests/oid/mod.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,57 @@ mod prefix {
7070
));
7171
}
7272
}
73+
74+
mod try_from {
75+
use std::{cmp::Ordering, convert::TryFrom};
76+
77+
use git_hash::prefix::from_hex::Error;
78+
use git_hash::Prefix;
79+
use git_testtools::hex_to_id;
80+
81+
#[test]
82+
fn id_8_chars() {
83+
let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd";
84+
let input = "abcdef";
85+
86+
let expected = hex_to_id(oid_hex);
87+
let actual = Prefix::try_from(input).expect("No errors");
88+
assert_eq!(actual.cmp_oid(&expected), Ordering::Equal);
89+
}
90+
91+
#[test]
92+
fn id_9_chars() {
93+
let oid_hex = "abcdefabcdefabcdefabcdefabcdefabcdefabcd";
94+
let input = "abcdefa";
95+
96+
let expected = hex_to_id(oid_hex);
97+
let actual = Prefix::try_from(input).expect("No errors");
98+
assert_eq!(actual.cmp_oid(&expected), Ordering::Equal);
99+
}
100+
#[test]
101+
fn id_to_short() {
102+
let input = "ab";
103+
let expected = Error::TooShort { hex_len: 2 };
104+
let actual = Prefix::try_from(input).unwrap_err();
105+
assert_eq!(actual, expected);
106+
}
107+
108+
#[test]
109+
fn id_to_long() {
110+
let input = "abcdefabcdefabcdefabcdefabcdefabcdefabcd123123123123123123";
111+
let expected = Error::TooLong { hex_len: 58 };
112+
let actual = Prefix::try_from(input).unwrap_err();
113+
assert_eq!(actual, expected);
114+
}
115+
116+
#[test]
117+
fn invalid_chars() {
118+
let input = "abcdfOsd";
119+
let expected = Error::Invalid { c: 'O', index: 5 };
120+
let actual = Prefix::try_from(input).unwrap_err();
121+
assert_eq!(actual, expected);
122+
}
123+
}
73124
}
74125

75126
mod short_hex {

0 commit comments

Comments
 (0)