use crate::entry::Mode; impl Mode { /// Return `true` if this is a sparse entry, as it points to a directory which usually isn't what an 'unsparse' index tracks. pub fn is_sparse(&self) -> bool { *self == Self::DIR } /// Return `true` if this is a submodule entry. pub fn is_submodule(&self) -> bool { *self == Self::DIR | Self::SYMLINK } /// Convert this instance to a tree's entry mode, or return `None` if for some /// and unexpected reason the bitflags don't resemble any known entry-mode. pub fn to_tree_entry_mode(&self) -> Option { gix_object::tree::EntryMode::try_from(self.bits()).ok() } /// Compares this mode to the file system version ([`std::fs::symlink_metadata`]) /// and returns the change needed to update this mode to match the file. /// /// * if `has_symlinks` is false symlink entries will simply check if there /// is a normal file on disk /// * if `executable_bit` is false the executable bit will not be compared /// `Change::ExecutableBit` will never be generated /// /// If there is a type change then we will use whatever information is /// present on the FS. Specifically if `has_symlinks` is false we will /// never generate `Change::TypeChange { new_mode: Mode::SYMLINK }`. and /// iff `executable_bit` is false we will never generate `Change::TypeChange /// { new_mode: Mode::FILE_EXECUTABLE }` (all files are assumed to be not /// executable). That measn that unstaging and staging files can be a lossy /// operation on such file systems. /// /// If a directory replaced a normal file/symlink we assume that the /// directory is a submodule. Normal (non-submodule) directories would /// cause a file to be deleted from the index and should be handled before /// calling this function. /// /// If the stat information belongs to something other than a normal file/ /// directory (like a socket) we just return an identity change (non-files /// can not be committed to git). pub fn change_to_match_fs( self, stat: &crate::fs::Metadata, has_symlinks: bool, executable_bit: bool, ) -> Option { match self { Mode::FILE if !stat.is_file() => (), Mode::SYMLINK if has_symlinks && !stat.is_symlink() => (), Mode::SYMLINK if !has_symlinks && !stat.is_file() => (), Mode::COMMIT | Mode::DIR if !stat.is_dir() => (), Mode::FILE if executable_bit && stat.is_executable() => return Some(Change::ExecutableBit), Mode::FILE_EXECUTABLE if executable_bit && !stat.is_executable() => return Some(Change::ExecutableBit), _ => return None, }; let new_mode = if stat.is_dir() { Mode::COMMIT } else if executable_bit && stat.is_executable() { Mode::FILE_EXECUTABLE } else { Mode::FILE }; Some(Change::Type { new_mode }) } } /// A change of a [`Mode`]. pub enum Change { /// The type of mode changed, like symlink => file. Type { /// The mode representing the new index type. new_mode: Mode, }, /// The executable permission of this file has changed. ExecutableBit, } impl Change { /// Applies this change to `mode` and returns the changed one. pub fn apply(self, mode: Mode) -> Mode { match self { Change::Type { new_mode } => new_mode, Change::ExecutableBit => match mode { Mode::FILE => Mode::FILE_EXECUTABLE, Mode::FILE_EXECUTABLE => Mode::FILE, _ => unreachable!("invalid mode change: can't flip executable bit of {mode:?}"), }, } } }