rvcs 0.1.0

A rusty version control system.
Documentation
use crate::{error, ok, get_current_branch, in_repo};
use std::{
    env, fs,
    fs::File,
    io,
    io::{Read, Write},
    path::Path,
};

pub fn merge(branch: &str) -> io::Result<()> {
    if !in_repo() {
        return Err(error!("Current directory is not a rvcs repository."));
    }

    if !branch_exists(branch)? {
        return Err(error!("Branch does not exist."));
    }

    let upcoming_full_path = &format!(".rvcs/commits/{}/", branch);
    let upcoming_commits = fs::read_dir(upcoming_full_path)?
        .collect::<Vec<_>>()
        .into_iter()
        .rev()
        .map(|e| {
            e.unwrap() /* TODO: Find a better method */
                .path()
                .to_str()
                .unwrap()
                .to_owned()
                .replace(upcoming_full_path, "")
        })
        .collect::<Vec<_>>();

    let current_full_path = &format!(".rvcs/commits/{}/", get_current_branch()?);
    let current_commits = fs::read_dir(current_full_path)?
        .collect::<Vec<_>>()
        .into_iter()
        .rev()
        .map(|e| {
            e.unwrap() /* TODO: Find a better method */
                .path()
                .to_str()
                .unwrap()
                .to_owned()
                .replace(current_full_path, "")
        })
        .collect::<Vec<_>>();

    let mut upcoming_since_sep = vec![];

    let to_iter = if upcoming_commits.len() > current_commits.len() {
        current_commits.len()
    } else {
        upcoming_commits.len()
    };

    for i in 0..to_iter {
        if upcoming_commits[i] != current_commits[i] {
            upcoming_since_sep = upcoming_commits[i..].to_vec();
            break;
        }
    }

    for commit in upcoming_since_sep {
        let upcoming_commit = &format!(".rvcs/commits/{}/{}", branch, commit);
        let upcoming_in_current = &format!(".rvcs/commits/{}/{}", get_current_branch()?, commit);

        if Path::new(upcoming_commit).is_dir() {
            copy_dir(upcoming_commit, upcoming_in_current)?;
        } else {
            fs::copy(upcoming_commit, upcoming_in_current)?;
        }
    }

    println!("{} Sucessfully merged branch `{}` into `{}`.", ok(), branch, get_current_branch()?);

    Ok(())
}

fn readr(dir: &str) -> io::Result<Vec<String>> {
    let mut elements = vec![];

    for entry in fs::read_dir(dir)? {
        let path = entry?.path();

        if path.is_dir() {
            elements.push(path.to_str().unwrap().to_string());
            elements.extend(readr(path.to_str().unwrap())?);
        } else {
            elements.push(path.to_str().unwrap().to_string());
        }
    }
    Ok(elements)
}

fn copy_dir(from: &str, to: &str) -> io::Result<()> {
    let current = env::current_dir()?;

    env::set_current_dir(from)?;
    let content = readr(".")?;

    env::set_current_dir(&current)?;
    fs::create_dir_all(to)?;
    env::set_current_dir(to)?;

    for element in content {
        let stringy = format!("{}/{}/{}", &current.to_str().unwrap(), from, &element);
        let path = Path::new(&stringy);
        if path.is_dir() {
            fs::create_dir_all(&element)?;
        } else {
            let mut buffer = vec![];
            File::open(path)?.read_to_end(&mut buffer)?;
            File::create(&element)?.write_all(&buffer)?;
        }
    }

    env::set_current_dir(current)
}

pub fn create_branch(name: &str) -> io::Result<()> {
    if !in_repo() {
        return Err(error!("Current directory is not a rvcs repository."));
    }

    if branch_exists(name)? {
        return Err(error!("Branch already exists."));
    }
    let current = &format!(".rvcs/commits/{}/", get_current_branch()?);
    let to_create = &format!(".rvcs/commits/{}/", name);

    copy_dir(&current, &to_create)?;
    println!("{} Sucessfully created branch `{}`.", ok(), name);
    Ok(())
}

pub fn goto_branch(name: &str) -> io::Result<()> {
    if !in_repo() {
        return Err(error!("Current directory is not a rvcs repository."));
    }

    if !branch_exists(name)? {
        return Err(error!("Branch does not exist."));
    }
    File::create(".rvcs/CURRENT_BRANCH")?.write_all(name.as_bytes())?;
    println!("{} Sucessfully switched to branch `{}`.", ok(), name);
    Ok(())
}

pub fn delete_branch(name: &str) -> io::Result<()> {
    if !in_repo() {
        return Err(error!("Current directory is not a rvcs repository."));
    }

    if !branch_exists(name)? {
        return Err(error!("Branch does not exist."));
    }

    fs::remove_dir_all(&format!(".rvcs/commits/{}", name))?;

    println!("{} Sucessfully deleted branch `{}`.", ok(), name);

    Ok(())
}

fn branch_exists(name: &str) -> io::Result<bool> {
    for entry in fs::read_dir(".rvcs/commits/")? {
        let path = entry?.path();

        if path.file_name().unwrap().to_str().unwrap() == name {
            return Ok(true);
        }
    }
    Ok(false)
}

pub fn list_branches() -> io::Result<()> {
    if !in_repo() {
        return Err(error!("Current directory is not a rvcs repository."));
    }

    let current = fs::read_to_string(".rvcs/CURRENT_BRANCH")?;

    for entry in fs::read_dir(".rvcs/commits/")? {
        let path = entry?.path();

        if path.file_name().unwrap().to_str().unwrap() == current {
            println!("\x1b[0;32m* {}\x1b[0m", current);
        } else {
            println!("  {}", path.file_name().unwrap().to_str().unwrap());
        }
    }
    Ok(())
}