git_odb/store_impls/loose/
write.rs

1use std::{convert::TryInto, fs, io, io::Write, path::PathBuf};
2
3use git_features::{hash, zlib::stream::deflate};
4use git_object::WriteTo;
5use tempfile::NamedTempFile;
6
7use super::Store;
8use crate::store_impls::loose;
9
10/// Returned by the [`crate::Write`] trait implementation of [`Store`]
11#[derive(thiserror::Error, Debug)]
12#[allow(missing_docs)]
13pub enum Error {
14    #[error("Could not {message} '{path}'")]
15    Io {
16        source: io::Error,
17        message: &'static str,
18        path: PathBuf,
19    },
20    #[error("An IO error occurred while writing an object")]
21    IoRaw(#[from] io::Error),
22    #[error("Could not turn temporary file into persisted file at '{target}'")]
23    Persist {
24        source: tempfile::PersistError,
25        target: PathBuf,
26    },
27}
28
29impl crate::traits::Write for Store {
30    type Error = Error;
31
32    fn write(&self, object: impl WriteTo) -> Result<git_hash::ObjectId, Self::Error> {
33        let mut to = self.dest()?;
34        to.write_all(&object.loose_header()).map_err(|err| Error::Io {
35            source: err,
36            message: "write header to tempfile in",
37            path: self.path.to_owned(),
38        })?;
39        object.write_to(&mut to).map_err(|err| Error::Io {
40            source: err,
41            message: "stream all data into tempfile in",
42            path: self.path.to_owned(),
43        })?;
44        to.flush()?;
45        self.finalize_object(to)
46    }
47
48    /// Write the given buffer in `from` to disk in one syscall at best.
49    ///
50    /// This will cost at least 4 IO operations.
51    fn write_buf(&self, kind: git_object::Kind, from: &[u8]) -> Result<git_hash::ObjectId, Self::Error> {
52        let mut to = self.dest()?;
53        to.write_all(&git_object::encode::loose_header(kind, from.len()))
54            .map_err(|err| Error::Io {
55                source: err,
56                message: "write header to tempfile in",
57                path: self.path.to_owned(),
58            })?;
59
60        to.write_all(from).map_err(|err| Error::Io {
61            source: err,
62            message: "stream all data into tempfile in",
63            path: self.path.to_owned(),
64        })?;
65        to.flush()?;
66        self.finalize_object(to)
67    }
68
69    /// Write the given stream in `from` to disk with at least one syscall.
70    ///
71    /// This will cost at least 4 IO operations.
72    fn write_stream(
73        &self,
74        kind: git_object::Kind,
75        size: u64,
76        mut from: impl io::Read,
77    ) -> Result<git_hash::ObjectId, Self::Error> {
78        let mut to = self.dest()?;
79        to.write_all(&git_object::encode::loose_header(
80            kind,
81            size.try_into().expect("object size to fit into usize"),
82        ))
83        .map_err(|err| Error::Io {
84            source: err,
85            message: "write header to tempfile in",
86            path: self.path.to_owned(),
87        })?;
88
89        io::copy(&mut from, &mut to).map_err(|err| Error::Io {
90            source: err,
91            message: "stream all data into tempfile in",
92            path: self.path.to_owned(),
93        })?;
94        to.flush()?;
95        self.finalize_object(to)
96    }
97}
98
99type CompressedTempfile = deflate::Write<NamedTempFile>;
100
101impl Store {
102    fn dest(&self) -> Result<hash::Write<CompressedTempfile>, Error> {
103        Ok(hash::Write::new(
104            deflate::Write::new(NamedTempFile::new_in(&self.path).map_err(|err| Error::Io {
105                source: err,
106                message: "create named temp file in",
107                path: self.path.to_owned(),
108            })?),
109            self.object_hash,
110        ))
111    }
112
113    fn finalize_object(
114        &self,
115        hash::Write { hash, inner: file }: hash::Write<CompressedTempfile>,
116    ) -> Result<git_hash::ObjectId, Error> {
117        let id = git_hash::ObjectId::from(hash.digest());
118        let object_path = loose::hash_path(&id, self.path.clone());
119        let object_dir = object_path
120            .parent()
121            .expect("each object path has a 1 hex-bytes directory");
122        if let Err(err) = fs::create_dir(object_dir) {
123            match err.kind() {
124                io::ErrorKind::AlreadyExists => {}
125                _ => return Err(err.into()),
126            }
127        }
128        let file = file.into_inner();
129        file.persist(&object_path).map_err(|err| Error::Persist {
130            source: err,
131            target: object_path,
132        })?;
133        Ok(id)
134    }
135}