pijul 0.7.2

A patch-based distributed version control system, easy to use and fast. Command-line interface.
use std::path::Path;
use std::mem;
use std::string::String;
use std::collections::HashSet;
use clap::{SubCommand, ArgMatches, Arg};

use libpijul::{Hash, Repository, DEFAULT_BRANCH};
use libpijul::fs_representation::{pristine_dir, find_repo_root, read_patch};
use base64::{URL_SAFE_NO_PAD};

use super::{StaticSubcommand, get_current_branch, get_wd, default_explain};
use error::{ErrorKind, Result};

pub fn invocation() -> StaticSubcommand {
    return SubCommand::with_name("show-dependencies")
        .about("Print the patch dependencies using the DOT syntax in stdout")
        .arg(Arg::with_name("hash")
             .help("Hash of a patch.")
             .takes_value(true)
             .required(false)
             .validator(|h| {
                 if Hash::from_base64(&h).is_some() {
                     Ok(())
                 } else {
                     Err(format!("The hash argument is not a valid hash: {}", h))
                 }
             })
        )
        .arg(Arg::with_name("depth")
             .long("depth")
             .help("The depth of the dependencies graph")
             .takes_value(true)
             .required(false)
             .validator(|x| {
                 if let Ok(x) = x.parse::<usize>() {
                     if x >= 1 {
                         return Ok(())
                     }
                 }
                 Err("The depth argument must be an integer, and at least 1".to_owned())
             }))
        .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)
             .required(false));
}

#[derive(Debug)]
enum Target<'a> {
    Branch(Option<&'a str>),
    Hash(&'a str, usize)
}

#[derive(Debug)]
pub struct Params<'a> {
    pub repository: Option<&'a Path>,
    target: Target<'a>
}

pub fn parse_args<'a>(args: &'a ArgMatches) -> Result<Params<'a>> {
    let target = if let Some(ref hash) = args.value_of("hash") {
        let depth = args.value_of("depth")
            .unwrap_or("1")
            .parse::<usize>()
            .unwrap();

        Target::Hash(hash, depth)
    } else {
        Target::Branch(args.value_of("branch"))
    };

    Ok(Params {
        repository: args.value_of("repository").map(|x| Path::new(x)),
        target: target
    })
}

fn hash_sanitize(str: String) -> String {
    let mut lab = String::from("LBL");

    lab.push_str(str.to_uppercase().replace("-", "MM").replace("_", "UU").as_str());

    lab
}

fn label_sanitize(str: String) -> String {
    str.replace("\"", "\\\"")
}

pub fn run(args: &ArgMatches) -> Result<()> {
    let args = parse_args(args)?;
    let wd = get_wd(args.repository)?;
    let target = if let Some(r) = find_repo_root(&wd) {
        r
    } else {
        return Err(ErrorKind::NotInARepository.into())
    };
    let repo_dir = pristine_dir(&target);
    let repo = Repository::open(&repo_dir, None)?;
    let txn = repo.txn_begin()?;

    match args.target {
        Target::Branch(branch_arg) => {
            let branch_name = if let Some(b) = branch_arg {
                b.to_string()
            } else if let Ok(b) = get_current_branch(&target) {
                b
            } else {
                DEFAULT_BRANCH.to_string()
            };

            if let Some(branch) = txn.get_branch(&branch_name) {
                println!("digraph dependencies {{");
                for (_, hash) in txn.rev_iter_applied(&branch, None) {
                    let hash_ext = txn.get_external(hash).unwrap();
                    let patch = read_patch(&target, hash_ext)?;

                    println!("  {} [label=\"{}\"]",
                             hash_sanitize(hash_ext.to_base64(URL_SAFE_NO_PAD)),
                             label_sanitize(patch.header().name.clone()));

                    for hash_dep in patch.dependencies() {
                        println!("  {} -> {}",
                                 hash_sanitize(hash_ext.to_base64(URL_SAFE_NO_PAD)),
                                 hash_sanitize(hash_dep.to_base64(URL_SAFE_NO_PAD)));
                    }
                }
                println!("}}");
            }
        }
        Target::Hash(ref hash, depth) => {
            let hash = Hash::from_base64(hash).unwrap();
            let mut seen = HashSet::new();
            let mut vec = Vec::new();
            seen.insert(hash.clone());
            vec.push(hash);
            let mut next = Vec::new();

            println!("digraph dependencies {{");
            for _ in 0..depth {
                for hash in vec.drain(..) {
                    let hash_ext = hash.as_ref();
                    let patch = read_patch(&target, hash_ext)?;

                    println!("  {} [label=\"{}\"]",
                             hash_sanitize(hash_ext.to_base64(URL_SAFE_NO_PAD)),
                             label_sanitize(patch.header().name.clone()));

                    for hash_dep in patch.dependencies() {
                        println!("  {} -> {}",
                                 hash_sanitize(hash_ext.to_base64(URL_SAFE_NO_PAD)),
                                 hash_sanitize(hash_dep.to_base64(URL_SAFE_NO_PAD)));

                        let h = hash_dep.to_owned();

                        if !seen.contains(&h) {
                            seen.insert(h.clone());
                            next.push(h);
                        }
                    }
                }

                // vec should be empty, has it has been consumed by drain
                // on the other hand, next contains all the
                // dependencies to walk into in the next loop
                // iteration
                mem::swap(&mut next, &mut vec);
            }

            // lets have a last for to get the name of the last dependencies
            for hash in vec.drain(..) {
                let hash_ext = hash.as_ref();
                let patch = read_patch(&target, hash_ext)?;

                println!("  {} [label=\"{}\"]",
                         hash_sanitize(hash_ext.to_base64(URL_SAFE_NO_PAD)),
                         label_sanitize(patch.header().name.clone()));
            }

            // and we are done
            println!("}}");
        }
    }

    Ok(())
}

pub fn explain(res: Result<()>) {
    default_explain(res)
}