use clap::{Arg, ArgMatches, SubCommand};
use commands::patch::print_patch;
use commands::{ask, default_explain, BasicOptions, StaticSubcommand};
use error::Error;
use libpijul::fs_representation::RepoPath;
use libpijul::patch::Patch;
use libpijul::{Branch, PatchId, Txn};
use regex::Regex;
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::PathBuf;
use term;
pub fn invocation() -> StaticSubcommand {
SubCommand::with_name("log")
.about("List the patches applied to the given branch")
.arg(
Arg::with_name("repository")
.long("repository")
.help("Path to the repository to list.")
.takes_value(true),
)
.arg(
Arg::with_name("branch")
.long("branch")
.help("The branch to list.")
.takes_value(true),
)
.arg(
Arg::with_name("internal-id")
.long("internal-id")
.help("Display only patches with these internal identifiers.")
.multiple(true)
.takes_value(true),
)
.arg(
Arg::with_name("hash-only")
.long("hash-only")
.help("Only display the hash of each path."),
)
.arg(
Arg::with_name("repository-id")
.long("repository-id")
.help("display a header with the repository id")
)
.arg(
Arg::with_name("path")
.long("path")
.multiple(true)
.takes_value(true)
.help("Only display patches that touch the given path."),
)
.arg(
Arg::with_name("grep")
.long("grep")
.multiple(true)
.takes_value(true)
.help("Search patch name and description with a regular expression."),
)
.arg(
Arg::with_name("last")
.long("last")
.takes_value(true)
.help("Show only the last n patches. If `--first m` is also used, then (a) if the command normally outputs the last patches first, this means the last n patches of the first m ones. (b) Else, it means the first m patches of the last n ones."),
)
.arg(
Arg::with_name("first")
.long("first")
.takes_value(true)
.help("Show only the last n patches. If `--last m` is also used, then (a) if the command normally outputs the last patches first, this means the last m patches of the first n ones. (b) Else, it means the first n patches of the last m ones."),
)
.arg(
Arg::with_name("patch")
.long("patch")
.short("p")
.help("Show patches"),
)
}
struct Settings<'a> {
hash_only: bool,
show_repoid: bool,
show_patches: bool,
regex: Vec<Regex>,
opts: BasicOptions<'a>,
path: Vec<RepoPath<PathBuf>>,
first: Option<usize>,
last: Option<usize>,
}
impl<'a> Settings<'a> {
fn parse(args: &'a ArgMatches) -> Result<Self, Error> {
let basic_opts = BasicOptions::from_args(args)?;
let hash_only = args.is_present("hash-only");
let first = args.value_of("first").and_then(|x| x.parse().ok());
let last = args.value_of("last").and_then(|x| x.parse().ok());
let show_patches = args.is_present("patch");
let show_repoid = args.is_present("repository-id");
let mut regex = Vec::new();
if let Some(regex_args) = args.values_of("grep") {
for r in regex_args {
debug!("regex: {:?}", r);
regex.push(Regex::new(r)?)
}
}
let path = match args.values_of("path") {
Some(arg_paths) => {
let mut paths = Vec::new();
for path in arg_paths {
let p = basic_opts.cwd.join(path);
let p = if let Ok(p) = std::fs::canonicalize(&p) {
p
} else {
p
};
paths.push(basic_opts.repo_root.relativize(&p)?.to_owned());
}
paths
}
None => Vec::new(),
};
Ok(Settings {
hash_only,
show_patches,
show_repoid,
regex,
opts: basic_opts,
path,
first,
last,
})
}
}
impl<'a> Settings<'a> {
fn display_patch_(
&self,
txn: &Txn,
branch: &Branch,
nth: u64,
patchid: PatchId,
) -> Result<(), Error> {
let hash_ext = txn.get_external(patchid).unwrap();
debug!("hash: {:?}", hash_ext.to_base58());
let (matches_regex, o_patch) = if self.regex.is_empty() {
(true, None)
} else {
let patch = self.opts.repo_root.read_patch_nochanges(hash_ext)?;
let does_match = {
let descr = match patch.description {
Some(ref d) => d,
None => "",
};
self.regex
.iter()
.any(|ref r| r.is_match(&patch.name) || r.is_match(descr))
};
(does_match, Some(patch))
};
if !matches_regex {
return Ok(());
};
if self.hash_only {
println!("{}:{}", hash_ext.to_base58(), nth);
} else {
let patch = match o_patch {
None => self.opts.repo_root.read_patch_nochanges(hash_ext)?,
Some(patch) => patch,
};
let mut term = if atty::is(atty::Stream::Stdout) {
term::stdout()
} else {
None
};
ask::print_patch_descr(&mut term, &hash_ext.to_owned(), Some(patchid), &patch);
}
if self.show_patches {
let mut patch_path = self.opts.repo_root.patches_dir().join(hash_ext.to_base58());
patch_path.set_extension("gz");
let f = File::open(&patch_path)?;
let mut f = BufReader::new(f);
let (hash, _, patch) = Patch::from_reader_compressed(&mut f)?;
print_patch(&hash, &patch, txn, branch)?;
println!();
}
Ok(())
}
fn display_patch(
&self,
txn: &Txn,
branch: &Branch,
n: u64,
patchid: PatchId,
) -> Result<(), Error> {
if self.path.is_empty() {
self.display_patch_(txn, branch, n, patchid)?;
} else {
for path in self.path.iter() {
let inode = txn.find_inode(&path)?;
let key = if let Some(key) = txn.get_inodes(inode) {
key.key
} else {
continue;
};
if txn.get_touched(key, patchid) {
self.display_patch_(txn, branch, n, patchid)?;
break;
}
}
}
Ok(())
}
fn is_touched(&self, txn: &Txn, patchid: PatchId) -> bool {
self.path.is_empty()
|| self.path.iter().any(|path| {
if let Ok(inode) = txn.find_inode(&path) {
if let Some(key) = txn.get_inodes(inode) {
return txn.get_touched(key.key, patchid);
}
}
false
})
}
}
pub fn run(args: &ArgMatches) -> Result<(), Error> {
let settings = Settings::parse(args)?;
let repo = settings.opts.open_repo()?;
let txn = repo.txn_begin()?;
let branch = match txn.get_branch(&settings.opts.branch()) {
Some(b) => b,
None => return Err(Error::NoSuchBranch),
};
if settings.show_repoid {
let id_file = settings.opts.repo_root.id_file();
let mut f = File::open(&id_file)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
if settings.hash_only {
println!("{}", s.trim());
} else {
println!("Repository id: {}", s.trim());
println!();
}
};
if settings.hash_only {
let start = settings.last.and_then(|last| {
txn.rev_iter_applied(&branch, None)
.filter(|(_, patchid)| {
settings.is_touched(&txn, *patchid)
})
.take(last)
.last()
.map(|(n, _)| n)
});
debug!("start {:?}", start);
for (n, (applied, patchid)) in txn.iter_applied(&branch, start).enumerate() {
if let Some(first) = settings.first {
if n >= first {
break;
}
}
settings.display_patch(&txn, &branch, applied, patchid)?
}
return Ok(());
}
let txn = repo.txn_begin()?;
if let Some(v) = args.values_of("internal-id") {
for (n, patchid) in v.filter_map(|x| PatchId::from_base58(x)).enumerate() {
settings.display_patch(&txn, &branch, n as u64, patchid)?;
}
} else {
let first = if let Some(first) = settings.first {
txn.iter_applied(&branch, None)
.filter(|(_, patchid)| settings.is_touched(&txn, *patchid))
.take(first)
.last()
.map(|(n, _)| n)
} else {
None
};
for (n, (applied, patchid)) in txn.rev_iter_applied(&branch, first).enumerate() {
if let Some(last) = settings.last {
if n >= last {
break;
}
}
settings.display_patch(&txn, &branch, applied, patchid)?;
}
}
Ok(())
}
pub fn explain(r: Result<(), Error>) {
default_explain(r)
}