@@ -44,11 +44,98 @@ pub struct Editor<'a> {
4444/// create it by converting [`EntryKind`] into `EntryMode`.
4545#[ derive( Clone , Copy , PartialEq , Eq , Ord , PartialOrd , Hash ) ]
4646#[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
47- pub struct EntryMode ( pub u16 ) ;
47+ pub struct EntryMode {
48+ // Represents the value read from Git, except that "040000" is represented with 0o140000 but
49+ // "40000" is represented with 0o40000
50+ internal : u16 ,
51+ }
52+
53+ impl TryFrom < u32 > for tree:: EntryMode {
54+ type Error = u32 ;
55+ fn try_from ( mode : u32 ) -> Result < Self , Self :: Error > {
56+ Ok ( match mode {
57+ 0o40000 | 0o120000 | 0o160000 => EntryMode { internal : mode as u16 } ,
58+ blob_mode if blob_mode & 0o100000 == 0o100000 => EntryMode { internal : mode as u16 } ,
59+ _ => return Err ( mode) ,
60+ } )
61+ }
62+ }
63+
64+ impl EntryMode {
65+ /// Expose the value as a u16 (lossy, unlike the internal representation that is hidden)
66+ pub const fn value ( self ) -> u16 {
67+ self . internal
68+ }
69+
70+ /// Return the representation as used in the git internal format, which is octal and written
71+ /// to the `backing` buffer. The respective sub-slice that was written to is returned.
72+ pub fn as_bytes < ' a > ( & self , backing : & ' a mut [ u8 ; 6 ] ) -> & ' a BStr {
73+ if self . internal == 0 {
74+ std:: slice:: from_ref ( & b'0' )
75+ } else {
76+ for ( idx, backing_octet) in backing. iter_mut ( ) . enumerate ( ) {
77+ let bit_pos = 3 /* because base 8 and 2^3 == 8*/ * ( 6 - idx - 1 ) ;
78+ let oct_mask = 0b111 << bit_pos;
79+ let digit = ( self . internal & oct_mask) >> bit_pos;
80+ * backing_octet = b'0' + digit as u8 ;
81+ }
82+ if backing[ 1 ] == b'4' {
83+ & backing[ 1 ..6 ]
84+ } else {
85+ & backing[ 0 ..6 ]
86+ }
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+ // Performance matters here, so `!(b'0'..=b'7').contains(&b)` won't do
110+ #[ allow( clippy:: manual_range_contains) ]
111+ if b < b'0' || b > b'7' {
112+ return None ;
113+ }
114+ // More than 6 octal digits we must have hit the delimiter or the input was malformed
115+ if idx > 6 {
116+ return None ;
117+ }
118+ mode = ( mode << 3 ) + ( b - b'0' ) as u16 ;
119+ idx += 1 ;
120+ }
121+ Some ( ( Self { internal : mode } , & i[ ( space_pos + 1 ) ..] ) )
122+ }
123+
124+ /// Construct an EntryMode from bytes represented as in the git internal format
125+ pub fn from_bytes ( i : & [ u8 ] ) -> Option < Self > {
126+ Self :: extract_from_bytes ( i) . map ( |( mode, _rest) | mode)
127+ }
128+ }
48129
49130impl std:: fmt:: Debug for EntryMode {
50131 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
51- write ! ( f, "EntryMode({:#o})" , self . 0 )
132+ write ! ( f, "EntryMode(0o{})" , self . as_bytes( & mut Default :: default ( ) ) )
133+ }
134+ }
135+
136+ impl std:: fmt:: Octal for EntryMode {
137+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
138+ write ! ( f, "{}" , self . as_bytes( & mut Default :: default ( ) ) )
52139 }
53140}
54141
@@ -74,7 +161,7 @@ pub enum EntryKind {
74161
75162impl From < EntryKind > for EntryMode {
76163 fn from ( value : EntryKind ) -> Self {
77- EntryMode ( value as u16 )
164+ EntryMode { internal : value as u16 }
78165 }
79166}
80167
@@ -100,22 +187,14 @@ impl EntryKind {
100187 }
101188}
102189
103- impl std:: ops:: Deref for EntryMode {
104- type Target = u16 ;
105-
106- fn deref ( & self ) -> & Self :: Target {
107- & self . 0
108- }
109- }
110-
111190const IFMT : u16 = 0o170000 ;
112191
113192impl EntryMode {
114193 /// Discretize the raw mode into an enum with well-known state while dropping unnecessary details.
115194 pub const fn kind ( & self ) -> EntryKind {
116- let etype = self . 0 & IFMT ;
195+ let etype = self . value ( ) & IFMT ;
117196 if etype == 0o100000 {
118- if self . 0 & 0o000100 == 0o000100 {
197+ if self . value ( ) & 0o000100 == 0o000100 {
119198 EntryKind :: BlobExecutable
120199 } else {
121200 EntryKind :: Blob
@@ -131,27 +210,27 @@ impl EntryMode {
131210
132211 /// Return true if this entry mode represents a Tree/directory
133212 pub const fn is_tree ( & self ) -> bool {
134- self . 0 & IFMT == EntryKind :: Tree as u16
213+ self . value ( ) & IFMT == EntryKind :: Tree as u16
135214 }
136215
137216 /// Return true if this entry mode represents the commit of a submodule.
138217 pub const fn is_commit ( & self ) -> bool {
139- self . 0 & IFMT == EntryKind :: Commit as u16
218+ self . value ( ) & IFMT == EntryKind :: Commit as u16
140219 }
141220
142221 /// Return true if this entry mode represents a symbolic link
143222 pub const fn is_link ( & self ) -> bool {
144- self . 0 & IFMT == EntryKind :: Link as u16
223+ self . value ( ) & IFMT == EntryKind :: Link as u16
145224 }
146225
147226 /// Return true if this entry mode represents anything BUT Tree/directory
148227 pub const fn is_no_tree ( & self ) -> bool {
149- self . 0 & IFMT != EntryKind :: Tree as u16
228+ self . value ( ) & IFMT != EntryKind :: Tree as u16
150229 }
151230
152231 /// Return true if the entry is any kind of blob.
153232 pub const fn is_blob ( & self ) -> bool {
154- self . 0 & IFMT == 0o100000
233+ self . value ( ) & IFMT == 0o100000
155234 }
156235
157236 /// Return true if the entry is an executable blob.
@@ -178,27 +257,6 @@ impl EntryMode {
178257 Commit => "commit" ,
179258 }
180259 }
181-
182- /// Return the representation as used in the git internal format, which is octal and written
183- /// 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 ( )
201- }
202260}
203261
204262impl TreeRef < ' _ > {
0 commit comments