rustgit 1.1.1

Pure-rust Git Client
Documentation
use core::{str::from_utf8};
use coolssh::{Run, RunEvent};
use super::internals::{Result, Error, Write};

pub enum PacketLine<'a> {
    String(&'a str),
    Bytes(&'a [u8]),
    FlushPacket,
    DelimiterPacket,
    ResponseEndPacket,
}

pub struct GitProtocol<'a> {
    run: Run<'a>,
    receive_buffer: Vec<u8>,
    send_buffer: Vec<u8>,
    to_skip: usize,
}

impl<'a> GitProtocol<'a> {
    pub fn new(run: Run<'a>) -> GitProtocol<'a> {
        Self {
            run,
            receive_buffer: Vec::new(),
            send_buffer: Vec::new(),
            to_skip: 0,
        }
    }

    pub fn read_line(&mut self) -> Result<Option<&[u8]>> {
        fn parse_len(bytes: &[u8]) -> Option<usize> {
            let hex_len = from_utf8(bytes).ok()?;
            usize::from_str_radix(hex_len, 16).ok()
        }

        self.receive_buffer.drain(0..self.to_skip);
        self.to_skip = 0;

        loop {
            if let Some(slice) = self.receive_buffer.get(..4) {
                let len = parse_len(slice).ok_or(Error::GitProtocolError)?;
                if len < 4 {
                    self.to_skip = 4;
                    break Ok(None);
                } else if self.receive_buffer.len() >= len {
                    self.to_skip = len;
                    break match self.receive_buffer.get(4..len) {
                        Some(data) => Ok(Some(data)),
                        None => Err(Error::GitProtocolError),
                    };
                }
            }

            match self.run.poll()? {
                RunEvent::None => (),
                RunEvent::Data(data) => self.receive_buffer.extend_from_slice(data),
                RunEvent::ExtDataStderr(data) => log::warn!("Remote stderr: {}", from_utf8(data).unwrap()),
                e => {
                    log::error!("Unexpected RunEvent: {:?}", e);
                    break Err(Error::GitProtocolError);
                },
            }
        }
    }

    pub fn read_line_str(&mut self) -> Result<Option<&str>> {
        Ok(match self.read_line()? {
            Some(b) => Some(from_utf8(b).ok().ok_or(Error::GitProtocolError)?.trim()),
            None => None,
        })
    }

    pub fn write_lines(&mut self, lines: &[PacketLine]) -> Result<()> {
        for line in lines {
            match line {
                PacketLine::String(string) => {
                    write!(&mut self.send_buffer, "{:04x}{}", string.len() + 4, string)
                },
                PacketLine::Bytes(bytes) => {
                    write!(&mut self.send_buffer, "{:04x}", bytes.len() + 4).unwrap();
                    self.send_buffer.write(bytes).map(|_| ())
                },
                PacketLine::FlushPacket => write!(&mut self.send_buffer, "0000"),
                PacketLine::DelimiterPacket => write!(&mut self.send_buffer, "0001"),
                PacketLine::ResponseEndPacket => write!(&mut self.send_buffer, "0002"),
            }.unwrap();
        }

        self.run.write(&self.send_buffer, Error::GitProtocolError)?;

        self.send_buffer.clear();

        Ok(())
    }

    pub fn write_raw(&mut self, data: &[u8]) -> Result<()> {
        self.run.write(data, Error::GitProtocolError)
    }

    pub fn wait_for_exit(&mut self, ignore_data: bool) -> Result<()> {
        loop {
            match self.run.poll()? {
                RunEvent::None => (),
                RunEvent::Data(_) if ignore_data => (),
                RunEvent::Stopped(Some(0)) => break Ok(()),
                RunEvent::ExtDataStderr(data) => log::warn!("Remote stderr: {}", from_utf8(data).unwrap()),
                e => {
                    log::error!("Unexpected RunEvent: {:?}", e);
                    break Err(Error::GitProtocolError);
                },
            }
        }
    }
}