1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
use super::Db;
use crate::{hash, loose, zlib::stream::DeflateWriter};
use git_object::{owned, HashKind};
use std::{fs, io, io::Write, path::PathBuf};
use tempfile::NamedTempFile;

/// Returned by the [`crate::Write`] trait implementation of [`Db`]
#[derive(thiserror::Error, Debug)]
#[allow(missing_docs)]
pub enum Error {
    #[error("Could not {message} '{path}'")]
    Io {
        source: io::Error,
        message: &'static str,
        path: PathBuf,
    },
    #[error("An IO error occurred while writing an object")]
    IoRaw(#[from] io::Error),
    #[error("Could not turn temporary file into persisted file at '{target}'")]
    Persist {
        source: tempfile::PersistError,
        target: PathBuf,
    },
}

impl crate::Write for Db {
    type Error = Error;

    /// Write the given buffer in `from` to disk in one syscall at best.
    ///
    /// This will cost at least 4 IO operations.
    fn write_buf(&self, kind: git_object::Kind, from: &[u8], hash: HashKind) -> Result<owned::Id, Self::Error> {
        match hash {
            HashKind::Sha1 => {
                let mut to = self.write_header(kind, from.len() as u64, hash)?;
                to.write_all(from).map_err(|err| Error::Io {
                    source: err,
                    message: "stream all data into tempfile in",
                    path: self.path.to_owned(),
                })?;
                to.flush()?;
                self.finalize_object(to)
            }
        }
    }

    /// Write the given stream in `from` to disk with at least one syscall.
    ///
    /// This will cost at least 4 IO operations.
    fn write_stream(
        &self,
        kind: git_object::Kind,
        size: u64,
        mut from: impl io::Read,
        hash: HashKind,
    ) -> Result<owned::Id, Self::Error> {
        match hash {
            HashKind::Sha1 => {
                let mut to = self.write_header(kind, size, hash)?;
                io::copy(&mut from, &mut to).map_err(|err| Error::Io {
                    source: err,
                    message: "stream all data into tempfile in",
                    path: self.path.to_owned(),
                })?;
                to.flush()?;
                self.finalize_object(to)
            }
        }
    }
}

type HashAndTempFile = DeflateWriter<NamedTempFile>;

impl Db {
    fn write_header(
        &self,
        kind: git_object::Kind,
        size: u64,
        hash: HashKind,
    ) -> Result<hash::Write<HashAndTempFile>, Error> {
        let mut to = hash::Write::new(
            DeflateWriter::new(NamedTempFile::new_in(&self.path).map_err(|err| Error::Io {
                source: err,
                message: "create named temp file in",
                path: self.path.to_owned(),
            })?),
            hash,
        );

        loose::object::header::encode(kind, size, &mut to).map_err(|err| Error::Io {
            source: err,
            message: "write header to tempfile in",
            path: self.path.to_owned(),
        })?;
        Ok(to)
    }

    fn finalize_object(
        &self,
        hash::Write { hash, inner: file }: hash::Write<HashAndTempFile>,
    ) -> Result<owned::Id, Error> {
        let id = owned::Id::from(hash.digest());
        let object_path = loose::db::sha1_path(id.to_borrowed(), self.path.clone());
        let object_dir = object_path
            .parent()
            .expect("each object path has a 1 hex-bytes directory");
        if let Err(err) = fs::create_dir(object_dir) {
            match err.kind() {
                io::ErrorKind::AlreadyExists => {}
                _ => return Err(err.into()),
            }
        }
        let file = file.into_inner();
        file.persist(&object_path).map_err(|err| Error::Persist {
            source: err,
            target: object_path,
        })?;
        Ok(id)
    }
}