use super::{default_explain, get_wd, validate_base58, StaticSubcommand};
use clap::{Arg, ArgMatches, SubCommand};
use error::Error;
use libpijul::fs_representation::find_repo_root;
use libpijul::{Hash, DEFAULT_BRANCH};
use std::collections::HashSet;
use std::mem;
use std::path::Path;
use std::string::String;
pub fn invocation() -> StaticSubcommand {
return SubCommand::with_name("dependencies")
.alias("deps")
.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)
.multiple(true)
.validator(validate_base58),
)
.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.")
.multiple(true)
.takes_value(true),
)
.arg(
Arg::with_name("branch")
.long("branch")
.help("Branch.")
.takes_value(true)
.required(false),
);
}
enum Target<'a> {
Branch(Option<&'a str>),
Hash(Vec<&'a str>, usize),
}
pub struct Params<'a> {
pub repository: Option<&'a Path>,
target: Target<'a>,
}
pub fn parse_args<'a>(args: &'a ArgMatches) -> Result<Params<'a>, Error> {
let target = if let Some(hash) = args.values_of("hash") {
let depth = args
.value_of("depth")
.unwrap_or("1")
.parse::<usize>()
.unwrap();
Target::Hash(hash.collect(), depth)
} else {
Target::Branch(args.value_of("branch"))
};
Ok(Params {
repository: args.value_of("repository").map(|x| Path::new(x)),
target: target,
})
}
fn label_sanitize(str: String) -> String {
let label = str.replace("\"", "\\\"");
let mut words = label.split_whitespace();
let mut nth = 0;
let mut res = String::from("");
if let Some(first_word) = words.next() {
res.push_str(first_word);
for word in words {
if nth >= 5 {
res.push_str("\\n");
nth = 0;
} else {
res.push_str(" ");
nth += 1;
}
res.push_str(word);
}
}
res
}
pub fn run(args: &ArgMatches) -> Result<(), Error> {
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(Error::NotInARepository);
};
let repo = target.open_repo(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) = target.get_current_branch() {
b
} else {
DEFAULT_BRANCH.to_string()
};
if let Some(branch) = txn.get_branch(&branch_name) {
println!("digraph dependencies {{");
println!(" graph [rankdir=LR];");
for (_, hash) in txn.rev_iter_applied(&branch, None) {
let hash_ext = txn.get_external(hash).unwrap();
let patch = target.read_patch(hash_ext)?;
patch_node(
hash_ext.to_base58(),
patch.header().name.clone(),
patch.is_tag(),
);
let deps = txn.minimize_deps(patch.dependencies());
for hash_dep in deps {
println!(" N{} -> N{}", hash_ext.to_base58(), hash_dep.to_base58());
}
}
println!("}}");
}
}
Target::Hash(hashes, depth) => {
let mut seen = HashSet::new();
let mut vec: Vec<_> = hashes
.iter()
.map(|h| Hash::from_base58(h).unwrap())
.collect();
let mut next = Vec::new();
println!("digraph dependencies {{");
println!(" graph [rankdir=LR];");
for _ in 0..depth {
for hash in vec.drain(..) {
debug!("hash: {:?}", hash);
seen.insert(hash.clone());
let hash_ext = hash.as_ref();
let patch = target.read_patch(hash_ext)?;
patch_node(
hash_ext.to_base58(),
patch.header().name.clone(),
patch.is_tag(),
);
let deps = txn.minimize_deps(patch.dependencies());
for hash_dep in deps.iter() {
debug!("dep: {:?}", hash_dep);
println!(" N{} -> N{}", hash_ext.to_base58(), hash_dep.to_base58());
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 = target.read_patch(hash_ext)?;
patch_node(
hash_ext.to_base58(),
patch.header().name.clone(),
patch.is_tag(),
);
}
println!("}}");
}
}
Ok(())
}
fn patch_node(hash: String, name: String, is_tag: bool) {
if is_tag {
println!(
" N{} [label=\"TAG: {}\", shape=box]",
hash,
label_sanitize(name)
);
} else {
println!(" N{} [label=\"{}\"]", hash, label_sanitize(name));
}
}
pub fn explain(res: Result<(), Error>) {
default_explain(res)
}