use clap::{SubCommand, ArgMatches, Arg};
use chrono;
use commands::{BasicOptions, StaticSubcommand, ask};
use libpijul::{Repository, Hash, InodeUpdate, Patch};
use libpijul::fs_representation::patches_dir;
use std::mem::drop;
use error::{Result, ErrorKind};
use std::io::{stderr, Write};
use std::process::exit;
use std::path::Path;
use meta::{Global, Meta, KeyType, load_global_or_local_key};
use base64::URL_SAFE_NO_PAD;
use super::ask::{ChangesDirection, ask_changes};
use rand;
pub fn invocation() -> StaticSubcommand {
return SubCommand::with_name("record")
.about("record changes in the repository")
.arg(Arg::with_name("repository")
.long("repository")
.help("The repository where to record, defaults to the current directory.")
.takes_value(true)
.required(false))
.arg(Arg::with_name("branch")
.long("branch")
.help("The branch where to record, defaults to the current branch.")
.takes_value(true)
.required(false))
.arg(Arg::with_name("all")
.short("a")
.long("all")
.help("Answer 'y' to all questions")
.takes_value(false))
.arg(Arg::with_name("message")
.short("m")
.long("name")
.help("The name of the patch to record")
.takes_value(true))
.arg(Arg::with_name("author")
.short("A")
.long("author")
.help("Author of this patch (multiple occurrences allowed)")
.takes_value(true))
.arg(Arg::with_name("prefix")
.help("Prefix to start from")
.takes_value(true)
.multiple(true))
}
pub fn run(args: &ArgMatches) -> Result<Option<Hash>> {
let opts = BasicOptions::from_args(args)?;
let yes_to_all = args.is_present("all");
let patch_name_arg = args.value_of("message");
let authors_arg = args.values_of("author").map(|x| x.collect::<Vec<_>>());
let branch_name = opts.branch();
let (changes, syncs) = {
let repo = opts.open_and_grow_repo(409600)?;
let mut txn = repo.mut_txn_begin(rand::thread_rng())?;
let (changes, syncs) = txn.record(&branch_name, &opts.repo_root, None)?;
if !yes_to_all {
let c = try!(ask_changes(&txn, &changes, ChangesDirection::Record));
let selected = changes.into_iter()
.enumerate()
.filter(|&(i, _)| *(c.get(&i).unwrap_or(&false)))
.map(|(_, x)| x)
.collect();
txn.commit()?;
(selected, syncs)
} else {
txn.commit()?;
(changes, syncs)
}
};
let mut save_meta = false;
let mut save_global = false;
let mut global = Global::load().unwrap_or_else(|e| {
info!("loading global key, error {:?}", e);
save_global = true;
Global::new()
});
let mut meta = match Meta::load(&opts.repo_root) {
Ok(m) => m,
Err(_) => {
save_meta = true;
Meta::new()
}
};
if changes.is_empty() {
println!("Nothing to record");
Ok(None)
} else {
let repo = opts.open_repo()?;
let patch = {
let txn = repo.txn_begin()?;
debug!("meta:{:?}", meta);
let authors: Vec<String> = if let Some(ref authors) = authors_arg {
let authors: Vec<String> = authors.iter().map(|x| x.to_string()).collect();
{
if meta.authors.len() == 0 {
meta.authors = authors.clone();
save_meta = true
}
if global.author.len() == 0 {
global.author = authors[0].clone();
save_global = true
}
}
authors
} else {
if meta.authors.len() > 0 {
meta.authors.clone()
} else if global.author.len() > 0 {
vec![global.author.clone()]
} else {
let authors = try!(ask::ask_authors());
save_meta = true;
meta.authors = authors.clone();
save_global = true;
global.author = authors[0].clone();
authors
}
};
debug!("authors:{:?}", authors);
let patch_name = if let Some(ref m) = patch_name_arg {
m.to_string()
} else {
try!(ask::ask_patch_name())
};
debug!("patch_name:{:?}", patch_name);
if save_meta {
meta.save(&opts.repo_root)?
}
if save_global {
global.save()?
}
debug!("new");
let changes = changes.into_iter().flat_map(|x| x.into_iter()).collect();
let branch = txn.get_branch(&branch_name).unwrap();
txn.new_patch(&branch, authors, patch_name, None, chrono::Utc::now(), changes)
};
drop(repo);
let dot_pijul = opts.repo_dir();
let key = if let Ok(Some(key)) = meta.signing_key() {
Some(key)
} else {
load_global_or_local_key(Some(&dot_pijul), KeyType::Signing).ok()
};
debug!("key.is_some(): {:?}", key.is_some());
let patches_dir = patches_dir(&opts.repo_root);
let hash = patch.save(&patches_dir, key.as_ref())?;
let pristine_dir = opts.pristine_dir();
let mut increase = 409600;
loop {
match record_no_resize(&pristine_dir, &opts.repo_root, &branch_name, &hash, &patch,
&syncs, increase) {
Err(ref e) if e.lacks_space() => { increase *= 2 },
e => return e
}
}
}
}
fn record_no_resize(pristine_dir: &Path, r: &Path, branch_name: &str, hash: &Hash,
patch: &Patch, syncs: &[InodeUpdate], increase: u64)
-> Result<Option<Hash>> {
let size_increase = increase + patch.size_upper_bound() as u64;
let repo = match Repository::open(&pristine_dir, Some(size_increase)) {
Ok(repo) => repo,
Err(x) => {
return Err(ErrorKind::Repository(x).into())
}
};
let mut txn = try!(repo.mut_txn_begin(rand::thread_rng()));
debug!("syncs: {:?}", syncs);
txn.apply_local_patch(&branch_name, r, &hash, &patch, &syncs, false)?;
txn.commit()?;
println!("Recorded patch {}", hash.to_base64(URL_SAFE_NO_PAD));
Ok(Some(hash.clone()))
}
pub fn explain(res: Result<Option<Hash>>) {
match res {
Ok(_) => (),
Err(e) => {
write!(stderr(), "error: {}", e).unwrap();
exit(1)
}
}
}