pijul 0.3.2

A patch-based distributed version control system, easy to use and fast. Command-line interface.
use clap::{SubCommand, ArgMatches, Arg};

use super::StaticSubcommand;
use error::Error;
use std::path::Path;

use libpijul::{Repository, DEFAULT_BRANCH, Hash};
use libpijul::patch::{Patch};
use libpijul::fs_representation::{pristine_dir, find_repo_root, patches_dir, patch_file_name};
use rand;
use super::{get_wd, ask, get_current_branch};
use std::collections::{HashMap};
use std::fs::File;
use std::io::{BufReader};
use std::mem::drop;

pub fn invocation() -> StaticSubcommand {
    return SubCommand::with_name("unrecord")
        .about("Unrecord some patches (remove them without reverting them)")
        .arg(Arg::with_name("repository")
             .long("repository")
             .help("Local repository.")
             .takes_value(true)
        )
        .arg(Arg::with_name("branch")
             .long("branch")
             .help("Branch.")
             .takes_value(true)
        )
        .arg(Arg::with_name("patch")
             .long("patch")
             .help("Patch to unrecord.")
             .takes_value(true)
             .multiple(true)
        )
        .arg(Arg::with_name("all")
             .short("a")
             .long("all")
             .help("Answer 'y' to all questions")
             .takes_value(false)
        );
}

#[derive(Debug)]
pub struct Params<'a> {
    pub repository: Option<&'a Path>,
    pub branch: Option<&'a str>,
    pub yes_to_all: bool,
    pub patches: Option<Vec<Hash>>
}


pub fn parse_args<'a>(args: &'a ArgMatches) -> Params<'a> {
    Params {
        repository: args.value_of("repository").map(Path::new),
        branch: args.value_of("branch"),
        yes_to_all: args.is_present("all"),
        patches: if let Some(patches) = args.values_of("patch") {
            Some(patches.map(|x| Hash::from_base64(x).unwrap()).collect())
        } else {
            None
        }
    }
}

pub fn run<'a>(args: &Params<'a>) -> Result<(), Error> {
    debug!("args {:?}", args);
    let wd = try!(get_wd(args.repository));
    match find_repo_root(&wd) {
        None => return Err(Error::NotInARepository),
        Some(ref repo_root) => {

            let repo_dir = pristine_dir(repo_root);
            let mut increase = 409600;
            let repo = try!(Repository::open(&repo_dir, Some(increase)).map_err(Error::Repository));
            let branch_name = if let Some(b) = args.branch {
                b.to_string()
            } else if let Ok(b) = get_current_branch(&repo_dir) {
                b
            } else {
                DEFAULT_BRANCH.to_string()
            };

            let mut patches:Vec<_> = {
                let mut txn = try!(repo.mut_txn_begin(rand::thread_rng()));
                let branch = txn.open_branch(&branch_name)?;
                let patches = txn.rev_iter_applied(&branch, None)
                    .map(|(t, h)| {

                        let ext = txn.get_external(h).unwrap();
                        let base = patch_file_name(ext);
                        let filename = patches_dir(repo_root).join(&base);
                        let file = File::open(&filename).unwrap();
                        let mut file = BufReader::new(file);
                        let (_, _, patch) = Patch::from_reader_compressed(&mut file).unwrap();
                        (ext.to_owned(), patch, t)

                    })
                    .collect();
                txn.commit_branch(branch)?;
                txn.commit()?;
                patches
            };
            patches.sort_by(|&(_, _, a), &(_, _, b)| b.cmp(&a));
            let patches:Vec<(Hash, Patch)> = patches.into_iter().map(|(a, b, _)| (a, b)).collect();
            // debug!("patches: {:?}", patches);
            let to_unrecord = ask::ask_patches(ask::Command::Unrecord, &patches).unwrap();
            debug!("to_unrecord: {:?}", to_unrecord);
            let mut patches: HashMap<_,_> =

                patches
                .into_iter()
                .filter(|&(ref k,_)| to_unrecord.contains(&k))
                .map(|(k, v)| (k, v))
                .collect();

            let mut selected = Vec::new();
            loop {
                let hash = if let Some((hash, patch)) = patches.iter().next() {
                    increase += patch.size_upper_bound() as u64;
                    hash.to_owned()
                } else {
                    break
                };
                deps_dfs(&mut selected, &mut patches, &hash)
            }
            drop(repo);

            loop {
                match unrecord_no_resize(&repo_dir, &repo_root, &branch_name, &mut selected, increase) {
                    Err(ref e) if e.lacks_space() => { increase *= 2 },
                    e => return e
                }
            }
        }
    }
}

fn unrecord_no_resize(repo_dir: &Path, repo_root: &Path, branch_name: &str, selected: &mut Vec<(Hash, Patch)>, increase: u64) -> Result<(), Error> {
    let repo = try!(Repository::open(repo_dir, Some(increase)).map_err(Error::Repository));

    let mut txn = try!(repo.mut_txn_begin(rand::thread_rng()));
    let mut branch = txn.open_branch(branch_name)?;
    while let Some((hash, patch)) = selected.pop() {
        let internal = txn.get_internal(hash.as_ref()).unwrap().to_owned();
        debug!("Unrecording {:?}", hash);
        try!(txn.unrecord(&mut branch, repo_root, &internal, &patch));
        debug!("Done unrecording {:?}", hash);
    }

    try!(txn.commit_branch(branch));
    try!(txn.commit());
    Ok(())
}


fn deps_dfs(selected: &mut Vec<(Hash, Patch)>,
            patches: &mut HashMap<Hash, Patch>,
            current: &Hash) {

    if let Some(patch) = patches.remove(current) {

        for dep in patch.dependencies.iter() {
            deps_dfs(selected, patches, dep)
        }

        selected.push((current.to_owned(), patch))
    }
}