summaryrefslogtreecommitdiffstats
path: root/vendor/gix-index/src/write.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-index/src/write.rs')
-rw-r--r--vendor/gix-index/src/write.rs215
1 files changed, 215 insertions, 0 deletions
diff --git a/vendor/gix-index/src/write.rs b/vendor/gix-index/src/write.rs
new file mode 100644
index 000000000..23643734c
--- /dev/null
+++ b/vendor/gix-index/src/write.rs
@@ -0,0 +1,215 @@
+use std::{convert::TryInto, io::Write};
+
+use crate::{entry, extension, write::util::CountBytes, State, Version};
+
+/// A way to specify which of the optional extensions to write.
+#[derive(Debug, Copy, Clone)]
+pub enum Extensions {
+ /// Writes all available optional extensions to avoid loosing any information.
+ All,
+ /// Only write the given optional extensions, with each extension being marked by a boolean flag.
+ ///
+ /// # Note: mandatory extensions
+ ///
+ /// Mandatory extensions, like `sdir` or other lower-case ones, may not be configured here as they need to be present
+ /// or absent depending on the state of the index itself and for it to be valid.
+ Given {
+ /// Write the tree-cache extension, if present.
+ tree_cache: bool,
+ /// Write the end-of-index-entry extension.
+ end_of_index_entry: bool,
+ },
+ /// Write no optional extension at all for what should be the smallest possible index
+ None,
+}
+
+impl Default for Extensions {
+ fn default() -> Self {
+ Extensions::All
+ }
+}
+
+impl Extensions {
+ /// Returns `Some(signature)` if it should be written out.
+ pub fn should_write(&self, signature: extension::Signature) -> Option<extension::Signature> {
+ match self {
+ Extensions::None => None,
+ Extensions::All => Some(signature),
+ Extensions::Given {
+ tree_cache,
+ end_of_index_entry,
+ } => match signature {
+ extension::tree::SIGNATURE => tree_cache,
+ extension::end_of_index_entry::SIGNATURE => end_of_index_entry,
+ _ => &false,
+ }
+ .then(|| signature),
+ }
+ }
+}
+
+/// The options for use when [writing an index][State::write_to()].
+///
+/// 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
+ pub extensions: Extensions,
+}
+
+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> {
+ let version = self.detect_required_version();
+
+ let mut write = CountBytes::new(out);
+ let num_entries: u32 = self
+ .entries()
+ .len()
+ .try_into()
+ .expect("definitely not 4billion entries");
+ let removed_entries: u32 = self
+ .entries()
+ .iter()
+ .filter(|e| e.flags.contains(entry::Flags::REMOVE))
+ .count()
+ .try_into()
+ .expect("definitely not too many entries");
+
+ let offset_to_entries = header(&mut write, version, num_entries - removed_entries)?;
+ let offset_to_extensions = entries(&mut write, self, offset_to_entries)?;
+ let (extension_toc, out) = self.write_extensions(write, offset_to_extensions, extensions)?;
+
+ if num_entries > 0
+ && extensions
+ .should_write(extension::end_of_index_entry::SIGNATURE)
+ .is_some()
+ && !extension_toc.is_empty()
+ {
+ extension::end_of_index_entry::write_to(out, self.object_hash, offset_to_extensions, extension_toc)?
+ }
+
+ Ok(version)
+ }
+
+ fn write_extensions<T>(
+ &self,
+ mut write: CountBytes<T>,
+ offset_to_extensions: u32,
+ extensions: Extensions,
+ ) -> std::io::Result<(Vec<(extension::Signature, u32)>, T)>
+ where
+ T: std::io::Write,
+ {
+ type WriteExtFn<'a> = &'a dyn Fn(&mut dyn std::io::Write) -> Option<std::io::Result<extension::Signature>>;
+ let extensions: &[WriteExtFn<'_>] = &[
+ &|write| {
+ extensions
+ .should_write(extension::tree::SIGNATURE)
+ .and_then(|signature| self.tree().map(|tree| tree.write_to(write).map(|_| signature)))
+ },
+ &|write| {
+ self.is_sparse()
+ .then(|| extension::sparse::write_to(write).map(|_| extension::sparse::SIGNATURE))
+ },
+ ];
+
+ let mut offset_to_previous_ext = offset_to_extensions;
+ let mut out = Vec::with_capacity(5);
+ for write_ext in extensions {
+ if let Some(signature) = write_ext(&mut write).transpose()? {
+ let offset_past_ext = write.count;
+ let ext_size = offset_past_ext - offset_to_previous_ext - (extension::MIN_SIZE as u32);
+ offset_to_previous_ext = offset_past_ext;
+ out.push((signature, ext_size));
+ }
+ }
+ Ok((out, write.inner))
+ }
+}
+
+impl State {
+ fn detect_required_version(&self) -> Version {
+ self.entries
+ .iter()
+ .find_map(|e| e.flags.contains(entry::Flags::EXTENDED).then_some(Version::V3))
+ .unwrap_or(Version::V2)
+ }
+}
+
+fn header<T: std::io::Write>(
+ out: &mut CountBytes<T>,
+ version: Version,
+ num_entries: u32,
+) -> Result<u32, std::io::Error> {
+ let version = match version {
+ Version::V2 => 2_u32.to_be_bytes(),
+ Version::V3 => 3_u32.to_be_bytes(),
+ Version::V4 => 4_u32.to_be_bytes(),
+ };
+
+ out.write_all(crate::decode::header::SIGNATURE)?;
+ out.write_all(&version)?;
+ out.write_all(&num_entries.to_be_bytes())?;
+
+ Ok(out.count)
+}
+
+fn entries<T: std::io::Write>(out: &mut CountBytes<T>, state: &State, header_size: u32) -> Result<u32, std::io::Error> {
+ for entry in state.entries() {
+ if entry.flags.contains(entry::Flags::REMOVE) {
+ continue;
+ }
+ entry.write_to(&mut *out, state)?;
+ match (out.count - header_size) % 8 {
+ 0 => {}
+ n => {
+ let eight_null_bytes = [0u8; 8];
+ out.write_all(&eight_null_bytes[n as usize..])?;
+ }
+ };
+ }
+
+ Ok(out.count)
+}
+
+mod util {
+ use std::convert::TryFrom;
+
+ pub struct CountBytes<T> {
+ pub count: u32,
+ pub inner: T,
+ }
+
+ impl<T> CountBytes<T>
+ where
+ T: std::io::Write,
+ {
+ pub fn new(inner: T) -> Self {
+ CountBytes { inner, count: 0 }
+ }
+ }
+
+ impl<T> std::io::Write for CountBytes<T>
+ where
+ T: std::io::Write,
+ {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ let written = self.inner.write(buf)?;
+ self.count = self
+ .count
+ .checked_add(u32::try_from(written).expect("we don't write 4GB buffers"))
+ .ok_or_else(|| {
+ std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "Cannot write indices larger than 4 gigabytes",
+ )
+ })?;
+ Ok(written)
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ self.inner.flush()
+ }
+ }
+}