Struct git_worktree::fs::Cache
source · pub struct Cache<'paths> { /* private fields */ }
Expand description
A cache for efficiently executing operations on directories and files which are encountered in sorted order. That way, these operations can be re-used for subsequent invocations in the same directory.
This cache can be configured to create directories efficiently, read git-ignore files and git-attribute files, in any combination.
A cache for directory creation to reduce the amount of stat calls when creating directories safely, that is without following symlinks that might be on the way.
As a special case, it offers a ‘prefix’ which (by itself) is assumed to exist and may contain symlinks. Everything past that prefix boundary must not contain a symlink. We do this by allowing any input path.
Another added benefit is its ability to store the path of full path of the entry to which leading directories are to be created to avoid allocating memory.
For this to work, it remembers the last ‘good’ path to a directory and assumes that all components of it are still valid, too. As directories are created, the cache will be adjusted to reflect the latest seen directory.
The caching is only useful if consecutive calls to create a directory are using a sorted list of entries.
Implementations§
source§impl<'paths> Cache<'paths>
impl<'paths> Cache<'paths>
pub fn set_case(&mut self, case: Case)
pub fn num_mkdir_calls(&self) -> usize
pub fn reset_mkdir_calls(&mut self)
pub fn unlink_on_collision(&mut self, value: bool)
source§impl<'paths> Cache<'paths>
impl<'paths> Cache<'paths>
sourcepub fn new(
worktree_root: impl Into<PathBuf>,
state: State,
case: Case,
buf: Vec<u8>,
attribute_files_in_index: Vec<(&'paths BStr, ObjectId)>
) -> Self
pub fn new(
worktree_root: impl Into<PathBuf>,
state: State,
case: Case,
buf: Vec<u8>,
attribute_files_in_index: Vec<(&'paths BStr, ObjectId)>
) -> Self
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.
The case
configures attribute and exclusion query case sensitivity.
Examples found in repository?
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
fn checkout_inner<Find, E>(
index: &mut git_index::State,
paths: &git_index::PathStorage,
dir: impl Into<std::path::PathBuf>,
find: Find,
files: &mut impl Progress,
bytes: &mut impl Progress,
should_interrupt: &AtomicBool,
options: checkout::Options,
) -> Result<checkout::Outcome, checkout::Error<E>>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<git_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 {
git_glob::pattern::Case::Fold
} else {
git_glob::pattern::Case::Sensitive
};
let (chunk_size, thread_limit, num_threads) = git_features::parallel::optimize_chunk_size_and_thread_limit(
100,
index.entries().len().into(),
options.thread_limit,
None,
);
let state = fs::cache::State::for_checkout(options.overwrite_existing, options.attribute_globals.clone().into());
let attribute_files = state.build_attribute_list(index, paths, case);
let mut ctx = chunk::Context {
buf: Vec::new(),
path_cache: fs::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(
git_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(checkout::Outcome {
files_updated: num_files.load(Ordering::Relaxed),
collisions,
errors,
bytes_written,
})
}
sourcepub fn at_path<Find, E>(
&mut self,
relative: impl AsRef<Path>,
is_dir: Option<bool>,
find: Find
) -> Result<Platform<'_, 'paths>>where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<BlobRef<'a>, E>,
E: Error + Send + Sync + 'static,
pub fn at_path<Find, E>(
&mut self,
relative: impl AsRef<Path>,
is_dir: Option<bool>,
find: Find
) -> Result<Platform<'_, 'paths>>where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<BlobRef<'a>, E>,
E: Error + Send + Sync + 'static,
Append the relative
path to the root directory the cache contains and efficiently create leading directories
unless is_dir
is known (Some(…)
) then relative
points to a directory itself in which case the entire resulting
path is created as directory. If it’s not known it is assumed to be a file.
Provide access to cached information for that relative
entry via the platform returned.
Examples found in repository?
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
pub fn at_entry<'r, Find, E>(
&mut self,
relative: impl Into<&'r BStr>,
is_dir: Option<bool>,
find: Find,
) -> std::io::Result<Platform<'_, 'paths>>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<git_object::BlobRef<'a>, E>,
E: std::error::Error + Send + Sync + 'static,
{
let relative = relative.into();
let relative_path = git_path::from_bstr(relative);
self.at_path(
relative_path,
is_dir.or_else(|| relative.ends_with_str("/").then(|| true)),
// is_dir,
find,
)
}
More examples
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
pub fn checkout<Find, E>(
entry: &mut Entry,
entry_path: &BStr,
Context { find, path_cache, buf }: Context<'_, '_, Find>,
index::checkout::Options {
fs: fs::Capabilities {
symlink,
executable_bit,
..
},
destination_is_initially_empty,
overwrite_existing,
..
}: index::checkout::Options,
) -> Result<usize, index::checkout::Error<E>>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<git_object::BlobRef<'a>, E>,
E: std::error::Error + Send + Sync + 'static,
{
let dest_relative = git_path::try_from_bstr(entry_path).map_err(|_| index::checkout::Error::IllformedUtf8 {
path: entry_path.to_owned(),
})?;
let is_dir = Some(entry.mode == git_index::entry::Mode::COMMIT || entry.mode == git_index::entry::Mode::DIR);
let dest = path_cache.at_path(dest_relative, is_dir, &mut *find)?.path();
let object_size = match entry.mode {
git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => {
let obj = find(&entry.id, buf).map_err(|err| index::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 == git_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.
update_fstat(entry, file.metadata()?)?;
file.close()?;
obj.data.len()
}
git_index::entry::Mode::SYMLINK => {
let obj = find(&entry.id, buf).map_err(|err| index::checkout::Error::Find {
err,
oid: entry.id,
path: dest.to_path_buf(),
})?;
let symlink_destination = git_path::try_from_byte_slice(obj.data)
.map_err(|_| index::checkout::Error::IllformedUtf8 { path: obj.data.into() })?;
if symlink {
try_write_or_unlink(dest, overwrite_existing, |p| os::create_symlink(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()?;
}
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)
}