From a168807b2d354bdcd51f1c4758d2407dab98362f Mon Sep 17 00:00:00 2001 From: Pierre Chevalier Date: Fri, 14 Mar 2025 11:34:43 +0000 Subject: [PATCH 1/4] fix!: Hide the internal representation of EntryMode Change the gix-object API so that client code can't explicitely rely on the internal representation of `EntryMode`. This is necessary as we need to change it to allow round-trip behaviour for modes like `b"040000"` and `b"40000"` which currently share the `0o40000u16` representation. Use the opportunity to sprinkle a couple of optimizations in the parsing of the EntryMode since we had to go deep in understanding this code anyway, so may as well. Mostly, don't `reverse` the bytes when parsing. ``` TreeRefIter() time: [46.251 ns 46.390 ns 46.549 ns] change: [-17.685% -16.512% -15.664%] (p = 0.00 < 0.05) Performance has improved. Found 1 outliers among 100 measurements (1.00%) ``` Also add a test that shows the current incorrect behaviour. --- gix-object/src/tree/mod.rs | 136 +++++++++++++++------ gix-object/src/tree/ref_iter.rs | 42 +------ gix-object/tests/object/tree/entry_mode.rs | 39 +++--- 3 files changed, 125 insertions(+), 92 deletions(-) diff --git a/gix-object/src/tree/mod.rs b/gix-object/src/tree/mod.rs index f5889b2ea9a..cf5105f58c4 100644 --- a/gix-object/src/tree/mod.rs +++ b/gix-object/src/tree/mod.rs @@ -44,11 +44,98 @@ pub struct Editor<'a> { /// create it by converting [`EntryKind`] into `EntryMode`. #[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EntryMode(pub u16); +pub struct EntryMode { + // Represents the value read from Git, except that "040000" is represented with 0o140000 but + // "40000" is represented with 0o40000 + internal: u16, +} + +impl TryFrom for tree::EntryMode { + type Error = u32; + fn try_from(mode: u32) -> Result { + Ok(match mode { + 0o40000 | 0o120000 | 0o160000 => EntryMode { internal: mode as u16 }, + blob_mode if blob_mode & 0o100000 == 0o100000 => EntryMode { internal: mode as u16 }, + _ => return Err(mode), + }) + } +} + +impl EntryMode { + /// Expose the value as a u16 (lossy, unlike the internal representation that is hidden) + pub const fn value(self) -> u16 { + self.internal + } + + /// Return the representation as used in the git internal format, which is octal and written + /// to the `backing` buffer. The respective sub-slice that was written to is returned. + pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr { + if self.internal == 0 { + std::slice::from_ref(&b'0') + } else { + for (idx, backing_octet) in backing.iter_mut().enumerate() { + let bit_pos = 3 /* because base 8 and 2^3 == 8*/ * (6 - idx - 1); + let oct_mask = 0b111 << bit_pos; + let digit = (self.internal & oct_mask) >> bit_pos; + *backing_octet = b'0' + digit as u8; + } + if backing[1] == b'4' { + &backing[1..6] + } else { + &backing[0..6] + } + } + .into() + } + + /// Construct an EntryMode from bytes represented as in the git internal format + /// Return the mode and the remainder of the bytes + pub(crate) fn extract_from_bytes(i: &[u8]) -> Option<(Self, &'_ [u8])> { + let mut mode = 0; + let mut idx = 0; + let mut space_pos = 0; + if i.is_empty() { + return None; + } + // const fn, this is why we can't have nice things (like `.iter().any()`) + while idx < i.len() { + let b = i[idx]; + // Delimiter, return what we got + if b == b' ' { + space_pos = idx; + break; + } + // Not a pure octal input + // Performance matters here, so `!(b'0'..=b'7').contains(&b)` won't do + #[allow(clippy::manual_range_contains)] + if b < b'0' || b > b'7' { + return None; + } + // More than 6 octal digits we must have hit the delimiter or the input was malformed + if idx > 6 { + return None; + } + mode = (mode << 3) + (b - b'0') as u16; + idx += 1; + } + Some((Self { internal: mode }, &i[(space_pos + 1)..])) + } + + /// Construct an EntryMode from bytes represented as in the git internal format + pub fn from_bytes(i: &[u8]) -> Option { + Self::extract_from_bytes(i).map(|(mode, _rest)| mode) + } +} impl std::fmt::Debug for EntryMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "EntryMode({:#o})", self.0) + write!(f, "EntryMode(0o{})", self.as_bytes(&mut Default::default())) + } +} + +impl std::fmt::Octal for EntryMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_bytes(&mut Default::default())) } } @@ -74,7 +161,7 @@ pub enum EntryKind { impl From for EntryMode { fn from(value: EntryKind) -> Self { - EntryMode(value as u16) + EntryMode { internal: value as u16 } } } @@ -100,22 +187,14 @@ impl EntryKind { } } -impl std::ops::Deref for EntryMode { - type Target = u16; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - const IFMT: u16 = 0o170000; impl EntryMode { /// Discretize the raw mode into an enum with well-known state while dropping unnecessary details. pub const fn kind(&self) -> EntryKind { - let etype = self.0 & IFMT; + let etype = self.value() & IFMT; if etype == 0o100000 { - if self.0 & 0o000100 == 0o000100 { + if self.value() & 0o000100 == 0o000100 { EntryKind::BlobExecutable } else { EntryKind::Blob @@ -131,27 +210,27 @@ impl EntryMode { /// Return true if this entry mode represents a Tree/directory pub const fn is_tree(&self) -> bool { - self.0 & IFMT == EntryKind::Tree as u16 + self.value() & IFMT == EntryKind::Tree as u16 } /// Return true if this entry mode represents the commit of a submodule. pub const fn is_commit(&self) -> bool { - self.0 & IFMT == EntryKind::Commit as u16 + self.value() & IFMT == EntryKind::Commit as u16 } /// Return true if this entry mode represents a symbolic link pub const fn is_link(&self) -> bool { - self.0 & IFMT == EntryKind::Link as u16 + self.value() & IFMT == EntryKind::Link as u16 } /// Return true if this entry mode represents anything BUT Tree/directory pub const fn is_no_tree(&self) -> bool { - self.0 & IFMT != EntryKind::Tree as u16 + self.value() & IFMT != EntryKind::Tree as u16 } /// Return true if the entry is any kind of blob. pub const fn is_blob(&self) -> bool { - self.0 & IFMT == 0o100000 + self.value() & IFMT == 0o100000 } /// Return true if the entry is an executable blob. @@ -178,27 +257,6 @@ impl EntryMode { Commit => "commit", } } - - /// Return the representation as used in the git internal format, which is octal and written - /// to the `backing` buffer. The respective sub-slice that was written to is returned. - pub fn as_bytes<'a>(&self, backing: &'a mut [u8; 6]) -> &'a BStr { - if self.0 == 0 { - std::slice::from_ref(&b'0') - } else { - let mut nb = 0; - let mut n = self.0; - while n > 0 { - let remainder = (n % 8) as u8; - backing[nb] = b'0' + remainder; - n /= 8; - nb += 1; - } - let res = &mut backing[..nb]; - res.reverse(); - res - } - .into() - } } impl TreeRef<'_> { diff --git a/gix-object/src/tree/ref_iter.rs b/gix-object/src/tree/ref_iter.rs index 9833a3c2c31..72f3233defc 100644 --- a/gix-object/src/tree/ref_iter.rs +++ b/gix-object/src/tree/ref_iter.rs @@ -171,38 +171,7 @@ impl<'a> TryFrom<&'a [u8]> for tree::EntryMode { type Error = &'a [u8]; fn try_from(mode: &'a [u8]) -> Result { - mode_from_decimal(mode) - .map(|(mode, _rest)| tree::EntryMode(mode as u16)) - .ok_or(mode) - } -} - -fn mode_from_decimal(i: &[u8]) -> Option<(u32, &[u8])> { - let mut mode = 0u32; - let mut spacer_pos = 1; - for b in i.iter().take_while(|b| **b != b' ') { - if *b < b'0' || *b > b'7' { - return None; - } - mode = (mode << 3) + u32::from(b - b'0'); - spacer_pos += 1; - } - if i.len() < spacer_pos { - return None; - } - let (_, i) = i.split_at(spacer_pos); - Some((mode, i)) -} - -impl TryFrom for tree::EntryMode { - type Error = u32; - - fn try_from(mode: u32) -> Result { - Ok(match mode { - 0o40000 | 0o120000 | 0o160000 => tree::EntryMode(mode as u16), - blob_mode if blob_mode & 0o100000 == 0o100000 => tree::EntryMode(mode as u16), - _ => return Err(mode), - }) + tree::EntryMode::from_bytes(mode).ok_or(mode) } } @@ -210,15 +179,10 @@ mod decode { use bstr::ByteSlice; use winnow::{error::ParserError, prelude::*}; - use crate::{ - tree, - tree::{ref_iter::mode_from_decimal, EntryRef}, - TreeRef, - }; + use crate::{tree, tree::EntryRef, TreeRef}; pub fn fast_entry(i: &[u8]) -> Option<(&[u8], EntryRef<'_>)> { - let (mode, i) = mode_from_decimal(i)?; - let mode = tree::EntryMode::try_from(mode).ok()?; + let (mode, i) = tree::EntryMode::extract_from_bytes(i)?; let (filename, i) = i.split_at(i.find_byte(0)?); let i = &i[1..]; const HASH_LEN_FIXME: usize = 20; // TODO(SHA256): know actual/desired length or we may overshoot diff --git a/gix-object/tests/object/tree/entry_mode.rs b/gix-object/tests/object/tree/entry_mode.rs index 0fe4c17fc59..4cf5b7eca23 100644 --- a/gix-object/tests/object/tree/entry_mode.rs +++ b/gix-object/tests/object/tree/entry_mode.rs @@ -16,11 +16,14 @@ fn is_methods() { } assert!(mode(EntryKind::Blob).is_blob()); - assert!(EntryMode(0o100645).is_blob()); - assert_eq!(EntryMode(0o100645).kind(), EntryKind::Blob); - assert!(!EntryMode(0o100675).is_executable()); - assert!(EntryMode(0o100700).is_executable()); - assert_eq!(EntryMode(0o100700).kind(), EntryKind::BlobExecutable); + assert!(EntryMode::from_bytes(b"100645").unwrap().is_blob()); + assert_eq!(EntryMode::from_bytes(b"100645").unwrap().kind(), EntryKind::Blob); + assert!(!EntryMode::from_bytes(b"100675").unwrap().is_executable()); + assert!(EntryMode::from_bytes(b"100700").unwrap().is_executable()); + assert_eq!( + EntryMode::from_bytes(b"100700").unwrap().kind(), + EntryKind::BlobExecutable + ); assert!(!mode(EntryKind::Blob).is_link()); assert!(mode(EntryKind::BlobExecutable).is_blob()); assert!(mode(EntryKind::BlobExecutable).is_executable()); @@ -29,17 +32,19 @@ fn is_methods() { assert!(!mode(EntryKind::Link).is_blob()); assert!(mode(EntryKind::Link).is_link()); - assert!(EntryMode(0o121234).is_link()); - assert_eq!(EntryMode(0o121234).kind(), EntryKind::Link); + assert!(EntryMode::from_bytes(b"121234").unwrap().is_link()); + assert_eq!(EntryMode::from_bytes(b"121234").unwrap().kind(), EntryKind::Link); assert!(mode(EntryKind::Link).is_blob_or_symlink()); assert!(mode(EntryKind::Tree).is_tree()); - assert!(EntryMode(0o040101).is_tree()); - assert_eq!(EntryMode(0o040101).kind(), EntryKind::Tree); + assert!(EntryMode::from_bytes(b"040101").unwrap().is_tree()); + assert_eq!(EntryMode::from_bytes(b"040101").unwrap().kind(), EntryKind::Tree); + assert!(EntryMode::from_bytes(b"40101").unwrap().is_tree()); + assert_eq!(EntryMode::from_bytes(b"40101").unwrap().kind(), EntryKind::Tree); assert!(mode(EntryKind::Commit).is_commit()); - assert!(EntryMode(0o167124).is_commit()); - assert_eq!(EntryMode(0o167124).kind(), EntryKind::Commit); + assert!(EntryMode::from_bytes(b"167124").unwrap().is_commit()); + assert_eq!(EntryMode::from_bytes(b"167124").unwrap().kind(), EntryKind::Commit); assert_eq!( - EntryMode(0o000000).kind(), + EntryMode::from_bytes(b"000000").unwrap().kind(), EntryKind::Commit, "commit is really 'anything else' as `kind()` can't fail" ); @@ -58,13 +63,19 @@ fn as_bytes() { (EntryKind::Link.into(), EntryKind::Link.as_octal_str()), (EntryKind::Commit.into(), EntryKind::Commit.as_octal_str()), ( - EntryMode::try_from(b"100744 ".as_ref()).expect("valid"), + EntryMode::from_bytes(b"100744 ".as_ref()).expect("valid"), "100744".into(), ), ( - EntryMode::try_from(b"100644 ".as_ref()).expect("valid"), + EntryMode::from_bytes(b"100644 ".as_ref()).expect("valid"), "100644".into(), ), + // Show incorrect behaviour: b"040000" doesn't round-trip + ( + EntryMode::from_bytes(b"040000".as_ref()).expect("valid"), + "40000".into(), + ), + (EntryMode::from_bytes(b"40000".as_ref()).expect("valid"), "40000".into()), ] { assert_eq!(mode.as_bytes(&mut buf), expected); } From 8969245a6b5874d8524f508569ae1266e48d100e Mon Sep 17 00:00:00 2001 From: Pierre Chevalier Date: Sat, 5 Apr 2025 09:50:35 +0800 Subject: [PATCH 2/4] adapt to changes in gix-object --- examples/ls-tree.rs | 4 ++-- gitoxide-core/src/repository/diff.rs | 8 ++++---- gix-index/src/entry/mode.rs | 3 ++- gix-merge/tests/merge/tree/baseline.rs | 4 ++-- gix/src/object/tree/iter.rs | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/examples/ls-tree.rs b/examples/ls-tree.rs index 17af144040f..fdce7c14cb4 100644 --- a/examples/ls-tree.rs +++ b/examples/ls-tree.rs @@ -54,8 +54,8 @@ fn run(args: Args) -> anyhow::Result<()> { for entry in entries { writeln!( out, - "{:06o} {:4} {} {}", - *entry.mode, + "{:>6o} {:4} {} {}", + entry.mode, entry.mode.as_str(), entry.hash, entry.path diff --git a/gitoxide-core/src/repository/diff.rs b/gitoxide-core/src/repository/diff.rs index 9e6c6d2edae..600eaf2efff 100644 --- a/gitoxide-core/src/repository/diff.rs +++ b/gitoxide-core/src/repository/diff.rs @@ -49,7 +49,7 @@ fn write_changes( } => { writeln!(out, "A: {}", typed_location(location, entry_mode))?; writeln!(out, " {}", id.attach(repo).shorten_or_id())?; - writeln!(out, " -> {:o}", entry_mode.0)?; + writeln!(out, " -> {entry_mode:o}")?; } gix::diff::tree_with_rewrites::Change::Deletion { location, @@ -59,7 +59,7 @@ fn write_changes( } => { writeln!(out, "D: {}", typed_location(location, entry_mode))?; writeln!(out, " {}", id.attach(repo).shorten_or_id())?; - writeln!(out, " {:o} ->", entry_mode.0)?; + writeln!(out, " {entry_mode:o} ->")?; } gix::diff::tree_with_rewrites::Change::Modification { location, @@ -76,7 +76,7 @@ fn write_changes( id = id.attach(repo).shorten_or_id() )?; if previous_entry_mode != entry_mode { - writeln!(out, " {:o} -> {:o}", previous_entry_mode.0, entry_mode.0)?; + writeln!(out, " {previous_entry_mode:o} -> {entry_mode:o}")?; } } gix::diff::tree_with_rewrites::Change::Rewrite { @@ -101,7 +101,7 @@ fn write_changes( id = id.attach(repo).shorten_or_id() )?; if source_entry_mode != entry_mode { - writeln!(out, " {:o} -> {:o}", source_entry_mode.0, entry_mode.0)?; + writeln!(out, " {source_entry_mode:o} -> {entry_mode:o}")?; } } } diff --git a/gix-index/src/entry/mode.rs b/gix-index/src/entry/mode.rs index ec839d5335e..817d7a06e0e 100644 --- a/gix-index/src/entry/mode.rs +++ b/gix-index/src/entry/mode.rs @@ -71,7 +71,8 @@ impl Mode { impl From for Mode { fn from(value: gix_object::tree::EntryMode) -> Self { - Self::from_bits_truncate(u32::from(value.0)) + let value: u16 = value.value(); + Self::from_bits_truncate(u32::from(value)) } } diff --git a/gix-merge/tests/merge/tree/baseline.rs b/gix-merge/tests/merge/tree/baseline.rs index e8e399f138c..32df03ffcb3 100644 --- a/gix-merge/tests/merge/tree/baseline.rs +++ b/gix-merge/tests/merge/tree/baseline.rs @@ -262,7 +262,7 @@ fn parse_conflict_file_info(line: &str) -> Option<(Entry, Side)> { Entry { location: path.to_owned(), id: gix_hash::ObjectId::from_hex(hex_id.as_bytes()).unwrap(), - mode: EntryMode(gix_utils::btoi::to_signed_with_radix::(oct_mode.as_bytes(), 8).unwrap() as u16), + mode: EntryMode::try_from(oct_mode.as_bytes()).unwrap(), }, match stage { "1" => Side::Ancestor, @@ -339,7 +339,7 @@ pub fn visualize_tree( mode = if mode.is_tree() { "".into() } else { - format!("{:o}:", mode.0) + format!("{mode:o}:") } ) } diff --git a/gix/src/object/tree/iter.rs b/gix/src/object/tree/iter.rs index 00519e6cef5..572f0a71f90 100644 --- a/gix/src/object/tree/iter.rs +++ b/gix/src/object/tree/iter.rs @@ -50,8 +50,8 @@ impl std::fmt::Display for EntryRef<'_, '_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{:06o} {:>6} {}\t{}", - *self.mode(), + "{:>6o} {:>6} {}\t{}", + self.mode(), self.mode().as_str(), self.id().shorten_or_id(), self.filename() From 5bfc2f540aac5be997159e93783d56ed147b0a2f Mon Sep 17 00:00:00 2001 From: Pierre Chevalier Date: Fri, 4 Apr 2025 12:23:33 +0100 Subject: [PATCH 3/4] fix!: Allow round-tripping for Trees Change the internal representation of `EntryMode` to fix a round-trip issue. Prior to this change, both `b"040000"` and `b"40000"` mapped to `0o40000u16`. After this change, * `b"040000"` is represented by `0o140000` * `b"40000"` is represented by `0o40000` *Tests*: We can see in the `as_bytes` test that the behaviour is fixed now. We also add a test to show we now can round-trip on the existing test fixtures which contain examples of this situation. *Performance*: We pay a cost for this compared to the parent commit: ``` TreeRefIter() time: [50.403 ns 50.611 ns 50.830 ns] change: [+8.6240% +9.0871% +9.5776%] (p = 0.00 < 0.05) Performance has regressed. ``` but we already did enough optimizations to pay for this earlier in this PR: * `main`: `~55 ns` * `parent`: `~46 ns` * `this commit`: `~50 ns` Fixes 1887 --- gix-object/src/tree/mod.rs | 20 ++++++++++++++++++-- gix-object/tests/object/tree/entry_mode.rs | 3 +-- gix-object/tests/object/tree/from_bytes.rs | 4 ++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/gix-object/src/tree/mod.rs b/gix-object/src/tree/mod.rs index cf5105f58c4..60399de4d32 100644 --- a/gix-object/src/tree/mod.rs +++ b/gix-object/src/tree/mod.rs @@ -64,7 +64,13 @@ impl TryFrom for tree::EntryMode { impl EntryMode { /// Expose the value as a u16 (lossy, unlike the internal representation that is hidden) pub const fn value(self) -> u16 { - self.internal + // Demangle the hack: In the case where the second leftmost octet is 4 (Tree), the leftmost bit is + // there to represent whether the bytes representation should have 5 or 6 octets + if self.internal & IFMT == 0o140000 { + 0o040000 + } else { + self.internal + } } /// Return the representation as used in the git internal format, which is octal and written @@ -79,8 +85,14 @@ impl EntryMode { let digit = (self.internal & oct_mask) >> bit_pos; *backing_octet = b'0' + digit as u8; } + // Hack: `0o140000` represents `"040000"`, `0o40000` represents `"40000"` if backing[1] == b'4' { - &backing[1..6] + if backing[0] == b'1' { + backing[0] = b'0'; + &backing[0..6] + } else { + &backing[1..6] + } } else { &backing[0..6] } @@ -118,6 +130,10 @@ impl EntryMode { mode = (mode << 3) + (b - b'0') as u16; idx += 1; } + // Hack: `0o140000` represents `"040000"`, `0o40000` represents `"40000"` + if mode == 0o040000 && i[0] == b'0' { + mode += 0o100000; + } Some((Self { internal: mode }, &i[(space_pos + 1)..])) } diff --git a/gix-object/tests/object/tree/entry_mode.rs b/gix-object/tests/object/tree/entry_mode.rs index 4cf5b7eca23..b218128b831 100644 --- a/gix-object/tests/object/tree/entry_mode.rs +++ b/gix-object/tests/object/tree/entry_mode.rs @@ -70,10 +70,9 @@ fn as_bytes() { EntryMode::from_bytes(b"100644 ".as_ref()).expect("valid"), "100644".into(), ), - // Show incorrect behaviour: b"040000" doesn't round-trip ( EntryMode::from_bytes(b"040000".as_ref()).expect("valid"), - "40000".into(), + "040000".into(), ), (EntryMode::from_bytes(b"40000".as_ref()).expect("valid"), "40000".into()), ] { diff --git a/gix-object/tests/object/tree/from_bytes.rs b/gix-object/tests/object/tree/from_bytes.rs index 97b68d8daa0..d546ac3d226 100644 --- a/gix-object/tests/object/tree/from_bytes.rs +++ b/gix-object/tests/object/tree/from_bytes.rs @@ -101,6 +101,10 @@ fn special_trees() -> crate::Result { expected_entry_count, "{name}" ); + // Show we can roundtrip + let mut buf: Vec = Default::default(); + actual.write_to(&mut buf).expect("Failed to write bytes to buffer"); + assert_eq!(buf, fixture); } Ok(()) } From 103819dbe2d86790a6ac4c6be3f21e5d4455dfd2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 5 Apr 2025 09:54:40 +0800 Subject: [PATCH 4/4] refactor (#1917) --- gix-object/src/tree/mod.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/gix-object/src/tree/mod.rs b/gix-object/src/tree/mod.rs index 60399de4d32..b7ae614965b 100644 --- a/gix-object/src/tree/mod.rs +++ b/gix-object/src/tree/mod.rs @@ -24,7 +24,7 @@ pub mod write; pub struct Editor<'a> { /// A way to lookup trees. find: &'a dyn crate::FindExt, - /// The kind of hashes to produce + /// The kind of hashes to produce> object_hash: gix_hash::Kind, /// All trees we currently hold in memory. Each of these may change while adding and removing entries. /// null-object-ids mark tree-entries whose value we don't know yet, they are placeholders that will be @@ -46,7 +46,7 @@ pub struct Editor<'a> { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct EntryMode { // Represents the value read from Git, except that "040000" is represented with 0o140000 but - // "40000" is represented with 0o40000 + // "40000" is represented with 0o40000. internal: u16, } @@ -62,10 +62,10 @@ impl TryFrom for tree::EntryMode { } impl EntryMode { - /// Expose the value as a u16 (lossy, unlike the internal representation that is hidden) + /// Expose the value as u16 (lossy, unlike the internal representation that is hidden). pub const fn value(self) -> u16 { // Demangle the hack: In the case where the second leftmost octet is 4 (Tree), the leftmost bit is - // there to represent whether the bytes representation should have 5 or 6 octets + // there to represent whether the bytes representation should have 5 or 6 octets. if self.internal & IFMT == 0o140000 { 0o040000 } else { @@ -85,7 +85,7 @@ impl EntryMode { let digit = (self.internal & oct_mask) >> bit_pos; *backing_octet = b'0' + digit as u8; } - // Hack: `0o140000` represents `"040000"`, `0o40000` represents `"40000"` + // Hack: `0o140000` represents `"040000"`, `0o40000` represents `"40000"`. if backing[1] == b'4' { if backing[0] == b'1' { backing[0] = b'0'; @@ -101,7 +101,7 @@ impl EntryMode { } /// Construct an EntryMode from bytes represented as in the git internal format - /// Return the mode and the remainder of the bytes + /// Return the mode and the remainder of the bytes. pub(crate) fn extract_from_bytes(i: &[u8]) -> Option<(Self, &'_ [u8])> { let mut mode = 0; let mut idx = 0; @@ -109,7 +109,7 @@ impl EntryMode { if i.is_empty() { return None; } - // const fn, this is why we can't have nice things (like `.iter().any()`) + // const fn, this is why we can't have nice things (like `.iter().any()`). while idx < i.len() { let b = i[idx]; // Delimiter, return what we got @@ -117,27 +117,27 @@ impl EntryMode { space_pos = idx; break; } - // Not a pure octal input - // Performance matters here, so `!(b'0'..=b'7').contains(&b)` won't do + // Not a pure octal input. + // Performance matters here, so `!(b'0'..=b'7').contains(&b)` won't do. #[allow(clippy::manual_range_contains)] if b < b'0' || b > b'7' { return None; } - // More than 6 octal digits we must have hit the delimiter or the input was malformed + // More than 6 octal digits we must have hit the delimiter or the input was malformed. if idx > 6 { return None; } mode = (mode << 3) + (b - b'0') as u16; idx += 1; } - // Hack: `0o140000` represents `"040000"`, `0o40000` represents `"40000"` + // Hack: `0o140000` represents `"040000"`, `0o40000` represents `"40000"`. if mode == 0o040000 && i[0] == b'0' { mode += 0o100000; } Some((Self { internal: mode }, &i[(space_pos + 1)..])) } - /// Construct an EntryMode from bytes represented as in the git internal format + /// Construct an EntryMode from bytes represented as in the git internal format. pub fn from_bytes(i: &[u8]) -> Option { Self::extract_from_bytes(i).map(|(mode, _rest)| mode) } @@ -158,7 +158,7 @@ impl std::fmt::Octal for EntryMode { /// A discretized version of ideal and valid values for entry modes. /// /// Note that even though it can represent every valid [mode](EntryMode), it might -/// loose information due to that as well. +/// lose information due to that as well. #[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)] #[repr(u16)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]