git_rs 0.1.0

A (work in progress) pure rust git implementation.
Documentation
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::fs::File;
use std::io::Read;

use crate::id::Id;

#[derive(Copy, Clone, Debug)]
pub enum Kind {
    Local,
    Remote,
    Tag
}

#[derive(Debug)]
pub enum RefPtr {
    Indirect(String),
    Direct(Id)
}

#[derive(Debug)]
pub struct Ref {
    kind: Kind,
    ptr: RefPtr
}

pub struct RefSet(HashMap<String, Ref>);

impl Ref {
    pub fn load(path: &Path, kind: Kind) -> Result<Ref, std::io::Error> {
        let mut f = File::open(path)?;
        let mut buffer = Vec::new();
        f.read_to_end(&mut buffer)?;

        if let Ok(contents) = std::str::from_utf8(&buffer) {
            if contents.len() < 5 {
                return Err(std::io::ErrorKind::InvalidData.into());
            }

            if &contents[0..16] == "ref: refs/heads/" {
                return Ok(Ref {
                    kind,
                    ptr: RefPtr::Indirect(String::from(contents[16..].trim()))
                });
            }

            if buffer.len() >= 40 {
                if let Ok(id) = Id::from_str(&contents[0..40]) {
                    return Ok(Ref {
                        ptr: RefPtr::Direct(id),
                        kind
                    });
                }
            }
        }

        Err(std::io::ErrorKind::InvalidData.into())
    }
}

fn recurse_dir(
    root: &mut PathBuf,
    dirs: &mut Vec<String>,
    map: &mut HashMap<String, Ref>,
    k: Kind
) -> Result<(), std::io::Error> {
    for entry in std::fs::read_dir(root.as_path())? {
        let entry = entry?;

        let typ = entry.file_type()?;
        let os_filename = entry.file_name();
        let opt_filename = os_filename.to_str();
        if opt_filename.is_none() {
            continue
        }
        let filename = opt_filename.unwrap();

        dirs.push(String::from(filename));
        if typ.is_dir() {
            recurse_dir(&mut entry.path(), dirs, map, k)?;
        } else {
            let ref_name: String = dirs.join("/");
            if let Ok(reference) = Ref::load(&entry.path(), k) {
                map.insert(ref_name, reference);
            }
        }
        dirs.pop();
    }

    Ok(())
}

impl RefSet {
    pub fn from_path(path: &Path) -> Result<RefSet, std::io::Error> {
        let mut root = std::path::PathBuf::new();
        let mut map = HashMap::new();
        let mut dirs = Vec::new();
        root.push(path);
        root.push(".git");
        root.push("refs");
        root.push("heads");
        recurse_dir(&mut root, &mut dirs, &mut map, Kind::Local)?;
        root.pop();
        root.push("remotes");
        recurse_dir(&mut root, &mut dirs, &mut map, Kind::Remote)?;
        root.pop();
        root.push("tags");
        recurse_dir(&mut root, &mut dirs, &mut map, Kind::Tag)?;
        root.pop();
        root.pop();
        root.push("HEAD");
        if let Ok(reference) = Ref::load(root.as_path(), Kind::Local) {
            map.insert(String::from("HEAD"), reference);
        };

        Ok(RefSet {
            0: map
        })
    }

    pub fn deref(&self, name: &str) -> Option<&Id> {
        let mut reference = self.0.get(name);
        loop {
            match reference {
                Some(xs) => {
                    match xs.ptr {
                        RefPtr::Direct(ref id) => return Some(&id),
                        RefPtr::Indirect(ref string) => {
                            reference = self.0.get(string.as_str());
                        }
                    }
                },
                None => return None
            }
        }
    }
}