From 67833dfd18c49ed8073e7a3c0461579c0a5c2017 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 22 Jan 2024 09:14:50 +0100 Subject: [PATCH 1/2] fix: `EntryMode` canonicalization (#1259) This means that `mode.kind()` and `mode.is_*` will also work correctly if the underlying mode wasn't canonicalized, which could happen when looking at trees that were stored with very old versions of Git. --- gix-object/src/tree/mod.rs | 34 +++++++++++++++++++--------------- gix-object/tests/tree/mod.rs | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/gix-object/src/tree/mod.rs b/gix-object/src/tree/mod.rs index 7375f1c69c3..a8103d278f0 100644 --- a/gix-object/src/tree/mod.rs +++ b/gix-object/src/tree/mod.rs @@ -72,46 +72,50 @@ impl std::ops::Deref for EntryMode { } } +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 { - match self.0 { - 0o40000 => EntryKind::Tree, - 0o120000 => EntryKind::Link, - 0o160000 => EntryKind::Commit, - blob_mode => { - if blob_mode & 0o000100 == 0o000100 { - EntryKind::BlobExecutable - } else { - EntryKind::Blob - } + let etype = self.0 & IFMT; + if etype == 0o100000 { + if self.0 & 0o000100 == 0o000100 { + EntryKind::BlobExecutable + } else { + EntryKind::Blob } + } else if etype == EntryKind::Link as u16 { + EntryKind::Link + } else if etype == EntryKind::Tree as u16 { + EntryKind::Tree + } else { + EntryKind::Commit } } /// Return true if this entry mode represents a Tree/directory pub const fn is_tree(&self) -> bool { - self.0 == EntryKind::Tree as u16 + self.0 & 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 == EntryKind::Commit as u16 + self.0 & IFMT == EntryKind::Commit as u16 } /// Return true if this entry mode represents a symbolic link pub const fn is_link(&self) -> bool { - self.0 == EntryKind::Link as u16 + self.0 & 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 != EntryKind::Tree as u16 + self.0 & IFMT != EntryKind::Tree as u16 } /// Return true if the entry is any kind of blob. pub const fn is_blob(&self) -> bool { - matches!(self.kind(), EntryKind::Blob | EntryKind::BlobExecutable) + self.0 & IFMT == 0o100000 } /// Return true if the entry is an executable blob. diff --git a/gix-object/tests/tree/mod.rs b/gix-object/tests/tree/mod.rs index 181042e6011..6a4a39349c9 100644 --- a/gix-object/tests/tree/mod.rs +++ b/gix-object/tests/tree/mod.rs @@ -213,6 +213,25 @@ mod entry_mode { ); } + #[test] + fn is_methods() { + fn mode(kind: EntryKind) -> EntryMode { + kind.into() + } + + assert!(mode(EntryKind::Blob).is_blob()); + assert!(!mode(EntryKind::Blob).is_link()); + assert!(mode(EntryKind::BlobExecutable).is_blob()); + assert!(mode(EntryKind::Blob).is_blob_or_symlink()); + assert!(mode(EntryKind::BlobExecutable).is_blob_or_symlink()); + + assert!(!mode(EntryKind::Link).is_blob()); + assert!(mode(EntryKind::Link).is_link()); + assert!(mode(EntryKind::Link).is_blob_or_symlink()); + assert!(mode(EntryKind::Tree).is_tree()); + assert!(mode(EntryKind::Commit).is_commit()); + } + #[test] fn as_bytes() { let mut buf = Default::default(); From 82e85fd192f59bc62bb27de13360a543971e8167 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 22 Jan 2024 09:37:44 +0100 Subject: [PATCH 2/2] feat: Allow the creation of any `EntryMode`. (#1259) This helps testing for one, but might also be useful in other contexts. After all, this is a plumbing crate. --- gix-object/src/tree/mod.rs | 5 ++++- gix-object/tests/tree/mod.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/gix-object/src/tree/mod.rs b/gix-object/src/tree/mod.rs index a8103d278f0..1014fcfde29 100644 --- a/gix-object/src/tree/mod.rs +++ b/gix-object/src/tree/mod.rs @@ -12,9 +12,12 @@ pub mod write; /// The mode of items storable in a tree, similar to the file mode on a unix file system. /// /// Used in [`mutable::Entry`][crate::tree::Entry] and [`EntryRef`]. +/// +/// Note that even though it can be created from any `u16`, it should be preferable to +/// create it by converting [`EntryKind`] into `EntryMode`. #[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EntryMode(u16); +pub struct EntryMode(pub u16); /// A discretized version of ideal and valid values for entry modes. /// diff --git a/gix-object/tests/tree/mod.rs b/gix-object/tests/tree/mod.rs index 6a4a39349c9..81a9d32b688 100644 --- a/gix-object/tests/tree/mod.rs +++ b/gix-object/tests/tree/mod.rs @@ -220,16 +220,33 @@ mod entry_mode { } 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!(!mode(EntryKind::Blob).is_link()); assert!(mode(EntryKind::BlobExecutable).is_blob()); + assert!(mode(EntryKind::BlobExecutable).is_executable()); assert!(mode(EntryKind::Blob).is_blob_or_symlink()); assert!(mode(EntryKind::BlobExecutable).is_blob_or_symlink()); 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!(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!(mode(EntryKind::Commit).is_commit()); + assert!(EntryMode(0o167124).is_commit()); + assert_eq!(EntryMode(0o167124).kind(), EntryKind::Commit); + assert_eq!( + EntryMode(0o000000).kind(), + EntryKind::Commit, + "commit is really 'anything else' as `kind()` can't fail" + ); } #[test]