diff options
Diffstat (limited to 'vendor/gix-index/src')
-rw-r--r-- | vendor/gix-index/src/access/mod.rs | 205 | ||||
-rw-r--r-- | vendor/gix-index/src/decode/mod.rs | 12 | ||||
-rw-r--r-- | vendor/gix-index/src/entry/mode.rs | 28 | ||||
-rw-r--r-- | vendor/gix-index/src/entry/stat.rs | 4 | ||||
-rw-r--r-- | vendor/gix-index/src/extension/link.rs | 2 | ||||
-rw-r--r-- | vendor/gix-index/src/extension/tree/verify.rs | 1 | ||||
-rw-r--r-- | vendor/gix-index/src/file/init.rs | 61 | ||||
-rw-r--r-- | vendor/gix-index/src/file/verify.rs | 34 | ||||
-rw-r--r-- | vendor/gix-index/src/file/write.rs | 27 | ||||
-rw-r--r-- | vendor/gix-index/src/init.rs | 1 | ||||
-rw-r--r-- | vendor/gix-index/src/verify.rs | 1 | ||||
-rw-r--r-- | vendor/gix-index/src/write.rs | 18 |
12 files changed, 326 insertions, 68 deletions
diff --git a/vendor/gix-index/src/access/mod.rs b/vendor/gix-index/src/access/mod.rs index d07a55bf0..08cb23020 100644 --- a/vendor/gix-index/src/access/mod.rs +++ b/vendor/gix-index/src/access/mod.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::ops::Range; use bstr::{BStr, ByteSlice, ByteVec}; use filetime::FileTime; @@ -70,9 +71,67 @@ impl State { /// /// Use the index for accessing multiple stages if they exists, but at least the single matching entry. pub fn entry_index_by_path_and_stage(&self, path: &BStr, stage: entry::Stage) -> Option<usize> { - self.entries - .binary_search_by(|e| e.path(self).cmp(path).then_with(|| e.stage().cmp(&stage))) - .ok() + let mut stage_cmp = Ordering::Equal; + let idx = self + .entries + .binary_search_by(|e| { + let res = e.path(self).cmp(path); + if res.is_eq() { + stage_cmp = e.stage().cmp(&stage); + } + res + }) + .ok()?; + self.entry_index_by_idx_and_stage(path, idx, stage, stage_cmp) + } + + /// Walk as far in `direction` as possible, with [`Ordering::Greater`] towards higher stages, and [`Ordering::Less`] + /// towards lower stages, and return the lowest or highest seen stage. + /// Return `None` if there is no greater or smaller stage. + fn walk_entry_stages(&self, path: &BStr, base: usize, direction: Ordering) -> Option<usize> { + match direction { + Ordering::Greater => self + .entries + .get(base + 1..)? + .iter() + .enumerate() + .take_while(|(_, e)| e.path(self) == path) + .last() + .map(|(idx, _)| base + 1 + idx), + Ordering::Equal => Some(base), + Ordering::Less => self.entries[..base] + .iter() + .enumerate() + .rev() + .take_while(|(_, e)| e.path(self) == path) + .last() + .map(|(idx, _)| idx), + } + } + + fn entry_index_by_idx_and_stage( + &self, + path: &BStr, + idx: usize, + wanted_stage: entry::Stage, + stage_cmp: Ordering, + ) -> Option<usize> { + match stage_cmp { + Ordering::Greater => self.entries[..idx] + .iter() + .enumerate() + .rev() + .take_while(|(_, e)| e.path(self) == path) + .find_map(|(idx, e)| (e.stage() == wanted_stage).then_some(idx)), + Ordering::Equal => Some(idx), + Ordering::Less => self + .entries + .get(idx + 1..)? + .iter() + .enumerate() + .take_while(|(_, e)| e.path(self) == path) + .find_map(|(ofs, e)| (e.stage() == wanted_stage).then_some(idx + ofs + 1)), + } } /// Find the entry index in [`entries()[..upper_bound]`][State::entries()] matching the given repository-relative @@ -101,6 +160,68 @@ impl State { .map(|idx| &self.entries[idx]) } + /// Return the entry at `path` that is either at stage 0, or at stage 2 (ours) in case of a merge conflict. + /// + /// Using this method is more efficient in comparison to doing two searches, one for stage 0 and one for stage 2. + pub fn entry_by_path(&self, path: &BStr) -> Option<&Entry> { + let mut stage_at_index = 0; + let idx = self + .entries + .binary_search_by(|e| { + let res = e.path(self).cmp(path); + if res.is_eq() { + stage_at_index = e.stage(); + } + res + }) + .ok()?; + let idx = if stage_at_index == 0 || stage_at_index == 2 { + idx + } else { + self.entry_index_by_idx_and_stage(path, idx, 2, stage_at_index.cmp(&2))? + }; + Some(&self.entries[idx]) + } + + /// Return the slice of entries which all share the same `prefix`, or `None` if there isn't a single such entry. + /// + /// If `prefix` is empty, all entries are returned. + pub fn prefixed_entries(&self, prefix: &BStr) -> Option<&[Entry]> { + self.prefixed_entries_range(prefix).map(|range| &self.entries[range]) + } + + /// Return the range of entries which all share the same `prefix`, or `None` if there isn't a single such entry. + /// + /// If `prefix` is empty, the range will include all entries. + pub fn prefixed_entries_range(&self, prefix: &BStr) -> Option<Range<usize>> { + if prefix.is_empty() { + return Some(0..self.entries.len()); + } + let prefix_len = prefix.len(); + let mut low = self.entries.partition_point(|e| { + e.path(self) + .get(..prefix_len) + .map_or_else(|| e.path(self) <= &prefix[..e.path.len()], |p| p < prefix) + }); + let mut high = low + + self.entries[low..].partition_point(|e| e.path(self).get(..prefix_len).map_or(false, |p| p <= prefix)); + + let low_entry = &self.entries.get(low)?; + if low_entry.stage() != 0 { + low = self + .walk_entry_stages(low_entry.path(self), low, Ordering::Less) + .unwrap_or(low); + } + if let Some(high_entry) = self.entries.get(high) { + if high_entry.stage() != 0 { + high = self + .walk_entry_stages(high_entry.path(self), high, Ordering::Less) + .unwrap_or(high); + } + } + (low != high).then_some(low..high) + } + /// Return the entry at `idx` or _panic_ if the index is out of bounds. /// /// The `idx` is typically returned by [`entry_by_path_and_stage()`][State::entry_by_path_and_stage()]. @@ -114,6 +235,30 @@ impl State { pub fn is_sparse(&self) -> bool { self.is_sparse } + + /// Return the range of entries that exactly match the given `path`, in all available stages, or `None` if no entry with such + /// path exists. + /// + /// The range can be used to access the respective entries via [`entries()`](Self::entries()) or [`entries_mut()](Self::entries_mut()). + pub fn entry_range(&self, path: &BStr) -> Option<Range<usize>> { + let mut stage_at_index = 0; + let idx = self + .entries + .binary_search_by(|e| { + let res = e.path(self).cmp(path); + if res.is_eq() { + stage_at_index = e.stage(); + } + res + }) + .ok()?; + + let (start, end) = ( + self.walk_entry_stages(path, idx, Ordering::Less).unwrap_or(idx), + self.walk_entry_stages(path, idx, Ordering::Greater).unwrap_or(idx) + 1, + ); + Some(start..end) + } } /// Mutation @@ -224,6 +369,25 @@ impl State { .then_with(|| compare(a, b)) }); } + + /// Physically remove all entries for which `should_remove(idx, path, entry)` returns `true`, traversing them from first to last. + /// + /// Note that the memory used for the removed entries paths is not freed, as it's append-only. + /// + /// ### Performance + /// + /// To implement this operation typically, one would rather add [entry::Flags::REMOVE] to each entry to remove + /// them when [writing the index](Self::write_to()). + pub fn remove_entries(&mut self, mut should_remove: impl FnMut(usize, &BStr, &mut Entry) -> bool) { + let mut index = 0; + let paths = &self.path_backing; + self.entries.retain_mut(|e| { + let path = e.path_in(paths); + let res = !should_remove(index, path, e); + index += 1; + res + }); + } } /// Extensions @@ -249,3 +413,38 @@ impl State { self.fs_monitor.as_ref() } } + +#[cfg(test)] +mod tests { + use std::path::{Path, PathBuf}; + + #[test] + fn entry_by_path_with_conflicting_file() { + let file = PathBuf::from("tests") + .join("fixtures") + .join(Path::new("loose_index").join("conflicting-file.git-index")); + let file = crate::File::at(file, gix_hash::Kind::Sha1, false, Default::default()).expect("valid file"); + assert_eq!( + file.entries().len(), + 3, + "we have a set of conflict entries for a single file" + ); + for idx in 0..3 { + for wanted_stage in 1..=3 { + let actual_idx = file + .entry_index_by_idx_and_stage( + "file".into(), + idx, + wanted_stage, + (idx + 1).cmp(&(wanted_stage as usize)), + ) + .expect("found"); + assert_eq!( + actual_idx + 1, + wanted_stage as usize, + "the index and stage have a relation, and that is upheld if we search correctly" + ); + } + } + } +} diff --git a/vendor/gix-index/src/decode/mod.rs b/vendor/gix-index/src/decode/mod.rs index f51a5d5e9..12c8c53e4 100644 --- a/vendor/gix-index/src/decode/mod.rs +++ b/vendor/gix-index/src/decode/mod.rs @@ -54,7 +54,7 @@ pub struct Options { impl State { /// Decode an index state from `data` and store `timestamp` in the resulting instance for pass-through, assuming `object_hash` - /// to be used through the file. + /// to be used through the file. Also return the stored hash over all bytes in `data` or `None` if none was written due to `index.skipHash`. pub fn from_bytes( data: &[u8], timestamp: FileTime, @@ -64,7 +64,8 @@ impl State { min_extension_block_in_bytes_for_threading, expected_checksum, }: Options, - ) -> Result<(Self, gix_hash::ObjectId), Error> { + ) -> Result<(Self, Option<gix_hash::ObjectId>), Error> { + let _span = gix_features::trace::detail!("gix_index::State::from_bytes()"); let (version, num_entries, post_header_data) = header::decode(data, object_hash)?; let start_of_extensions = extension::end_of_index_entry::decode(data, object_hash); @@ -213,10 +214,11 @@ impl State { } let checksum = gix_hash::ObjectId::from(data); - if let Some(expected_checksum) = expected_checksum { - if checksum != expected_checksum { + let checksum = (!checksum.is_null()).then_some(checksum); + if let Some((expected_checksum, actual_checksum)) = expected_checksum.zip(checksum) { + if actual_checksum != expected_checksum { return Err(Error::ChecksumMismatch { - actual_checksum: checksum, + actual_checksum, expected_checksum, }); } diff --git a/vendor/gix-index/src/entry/mode.rs b/vendor/gix-index/src/entry/mode.rs index 7d3fdf506..0301df438 100644 --- a/vendor/gix-index/src/entry/mode.rs +++ b/vendor/gix-index/src/entry/mode.rs @@ -1,24 +1,16 @@ use crate::entry::Mode; -#[cfg(unix)] -/// Returns whether a a file has the executable permission set. -fn is_executable(metadata: &std::fs::Metadata) -> bool { - use std::os::unix::fs::MetadataExt; - (metadata.mode() & 0o100) != 0 -} - -#[cfg(not(unix))] -/// Returns whether a a file has the executable permission set. -fn is_executable(_metadata: &std::fs::Metadata) -> bool { - false -} - impl Mode { - /// Return true if this is a sparse entry, as it points to a directory which usually isn't what an 'unsparse' index tracks. + /// Return `true` if this is a sparse entry, as it points to a directory which usually isn't what an 'unsparse' index tracks. pub fn is_sparse(&self) -> bool { *self == Self::DIR } + /// Return `true` if this is a submodule entry. + pub fn is_submodule(&self) -> bool { + *self == Self::DIR | Self::SYMLINK + } + /// Compares this mode to the file system version ([`std::fs::symlink_metadata`]) /// and returns the change needed to update this mode to match the file. /// @@ -54,13 +46,15 @@ impl Mode { Mode::SYMLINK if has_symlinks && !stat.is_symlink() => (), Mode::SYMLINK if !has_symlinks && !stat.is_file() => (), Mode::COMMIT | Mode::DIR if !stat.is_dir() => (), - Mode::FILE if executable_bit && is_executable(stat) => return Some(Change::ExecutableBit), - Mode::FILE_EXECUTABLE if executable_bit && !is_executable(stat) => return Some(Change::ExecutableBit), + Mode::FILE if executable_bit && gix_fs::is_executable(stat) => return Some(Change::ExecutableBit), + Mode::FILE_EXECUTABLE if executable_bit && !gix_fs::is_executable(stat) => { + return Some(Change::ExecutableBit) + } _ => return None, }; let new_mode = if stat.is_dir() { Mode::COMMIT - } else if executable_bit && is_executable(stat) { + } else if executable_bit && gix_fs::is_executable(stat) { Mode::FILE_EXECUTABLE } else { Mode::FILE diff --git a/vendor/gix-index/src/entry/stat.rs b/vendor/gix-index/src/entry/stat.rs index 65063dc16..7bde71763 100644 --- a/vendor/gix-index/src/entry/stat.rs +++ b/vendor/gix-index/src/entry/stat.rs @@ -95,8 +95,8 @@ impl Stat { use std::os::unix::fs::MetadataExt; #[cfg(unix)] let res = Stat { - mtime: mtime.try_into()?, - ctime: ctime.try_into()?, + mtime: mtime.try_into().unwrap_or_default(), + ctime: ctime.try_into().unwrap_or_default(), // truncating to 32 bits is fine here because // that's what the linux syscalls returns // just rust upcasts to 64 bits for some reason? diff --git a/vendor/gix-index/src/extension/link.rs b/vendor/gix-index/src/extension/link.rs index 20ce9cb21..5fed2f960 100644 --- a/vendor/gix-index/src/extension/link.rs +++ b/vendor/gix-index/src/extension/link.rs @@ -72,6 +72,7 @@ impl Link { self, split_index: &mut crate::File, object_hash: gix_hash::Kind, + skip_hash: bool, options: crate::decode::Options, ) -> Result<(), crate::file::init::Error> { let shared_index_path = split_index @@ -82,6 +83,7 @@ impl Link { let mut shared_index = crate::File::at( &shared_index_path, object_hash, + skip_hash, crate::decode::Options { expected_checksum: self.shared_index_checksum.into(), ..options diff --git a/vendor/gix-index/src/extension/tree/verify.rs b/vendor/gix-index/src/extension/tree/verify.rs index 6280cecf8..793f31325 100644 --- a/vendor/gix-index/src/extension/tree/verify.rs +++ b/vendor/gix-index/src/extension/tree/verify.rs @@ -111,6 +111,7 @@ impl Tree { } Ok(entries.into()) } + let _span = gix_features::trace::coarse!("gix_index::extension::Tree::verify()"); if !self.name.is_empty() { return Err(Error::RootWithName { diff --git a/vendor/gix-index/src/file/init.rs b/vendor/gix-index/src/file/init.rs index 534f1f08b..99f4be258 100644 --- a/vendor/gix-index/src/file/init.rs +++ b/vendor/gix-index/src/file/init.rs @@ -26,16 +26,18 @@ pub use error::Error; /// Initialization impl File { /// Try to open the index file at `path` with `options`, assuming `object_hash` is used throughout the file, or create a new - /// index that merely exists in memory and is empty. + /// index that merely exists in memory and is empty. `skip_hash` will increase the performance by a factor of 2, at the cost of + /// possibly not detecting corruption. /// /// Note that the `path` will not be written if it doesn't exist. pub fn at_or_default( path: impl Into<PathBuf>, object_hash: gix_hash::Kind, + skip_hash: bool, options: decode::Options, ) -> Result<Self, Error> { let path = path.into(); - Ok(match Self::at(&path, object_hash, options) { + Ok(match Self::at(&path, object_hash, skip_hash, options) { Ok(f) => f, Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => { File::from_state(State::new(object_hash), path) @@ -44,25 +46,60 @@ impl File { }) } - /// Open an index file at `path` with `options`, assuming `object_hash` is used throughout the file. - pub fn at(path: impl Into<PathBuf>, object_hash: gix_hash::Kind, options: decode::Options) -> Result<Self, Error> { + /// Open an index file at `path` with `options`, assuming `object_hash` is used throughout the file. If `skip_hash` is `true`, + /// we will not get or compare the checksum of the index at all, which generally increases performance of this method by a factor + /// of 2 or more. + /// + /// Note that the verification of the file hash depends on `options`, and even then it's performed after the file was read and not + /// before it is read. That way, invalid files would see a more descriptive error message as we try to parse them. + pub fn at( + path: impl Into<PathBuf>, + object_hash: gix_hash::Kind, + skip_hash: bool, + options: decode::Options, + ) -> Result<Self, Error> { + let _span = gix_features::trace::detail!("gix_index::File::at()"); let path = path.into(); let (data, mtime) = { + let mut file = std::fs::File::open(&path)?; // SAFETY: we have to take the risk of somebody changing the file underneath. Git never writes into the same file. - let file = std::fs::File::open(&path)?; #[allow(unsafe_code)] let data = unsafe { Mmap::map(&file)? }; + + if !skip_hash { + // Note that even though it's trivial to offload this into a thread, which is worth it for all but the smallest + // index files, we choose more safety here just like git does and don't even try to decode the index if the hashes + // don't match. + // Thanks to `skip_hash`, we can get performance and it's under caller control, at the cost of some safety. + let expected = gix_hash::ObjectId::from(&data[data.len() - object_hash.len_in_bytes()..]); + if !expected.is_null() { + let _span = gix_features::trace::detail!("gix::open_index::hash_index", path = ?path); + let meta = file.metadata()?; + let num_bytes_to_hash = meta.len() - object_hash.len_in_bytes() as u64; + let actual_hash = gix_features::hash::bytes( + &mut file, + num_bytes_to_hash as usize, + object_hash, + &mut gix_features::progress::Discard, + &Default::default(), + )?; + + if actual_hash != expected { + return Err(Error::Decode(decode::Error::ChecksumMismatch { + actual_checksum: actual_hash, + expected_checksum: expected, + })); + } + } + } + (data, filetime::FileTime::from_last_modification_time(&file.metadata()?)) }; let (state, checksum) = State::from_bytes(&data, mtime, object_hash, options)?; - let mut file = File { - state, - path, - checksum: Some(checksum), - }; + let mut file = File { state, path, checksum }; if let Some(mut link) = file.link.take() { - link.dissolve_into(&mut file, object_hash, options)?; + link.dissolve_into(&mut file, object_hash, skip_hash, options)?; } Ok(file) @@ -71,7 +108,7 @@ impl File { /// Consume `state` and pretend it was read from `path`, setting our checksum to `null`. /// /// `File` instances created like that should be written to disk to set the correct checksum via `[File::write()]`. - pub fn from_state(state: crate::State, path: impl Into<PathBuf>) -> Self { + pub fn from_state(state: State, path: impl Into<PathBuf>) -> Self { File { state, path: path.into(), diff --git a/vendor/gix-index/src/file/verify.rs b/vendor/gix-index/src/file/verify.rs index 6743b37a7..3890acd95 100644 --- a/vendor/gix-index/src/file/verify.rs +++ b/vendor/gix-index/src/file/verify.rs @@ -14,8 +14,6 @@ mod error { actual: gix_hash::ObjectId, expected: gix_hash::ObjectId, }, - #[error("Checksum of in-memory index wasn't computed yet")] - NoChecksum, } } pub use error::Error; @@ -23,19 +21,23 @@ pub use error::Error; impl File { /// Verify the integrity of the index to assure its consistency. pub fn verify_integrity(&self) -> Result<(), Error> { - let checksum = self.checksum.ok_or(Error::NoChecksum)?; - let num_bytes_to_hash = self.path.metadata()?.len() - checksum.as_bytes().len() as u64; - let should_interrupt = AtomicBool::new(false); - let actual = gix_features::hash::bytes_of_file( - &self.path, - num_bytes_to_hash as usize, - checksum.kind(), - &mut gix_features::progress::Discard, - &should_interrupt, - )?; - (actual == checksum).then_some(()).ok_or(Error::ChecksumMismatch { - actual, - expected: checksum, - }) + let _span = gix_features::trace::coarse!("gix_index::File::verify_integrity()"); + if let Some(checksum) = self.checksum { + let num_bytes_to_hash = self.path.metadata()?.len() - checksum.as_bytes().len() as u64; + let should_interrupt = AtomicBool::new(false); + let actual = gix_features::hash::bytes_of_file( + &self.path, + num_bytes_to_hash as usize, + checksum.kind(), + &mut gix_features::progress::Discard, + &should_interrupt, + )?; + (actual == checksum).then_some(()).ok_or(Error::ChecksumMismatch { + actual, + expected: checksum, + }) + } else { + Ok(()) + } } } diff --git a/vendor/gix-index/src/file/write.rs b/vendor/gix-index/src/file/write.rs index 1e8afc07d..47a4cde96 100644 --- a/vendor/gix-index/src/file/write.rs +++ b/vendor/gix-index/src/file/write.rs @@ -22,23 +22,28 @@ impl File { mut out: impl std::io::Write, options: write::Options, ) -> std::io::Result<(Version, gix_hash::ObjectId)> { - let mut hasher = hash::Write::new(&mut out, self.state.object_hash); - let version = self.state.write_to(&mut hasher, options)?; - - let hash = hasher.hash.digest(); - out.write_all(&hash)?; - Ok((version, gix_hash::ObjectId::from(hash))) + let (version, hash) = if options.skip_hash { + let out: &mut dyn std::io::Write = &mut out; + let version = self.state.write_to(out, options)?; + (version, self.state.object_hash.null()) + } else { + let mut hasher = hash::Write::new(&mut out, self.state.object_hash); + let out: &mut dyn std::io::Write = &mut hasher; + let version = self.state.write_to(out, options)?; + (version, gix_hash::ObjectId::from(hasher.hash.digest())) + }; + out.write_all(hash.as_slice())?; + Ok((version, hash)) } /// Write ourselves to the path we were read from after acquiring a lock, using `options`. /// /// Note that the hash produced will be stored which is why we need to be mutable. pub fn write(&mut self, options: write::Options) -> Result<(), Error> { - let mut lock = std::io::BufWriter::new(gix_lock::File::acquire_to_update_resource( - &self.path, - gix_lock::acquire::Fail::Immediately, - None, - )?); + let mut lock = std::io::BufWriter::with_capacity( + 64 * 1024, + gix_lock::File::acquire_to_update_resource(&self.path, gix_lock::acquire::Fail::Immediately, None)?, + ); let (version, digest) = self.write_to(&mut lock, options)?; match lock.into_inner() { Ok(lock) => lock.commit()?, diff --git a/vendor/gix-index/src/init.rs b/vendor/gix-index/src/init.rs index abd71ffdd..9fe0b8e27 100644 --- a/vendor/gix-index/src/init.rs +++ b/vendor/gix-index/src/init.rs @@ -39,6 +39,7 @@ mod from_tree { where Find: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>, { + let _span = gix_features::trace::coarse!("gix_index::State::from_tree()"); let mut buf = Vec::new(); let root = find(tree, &mut buf).ok_or(breadthfirst::Error::NotFound { oid: tree.into() })?; diff --git a/vendor/gix-index/src/verify.rs b/vendor/gix-index/src/verify.rs index ba7ec3872..7782cccbc 100644 --- a/vendor/gix-index/src/verify.rs +++ b/vendor/gix-index/src/verify.rs @@ -42,6 +42,7 @@ pub mod extensions { impl State { /// Assure our entries are consistent. pub fn verify_entries(&self) -> Result<(), entries::Error> { + let _span = gix_features::trace::coarse!("gix_index::File::verify_entries()"); let mut previous = None::<&crate::Entry>; for (idx, entry) in self.entries.iter().enumerate() { if let Some(prev) = previous { diff --git a/vendor/gix-index/src/write.rs b/vendor/gix-index/src/write.rs index 43f9b3255..2050ed809 100644 --- a/vendor/gix-index/src/write.rs +++ b/vendor/gix-index/src/write.rs @@ -48,13 +48,27 @@ impl Extensions { /// Note that default options write either index V2 or V3 depending on the content of the entries. #[derive(Debug, Default, Clone, Copy)] pub struct Options { - /// Configures which extensions to write + /// Configures which extensions to write. pub extensions: Extensions, + /// Set the trailing hash of the produced index to all zeroes to save some time. + /// + /// This value is typically controlled by `index.skipHash` and is respected when the index is written + /// via [`File::write()`](crate::File::write()) and [`File::write_to()`](crate::File::write_to()). + /// Note that + pub skip_hash: bool, } impl State { /// Serialize this instance to `out` with [`options`][Options]. - pub fn write_to(&self, out: impl std::io::Write, Options { extensions }: Options) -> std::io::Result<Version> { + pub fn write_to( + &self, + out: impl std::io::Write, + Options { + extensions, + skip_hash: _, + }: Options, + ) -> std::io::Result<Version> { + let _span = gix_features::trace::detail!("gix_index::State::write()"); let version = self.detect_required_version(); let mut write = CountBytes::new(out); |