Sebastian Thielthanks clippy0e2a2434 years agoHistory
107 lines · 3.8 KB · Rust
RawDownload
use std::{convert::TryInto, fs::OpenOptions, io::Write, time::Duration}; use bstr::BStr;use git_hash::oid;use git_index::Entry; use crate::index;use crate::index::checkout::PathCache; #[cfg_attr(not(unix), allow(unused_variables))]pub fn checkout<Find>(    entry: &mut Entry,    entry_path: &BStr,    find: &mut Find,    cache: &mut PathCache,    index::checkout::Options {        fs: crate::fs::Capabilities {            symlink,            executable_bit,            ..        },        destination_is_initially_empty,        ..    }: index::checkout::Options,    buf: &mut Vec<u8>,) -> Result<usize, index::checkout::Error>where    Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,{    let dest = cache.append_relative_path_assure_leading_dir(        git_features::path::from_byte_slice(entry_path).map_err(|_| index::checkout::Error::IllformedUtf8 {            path: entry_path.to_owned(),        })?,        entry.mode,    )?;     let object_size = 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: dest.to_path_buf(),            })?;            let mut options = OpenOptions::new();            options                .create_new(destination_is_initially_empty)                .create(!destination_is_initially_empty)                .write(true);            #[cfg(unix)]            if executable_bit && 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)?;            // NOTE: we don't call `file.sync_all()` here knowing that some filesystems don't handle this well.            //       revisit this once there is a bug to fix.            update_fstat(entry, file.metadata()?)?;            obj.data.len()        }        git_index::entry::Mode::SYMLINK => {            let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {                oid: entry.id,                path: dest.to_path_buf(),            })?;            let symlink_destination = git_features::path::from_byte_slice(obj.data)                .map_err(|_| index::checkout::Error::IllformedUtf8 { path: obj.data.into() })?;             // TODO: how to deal with mode changes? Maybe this info can be passed once we check for whether            // a checkout is needed at all.            if symlink {                crate::os::create_symlink(symlink_destination, dest)?;            } else {                std::fs::write(&dest, obj.data)?;            }             update_fstat(entry, std::fs::symlink_metadata(&dest)?)?;            obj.data.len()        }        git_index::entry::Mode::DIR => todo!(),        git_index::entry::Mode::COMMIT => todo!(),        _ => unreachable!(),    };    Ok(object_size)} fn update_fstat(entry: &mut Entry, meta: std::fs::Metadata) -> Result<(), index::checkout::Error> {    let ctime = meta        .created()        .map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;    let mtime = meta        .modified()        .map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;     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(())}