use std::io::{self, BufReader, BufRead};
use std::process::{Child, Command, Stdio};
use std::sync::mpsc::SyncSender;
use time::Timespec;
use types::*;
#[derive(Debug, Clone)]
pub struct ParsedCommit {
pub id: SHA1,
pub when: Timespec,
pub deltas: Vec<FileDelta>,
}
impl Default for ParsedCommit {
fn default() -> ParsedCommit
{
ParsedCommit {
id: SHA1::default(),
when: Timespec::new(0, 0),
deltas: Vec::new()
}
}
}
fn start_history_process() -> Result<Child, io::Error> {
Command::new("git")
.arg("log")
.arg("--name-status")
.arg("-M")
.arg("-C")
.arg("--pretty=format:%H%n%at") .stdout(Stdio::piped())
.spawn()
}
pub fn get_history(sink: &SyncSender<ParsedCommit>) {
enum ParseState { Hash,
Timestamp,
Changes
}
let child = start_history_process().expect("Couldn't open repo history");
let br = BufReader::new(child.stdout.unwrap());
let mut state = ParseState::Hash;
let mut current_commit = ParsedCommit::default();
for line in br.lines().map(|l| l.unwrap()) {
if line.is_empty() { continue; }
let next_state;
match state {
ParseState::Hash => {
current_commit.id = SHA1::parse(&line).unwrap();
next_state = ParseState::Timestamp;
}
ParseState::Timestamp => {
current_commit.when = Timespec{ sec: line.parse().unwrap(),
nsec: 0 };
next_state = ParseState::Changes;
}
ParseState::Changes => {
if let Ok(id) = SHA1::parse(&line) {
commit_sink(current_commit, sink);
current_commit = ParsedCommit::default();
current_commit.id = id;
next_state = ParseState::Timestamp;
}
else {
next_state = state;
current_commit.deltas.push(parse_delta(&line));
}
}
}
state = next_state;
}
commit_sink(current_commit, sink);
}
#[inline]
fn commit_sink(c: ParsedCommit, sink: &SyncSender<ParsedCommit>) {
sink.send(c).expect("The other end stopped listening for commits.");
}
fn parse_delta(s: &str) -> FileDelta {
let tokens : Vec<&str> = s.split('\t').collect();
assert!(tokens.len() > 1, "Expected at least one token");
let c = parse_change_code(tokens[0]);
let previous : String;
let current : String;
match c {
Change::Renamed { .. } |
Change::Copied { .. }=> {
assert_eq!(tokens.len(), 3, "Expected three tokens from string {:?}", s);
current = tokens[2].to_string();
previous = tokens[1].to_string();
}
_ => {
assert_eq!(tokens.len(), 2, "Expected two tokens from string {:?}", s);
current = tokens[1].to_string();
previous = String::new();
}
};
FileDelta{ change: c, path: current, from: previous }
}
fn parse_change_code(c: &str) -> Change {
assert!(!c.is_empty());
let ret = match c.chars().nth(0).unwrap() {
'A' => Change::Added,
'D' => Change::Deleted,
'M' |
'T' => Change::Modified, 'R' => Change::Renamed{ percent_changed: c[1..].parse().unwrap() },
'C' => Change::Copied{ percent_changed: c[1..].parse().unwrap() },
_ => panic!("Unknown delta code: {}", c)
};
match ret {
Change::Renamed{ percent_changed: r} => { assert!(r <= 100); },
Change::Copied{ percent_changed: c} => { assert!(c <= 100); },
_ => { }
};
ret
}