use std::collections::HashMap;
use std::error::Error;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use libpijul::Base32;
use regex::Regex;
use simple_error::SimpleError;
use crate::repo::Change;
use crate::repo::FileOp;
use crate::repo::FileSet;
fn escape_string(s: &str) -> String {
let mut result = String::with_capacity(s.len());
for c in s.chars() {
match c {
'\n' => result.push_str("\\n"),
'\\' => result.push_str("\\\\"),
'\"' => result.push_str("\\\""),
_ => result.push(c),
}
}
return result;
}
pub struct FastExportStream {
pub branch_name: String,
max_commit_mark: i32,
commit_marks: HashMap<libpijul::Merkle, i32>,
mark_file: Vec<u8>,
max_blob_mark: i32,
blob_marks: HashMap<String, i32>,
}
impl FastExportStream {
pub fn new(branch_name: String) -> FastExportStream {
return FastExportStream {
branch_name,
max_commit_mark: 0,
commit_marks: HashMap::new(),
mark_file: Vec::new(),
max_blob_mark: 1000000000,
blob_marks: HashMap::new(),
};
}
pub fn write_commit(&mut self, c: &Change, parent: Option<libpijul::Merkle>, files: &FileSet) {
let mut file_marks = HashMap::new();
for op in &*files.operations.lock().unwrap() {
match &op {
FileOp::Modify { fw } => {
let ct = fw.content.lock().unwrap();
let content: &[u8] = &*ct;
let h = sha256::digest(content);
let mut new_blob = false;
let blob_mark = *self.blob_marks.entry(h).or_insert_with(|| {
new_blob = true;
self.max_blob_mark += 1;
self.max_blob_mark
});
file_marks.insert(fw.name.clone(), blob_mark);
if new_blob {
println!("blob");
println!("mark :{}", blob_mark);
println!("data {}", content.len());
std::io::stdout().write_all(content).unwrap();
println!("");
}
}
_ => (),
}
}
println!("commit refs/heads/{}", self.branch_name);
let mark = self.commit_marks.entry(c.state).or_insert_with(|| {
self.max_commit_mark += 1;
writeln!(
&mut self.mark_file,
":{} {}",
self.max_commit_mark,
c.state.to_base32()
)
.unwrap();
self.max_commit_mark
});
println!("mark :{}", mark);
let committer = if c.authors.len() > 0 {
&c.authors[0]
} else {
"<>"
};
println!("committer {} {} +0000", committer, c.timestamp.timestamp());
let message = match &c.description {
Some(description) => format!("{}\n\n{}", c.message, description),
None => c.message.to_string(),
};
println!("data {}", message.len());
println!("{}", message);
match parent {
Some(p) => match self.commit_marks.get(&p) {
Some(p_mark) => println!("from :{}", p_mark),
None => {}
},
None => {}
}
for op in &*files.operations.lock().unwrap() {
match &op {
FileOp::Modify { fw } => println!(
"M 644 :{} \"{}\"",
file_marks.get(&fw.name).unwrap(),
escape_string(&fw.name)
),
FileOp::Delete { path } => println!("D \"{}\"", escape_string(&path)),
FileOp::Rename { old, new } => {
println!("R \"{}\" \"{}\"", escape_string(&old), escape_string(&new))
}
};
}
println!("");
}
fn parse_line(re: &Regex, line: &str) -> Option<(i32, libpijul::Merkle)> {
let caps = re.captures(line)?;
let mark: i32 = caps[1].parse().ok()?;
let state = libpijul::Merkle::from_base32(&caps[2].as_bytes())?;
Some((mark, state))
}
pub fn load_marks(&mut self, filename: &str) -> Result<(), Box<dyn Error>> {
self.mark_file = std::fs::read(filename)?;
let re = Regex::new(r"^:(\d+) (\w+)$")?;
let mark_file_content: &[u8] = &self.mark_file;
for line in BufReader::new(mark_file_content).lines() {
let line = line?.to_string();
match Self::parse_line(&re, &line) {
Some((mark, state)) => {
self.commit_marks.insert(state, mark);
if mark > self.max_commit_mark {
self.max_commit_mark = mark;
}
}
None => {
return Err(
SimpleError::new(format!("syntax error in mark file: {}", line)).into(),
)
}
}
}
return Ok(());
}
pub fn save_marks(&self, filename: &str) -> Result<(), Box<dyn Error>> {
std::fs::write(&filename, &self.mark_file)?;
Ok(())
}
pub fn already_exported(&self, state: &libpijul::Merkle) -> bool {
self.commit_marks.contains_key(state)
}
}