Skip to content

Commit 493615a

Browse files
pierrechevalier83Pierre Chevalier
authored and
Pierre Chevalier
committed
WIP
1 parent fc7fa92 commit 493615a

File tree

3 files changed

+125
-107
lines changed

3 files changed

+125
-107
lines changed

gix-object/src/tree/mod.rs

Lines changed: 94 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,99 @@ pub struct Editor<'a> {
4545
/// create it by converting [`EntryKind`] into `EntryMode`.
4646
#[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
4747
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48-
pub struct EntryMode(u16);
48+
pub struct EntryMode {
49+
// Represents the value read from Git, except that "040000" is represented with 0o140000 but
50+
// "40000" is represented with 0o40000
51+
internal: u16,
52+
}
53+
54+
impl EntryMode {
55+
/// Expose the value as a u16 (lossy, unlike the internal representation that is hidden)
56+
const fn value(self) -> u16 {
57+
// Demangle the hack: In the case where the second leftmost octet is 4 (Tree), the leftmost bit is
58+
// there to represent whether the bytes representation should have 5 or 6 octets
59+
if self.internal & IFMT == 0o140000 {
60+
0o040000
61+
} else {
62+
self.internal
63+
}
64+
}
65+
66+
/// Return the representation as used in the git internal format, which is octal and written
67+
/// to the `backing` buffer. The respective sub-slice that was written to is returned.
68+
pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr {
69+
if self.internal == 0 {
70+
std::slice::from_ref(&b'0')
71+
} else {
72+
let mut nb = 0;
73+
let mut n = self.internal;
74+
while n > 0 {
75+
let remainder = (n % 8) as u8;
76+
backing[nb] = b'0' + remainder;
77+
n /= 8;
78+
nb += 1;
79+
}
80+
let res = &mut backing[..nb];
81+
res.reverse();
82+
// Hack: `0o140000` represents `"040000"`, `0o40000` represents `"40000"`
83+
if res[0] == b'1' && res[1] == b'4' {
84+
res[0] = b'0';
85+
}
86+
res
87+
}
88+
.into()
89+
}
90+
91+
/// Construct an EntryMode from bytes represented as in the git internal format
92+
/// Return the mode and the remainder of the bytes
93+
pub(crate) fn extract_from_bytes(i: &[u8]) -> Option<(Self, &'_ [u8])> {
94+
let mut mode = 0;
95+
let mut idx = 0;
96+
let mut space_pos = 0;
97+
if i.is_empty() {
98+
return None;
99+
}
100+
// const fn, this is why we can't have nice things (like `.iter().any()`)
101+
while idx < i.len() {
102+
let b = i[idx];
103+
// Delimiter, return what we got
104+
if b == b' ' {
105+
space_pos = idx;
106+
break;
107+
}
108+
// Not a pure octal input
109+
if b < b'0' || b > b'7' {
110+
return None;
111+
}
112+
// More than 6 octal digits we must have hit the delimiter or the input was malformed
113+
if idx > 6 {
114+
return None;
115+
}
116+
mode = (mode << 3) + (b - b'0') as u16;
117+
idx += 1;
118+
}
119+
// Hack: `0o140000` represents `"040000"`, `0o40000` represents `"40000"`
120+
if mode == 0o40000 && i[0] == b'0' {
121+
mode += 0o100000;
122+
}
123+
Some((Self { internal: mode }, &i[(space_pos + 1)..]))
124+
}
125+
126+
/// Construct an EntryMode from bytes represented as in the git internal format
127+
pub fn from_bytes(i: &[u8]) -> Option<Self> {
128+
Self::extract_from_bytes(i).map(|(mode, _rest)| mode)
129+
}
130+
}
49131

50132
impl std::fmt::Debug for EntryMode {
51133
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52-
write!(f, "EntryMode({:#o})", self.0)
134+
write!(f, "EntryMode(0o{})", self.as_bytes(&mut Default::default()))
53135
}
54136
}
55137

