diff options
Diffstat (limited to 'vendor/gix-worktree/src/checkout')
-rw-r--r-- | vendor/gix-worktree/src/checkout/chunk.rs | 182 | ||||
-rw-r--r-- | vendor/gix-worktree/src/checkout/entry.rs | 166 | ||||
-rw-r--r-- | vendor/gix-worktree/src/checkout/function.rs | 119 | ||||
-rw-r--r-- | vendor/gix-worktree/src/checkout/mod.rs | 77 |
4 files changed, 0 insertions, 544 deletions
diff --git a/vendor/gix-worktree/src/checkout/chunk.rs b/vendor/gix-worktree/src/checkout/chunk.rs deleted file mode 100644 index 9de9e424e..000000000 --- a/vendor/gix-worktree/src/checkout/chunk.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; - -use bstr::BStr; -use gix_features::progress::Progress; -use gix_hash::oid; - -use crate::{checkout, checkout::entry, Cache}; - -mod reduce { - use std::{ - marker::PhantomData, - sync::atomic::{AtomicUsize, Ordering}, - }; - - use gix_features::progress::Progress; - - use crate::checkout; - - pub struct Reduce<'a, 'entry, P1, P2, E> { - pub files: &'a mut P1, - pub bytes: &'a mut P2, - pub num_files: &'a AtomicUsize, - pub aggregate: super::Outcome<'entry>, - pub marker: PhantomData<E>, - } - - impl<'a, 'entry, P1, P2, E> gix_features::parallel::Reduce for Reduce<'a, 'entry, P1, P2, E> - where - P1: Progress, - P2: Progress, - E: std::error::Error + Send + Sync + 'static, - { - type Input = Result<super::Outcome<'entry>, checkout::Error<E>>; - type FeedProduce = (); - type Output = super::Outcome<'entry>; - type Error = checkout::Error<E>; - - fn feed(&mut self, item: Self::Input) -> Result<Self::FeedProduce, Self::Error> { - let item = item?; - let super::Outcome { - bytes_written, - delayed, - errors, - collisions, - } = item; - self.aggregate.bytes_written += bytes_written; - self.aggregate.delayed.extend(delayed); - self.aggregate.errors.extend(errors); - self.aggregate.collisions.extend(collisions); - - self.bytes.set(self.aggregate.bytes_written as usize); - self.files.set(self.num_files.load(Ordering::Relaxed)); - - Ok(()) - } - - fn finalize(self) -> Result<Self::Output, Self::Error> { - Ok(self.aggregate) - } - } -} -pub use reduce::Reduce; - -#[derive(Default)] -pub struct Outcome<'a> { - pub collisions: Vec<checkout::Collision>, - pub errors: Vec<checkout::ErrorRecord>, - pub delayed: Vec<(&'a mut gix_index::Entry, &'a BStr)>, - pub bytes_written: u64, -} - -#[derive(Clone)] -pub struct Context<'a, Find: Clone> { - pub find: Find, - pub path_cache: Cache, - pub buf: Vec<u8>, - pub options: checkout::Options, - /// We keep these shared so that there is the chance for printing numbers that aren't looking like - /// multiple of chunk sizes. Purely cosmetic. Otherwise it's the same as `files`. - pub num_files: &'a AtomicUsize, -} - -pub fn process<'entry, Find, E>( - entries_with_paths: impl Iterator<Item = (&'entry mut gix_index::Entry, &'entry BStr)>, - files: &mut impl Progress, - bytes: &mut impl Progress, - ctx: &mut Context<'_, Find>, -) -> Result<Outcome<'entry>, checkout::Error<E>> -where - Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<gix_object::BlobRef<'a>, E> + Clone, - E: std::error::Error + Send + Sync + 'static, -{ - let mut delayed = Vec::new(); - let mut collisions = Vec::new(); - let mut errors = Vec::new(); - let mut bytes_written = 0; - - for (entry, entry_path) in entries_with_paths { - // TODO: write test for that - if entry.flags.contains(gix_index::entry::Flags::SKIP_WORKTREE) { - files.inc(); - continue; - } - - // Symlinks always have to be delayed on windows as they have to point to something that exists on creation. - // And even if not, there is a distinction between file and directory symlinks, hence we have to check what the target is - // before creating it. - // And to keep things sane, we just do the same on non-windows as well which is similar to what git does and adds some safety - // around writing through symlinks (even though we handle this). - // This also means that we prefer content in files over symlinks in case of collisions, which probably is for the better, too. - if entry.mode == gix_index::entry::Mode::SYMLINK { - delayed.push((entry, entry_path)); - continue; - } - - bytes_written += - checkout_entry_handle_result(entry, entry_path, &mut errors, &mut collisions, files, bytes, ctx)? as u64; - } - - Ok(Outcome { - bytes_written, - errors, - collisions, - delayed, - }) -} - -pub fn checkout_entry_handle_result<Find, E>( - entry: &mut gix_index::Entry, - entry_path: &BStr, - errors: &mut Vec<checkout::ErrorRecord>, - collisions: &mut Vec<checkout::Collision>, - files: &mut impl Progress, - bytes: &mut impl Progress, - Context { - find, - path_cache, - buf, - options, - num_files, - }: &mut Context<'_, Find>, -) -> Result<usize, checkout::Error<E>> -where - Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<gix_object::BlobRef<'a>, E> + Clone, - E: std::error::Error + Send + Sync + 'static, -{ - let res = entry::checkout( - entry, - entry_path, - entry::Context { find, path_cache, buf }, - options.clone(), - ); - files.inc(); - num_files.fetch_add(1, Ordering::SeqCst); - match res { - Ok(object_size) => { - bytes.inc_by(object_size); - Ok(object_size) - } - Err(checkout::Error::Io(err)) if gix_fs::symlink::is_collision_error(&err) => { - // We are here because a file existed or was blocked by a directory which shouldn't be possible unless - // we are on a file insensitive file system. - files.fail(format!("{}: collided ({:?})", entry_path, err.kind())); - collisions.push(checkout::Collision { - path: entry_path.into(), - error_kind: err.kind(), - }); - Ok(0) - } - Err(err) => { - if options.keep_going { - errors.push(checkout::ErrorRecord { - path: entry_path.into(), - error: Box::new(err), - }); - Ok(0) - } else { - Err(err) - } - } - } -} diff --git a/vendor/gix-worktree/src/checkout/entry.rs b/vendor/gix-worktree/src/checkout/entry.rs deleted file mode 100644 index 524cf90f2..000000000 --- a/vendor/gix-worktree/src/checkout/entry.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::{fs::OpenOptions, io::Write, path::Path}; - -use bstr::BStr; -use gix_hash::oid; -use gix_index::{entry::Stat, Entry}; -use io_close::Close; - -use crate::Cache; - -pub struct Context<'a, Find> { - pub find: &'a mut Find, - pub path_cache: &'a mut Cache, - pub buf: &'a mut Vec<u8>, -} - -#[cfg_attr(not(unix), allow(unused_variables))] -pub fn checkout<Find, E>( - entry: &mut Entry, - entry_path: &BStr, - Context { find, path_cache, buf }: Context<'_, Find>, - crate::checkout::Options { - fs: gix_fs::Capabilities { - symlink, - executable_bit, - .. - }, - destination_is_initially_empty, - overwrite_existing, - .. - }: crate::checkout::Options, -) -> Result<usize, crate::checkout::Error<E>> -where - Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<gix_object::BlobRef<'a>, E>, - E: std::error::Error + Send + Sync + 'static, -{ - let dest_relative = gix_path::try_from_bstr(entry_path).map_err(|_| crate::checkout::Error::IllformedUtf8 { - path: entry_path.to_owned(), - })?; - let is_dir = Some(entry.mode == gix_index::entry::Mode::COMMIT || entry.mode == gix_index::entry::Mode::DIR); - let dest = path_cache.at_path(dest_relative, is_dir, &mut *find)?.path(); - - let object_size = match entry.mode { - gix_index::entry::Mode::FILE | gix_index::entry::Mode::FILE_EXECUTABLE => { - let obj = find(&entry.id, buf).map_err(|err| crate::checkout::Error::Find { - err, - oid: entry.id, - path: dest.to_path_buf(), - })?; - - #[cfg_attr(not(unix), allow(unused_mut))] - let mut options = open_options(dest, destination_is_initially_empty, overwrite_existing); - let needs_executable_bit = executable_bit && entry.mode == gix_index::entry::Mode::FILE_EXECUTABLE; - #[cfg(unix)] - if needs_executable_bit && destination_is_initially_empty { - use std::os::unix::fs::OpenOptionsExt; - // Note that these only work if the file was newly created, but won't if it's already - // existing, possibly without the executable bit set. Thus we do this only if the file is new. - options.mode(0o777); - } - - let mut file = try_write_or_unlink(dest, overwrite_existing, |p| options.open(p))?; - file.write_all(obj.data)?; - - // For possibly existing, overwritten files, we must change the file mode explicitly. - #[cfg(unix)] - if needs_executable_bit && !destination_is_initially_empty { - use std::os::unix::fs::PermissionsExt; - let mut perm = std::fs::symlink_metadata(dest)?.permissions(); - perm.set_mode(0o777); - std::fs::set_permissions(dest, perm)?; - } - // 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. - entry.stat = Stat::from_fs(&file.metadata()?)?; - file.close()?; - obj.data.len() - } - gix_index::entry::Mode::SYMLINK => { - let obj = find(&entry.id, buf).map_err(|err| crate::checkout::Error::Find { - err, - oid: entry.id, - path: dest.to_path_buf(), - })?; - let symlink_destination = gix_path::try_from_byte_slice(obj.data) - .map_err(|_| crate::checkout::Error::IllformedUtf8 { path: obj.data.into() })?; - - if symlink { - try_write_or_unlink(dest, overwrite_existing, |p| { - gix_fs::symlink::create(symlink_destination, p) - })?; - } else { - let mut file = try_write_or_unlink(dest, overwrite_existing, |p| { - open_options(p, destination_is_initially_empty, overwrite_existing).open(dest) - })?; - file.write_all(obj.data)?; - file.close()?; - } - - entry.stat = Stat::from_fs(&std::fs::symlink_metadata(dest)?)?; - obj.data.len() - } - gix_index::entry::Mode::DIR => todo!(), - gix_index::entry::Mode::COMMIT => todo!(), - _ => unreachable!(), - }; - Ok(object_size) -} - -/// Note that this works only because we assume to not race ourselves when symlinks are involved, and we do this by -/// delaying symlink creation to the end and will always do that sequentially. -/// It's still possible to fall for a race if other actors create symlinks in our path, but that's nothing to defend against. -fn try_write_or_unlink<T>( - path: &Path, - overwrite_existing: bool, - op: impl Fn(&Path) -> std::io::Result<T>, -) -> std::io::Result<T> { - if overwrite_existing { - match op(path) { - Ok(res) => Ok(res), - Err(err) if gix_fs::symlink::is_collision_error(&err) => { - try_unlink_path_recursively(path, &std::fs::symlink_metadata(path)?)?; - op(path) - } - Err(err) => Err(err), - } - } else { - op(path) - } -} - -fn try_unlink_path_recursively(path: &Path, path_meta: &std::fs::Metadata) -> std::io::Result<()> { - if path_meta.is_dir() { - std::fs::remove_dir_all(path) - } else if path_meta.file_type().is_symlink() { - gix_fs::symlink::remove(path) - } else { - std::fs::remove_file(path) - } -} - -#[cfg(not(debug_assertions))] -fn debug_assert_dest_is_no_symlink(_path: &Path) {} - -/// This is a debug assertion as we expect the machinery calling this to prevent this possibility in the first place -#[cfg(debug_assertions)] -fn debug_assert_dest_is_no_symlink(path: &Path) { - if let Ok(meta) = path.metadata() { - debug_assert!( - !meta.file_type().is_symlink(), - "BUG: should not ever allow to overwrite/write-into the target of a symbolic link: {}", - path.display() - ); - } -} - -fn open_options(path: &Path, destination_is_initially_empty: bool, overwrite_existing: bool) -> OpenOptions { - if overwrite_existing || !destination_is_initially_empty { - debug_assert_dest_is_no_symlink(path); - } - let mut options = gix_features::fs::open_options_no_follow(); - options - .create_new(destination_is_initially_empty && !overwrite_existing) - .create(!destination_is_initially_empty || overwrite_existing) - .write(true); - options -} diff --git a/vendor/gix-worktree/src/checkout/function.rs b/vendor/gix-worktree/src/checkout/function.rs deleted file mode 100644 index 8e69fd4d6..000000000 --- a/vendor/gix-worktree/src/checkout/function.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; - -use gix_features::{interrupt, parallel::in_parallel, progress, progress::Progress}; -use gix_hash::oid; - -use crate::{cache, checkout::chunk, Cache}; - -/// Note that interruption still produce an `Ok(…)` value, so the caller should look at `should_interrupt` to communicate the outcome. -/// `dir` is the directory into which to checkout the `index`. -/// `git_dir` is the `.git` directory for reading additional per-repository configuration files. -#[allow(clippy::too_many_arguments)] -pub fn checkout<Find, E>( - index: &mut gix_index::State, - dir: impl Into<std::path::PathBuf>, - find: Find, - files: &mut impl Progress, - bytes: &mut impl Progress, - should_interrupt: &AtomicBool, - options: crate::checkout::Options, -) -> Result<crate::checkout::Outcome, crate::checkout::Error<E>> -where - Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<gix_object::BlobRef<'a>, E> + Send + Clone, - E: std::error::Error + Send + Sync + 'static, -{ - let paths = index.take_path_backing(); - let res = checkout_inner(index, &paths, dir, find, files, bytes, should_interrupt, options); - index.return_path_backing(paths); - res -} - -#[allow(clippy::too_many_arguments)] -fn checkout_inner<Find, E>( - index: &mut gix_index::State, - paths: &gix_index::PathStorage, - dir: impl Into<std::path::PathBuf>, - find: Find, - files: &mut impl Progress, - bytes: &mut impl Progress, - should_interrupt: &AtomicBool, - options: crate::checkout::Options, -) -> Result<crate::checkout::Outcome, crate::checkout::Error<E>> -where - Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<gix_object::BlobRef<'a>, E> + Send + Clone, - E: std::error::Error + Send + Sync + 'static, -{ - let num_files = AtomicUsize::default(); - let dir = dir.into(); - let case = if options.fs.ignore_case { - gix_glob::pattern::Case::Fold - } else { - gix_glob::pattern::Case::Sensitive - }; - let (chunk_size, thread_limit, num_threads) = gix_features::parallel::optimize_chunk_size_and_thread_limit( - 100, - index.entries().len().into(), - options.thread_limit, - None, - ); - - let state = cache::State::for_checkout(options.overwrite_existing, options.attributes.clone()); - let attribute_files = state.id_mappings_from_index(index, paths, Default::default(), case); - let mut ctx = chunk::Context { - buf: Vec::new(), - path_cache: Cache::new(dir, state, case, Vec::with_capacity(512), attribute_files), - find, - options, - num_files: &num_files, - }; - - let chunk::Outcome { - mut collisions, - mut errors, - mut bytes_written, - delayed, - } = if num_threads == 1 { - let entries_with_paths = interrupt::Iter::new(index.entries_mut_with_paths_in(paths), should_interrupt); - chunk::process(entries_with_paths, files, bytes, &mut ctx)? - } else { - let entries_with_paths = interrupt::Iter::new(index.entries_mut_with_paths_in(paths), should_interrupt); - in_parallel( - gix_features::iter::Chunks { - inner: entries_with_paths, - size: chunk_size, - }, - thread_limit, - { - let ctx = ctx.clone(); - move |_| (progress::Discard, progress::Discard, ctx.clone()) - }, - |chunk, (files, bytes, ctx)| chunk::process(chunk.into_iter(), files, bytes, ctx), - chunk::Reduce { - files, - bytes, - num_files: &num_files, - aggregate: Default::default(), - marker: Default::default(), - }, - )? - }; - - for (entry, entry_path) in delayed { - bytes_written += chunk::checkout_entry_handle_result( - entry, - entry_path, - &mut errors, - &mut collisions, - files, - bytes, - &mut ctx, - )? as u64; - } - - Ok(crate::checkout::Outcome { - files_updated: num_files.load(Ordering::Relaxed), - collisions, - errors, - bytes_written, - }) -} diff --git a/vendor/gix-worktree/src/checkout/mod.rs b/vendor/gix-worktree/src/checkout/mod.rs deleted file mode 100644 index 11f39b1b2..000000000 --- a/vendor/gix-worktree/src/checkout/mod.rs +++ /dev/null @@ -1,77 +0,0 @@ -#![allow(missing_docs)] - -use bstr::BString; -use gix_index::entry::stat; - -#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct Collision { - /// the path that collided with something already present on disk. - pub path: BString, - /// The io error we encountered when checking out `path`. - pub error_kind: std::io::ErrorKind, -} - -pub struct ErrorRecord { - /// the path that encountered the error. - pub path: BString, - /// The error - pub error: Box<dyn std::error::Error + Send + Sync + 'static>, -} - -#[derive(Default)] -pub struct Outcome { - /// The amount of files updated, or created. - pub files_updated: usize, - /// The amount of bytes written to disk, - pub bytes_written: u64, - pub collisions: Vec<Collision>, - pub errors: Vec<ErrorRecord>, -} - -#[derive(Clone, Default)] -pub struct Options { - /// capabilities of the file system - pub fs: gix_fs::Capabilities, - /// If set, don't use more than this amount of threads. - /// Otherwise, usually use as many threads as there are logical cores. - /// A value of 0 is interpreted as no-limit - pub thread_limit: Option<usize>, - /// If true, we assume no file to exist in the target directory, and want exclusive access to it. - /// This should be enabled when cloning to avoid checks for freshness of files. This also enables - /// detection of collisions based on whether or not exclusive file creation succeeds or fails. - pub destination_is_initially_empty: bool, - /// If true, default false, worktree entries on disk will be overwritten with content from the index - /// even if they appear to be changed. When creating directories that clash with existing worktree entries, - /// these will try to delete the existing entry. - /// This is similar in behaviour as `git checkout --force`. - pub overwrite_existing: bool, - /// If true, default false, try to checkout as much as possible and don't abort on first error which isn't - /// due to a conflict. - /// The checkout operation will never fail, but count the encountered errors instead along with their paths. - pub keep_going: bool, - /// Control how stat comparisons are made when checking if a file is fresh. - pub stat_options: stat::Options, - /// A stack of attributes to use with the filesystem cache to use as driver for filters. - pub attributes: crate::cache::state::Attributes, -} - -#[derive(Debug, thiserror::Error)] -pub enum Error<E: std::error::Error + Send + Sync + 'static> { - #[error("Could not convert path to UTF8: {}", .path)] - IllformedUtf8 { path: BString }, - #[error("The clock was off when reading file related metadata after updating a file on disk")] - Time(#[from] std::time::SystemTimeError), - #[error("IO error while writing blob or reading file metadata or changing filetype")] - Io(#[from] std::io::Error), - #[error("object {} for checkout at {} could not be retrieved from object database", .oid.to_hex(), .path.display())] - Find { - #[source] - err: E, - oid: gix_hash::ObjectId, - path: std::path::PathBuf, - }, -} - -mod chunk; -mod entry; -pub(crate) mod function; |