use std::io::prelude::*;
use rustc_serialize::base64::{ToBase64, URL_SAFE};
use getch;
use libpijul::patch::{Change, Patch, Record, EdgeMap};
use std::io::stdout;
use std::collections::{HashMap, HashSet};
use error::Error;
use libpijul::{MutTxn, LineId, FOLDER_EDGE, PARENT_EDGE, Hash};
use std::io::stdin;
use std::char::from_u32;
use std::str;
use std;
use rand;
const BINARY_CONTENTS:&'static str = "<binary contents>";
#[derive(Clone,Copy)]
pub enum Command {
Pull,
Push,
Unrecord,
}
pub fn print_patch_descr(hash: &Hash, patch: &Patch) {
println!("Hash: {}", hash.to_base64(URL_SAFE));
println!("Authors: {:?}", patch.authors);
println!("Timestamp {}", patch.timestamp);
println!(" * {}", patch.name);
match patch.description {
Some(ref d) => println!(" {}", d),
None => {}
};
}
fn check_forced_decision(command: Command,
choices: &HashMap<&Hash, bool>,
rev_dependencies: &HashMap<&Hash, Vec<&Hash>>,
a: &Hash,
b: &Patch)
-> Option<bool> {
let covariant = match command {
Command::Pull | Command::Push => true,
Command::Unrecord => false,
};
if let Some(x) = rev_dependencies.get(a) {
for y in x {
if let Some(&choice) = choices.get(y) {
if choice == covariant {
return Some(covariant);
}
}
}
};
for y in b.dependencies.iter() {
if let Some(&choice) = choices.get(&y){
if choice != covariant {
return Some(!covariant);
}
}
}
None
}
fn interactive_ask(getch: &getch::Getch,
a: &Hash,
b: &Patch,
command_name: Command)
-> Result<(char, Option<bool>), Error> {
print_patch_descr(a, b);
print!("{} [ynkad] ",
match command_name {
Command::Push => "Shall I push this patch?",
Command::Pull => "Shall I pull this patch?",
Command::Unrecord => "Shall I unrecord this patch?",
});
try!(stdout().flush());
match getch.getch().ok().and_then(|x| from_u32(x as u32)) {
Some(e) => {
println!("{}", e);
let e = e.to_uppercase().next().unwrap_or('\0');
match e {
'A' => Ok(('Y', Some(true))),
'D' => Ok(('N', Some(false))),
e => Ok((e, None)),
}
}
_ => Ok(('\0', None)),
}
}
pub fn ask_patches(command: Command, patches: &[(Hash, Patch)]) -> Result<HashSet<Hash>, Error> {
let getch = try!(getch::Getch::new());
let mut i = 0;
let mut choices: HashMap<&Hash, bool> = HashMap::new();
let mut rev_dependencies: HashMap<&Hash, Vec<&Hash>> = HashMap::new();
let mut final_decision = None;
while i < patches.len() {
let (ref a, ref b) = patches[i];
let forced_decision = check_forced_decision(command, &choices, &rev_dependencies, a, b);
let e = match forced_decision.or(final_decision) {
Some(true) => 'Y',
Some(false) => 'N',
None => {
debug!("decision not forced");
let (current, remaining) = try!(interactive_ask(&getch, a, b, command));
final_decision = remaining;
current
}
};
debug!("decision: {:?}", e);
match e {
'Y' => {
choices.insert(a, true);
match command {
Command::Pull | Command::Push => {
for ref dep in b.dependencies.iter() {
let d = rev_dependencies.entry(dep).or_insert(vec![]);
d.push(a)
}
}
Command::Unrecord => {}
}
i += 1
}
'N' => {
choices.insert(a, false);
match command {
Command::Unrecord => {
for ref dep in b.dependencies.iter() {
let d = rev_dependencies.entry(dep).or_insert(vec![]);
d.push(a)
}
}
Command::Pull | Command::Push => {}
}
i += 1
}
'K' if i > 0 => {
let (ref a, _) = patches[i];
choices.remove(a);
i -= 1
}
_ => {}
}
}
Ok(choices.into_iter()
.filter(|&(_, selected)| selected)
.map(|(x, _)| x.to_owned())
.collect())
}
fn change_deps(id: usize, c: &Record, provided_by: &mut HashMap<LineId, usize>) -> HashSet<LineId> {
let mut s = HashSet::new();
for c in c.iter() {
match *c {
Change::NewNodes { ref up_context, ref down_context, ref line_num, ref nodes, .. } => {
for cont in up_context.iter().chain(down_context) {
if cont.patch.is_none() && !cont.line.is_root() {
s.insert(cont.line.clone());
}
}
for i in 0..nodes.len() {
provided_by.insert(*line_num + i, id);
}
}
Change::NewEdges { ref edges, .. } => {
for e in edges {
if e.from.patch.is_none() && !e.from.line.is_root() {
s.insert(e.from.line.clone());
}
if e.to.patch.is_none() && !e.from.line.is_root() {
s.insert(e.to.line.clone());
}
}
}
}
}
s
}
fn print_change<T: rand::Rng>(repo: &MutTxn<T>, c: &Record) -> Result<(), Error> {
match *c {
Record::FileAdd { ref name, .. } => {
println!("added file {}", name);
Ok(())
}
Record::FileDel { ref name, .. } => {
println!("deleted file: {}", name);
Ok(())
}
Record::FileMove { ref new_name, .. } => {
println!("file moved to: {}", new_name);
Ok(())
}
Record::Change(ref c) => {
match *c {
Change::NewNodes { ref flag,
ref nodes,
.. } => {
for n in nodes {
if flag.contains(FOLDER_EDGE) {
if n.len() >= 2 {
println!("new file {}", str::from_utf8(&n[2..]).unwrap_or(""));
}
} else {
let s = str::from_utf8(n).unwrap_or(BINARY_CONTENTS);
if s.ends_with("\n") {
print!("+ {}", s);
} else {
println!("+ {}", s);
}
}
}
Ok(())
}
Change::NewEdges { ref edges, ref flag, .. } => {
let mut h_targets = HashSet::with_capacity(edges.len());
for e in edges {
let target = match *flag {
EdgeMap::Map { flag, .. } |
EdgeMap::New { flag, .. } |
EdgeMap::Forget { previous: flag } => {
if !flag.contains(PARENT_EDGE) {
if h_targets.insert(&e.to) {
Some(&e.to)
} else {
None
}
} else {
if h_targets.insert(&e.from) {
Some(&e.from)
} else {
None
}
}
},
};
if let Some(target) = target {
let internal = repo.internal_key_unwrap(target);
let l = repo.get_contents(&internal).unwrap();
let l = l.into_cow();
let s = str::from_utf8(&l).unwrap_or(BINARY_CONTENTS);
if s.ends_with("\n") {
print!("- {}", s)
} else {
println!("- {}", s)
}
}
}
Ok(())
}
}
}
}
}
pub fn ask_record<T: rand::Rng>(repository: &MutTxn<T>,
changes: &[Record])
-> Result<HashMap<usize, bool>, Error> {
debug!("changes: {:?}", changes);
let getch = try!(getch::Getch::new());
let mut i = 0;
let mut choices: HashMap<usize, bool> = HashMap::new();
let mut final_decision = None;
let mut provided_by = HashMap::new();
let mut line_deps = Vec::with_capacity(changes.len());
for i in 0..changes.len() {
line_deps.push(change_deps(i, &changes[i], &mut provided_by));
}
let mut deps: HashMap<usize, Vec<usize>> = HashMap::new();
let mut rev_deps: HashMap<usize, Vec<usize>> = HashMap::new();
for i in 0..changes.len() {
for dep in line_deps[i].iter() {
debug!("provided: i {}, dep {:?}", i, dep);
let p = provided_by.get(dep).unwrap();
debug!("provided: p= {}", p);
let e = deps.entry(i).or_insert(Vec::new());
e.push(*p);
let e = rev_deps.entry(*p).or_insert(Vec::new());
e.push(i);
}
}
let empty_deps = Vec::new();
while i < changes.len() {
let decision=
if deps.get(&i)
.unwrap_or(&empty_deps)
.iter()
.any(|x| { ! *(choices.get(x).unwrap_or(&true)) }) {
Some(false)
} else if rev_deps.get(&i).unwrap_or(&empty_deps)
.iter().any(|x| { *(choices.get(x).unwrap_or(&false)) }) {
Some(true)
} else {
None
};
let e = match decision {
Some(true) => 'Y',
Some(false) => 'N',
None => {
if let Some(d) = final_decision {
d
} else {
try!(print_change(repository, &changes[i]));
print!("Shall I record this change? [ynkad] ");
try!(stdout().flush());
match getch.getch().ok().and_then(|x| from_u32(x as u32)) {
Some(e) => {
println!("{}", e);
let e = e.to_uppercase().next().unwrap_or('\0');
match e {
'A' => {
final_decision = Some('Y');
'Y'
}
'D' => {
final_decision = Some('N');
'N'
}
e => e,
}
}
_ => '\0',
}
}
}
};
match e {
'Y' => {
choices.insert(i, true);
i += 1
}
'N' => {
choices.insert(i, false);
i += 1
}
'K' if i > 0 => {
choices.remove(&i);
i -= 1
}
_ => {}
}
}
Ok(choices)
}
pub fn ask_authors() -> Result<Vec<String>, Error> {
print!("What is your name <and email address>? ");
try!(std::io::stdout().flush());
let mut input = String::new();
try!(stdin().read_line(&mut input));
if let Some(c) = input.pop() {
if c != '\n' {
input.push(c)
}
}
Ok(vec![input])
}
pub fn ask_patch_name() -> Result<String, Error> {
print!("What is the name of this patch? ");
try!(std::io::stdout().flush());
let mut input = String::new();
try!(stdin().read_line(&mut input));
if let Some(c) = input.pop() {
if c != '\n' {
input.push(c)
}
}
Ok(input)
}
pub fn ask_learn_ssh(host: &str, port: u16, fingerprint: &str) -> Result<bool, Error> {
print!("The authenticity of host {:?}:{} cannot be established.\nThe fingerprint is \
{:?}.\nAre you sure you want to continue (yes/no)? ",
host,
port,
fingerprint);
try!(std::io::stdout().flush());
let mut input = String::new();
try!(stdin().read_line(&mut input));
let mut input = input.to_uppercase();
input.pop();
if let Some(c) = input.pop() {
if c != '\n' {
input.push(c)
}
}
println!("input={:?}", input);
Ok(input == "YES")
}