Skip to content

Commit 3ac5d0b

Browse files
committed
fix!: Keep verbatim tree-entry mode, but support discretization using the new EntryKind.
Please note that `EntryKind` and `EntryMode` can be converted from and to each other.
1 parent c36c95f commit 3ac5d0b

File tree

4 files changed

+180
-75
lines changed

4 files changed

+180
-75
lines changed

gix-object/src/tree/mod.rs

Lines changed: 111 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ pub mod write;
1313
///
1414
/// Used in [`mutable::Entry`][crate::tree::Entry] and [`EntryRef`].
1515
#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
16+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17+
pub struct EntryMode(u16);
18+
19+
/// A discretized version of ideal and valid values for entry modes.
20+
///
21+
/// Note that even though it can represent every valid [mode](EntryMode), it might
22+
/// loose information due to that as well.
23+
#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)]
1624
#[repr(u16)]
1725
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18-
pub enum EntryMode {
26+
pub enum EntryKind {
1927
/// A tree, or directory
2028
Tree = 0o040000u16,
2129
/// A file that is not executable
@@ -28,38 +36,129 @@ pub enum EntryMode {
2836
Commit = 0o160000,
2937
}
3038

39+
impl From<EntryKind> for EntryMode {
40+
fn from(value: EntryKind) -> Self {
41+
EntryMode(value as u16)
42+
}
43+
}
44+
45+
impl From<EntryMode> for EntryKind {
46+
fn from(value: EntryMode) -> Self {
47+
value.kind()
48+
}
49+
}
50+
51+
/// Serialization
52+
impl EntryKind {
53+
/// Return the representation as used in the git internal format.
54+
pub fn as_octal_str(&self) -> &'static BStr {
55+
use EntryKind::*;
56+
let bytes: &[u8] = match self {
57+
Tree => b"40000",
58+
Blob => b"100644",
59+
BlobExecutable => b"100755",
60+
Link => b"120000",
61+
Commit => b"160000",
62+
};
63+
bytes.into()
64+
}
65+
}
66+
67+
impl std::ops::Deref for EntryMode {
68+
type Target = u16;
69+
70+
fn deref(&self) -> &Self::Target {
71+
&self.0
72+
}
73+
}
74+
3175
impl EntryMode {
76+
/// Discretize the raw mode into an enum with well-known state while dropping unnecessary details.
77+
pub const fn kind(&self) -> EntryKind {
78+
match self.0 {
79+
0o40000 => EntryKind::Tree,
80+
0o120000 => EntryKind::Link,
81+
0o160000 => EntryKind::Commit,
82+
blob_mode => {
83+
if blob_mode & 0o000100 == 0o000100 {
84+
EntryKind::BlobExecutable
85+
} else {
86+
EntryKind::Blob
87+
}
88+
}
89+
}
90+
}
91+
3292
/// Return true if this entry mode represents a Tree/directory
33-
pub fn is_tree(&self) -> bool {
34-
*self == EntryMode::Tree
93+
pub const fn is_tree(&self) -> bool {
94+
self.0 == EntryKind::Tree as u16
95+
}
96+
97+
/// Return true if this entry mode represents the commit of a submodule.
98+
pub const fn is_commit(&self) -> bool {
99+
self.0 == EntryKind::Commit as u16
100+
}
101+
102+
/// Return true if this entry mode represents a symbolic link
103+
pub const fn is_link(&self) -> bool {
104+
self.0 == EntryKind::Link as u16
35105
}
36106

37107
/// Return true if this entry mode represents anything BUT Tree/directory
38-
pub fn is_no_tree(&self) -> bool {
39-
*self != EntryMode::Tree
108+
pub const fn is_no_tree(&self) -> bool {
109+
self.0 != EntryKind::Tree as u16
40110
}
41111

42112
/// Return true if the entry is any kind of blob.
43-
pub fn is_blob(&self) -> bool {
44-
matches!(self, EntryMode::Blob | EntryMode::BlobExecutable)
113+
pub const fn is_blob(&self) -> bool {
114+
matches!(self.kind(), EntryKind::Blob | EntryKind::BlobExecutable)
115+
}
116+
117+
/// Return true if the entry is an executable blob.
118+
pub const fn is_executable(&self) -> bool {
119+
matches!(self.kind(), EntryKind::BlobExecutable)
45120
}
46121

47122
/// Return true if the entry is any kind of blob or symlink.
48-
pub fn is_blob_or_symlink(&self) -> bool {
49-
matches!(self, EntryMode::Blob | EntryMode::BlobExecutable | EntryMode::Link)
123+
pub const fn is_blob_or_symlink(&self) -> bool {
124+
matches!(
125+
self.kind(),
126+
EntryKind::Blob | EntryKind::BlobExecutable | EntryKind::Link
127+
)
50128
}
51129

52130
/// Represent the mode as descriptive string.
53-
pub fn as_str(&self) -> &'static str {
54-
use EntryMode::*;
55-
match self {
131+
pub const fn as_str(&self) -> &'static str {
132+
use EntryKind::*;
133+
match self.kind() {
56134
Tree => "tree",
57135
Blob => "blob",
58136
BlobExecutable => "exe",
59137
Link => "link",
60138
Commit => "commit",
61139
}
62140
}
141+
142+
/// Return the representation as used in the git internal format, which is octal and written
143+
/// to the `backing` buffer. The respective sub-slice that was written to is returned.
144+
pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr {
145+
if self.0 == 0 {
146+
std::slice::from_ref(&b'0')
147+
} else {
148+
let mut nb = 0;
149+
let mut n = self.0;
150+
while n > 0 {
151+
let remainder = (n % 8) as u8;
152+
backing[nb] = b'0' + remainder;
153+
n /= 8;
154+
nb += 1;
155+
}
156+
let res = &mut backing[..nb];
157+
res.reverse();
158+
res
159+
}
160+
.into()
161+
}
63162
}
64163

65164
/// An element of a [`TreeRef`][crate::TreeRef::entries].
@@ -124,18 +223,3 @@ impl Ord for Entry {
124223
})
125224
}
126225
}
127-
128-
/// Serialization
129-
impl EntryMode {
130-
/// Return the representation as used in the git internal format.
131-
pub fn as_bytes(&self) -> &'static [u8] {
132-
use EntryMode::*;
133-
match self {
134-
Tree => b"40000",
135-
Blob => b"100644",
136-
BlobExecutable => b"100755",
137-
Link => b"120000",
138-
Commit => b"160000",
139-
}
140-
}
141-
}

gix-object/src/tree/ref_iter.rs

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ impl<'a> TreeRef<'a> {
2626

2727
let search = EntryRef {
2828
mode: if is_dir {
29-
tree::EntryMode::Tree
29+
tree::EntryKind::Tree
3030
} else {
31-
tree::EntryMode::Blob
32-
},
31+
tree::EntryKind::Blob
32+
}
33+
.into(),
3334
filename: name,
3435
oid: &NULL_HASH,
3536
};
@@ -82,34 +83,33 @@ impl<'a> TryFrom<&'a [u8]> for tree::EntryMode {
8283
type Error = &'a [u8];
8384

8485
fn try_from(mode: &'a [u8]) -> Result<Self, Self::Error> {
85-
Ok(match mode {
86-
b"40000" => tree::EntryMode::Tree,
87-
b"100644" => tree::EntryMode::Blob,
88-
b"100755" => tree::EntryMode::BlobExecutable,
89-
b"120000" => tree::EntryMode::Link,
90-
b"160000" => tree::EntryMode::Commit,
91-
b"100664" => tree::EntryMode::Blob, // rare and found in the linux kernel
92-
b"100640" => tree::EntryMode::Blob, // rare and found in the Rust repo
93-
_ => return Err(mode),
94-
})
86+
mode_from_decimal(mode)
87+
.map(|(mode, _rest)| tree::EntryMode(mode as u16))
88+
.ok_or(mode)
9589
}
9690
}
9791

92+
fn mode_from_decimal(i: &[u8]) -> Option<(u32, &[u8])> {
93+
let mut mode = 0u32;
94+
let mut spacer_pos = 1;
95+
for b in i.iter().take_while(|b| **b != b' ') {
96+
if *b < b'0' || *b > b'7' {
97+
return None;
98+
}
99+
mode = (mode << 3) + (b - b'0') as u32;
100+
spacer_pos += 1;
101+
}
102+
let (_, i) = i.split_at(spacer_pos);
103+
Some((mode, i))
104+
}
105+
98106
impl TryFrom<u32> for tree::EntryMode {
99107
type Error = u32;
100108

101109
fn try_from(mode: u32) -> Result<Self, Self::Error> {
102110
Ok(match mode {
103-
0o40000 => tree::EntryMode::Tree,
104-
0o120000 => tree::EntryMode::Link,
105-
0o160000 => tree::EntryMode::Commit,
106-
blob_mode if blob_mode & 0o100000 == 0o100000 => {
107-
if blob_mode & 0o000100 == 0o000100 {
108-
tree::EntryMode::BlobExecutable
109-
} else {
110-
tree::EntryMode::Blob
111-
}
112-
}
111+
0o40000 | 0o120000 | 0o160000 => tree::EntryMode(mode as u16),
112+
blob_mode if blob_mode & 0o100000 == 0o100000 => tree::EntryMode(mode as u16),
113113
_ => return Err(mode),
114114
})
115115
}
@@ -121,19 +121,11 @@ mod decode {
121121
use bstr::ByteSlice;
122122
use winnow::{error::ParserError, prelude::*};
123123

124+
use crate::tree::ref_iter::mode_from_decimal;
124125
use crate::{tree, tree::EntryRef, TreeRef};
125126

126127
pub fn fast_entry(i: &[u8]) -> Option<(&[u8], EntryRef<'_>)> {
127-
let mut mode = 0u32;
128-
let mut spacer_pos = 1;
129-
for b in i.iter().take_while(|b| **b != b' ') {
130-
if *b < b'0' || *b > b'7' {
131-
return None;
132-
}
133-
mode = (mode << 3) + (b - b'0') as u32;
134-
spacer_pos += 1;
135-
}
136-
let (_, i) = i.split_at(spacer_pos);
128+
let (mode, i) = mode_from_decimal(i)?;
137129
let mode = tree::EntryMode::try_from(mode).ok()?;
138130
let (filename, i) = i.split_at(i.find_byte(0)?);
139131
let i = &i[1..];

gix-object/src/tree/write.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ impl crate::WriteTo for Tree {
3535
&self.entries,
3636
"entries for serialization must be sorted by filename"
3737
);
38+
let mut buf = Default::default();
3839
for Entry { mode, filename, oid } in &self.entries {
39-
out.write_all(mode.as_bytes())?;
40+
out.write_all(mode.as_bytes(&mut buf))?;
4041
out.write_all(SPACE)?;
4142

4243
if filename.find_byte(b'\n').is_some() {
@@ -58,10 +59,11 @@ impl crate::WriteTo for Tree {
5859
}
5960

6061
fn size(&self) -> u64 {
62+
let mut buf = Default::default();
6163
self.entries
6264
.iter()
6365
.map(|Entry { mode, filename, oid }| {
64-
(mode.as_bytes().len() + 1 + filename.len() + 1 + oid.as_bytes().len()) as u64
66+
(mode.as_bytes(&mut buf).len() + 1 + filename.len() + 1 + oid.as_bytes().len()) as u64
6567
})
6668
.sum()
6769
}
@@ -80,8 +82,9 @@ impl<'a> crate::WriteTo for TreeRef<'a> {
8082
&self.entries,
8183
"entries for serialization must be sorted by filename"
8284
);
85+
let mut buf = Default::default();
8386
for EntryRef { mode, filename, oid } in &self.entries {
84-
out.write_all(mode.as_bytes())?;
87+
out.write_all(mode.as_bytes(&mut buf))?;
8588
out.write_all(SPACE)?;
8689

8790
if filename.find_byte(b'\n').is_some() {
@@ -103,10 +106,11 @@ impl<'a> crate::WriteTo for TreeRef<'a> {
103106
}
104107

105108
fn size(&self) -> u64 {
109+
let mut buf = Default::default();
106110
self.entries
107111
.iter()
108112
.map(|EntryRef { mode, filename, oid }| {
109-
(mode.as_bytes().len() + 1 + filename.len() + 1 + oid.as_bytes().len()) as u64
113+
(mode.as_bytes(&mut buf).len() + 1 + filename.len() + 1 + oid.as_bytes().len()) as u64
110114
})
111115
.sum()
112116
}

0 commit comments

Comments
 (0)