use chrono;
use chrono::{DateTime, Utc};
use flate2;
use std::collections::HashSet;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, Read, Write};
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use std::str::from_utf8;
pub type Flag = u8;
use bincode::{deserialize, deserialize_from, serialize};
use sequoia_openpgp::constants::DataFormat;
use sequoia_openpgp::crypto;
use sequoia_openpgp::serialize::stream::{LiteralWriter, Message, Signer};
use {Error, Result};
use super::fs_representation::RepoPath;
mod pretty;
bitflags! {
#[derive(Serialize, Deserialize)]
pub struct PatchFlags: u32 {
const TAG = 1;
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UnsignedPatch {
pub header: PatchHeader,
pub dependencies: HashSet<Hash>,
pub changes: Vec<Change<ChangeContext<Hash>>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Patch {
Unsigned0,
Signed0,
Unsigned(UnsignedPatch),
}
impl Patch {
pub fn changes(&self) -> &[Change<ChangeContext<Hash>>] {
match *self {
Patch::Signed0 | Patch::Unsigned0 => {
panic!("refusing to interact with old patch version")
}
Patch::Unsigned(ref patch) => &patch.changes,
}
}
pub fn changes_mut(&mut self) -> &mut Vec<Change<ChangeContext<Hash>>> {
match *self {
Patch::Signed0 | Patch::Unsigned0 => {
panic!("refusing to interact with old patch version")
}
Patch::Unsigned(ref mut patch) => &mut patch.changes,
}
}
pub fn dependencies(&self) -> &HashSet<Hash> {
match *self {
Patch::Signed0 | Patch::Unsigned0 => {
panic!("refusing to interact with old patch version")
}
Patch::Unsigned(ref patch) => &patch.dependencies,
}
}
pub fn dependencies_mut(&mut self) -> &mut HashSet<Hash> {
match *self {
Patch::Signed0 | Patch::Unsigned0 => {
panic!("refusing to interact with old patch version")
}
Patch::Unsigned(ref mut patch) => &mut patch.dependencies,
}
}
pub fn header(&self) -> &PatchHeader {
match *self {
Patch::Signed0 | Patch::Unsigned0 => {
panic!("refusing to interact with old patch version")
}
Patch::Unsigned(ref patch) => &patch.header,
}
}
pub fn header_mut(&mut self) -> &mut PatchHeader {
match *self {
Patch::Signed0 | Patch::Unsigned0 => {
panic!("refusing to interact with old patch version")
}
Patch::Unsigned(ref mut patch) => &mut patch.header,
}
}
pub fn read_dependencies<R: Read>(mut r: R) -> Result<Vec<Hash>> {
let version: u32 = deserialize_from(&mut r)?;
assert_eq!(version, 2);
let _header: PatchHeader = deserialize_from(&mut r)?;
debug!("version: {:?}", version);
Ok(deserialize_from(&mut r)?)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PatchHeader {
pub authors: Vec<String>,
pub name: String,
pub description: Option<String>,
pub timestamp: DateTime<Utc>,
pub flag: PatchFlags,
}
use std::ops::{Deref, DerefMut};
impl Deref for Patch {
type Target = PatchHeader;
fn deref(&self) -> &Self::Target {
self.header()
}
}
impl DerefMut for Patch {
fn deref_mut(&mut self) -> &mut Self::Target {
self.header_mut()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewEdge {
pub from: Key<Option<Hash>>,
pub to: Key<Option<Hash>>,
pub introduced_by: Option<Hash>,
}
pub type ChangeContext<H> = Vec<Key<Option<H>>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Change<Context> {
NewNodes {
up_context: Context,
down_context: Context,
flag: EdgeFlags,
line_num: LineId,
nodes: Vec<Vec<u8>>,
inode: Key<Option<Hash>>,
},
NewEdges {
previous: EdgeFlags,
flag: EdgeFlags,
edges: Vec<NewEdge>,
inode: Key<Option<Hash>>,
},
}
impl PatchHeader {
pub fn from_reader_nochanges<R: Read>(mut r: R) -> Result<PatchHeader> {
let version: u32 = deserialize_from(&mut r)?;
debug!("version: {:?}", version);
Ok(deserialize_from(&mut r)?)
}
}
#[derive(Debug)]
pub enum Record<Context> {
FileMove {
new_name: RepoPath<PathBuf>,
del: Change<Context>,
add: Change<Context>,
},
FileDel {
name: RepoPath<PathBuf>,
del: Change<Context>,
contents: Option<Change<Context>>,
},
FileAdd {
name: RepoPath<PathBuf>,
add: Change<Context>,
contents: Option<Change<Context>>,
},
Change {
file: Rc<RepoPath<PathBuf>>,
change: Change<Context>,
replacement: Option<Change<Context>>,
old_line: usize,
new_line: usize,
},
}
pub struct RecordIter<R, C> {
rec: Option<R>,
extra: Option<C>,
}
impl<Context> IntoIterator for Record<Context> {
type IntoIter = RecordIter<Record<Context>, Change<Context>>;
type Item = Change<Context>;
fn into_iter(self) -> Self::IntoIter {
RecordIter {
rec: Some(self),
extra: None,
}
}
}
impl<Context> Record<Context> {
pub fn iter(&self) -> RecordIter<&Record<Context>, &Change<Context>> {
RecordIter {
rec: Some(self),
extra: None,
}
}
}
impl<Context> Iterator for RecordIter<Record<Context>, Change<Context>> {
type Item = Change<Context>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(extra) = self.extra.take() {
return Some(extra);
} else if let Some(rec) = self.rec.take() {
match rec {
Record::FileMove { del, add, .. } => {
self.extra = Some(add);
return Some(del);
}
Record::FileDel { del, contents, .. } => {
self.extra = contents;
return Some(del);
}
Record::FileAdd { add, contents, .. } => {
self.extra = contents;
return Some(add);
}
Record::Change {
change,
replacement,
..
} => {
if let Some(r) = replacement {
self.extra = Some(r)
}
return Some(change);
}
}
} else {
return None;
}
}
}
impl<'a, Context> Iterator for RecordIter<&'a Record<Context>, &'a Change<Context>> {
type Item = &'a Change<Context>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(extra) = self.extra.take() {
return Some(extra);
} else if let Some(rec) = self.rec.take() {
match *rec {
Record::FileMove {
ref del, ref add, ..
} => {
self.extra = Some(add);
return Some(del);
}
Record::FileDel {
ref del,
ref contents,
..
} => {
self.extra = contents.as_ref();
return Some(del);
}
Record::FileAdd {
ref add,
ref contents,
..
} => {
self.extra = contents.as_ref();
return Some(add);
}
Record::Change {
replacement: ref r,
change: ref c,
..
} => {
if let Some(ref r) = r {
self.extra = Some(r)
}
return Some(c);
}
}
} else {
return None;
}
}
}
impl UnsignedPatch {
pub fn empty() -> Self {
UnsignedPatch {
header: PatchHeader {
authors: vec![],
name: "".to_string(),
description: None,
timestamp: chrono::Utc::now(),
flag: PatchFlags::empty(),
},
dependencies: HashSet::new(),
changes: vec![],
}
}
pub fn leave_unsigned(self) -> Patch {
Patch::Unsigned(self)
}
fn is_tag(&self) -> bool {
self.header.flag.contains(PatchFlags::TAG)
}
pub fn inverse(&self, hash: &Hash, changes: &mut Vec<Change<ChangeContext<Hash>>>) {
for ch in self.changes.iter() {
debug!("inverse {:?}", ch);
match *ch {
Change::NewNodes {
ref up_context,
flag,
line_num,
ref nodes,
ref inode,
..
} => {
let edges = up_context
.iter()
.map(|up| NewEdge {
from: Key {
patch: match up.patch {
Some(ref h) => Some(h.clone()),
None => Some(hash.clone()),
},
line: up.line,
},
to: Key {
patch: Some(hash.clone()),
line: line_num,
},
introduced_by: Some(hash.clone()),
})
.chain((1..nodes.len()).map(|i| NewEdge {
from: Key {
patch: Some(hash.clone()),
line: line_num + (i - 1),
},
to: Key {
patch: Some(hash.clone()),
line: line_num + i,
},
introduced_by: Some(hash.clone()),
}))
.collect();
changes.push(Change::NewEdges {
edges,
inode: inode.clone(),
previous: flag,
flag: flag ^ EdgeFlags::DELETED_EDGE,
})
}
Change::NewEdges {
previous,
flag,
ref edges,
ref inode,
} => changes.push(Change::NewEdges {
previous: flag,
flag: previous,
inode: inode.clone(),
edges: edges
.iter()
.map(|e| NewEdge {
from: e.from.clone(),
to: e.to.clone(),
introduced_by: Some(hash.clone()),
})
.collect(),
}),
}
}
}
}
impl Patch {
pub fn size_upper_bound(&self) -> usize {
let mut size: usize = 1 << 15;
for c in self.changes().iter() {
match *c {
Change::NewNodes { ref nodes, .. } => {
size += nodes.iter().map(|x| x.len()).sum::<usize>();
size += nodes.len() * 2048 }
Change::NewEdges { ref edges, .. } => size += edges.len() * 2048,
}
}
size
}
pub fn is_tag(&self) -> bool {
match *self {
Patch::Unsigned(ref patch) => patch.is_tag(),
_ => false,
}
}
pub fn from_reader_compressed<R: BufRead>(r: &mut R) -> Result<(Hash, Vec<u8>, Patch)> {
let mut rr = flate2::bufread::GzDecoder::new(r);
let filename = {
let filename = if let Some(header) = rr.header() {
if let Some(filename) = header.filename() {
from_utf8(filename)?
} else {
return Err(Error::EOF);
}
} else {
return Err(Error::EOF);
};
if let Some(h) = Hash::from_base58(filename) {
h
} else {
return Err(Error::WrongHash);
}
};
let mut buf = Vec::new();
rr.read_to_end(&mut buf)?;
let patch: Patch = deserialize(&buf[..])?;
patch.check_hash(&buf, &filename)?;
Ok((filename, buf, patch))
}
fn check_hash(&self, buf: &[u8], filename: &Hash) -> Result<()> {
let buf = match *self {
Patch::Signed0 | Patch::Unsigned0 => {
panic!("refusing to interact with old patch version")
}
Patch::Unsigned(_) => buf,
};
let hash = Hash::of_slice(buf)?;
match (filename, &hash) {
(&Hash::Sha512(ref filename), &Hash::Sha512(ref hash))
if &filename.0[..] == &hash.0[..] =>
{
Ok(())
}
_ => Err(Error::WrongHash),
}
}
pub fn to_buf(&self) -> Result<(Vec<u8>, Hash)> {
let buf = serialize(&self)?;
let hash = Hash::of_slice(&buf)?;
Ok((buf, hash))
}
pub fn save<P: AsRef<Path>>(&self, dir: P, key: Option<&mut crypto::KeyPair>) -> Result<Hash> {
let (buf, hash) = self.to_buf()?;
let h = hash.to_base58();
let mut path = dir.as_ref().join(&h);
path.set_extension("gz");
debug!("save, path {:?}", path);
let f = File::create(&path)?;
debug!("created");
let mut w = flate2::GzBuilder::new()
.filename(h.as_bytes())
.write(f, flate2::Compression::best());
w.write_all(&buf)?;
w.finish()?;
debug!("saved");
if let Some(key) = key {
path.set_extension("sig");
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)?;
let message = Message::new(file);
let mut signer = Signer::detached(message, vec![key as &mut crypto::Signer])?;
let mut w = LiteralWriter::new(signer, DataFormat::Binary, Some(h.as_bytes()), None)?;
w.write_all(&buf)?;
w.finalize()?
}
Ok(hash)
}
pub fn inverse(&self, hash: &Hash, changes: &mut Vec<Change<ChangeContext<Hash>>>) {
match *self {
Patch::Unsigned0 => panic!("Can't reverse old patches"),
Patch::Signed0 => panic!("Can't reverse old patches"),
Patch::Unsigned(ref u) => u.inverse(hash, changes),
}
}
}
pub fn read_changes(r: &mut Read) -> Result<Vec<(Hash, ApplyTimestamp)>> {
let mut s = String::new();
r.read_to_string(&mut s)?;
let mut result = Vec::new();
for l in s.lines() {
let mut sp = l.split(':');
match (
sp.next().and_then(Hash::from_base58),
sp.next().and_then(|s| s.parse().ok()),
) {
(Some(h), Some(s)) => {
result.push((h, s));
}
_ => {}
}
}
Ok(result)
}
pub fn read_changes_from_file<P: AsRef<Path>>(
changes_file: P,
) -> Result<Vec<(Hash, ApplyTimestamp)>> {
let mut file = File::open(changes_file)?;
read_changes(&mut file)
}
impl<U: Transaction, R> GenericTxn<U, R> {
pub fn new_patch<I: Iterator<Item = Hash>>(
&self,
branch: &Branch,
authors: Vec<String>,
name: String,
description: Option<String>,
timestamp: DateTime<Utc>,
changes: Vec<Change<ChangeContext<Hash>>>,
extra_dependencies: I,
flag: PatchFlags,
) -> Patch {
let mut dependencies = self.dependencies(branch, changes.iter());
dependencies.extend(extra_dependencies);
Patch::Unsigned(UnsignedPatch {
header: PatchHeader {
authors,
name,
description,
timestamp,
flag,
},
dependencies,
changes,
})
}
pub fn dependencies<'a, I: Iterator<Item = &'a Change<ChangeContext<Hash>>>>(
&self,
branch: &Branch,
changes: I,
) -> HashSet<Hash> {
let mut deps = HashSet::new();
let mut zombie_deps = HashSet::new();
for ch in changes {
match *ch {
Change::NewNodes {
ref up_context,
ref down_context,
..
} => {
for c in up_context.iter().chain(down_context.iter()) {
match c.patch {
None | Some(Hash::None) => {}
Some(ref dep) => {
debug!("dependencies (line {}) += {:?}", line!(), dep);
deps.insert(dep.clone());
}
}
}
}
Change::NewEdges {
flag, ref edges, ..
} => {
for e in edges {
let (from, to) = if flag.contains(EdgeFlags::PARENT_EDGE) {
(&e.to, &e.from)
} else {
(&e.from, &e.to)
};
match from.patch {
None | Some(Hash::None) => {}
Some(ref h) => {
debug!("dependencies (line {}) += {:?}", line!(), h);
deps.insert(h.clone());
if flag.contains(EdgeFlags::DELETED_EDGE) {
let k = Key {
patch: self.get_internal(h.as_ref()).unwrap().to_owned(),
line: from.line.clone(),
};
self.edge_context_deps(branch, k, &mut zombie_deps)
}
}
}
match to.patch {
None | Some(Hash::None) => {}
Some(ref h) => {
debug!("dependencies (line {}) += {:?}", line!(), h);
deps.insert(h.clone());
if flag.contains(EdgeFlags::DELETED_EDGE) {
let k = Key {
patch: self.get_internal(h.as_ref()).unwrap().to_owned(),
line: to.line.clone(),
};
self.edge_context_deps(branch, k, &mut zombie_deps)
}
}
}
match e.introduced_by {
None | Some(Hash::None) => {}
Some(ref h) => {
debug!("dependencies (line {}) += {:?}", line!(), h);
zombie_deps.insert(h.clone());
}
}
}
}
}
}
let mut h = self.minimize_deps(&deps);
for z in zombie_deps.drain() {
h.insert(z);
}
h
}
pub fn minimize_deps(&self, deps: &HashSet<Hash>) -> HashSet<Hash> {
debug!("minimize_deps {:?}", deps);
let mut covered = HashSet::new();
let mut stack = Vec::new();
let mut seen = HashSet::new();
for dep_ext in deps.iter() {
let dep = self.get_internal(dep_ext.as_ref()).unwrap();
debug!("dep = {:?}", dep);
stack.clear();
stack.push((dep, false));
while let Some((current, on_path)) = stack.pop() {
let already_covered = covered.get(¤t).is_some()
|| (current != dep && {
let current_ext = self.get_external(current).unwrap();
deps.get(¤t_ext.to_owned()).is_some()
});
if already_covered {
for &(h, h_on_path) in stack.iter() {
if h_on_path {
debug!("covered: h {:?}", h);
covered.insert(h);
}
}
break;
}
if seen.insert(current) && !on_path {
stack.push((current, true));
for (_, parent) in self
.iter_revdep(Some((current, None)))
.take_while(|k| k.0 == current)
{
stack.push((parent, false))
}
}
}
}
deps.iter()
.filter_map(|dep_ext| {
let dep = self.get_internal(dep_ext.as_ref()).unwrap();
if covered.get(&dep).is_none() {
Some(dep_ext.to_owned())
} else {
None
}
})
.collect()
}
fn edge_context_deps(&self, branch: &Branch, k: Key<PatchId>, deps: &mut HashSet<Hash>) {
for edge in self
.iter_adjacent(branch, k, EdgeFlags::empty(), EdgeFlags::all())
.filter(|e_| !e_.flag.contains(EdgeFlags::PARENT_EDGE))
{
if let Some(ext) = self.get_external(edge.introduced_by) {
deps.insert(ext.to_owned());
}
}
}
}
use backend::*;
use sanakirja;
impl<A: sanakirja::Transaction, R> GenericTxn<A, R> {
pub fn external_key(&self, key: &Key<PatchId>) -> Option<Key<Option<Hash>>> {
Some(Key {
patch: Some(self.external_hash(key.patch).to_owned()),
line: key.line,
})
}
pub fn external_hash(&self, key: PatchId) -> HashRef {
if key == ROOT_PATCH_ID {
ROOT_HASH.as_ref()
} else {
self.get_external(key).unwrap()
}
}
pub(crate) fn external_hash_opt(&self, h: Option<PatchId>) -> Option<Hash> {
h.map(|x| self.external_hash(x).to_owned())
}
pub(crate) fn external_key_opt(&self, h: Key<Option<PatchId>>) -> Key<Option<Hash>> {
Key {
line: h.line,
patch: self.external_hash_opt(h.patch),
}
}
pub fn new_internal(&self, ext: HashRef) -> PatchId {
let mut result = match ext {
HashRef::None => return ROOT_PATCH_ID,
HashRef::Sha512(h) => PatchId::from_slice(h),
HashRef::Recursive(p) => PatchId::from_slice(p),
};
while self.get_external(result).is_some() {
result.0 += 1
}
result
}
}