56138
impl std::fmt::Octal for EntryMode {
57139
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58-
write!(f, "{:o}", self.0)
140+
write!(f, "{}", self.as_bytes(&mut Default::default()))
59141
}
60142
}
61143

@@ -79,21 +161,9 @@ pub enum EntryKind {
79161
Commit = 0o160000,
80162
}
81163

82-
impl From<u16> for EntryMode {
83-
fn from(value: u16) -> Self {
84-
EntryMode(value)
85-
}
86-
}
87-
88-
impl From<EntryMode> for u16 {
89-
fn from(value: EntryMode) -> Self {
90-
value.0
91-
}
92-
}
93-
94164
impl From<EntryKind> for EntryMode {
95165
fn from(value: EntryKind) -> Self {
96-
EntryMode(value as u16)
166+
EntryMode { internal: value as u16 }
97167
}
98168
}
99169

@@ -119,22 +189,14 @@ impl EntryKind {
119189
}
120190
}
121191

122-
impl std::ops::Deref for EntryMode {
123-
type Target = u16;
124-
125-
fn deref(&self) -> &Self::Target {
126-
&self.0
127-
}
128-
}
129-
130192
const IFMT: u16 = 0o170000;
131193

132194
impl EntryMode {
133195
/// Discretize the raw mode into an enum with well-known state while dropping unnecessary details.
134196
pub const fn kind(&self) -> EntryKind {
135-
let etype = self.0 & IFMT;
197+
let etype = self.value() & IFMT;
136198
if etype == 0o100000 {
137-
if self.0 & 0o000100 == 0o000100 {
199+
if self.value() & 0o000100 == 0o000100 {
138200
EntryKind::BlobExecutable
139201
} else {
140202
EntryKind::Blob
@@ -150,27 +212,27 @@ impl EntryMode {
150212

151213
/// Return true if this entry mode represents a Tree/directory
152214
pub const fn is_tree(&self) -> bool {
153-
self.0 & IFMT == EntryKind::Tree as u16
215+
self.value() & IFMT == EntryKind::Tree as u16
154216
}
155217

156218
/// Return true if this entry mode represents the commit of a submodule.
157219
pub const fn is_commit(&self) -> bool {
158-
self.0 & IFMT == EntryKind::Commit as u16
220+
self.value() & IFMT == EntryKind::Commit as u16
159221
}
160222

161223
/// Return true if this entry mode represents a symbolic link
162224
pub const fn is_link(&self) -> bool {
163-
self.0 & IFMT == EntryKind::Link as u16
225+
self.value() & IFMT == EntryKind::Link as u16
164226
}
165227

166228
/// Return true if this entry mode represents anything BUT Tree/directory
167229
pub const fn is_no_tree(&self) -> bool {
168-
self.0 & IFMT != EntryKind::Tree as u16
230+
self.value() & IFMT != EntryKind::Tree as u16
169231
}
170232

171233
/// Return true if the entry is any kind of blob.
172234
pub const fn is_blob(&self) -> bool {
173-
self.0 & IFMT == 0o100000
235+
self.value() & IFMT == 0o100000
174236
}
175237

176238
/// Return true if the entry is an executable blob.
@@ -198,30 +260,9 @@ impl EntryMode {
198260
}
199261
}
200262

201-
/// Return the representation as used in the git internal format, which is octal and written
202-
/// to the `backing` buffer. The respective sub-slice that was written to is returned.
203-
pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr {
204-
if self.0 == 0 {
205-
std::slice::from_ref(&b'0')
206-
} else {
207-
let mut nb = 0;
208-
let mut n = self.0;
209-
while n > 0 {
210-
let remainder = (n % 8) as u8;
211-
backing[nb] = b'0' + remainder;
212-
n /= 8;
213-
nb += 1;
214-
}
215-
let res = &mut backing[..nb];
216-
res.reverse();
217-
res
218-
}
219-
.into()
220-
}
221-
222263
/// Display the octal representation of this EntryMode
223264
pub fn as_octal_representation(&self) -> fmt::Result {
224-
Ok(print!("{:o}", self.0))
265+
Ok(print!("{:}", self.as_bytes(&mut Default::default())))
225266
}
226267
}
227268

gix-object/src/tree/ref_iter.rs

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -171,54 +171,18 @@ impl<'a> TryFrom<&'a [u8]> for tree::EntryMode {
171171
type Error = &'a [u8];
172172

173173
fn try_from(mode: &'a [u8]) -> Result<Self, Self::Error> {
174-
mode_from_decimal(mode)
175-
.map(|(mode, _rest)| tree::EntryMode(mode as u16))
176-
.ok_or(mode)
177-
}
178-
}
179-
180-
fn mode_from_decimal(i: &[u8]) -> Option<(u32, &[u8])> {
181-
let mut mode = 0u32;
182-
let mut spacer_pos = 1;
183-
for b in i.iter().take_while(|b| **b != b' ') {
184-
if *b < b'0' || *b > b'7' {
185-
return None;
186-
}
187-
mode = (mode << 3) + u32::from(b - b'0');
188-
spacer_pos += 1;
189-
}
190-
if i.len() < spacer_pos {
191-
return None;
192-
}
193-
let (_, i) = i.split_at(spacer_pos);
194-
Some((mode, i))
195-
}
196-
197-
impl TryFrom<u32> for tree::EntryMode {
198-
type Error = u32;
199-
200-
fn try_from(mode: u32) -> Result<Self, Self::Error> {
201-
Ok(match mode {
202-
0o40000 | 0o120000 | 0o160000 => tree::EntryMode(mode as u16),
203-
blob_mode if blob_mode & 0o100000 == 0o100000 => tree::EntryMode(mode as u16),
204-
_ => return Err(mode),
205-
})
174+
tree::EntryMode::from_bytes(mode).ok_or(mode)
206175
}
207176
}
208177

209178
mod decode {
210179
use bstr::ByteSlice;
211180
use winnow::{error::ParserError, prelude::*};
212181

213-
use crate::{
214-
tree,
215-
tree::{ref_iter::mode_from_decimal, EntryRef},
216-
TreeRef,
217-
};
182+
use crate::{tree, tree::EntryRef, TreeRef};
218183

219184
pub fn fast_entry(i: &[u8]) -> Option<(&[u8], EntryRef<'_>)> {
220-
let (mode, i) = mode_from_decimal(i)?;
221-
let mode = tree::EntryMode::try_from(mode).ok()?;
185+
let (mode, i) = tree::EntryMode::extract_from_bytes(i)?;
222186
let (filename, i) = i.split_at(i.find_byte(0)?);
223187
let i = &i[1..];
224188
const HASH_LEN_FIXME: usize = 20; // TODO(SHA256): know actual/desired length or we may overshoot

gix-object/tests/object/tree/entry_mode.rs

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ fn is_methods() {
1616
}
1717

1818
assert!(mode(EntryKind::Blob).is_blob());
19-
assert!(EntryMode::from(0o100645).is_blob());
20-
assert_eq!(EntryMode::from(0o100645).kind(), EntryKind::Blob);
21-
assert!(!EntryMode::from(0o100675).is_executable());
22-
assert!(EntryMode::from(0o100700).is_executable());
23-
assert_eq!(EntryMode::from(0o100700).kind(), EntryKind::BlobExecutable);
19+
assert!(EntryMode::from_bytes(b"100645").unwrap().is_blob());
20+
assert_eq!(EntryMode::from_bytes(b"100645").unwrap().kind(), EntryKind::Blob);
21+
assert!(!EntryMode::from_bytes(b"100675").unwrap().is_executable());
22+
assert!(EntryMode::from_bytes(b"100700").unwrap().is_executable());
23+
assert_eq!(
24+
EntryMode::from_bytes(b"100700").unwrap().kind(),
25+
EntryKind::BlobExecutable
26+
);
2427
assert!(!mode(EntryKind::Blob).is_link());
2528
assert!(mode(EntryKind::BlobExecutable).is_blob());
2629
assert!(mode(EntryKind::BlobExecutable).is_executable());
@@ -29,17 +32,19 @@ fn is_methods() {
2932

3033
assert!(!mode(EntryKind::Link).is_blob());
3134
assert!(mode(EntryKind::Link).is_link());
32-
assert!(EntryMode::from(0o121234).is_link());
33-
assert_eq!(EntryMode::from(0o121234).kind(), EntryKind::Link);
35+
assert!(EntryMode::from_bytes(b"121234").unwrap().is_link());
36+
assert_eq!(EntryMode::from_bytes(b"121234").unwrap().kind(), EntryKind::Link);
3437
assert!(mode(EntryKind::Link).is_blob_or_symlink());
3538
assert!(mode(EntryKind::Tree).is_tree());
36-
assert!(EntryMode::from(0o040101).is_tree());
37-
assert_eq!(EntryMode::from(0o040101).kind(), EntryKind::Tree);
39+
assert!(EntryMode::from_bytes(b"040101").unwrap().is_tree());
40+
assert_eq!(EntryMode::from_bytes(b"040101").unwrap().kind(), EntryKind::Tree);
41+
assert!(EntryMode::from_bytes(b"40101").unwrap().is_tree());
42+
assert_eq!(EntryMode::from_bytes(b"40101").unwrap().kind(), EntryKind::Tree);
3843
assert!(mode(EntryKind::Commit).is_commit());
39-
assert!(EntryMode::from(0o167124).is_commit());
40-
assert_eq!(EntryMode::from(0o167124).kind(), EntryKind::Commit);
44+
assert!(EntryMode::from_bytes(b"167124").unwrap().is_commit());
45+
assert_eq!(EntryMode::from_bytes(b"167124").unwrap().kind(), EntryKind::Commit);
4146
assert_eq!(
42-
EntryMode::from(0o000000).kind(),
47+
EntryMode::from_bytes(b"000000").unwrap().kind(),
4348
EntryKind::Commit,
4449
"commit is really 'anything else' as `kind()` can't fail"
4550
);
@@ -49,7 +54,10 @@ fn is_methods() {
4954
fn as_bytes() {
5055
let mut buf = Default::default();
5156
for (mode, expected) in [
52-
(EntryMode::from(EntryKind::Tree), EntryKind::Tree.as_octal_str()),
57+
(
58+
EntryMode::try_from(EntryKind::Tree).unwrap(),
59+
EntryKind::Tree.as_octal_str(),
60+
),
5361
(EntryKind::Blob.into(), EntryKind::Blob.as_octal_str()),
5462
(
5563
EntryKind::BlobExecutable.into(),
@@ -58,13 +66,18 @@ fn as_bytes() {
5866
(EntryKind::Link.into(), EntryKind::Link.as_octal_str()),
5967
(EntryKind::Commit.into(), EntryKind::Commit.as_octal_str()),
6068
(
61-
EntryMode::try_from(b"100744 ".as_ref()).expect("valid"),
69+
EntryMode::from_bytes(b"100744 ".as_ref()).expect("valid"),
6270
"100744".into(),
6371
),
6472
(
65-
EntryMode::try_from(b"100644 ".as_ref()).expect("valid"),
73+
EntryMode::from_bytes(b"100644 ".as_ref()).expect("valid"),
6674
"100644".into(),
6775
),
76+
(
77+
EntryMode::from_bytes(b"040000".as_ref()).expect("valid"),
78+
"040000".into(),
79+
),
80+
(EntryMode::from_bytes(b"40000".as_ref()).expect("valid"), "40000".into()),
6881
] {
6982
assert_eq!(mode.as_bytes(&mut buf), expected);
7083
}

0 commit comments

Comments
 (0)