Sebastian Thielthanks clippya8e94974 years agoHistory
175 lines · 6.8 KB · Rust
RawDownload
#![forbid(unsafe_code, rust_2018_idioms)] pub mod index {    use git_hash::oid;     pub mod checkout {        use bstr::BString;        use quick_error::quick_error;         #[derive(Clone, Copy)]        pub struct Options {            pub symlinks: bool,        }         impl Default for Options {            fn default() -> Self {                Options { symlinks: true }            }        }         quick_error! {            #[derive(Debug)]            pub enum Error {                IllformedUtf8{ path: BString } {                    display("Could not convert path to UTF8: {}", path)                }                Time(err: std::time::SystemTimeError) {                    from()                    source(err)                    display("The clock was off when reading file related metadata after updating a file on disk")                }                Io(err: std::io::Error) {                    from()                    source(err)                    display("IO error while writing blob or reading file metadata or changing filetype")                }                ObjectNotFound{ oid: git_hash::ObjectId, path: std::path::PathBuf } {                    display("object {} for checkout at {} not found in object database", oid.to_hex(), path.display())                }            }        }    }     pub fn checkout<Find>(        index: &mut git_index::State,        path: impl AsRef<std::path::Path>,        mut find: Find,        options: checkout::Options,    ) -> Result<(), checkout::Error>    where        Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,    {        let root = path.as_ref();        let mut buf = Vec::new();        for (entry, entry_path) in index.entries_mut_with_paths() {            // TODO: write test for that            if entry.flags.contains(git_index::entry::Flags::SKIP_WORKTREE) {                continue;            }             entry::checkout(entry, entry_path, &mut find, root, options, &mut buf)?;        }        Ok(())    }     pub(crate) mod entry {        use std::{            convert::TryInto,            fs::{create_dir_all, OpenOptions},            io::Write,            time::Duration,        };         use bstr::BStr;        use git_hash::oid;        use git_index::Entry;         use crate::index;         pub fn checkout<Find>(            entry: &mut Entry,            entry_path: &BStr,            find: &mut Find,            root: &std::path::Path,            index::checkout::Options { symlinks }: index::checkout::Options,            buf: &mut Vec<u8>,        ) -> Result<(), index::checkout::Error>        where            Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,        {            let dest = root.join(git_features::path::from_byte_slice(entry_path).ok_or_else(|| {                index::checkout::Error::IllformedUtf8 {                    path: entry_path.to_owned(),                }            })?);            create_dir_all(dest.parent().expect("entry paths are never empty"))?; // TODO: can this be avoided to create dirs when needed only?             match entry.mode {                git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => {                    let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {                        oid: entry.id,                        path: root.to_path_buf(),                    })?;                    let mut options = OpenOptions::new();                    options.write(true).create_new(true);                    #[cfg(unix)]                    if entry.mode == git_index::entry::Mode::FILE_EXECUTABLE {                        use std::os::unix::fs::OpenOptionsExt;                        options.mode(0o777);                    }                    let mut file = options.open(&dest)?;                    file.write_all(obj.data)?;                    let met = file.metadata()?;                    let ctime = met                        .created()                        .map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;                    let mtime = met                        .modified()                        .map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;                     update_fstat(entry, ctime, mtime)?;                }                git_index::entry::Mode::SYMLINK => {                    let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {                        oid: entry.id,                        path: root.to_path_buf(),                    })?;                    let symlink_destination = git_features::path::from_byte_slice(obj.data)                        .ok_or_else(|| index::checkout::Error::IllformedUtf8 { path: obj.data.into() })?;                    if symlinks {                        #[cfg(unix)]                        std::os::unix::fs::symlink(symlink_destination, &dest)?;                        #[cfg(windows)]                        if dest.exists() {                            if dest.is_file() {                                std::os::windows::fs::symlink_file(symlink_destination, &dest)?;                            } else {                                std::os::windows::fs::symlink_dir(symlink_destination, &dest)?;                            }                        }                    } else {                        std::fs::write(&dest, obj.data)?;                    }                    let met = std::fs::symlink_metadata(&dest)?;                    let ctime = met                        .created()                        .map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;                    let mtime = met                        .modified()                        .map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;                    update_fstat(entry, ctime, mtime)?;                }                git_index::entry::Mode::DIR => todo!(),                git_index::entry::Mode::COMMIT => todo!(),                _ => unreachable!(),            }            Ok(())        }         fn update_fstat(entry: &mut Entry, ctime: Duration, mtime: Duration) -> Result<(), index::checkout::Error> {            let stat = &mut entry.stat;            stat.mtime.secs = mtime                .as_secs()                .try_into()                .expect("by 2038 we found a solution for this");            stat.mtime.nsecs = mtime.subsec_nanos();            stat.ctime.secs = ctime                .as_secs()                .try_into()                .expect("by 2038 we found a solution for this");            stat.ctime.nsecs = ctime.subsec_nanos();            Ok(())        }    }}