async-git 0.0.0-squat-name

Pure-rust async implementation of git built on tokio
Documentation
use std::io::Error as IOError;
use std::str::FromStr;

use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, BufReader};

use crate::errors::{Result, Error};
use crate::plumbing::Oid;

#[derive(Clone, Debug)]
pub struct Commit {
    tree: Oid,
    parents: Vec<Oid>,
    author: String,
    committer: String,
    gpgsig: Option<String>,
    message: String,
}

impl Commit {
    pub async fn parse<R: AsyncRead + Unpin>(r: R) -> Result<Self> {
        let mut r = BufReader::new(r);
        let size = {
            let mut size_buf = Vec::new();
            r.read_until(0, &mut size_buf).await?;
            // get rid of the null byte
            size_buf.truncate(size_buf.len() - 1);
            String::from_utf8(size_buf)?.parse::<usize>()?
        };

        let mut tree = None;
        let mut parents = Vec::new();
        let mut author = None;
        let mut committer = None;
        let mut gpgsig = None;

        let mut process_header = |line: &str| -> Result<()> {
            let mut parts = line.split(" ");
            let key = parts.next().unwrap();
            let value = parts.collect::<Vec<_>>().join(" ");
            let value = value.trim();
            match key {
                "tree" => tree = Some(Oid::from_str(&value)?),
                "parent" => parents.push(Oid::from_str(&value)?),
                "author" => author = Some(value.to_string()),
                "committer" => committer = Some(value.to_string()),
                "gpgsig" => gpgsig = Some(value.to_string()),
                _ => return Err(Error::InvalidCommitHeader(key.to_string())),
            }
            Ok(())
        };

        let mut prev_line = String::new();
        r.read_line(&mut prev_line).await?;
        loop {
            let mut this_line = String::new();
            r.read_line(&mut this_line).await?;

            if this_line.starts_with(" ") {
                prev_line += &this_line[1..];
            } else {
                // TODO: there's gotta be a cleaner way to do this
                if this_line.len() == 1 && this_line.as_bytes()[0] == b'\n' {
                    break;
                }
                process_header(&prev_line)?;
                prev_line = this_line;
            }
        }
        process_header(&prev_line)?;

        let mut message = String::new();
        r.read_to_string(&mut message).await?;
        message = message.trim().to_string();

        Ok(Commit {
            tree: tree.unwrap(),
            parents,
            author: author.unwrap(),
            committer: committer.unwrap(),
            gpgsig,
            message,
        })
    }

    pub async fn write<W: AsyncWrite + Unpin>(self, w: W) -> Result<(), IOError> {
        Ok(())
    }
}