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);
}
}
}
mem::swap(&mut next, &mut vec);
}
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()));
}
println!("}}");
}
}
Ok(())
}
pub fn explain(res: Result<()>) {
default_explain(res)
}