1
1
use crate :: {
2
- bstr:: { BStr , BString } ,
2
+ bstr:: { BStr , BString , ByteSlice } ,
3
3
tree, Tree , TreeRef ,
4
4
} ;
5
5
use std:: cell:: RefCell ;
@@ -36,19 +36,100 @@ pub struct Editor<'a> {
36
36
tree_buf : Vec < u8 > ,
37
37
}
38
38
39
+ /// Parse a valid git mode into a u16
40
+ /// A valid git mode can be represented by a set of 5-6 octal digits. The leftmost octal digit can
41
+ /// be at most one. These conditions guarantee that it can be represented as a `u16`, although that
42
+ /// representation is lossy compared to the byte slice it came from as `"040000"` and `"40000"` will
43
+ /// both be represented as `0o40000`.
44
+ /// Input:
45
+ /// We accept input that contains exactly a valid git mode or a valid git mode followed by a
46
+ /// space, then anything (in case we are just pointing in the memory from the Git Tree
47
+ /// representation)
48
+ /// Return value:
49
+ /// The value (`u16`) given a valid input or `None` otherwise
50
+ pub const fn parse_git_mode ( i : & [ u8 ] ) -> Option < u16 > {
51
+ let mut mode = 0 ;
52
+ let mut idx = 0 ;
53
+ // const fn, this is why we can't have nice things (like `.iter().any()`)
54
+ while idx < i. len ( ) {
55
+ let b = i[ idx] ;
56
+ // Delimiter, return what we got
57
+ if b == b' ' {
58
+ return Some ( mode) ;
59
+ }
60
+ // Not a pure octal input
61
+ if b < b'0' || b > b'7' {
62
+ return None ;
63
+ }
64
+ // More than 6 octal digits we must have hit the delimiter or the input was malformed
65
+ if idx > 6 {
66
+ return None ;
67
+ }
68
+ mode = ( mode << 3 ) + ( b - b'0' ) as u16 ;
69
+ idx += 1 ;
70
+ }
71
+ Some ( mode)
72
+ }
73
+
74
+ /// Just a place-holder until the `slice_split_once` feature stabilizes
75
+ fn split_once ( slice : & [ u8 ] , value : u8 ) -> Option < ( & ' _ [ u8 ] , & ' _ [ u8 ] ) > {
76
+ let mut iterator = slice. splitn ( 2 , |b| * b == value) ;
77
+ let first = iterator. next ( ) ;
78
+ let second = iterator. next ( ) ;
79
+ first. and_then ( |first| second. map ( |second| ( first, second) ) )
80
+ }
81
+
82
+ /// From the slice we get from a Git Tree representation, extract and parse the "mode" part
83
+ /// Return:
84
+ /// * `Some((mode as u16, (mode as slice, rest as slice)))` if the input slice is valid
85
+ /// * `None` otherwise
86
+ #[ allow( clippy:: type_complexity) ]
87
+ fn extract_git_mode ( i : & [ u8 ] ) -> Option < ( u16 , ( & [ u8 ] , & [ u8 ] ) ) > {
88
+ if let Some ( ( mode_slice, rest_slice) ) = split_once ( i, b' ' ) {
89
+ if rest_slice. is_empty ( ) {
90
+ return None ;
91
+ }
92
+
93
+ parse_git_mode ( mode_slice) . map ( |mode_num| ( mode_num, ( mode_slice, rest_slice) ) )
94
+ } else {
95
+ None
96
+ }
97
+ }
98
+
99
+ impl TryFrom < u32 > for tree:: EntryMode {
100
+ type Error = u32 ;
101
+
102
+ fn try_from ( mode : u32 ) -> Result < Self , Self :: Error > {
103
+ Ok ( match mode {
104
+ 0o40000 | 0o120000 | 0o160000 => ( mode as u16 ) . into ( ) ,
105
+ blob_mode if blob_mode & 0o100000 == 0o100000 => ( mode as u16 ) . into ( ) ,
106
+ _ => return Err ( mode) ,
107
+ } )
108
+ }
109
+ }
110
+
39
111
/// The mode of items storable in a tree, similar to the file mode on a unix file system.
40
112
///
41
- /// Used in [`mutable::Entry`][crate::tree::Entry] and [`EntryRef`] .
113
+ /// Used in [`mutable::Entry`][crate::tree::Entry].
42
114
///
43
115
/// Note that even though it can be created from any `u16`, it should be preferable to
44
116
/// create it by converting [`EntryKind`] into `EntryMode`.
45
117
#[ derive( Clone , Copy , PartialEq , Eq , Ord , PartialOrd , Hash ) ]
46
118
#[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
47
- pub struct EntryMode ( pub u16 ) ;
119
+ pub struct EntryMode {
120
+ pub ( crate ) value : u16 ,
121
+ pub ( crate ) git_representation : [ u8 ; 6 ] ,
122
+ }
48
123
49
124
impl std:: fmt:: Debug for EntryMode {
50
125
fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
51
- write ! ( f, "EntryMode({:#o})" , self . 0 )
126
+ write ! ( f, "EntryMode(0o{})" , self . as_bstr( ) )
127
+ }
128
+ }
129
+
130
+ impl std:: fmt:: Octal for EntryMode {
131
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
132
+ write ! ( f, "{}" , self . as_bstr( ) )
52
133
}
53
134
}
54
135
@@ -72,9 +153,41 @@ pub enum EntryKind {
72
153
Commit = 0o160000 ,
73
154
}
74
155
75
- impl From < EntryKind > for EntryMode {
76
- fn from ( value : EntryKind ) -> Self {
77
- EntryMode ( value as u16 )
156
+ // Mask away the bottom 12 bits and the top 2 bits
157
+ const IFMT : u16 = 0o170000 ;
158
+ const fn parse_entry_kind_from_value ( mode : u16 ) -> EntryKind {
159
+ let etype = mode & IFMT ;
160
+ if etype == 0o100000 {
161
+ if mode & 0o000100 == 0o000100 {
162
+ EntryKind :: BlobExecutable
163
+ } else {
164
+ EntryKind :: Blob
165
+ }
166
+ } else if etype == EntryKind :: Link as u16 {
167
+ EntryKind :: Link
168
+ } else if etype == EntryKind :: Tree as u16 {
169
+ EntryKind :: Tree
170
+ } else {
171
+ EntryKind :: Commit
172
+ }
173
+ }
174
+
175
+ impl From < u16 > for EntryKind {
176
+ fn from ( mode : u16 ) -> Self {
177
+ parse_entry_kind_from_value ( mode)
178
+ }
179
+ }
180
+
181
+ impl From < u16 > for EntryMode {
182
+ fn from ( value : u16 ) -> Self {
183
+ let kind: EntryKind = value. into ( ) ;
184
+ Self :: from ( kind)
185
+ }
186
+ }
187
+
188
+ impl From < EntryMode > for u16 {
189
+ fn from ( value : EntryMode ) -> Self {
190
+ value. value
78
191
}
79
192
}
80
193
@@ -88,70 +201,73 @@ impl From<EntryMode> for EntryKind {
88
201
impl EntryKind {
89
202
/// Return the representation as used in the git internal format.
90
203
pub fn as_octal_str ( & self ) -> & ' static BStr {
204
+ self . as_octal_bytes ( ) . as_bstr ( )
205
+ }
206
+
207
+ /// Return the representation as used in the git internal format.
208
+ pub fn as_octal_bytes ( & self ) -> & ' static [ u8 ] {
91
209
use EntryKind :: * ;
92
- let bytes : & [ u8 ] = match self {
210
+ match self {
93
211
Tree => b"40000" ,
94
212
Blob => b"100644" ,
95
213
BlobExecutable => b"100755" ,
96
214
Link => b"120000" ,
97
215
Commit => b"160000" ,
98
- } ;
99
- bytes. into ( )
216
+ }
217
+ }
218
+ /// Return the representation as a human readable description
219
+ pub fn as_descriptive_str ( & self ) -> & ' static str {
220
+ use EntryKind :: * ;
221
+ match self {
222
+ Tree => "tree" ,
223
+ Blob => "blob" ,
224
+ BlobExecutable => "exe" ,
225
+ Link => "link" ,
226
+ Commit => "commit" ,
227
+ }
100
228
}
101
229
}
102
230
103
- impl std:: ops:: Deref for EntryMode {
104
- type Target = u16 ;
105
-
106
- fn deref ( & self ) -> & Self :: Target {
107
- & self . 0
231
+ impl From < EntryKind > for EntryMode {
232
+ fn from ( value : EntryKind ) -> Self {
233
+ let mut git_representation = [ b' ' ; 6 ] ;
234
+ git_representation[ ..value. as_octal_str ( ) . len ( ) ] . copy_from_slice ( value. as_octal_str ( ) ) ;
235
+ EntryMode {
236
+ git_representation,
237
+ value : value as u16 ,
238
+ }
108
239
}
109
240
}
110
241
111
- const IFMT : u16 = 0o170000 ;
112
-
113
242
impl EntryMode {
114
243
/// Discretize the raw mode into an enum with well-known state while dropping unnecessary details.
115
244
pub const fn kind ( & self ) -> EntryKind {
116
- let etype = self . 0 & IFMT ;
117
- if etype == 0o100000 {
118
- if self . 0 & 0o000100 == 0o000100 {
119
- EntryKind :: BlobExecutable
120
- } else {
121
- EntryKind :: Blob
122
- }
123
- } else if etype == EntryKind :: Link as u16 {
124
- EntryKind :: Link
125
- } else if etype == EntryKind :: Tree as u16 {
126
- EntryKind :: Tree
127
- } else {
128
- EntryKind :: Commit
129
- }
130
- }
131
-
132
- /// Return true if this entry mode represents a Tree/directory
133
- pub const fn is_tree ( & self ) -> bool {
134
- self . 0 & IFMT == EntryKind :: Tree as u16
245
+ parse_entry_kind_from_value ( self . value )
135
246
}
136
247
137
248
/// Return true if this entry mode represents the commit of a submodule.
138
249
pub const fn is_commit ( & self ) -> bool {
139
- self . 0 & IFMT == EntryKind :: Commit as u16
250
+ matches ! ( self . kind ( ) , EntryKind :: Commit )
140
251
}
141
252
142
253
/// Return true if this entry mode represents a symbolic link
143
254
pub const fn is_link ( & self ) -> bool {
144
- self . 0 & IFMT == EntryKind :: Link as u16
255
+ matches ! ( self . kind( ) , EntryKind :: Link )
256
+ }
257
+
258
+ /// Return true if this entry mode represents anything BUT Tree/directory
259
+ pub const fn is_tree ( & self ) -> bool {
260
+ matches ! ( self . kind( ) , EntryKind :: Tree )
145
261
}
146
262
147
263
/// Return true if this entry mode represents anything BUT Tree/directory
148
264
pub const fn is_no_tree ( & self ) -> bool {
149
- self . 0 & IFMT != EntryKind :: Tree as u16
265
+ ! matches ! ( self . kind ( ) , EntryKind :: Tree )
150
266
}
151
267
152
268
/// Return true if the entry is any kind of blob.
153
269
pub const fn is_blob ( & self ) -> bool {
154
- self . 0 & IFMT == 0o100000
270
+ matches ! ( self . kind ( ) , EntryKind :: Blob | EntryKind :: BlobExecutable )
155
271
}
156
272
157
273
/// Return true if the entry is an executable blob.
@@ -167,37 +283,26 @@ impl EntryMode {
167
283
)
168
284
}
169
285
170
- /// Represent the mode as descriptive string.
171
- pub const fn as_str ( & self ) -> & ' static str {
172
- use EntryKind :: * ;
173
- match self . kind ( ) {
174
- Tree => "tree" ,
175
- Blob => "blob" ,
176
- BlobExecutable => "exe" ,
177
- Link => "link" ,
178
- Commit => "commit" ,
286
+ /// How many bytes of the backing representation are significant?
287
+ #[ allow( clippy:: len_without_is_empty) ]
288
+ pub fn len ( & self ) -> usize {
289
+ if let Some ( delim) = self . git_representation . iter ( ) . position ( |b| * b == b' ' ) {
290
+ delim
291
+ } else {
292
+ self . git_representation . len ( )
179
293
}
180
294
}
181
295
182
296
/// Return the representation as used in the git internal format, which is octal and written
183
297
/// to the `backing` buffer. The respective sub-slice that was written to is returned.
184
- pub fn as_bytes < ' a > ( & self , backing : & ' a mut [ u8 ; 6 ] ) -> & ' a BStr {
185
- if self . 0 == 0 {
186
- std:: slice:: from_ref ( & b'0' )
187
- } else {
188
- let mut nb = 0 ;
189
- let mut n = self . 0 ;
190
- while n > 0 {
191
- let remainder = ( n % 8 ) as u8 ;
192
- backing[ nb] = b'0' + remainder;
193
- n /= 8 ;
194
- nb += 1 ;
195
- }
196
- let res = & mut backing[ ..nb] ;
197
- res. reverse ( ) ;
198
- res
199
- }
200
- . into ( )
298
+ pub fn as_bytes ( & self ) -> & ' _ [ u8 ] {
299
+ & self . git_representation [ ..self . len ( ) ]
300
+ }
301
+
302
+ /// Return the representation as used in the git internal format, which is octal and written
303
+ /// to the `backing` buffer. The respective sub-slice that was written to is returned.
304
+ pub fn as_bstr ( & self ) -> & ' _ BStr {
305
+ self . as_bytes ( ) . as_bstr ( )
201
306
}
202
307
}
203
308
0 commit comments