176 lines · 5.8 KB · Rust
RawDownload
use super::Cache;use crate::fs::Stack;use crate::{fs, os};use std::path::{Path, PathBuf}; #[derive(Clone)]pub enum Mode {    /// Useful for checkout where directories need creation, but we need to access attributes as well.    CreateDirectoryAndProvideAttributes {        /// If there is a symlink or a file in our path, try to unlink it before creating the directory.        unlink_on_collision: bool,         /// just for testing        #[cfg(debug_assertions)]        test_mkdir_calls: usize,        /// An additional per-user attributes file, similar to `$GIT_DIR/info/attributes`        attributes_file: Option<PathBuf>,    },    /// Used when adding files, requiring access to both attributes and ignore information.    ProvideAttributesAndIgnore {        /// An additional per-user excludes file, similar to `$GIT_DIR/info/exclude`. It's an error if it is set but can't be read/opened.        excludes_file: Option<PathBuf>,        /// An additional per-user attributes file, similar to `$GIT_DIR/info/attributes`        attributes_file: Option<PathBuf>,    },} impl Mode {    /// Configure a mode to be suitable for checking out files.    pub fn checkout(unlink_on_collision: bool, attributes_file: Option<PathBuf>) -> Self {        Mode::CreateDirectoryAndProvideAttributes {            unlink_on_collision,            #[cfg(debug_assertions)]            test_mkdir_calls: 0,            attributes_file,        }    }     /// Configure a mode for adding files.    pub fn add(excludes_file: Option<PathBuf>, attributes_file: Option<PathBuf>) -> Self {        Mode::ProvideAttributesAndIgnore {            excludes_file,            attributes_file,        }    }} #[cfg(debug_assertions)]impl Cache {    pub fn num_mkdir_calls(&self) -> usize {        match self.mode {            Mode::CreateDirectoryAndProvideAttributes { test_mkdir_calls, .. } => test_mkdir_calls,            _ => 0,        }    }     pub fn reset_mkdir_calls(&mut self) {        if let Mode::CreateDirectoryAndProvideAttributes { test_mkdir_calls, .. } = &mut self.mode {            *test_mkdir_calls = 0;        }    }     pub fn unlink_on_collision(&mut self, value: bool) {        if let Mode::CreateDirectoryAndProvideAttributes {            unlink_on_collision, ..        } = &mut self.mode        {            *unlink_on_collision = value;        }    }} pub struct Platform<'a> {    parent: &'a Cache,} impl<'a> Platform<'a> {    /// The full path to `relative` will be returned for use on the file system.    pub fn leading_dir(&self) -> &'a Path {        self.parent.stack.current()    }} impl<'a> std::fmt::Debug for Platform<'a> {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        std::fmt::Debug::fmt(&self.leading_dir(), f)    }} impl Cache {    fn assure_init(&mut self) -> std::io::Result<()> {        Ok(())    }} impl Cache {    /// Create a new instance with `worktree_root` being the base for all future paths we handle, assuming it to be valid which includes    /// symbolic links to be included in it as well.    pub fn new(worktree_root: impl Into<PathBuf>, mode: Mode) -> Self {        let root = worktree_root.into();        Cache {            stack: fs::Stack::new(root),            mode,        }    }     /// Append the `relative` path to the root directory the cache contains and efficiently create leading directories    /// unless `mode` indicates `relative` points to a directory itself in which case the entire resulting path is created as directory.    ///    /// Provide access to cached information for that `relative` entry via the platform returned.    pub fn at_entry(        &mut self,        relative: impl AsRef<Path>,        mode: git_index::entry::Mode,    ) -> std::io::Result<Platform<'_>> {        self.assure_init()?;        let op_mode = &mut self.mode;        self.stack.make_relative_path_current(            relative,            |components, stack: &fs::Stack| {                match op_mode {                    Mode::CreateDirectoryAndProvideAttributes {                        #[cfg(debug_assertions)]                        test_mkdir_calls,                        unlink_on_collision,                        attributes_file: _,                    } => create_leading_directory(components, stack, mode, test_mkdir_calls, *unlink_on_collision)?,                    Mode::ProvideAttributesAndIgnore { .. } => todo!(),                }                Ok(())            },            |_| {},        )?;        Ok(Platform { parent: self })    }} fn create_leading_directory(    components: &mut std::iter::Peekable<std::path::Components<'_>>,    stack: &Stack,    mode: git_index::entry::Mode,    mkdir_calls: &mut usize,    unlink_on_collision: bool,) -> std::io::Result<()> {    let target_is_dir = mode == git_index::entry::Mode::COMMIT || mode == git_index::entry::Mode::DIR;    if !(components.peek().is_some() || target_is_dir) {        return Ok(());    }    #[cfg(debug_assertions)]    {        *mkdir_calls += 1;    }    match std::fs::create_dir(stack.current()) {        Ok(()) => Ok(()),        Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {            let meta = stack.current().symlink_metadata()?;            if meta.is_dir() {                Ok(())            } else if unlink_on_collision {                if meta.file_type().is_symlink() {                    os::remove_symlink(stack.current())?;                } else {                    std::fs::remove_file(stack.current())?;                }                #[cfg(debug_assertions)]                {                    *mkdir_calls += 1;                }                std::fs::create_dir(stack.current())            } else {                Err(err)            }        }        Err(err) => Err(err),    }}