use crate::HashMap;
use std::collections::hash_map::Entry;
use std::io::BufRead;
use regex::Captures;
use super::*;
use crate::changestore::*;
#[derive(Debug, Error)]
pub enum TextDeError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
TomlDe(#[from] toml::de::Error),
#[error("Missing dependency [{0}]")]
MissingChange(usize),
#[error("Byte position {0} from this change missing")]
MissingPosition(u64),
}
#[derive(Debug, Error)]
pub enum TextSerError<C: std::error::Error + 'static> {
#[error(transparent)]
C(C),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
TomlSer(#[from] toml::ser::Error),
#[error("Missing contents in change {:?}", h)]
MissingContents { h: Hash },
#[error(transparent)]
Change(#[from] ChangeError),
#[error("Invalid change")]
InvalidChange,
}
impl LocalChange<Hunk<Option<Hash>, Local>, Author> {
const DEPS_LINE_: &'static str = "# Dependencies\n";
const HUNKS_LINE_: &'static str = "# Hunks\n";
pub fn write_all_deps<F: FnMut(Hash) -> Result<(), ChangeError>>(
&self,
mut f: F,
) -> Result<(), ChangeError> {
for c in self.changes.iter() {
for c in c.iter() {
match *c {
Atom::NewVertex(ref n) => {
for change in n
.up_context
.iter()
.chain(n.down_context.iter())
.map(|c| c.change)
.chain(std::iter::once(n.inode.change))
{
if let Some(change) = change {
if let Hash::None = change {
continue;
}
f(change)?
}
}
}
Atom::EdgeMap(ref e) => {
for edge in e.edges.iter() {
for change in &[
edge.from.change,
edge.to.change,
edge.introduced_by,
e.inode.change,
] {
if let Some(change) = *change {
if let Hash::None = change {
continue;
}
f(change)?
}
}
}
}
}
}
}
Ok(())
}
pub fn write_old<W: WriteChangeLine, C: ChangeStore>(
&self,
changes: &C,
hash: Option<Hash>,
write_header: bool,
mut w: W,
) -> Result<(), TextSerError<C::Error>> {
if let Some(h) = hash {
let mut hasher = Hasher::default();
hasher.update(&self.contents);
let hash = hasher.finish();
if hash != self.contents_hash {
return Err((TextSerError::MissingContents { h }).into());
}
}
if write_header {
let s = toml::ser::to_string_pretty(&self.header)?;
writeln!(w, "{}", s)?;
}
let mut hashes = HashMap::default();
let mut i = 2;
let mut needs_newline = false;
if !self.dependencies.is_empty() {
w.write_all(Self::DEPS_LINE_.as_bytes())?;
needs_newline = true;
for dep in self.dependencies.iter() {
hashes.insert(*dep, i);
writeln!(w, "[{}] {}", i, dep.to_base32())?;
i += 1;
}
}
self.write_all_deps(|change| {
if let Entry::Vacant(e) = hashes.entry(change) {
e.insert(i);
if !needs_newline {
w.write_all(Self::DEPS_LINE_.as_bytes())?;
needs_newline = true;
}
writeln!(w, "[{}]+{}", i, change.to_base32())?;
i += 1;
}
Ok(())
})?;
if !self.extra_known.is_empty() {
needs_newline = true;
for dep in self.extra_known.iter() {
writeln!(w, "[*] {}", dep.to_base32())?;
i += 1;
}
}
if !self.changes.is_empty() {
if needs_newline {
w.write_all(b"\n")?
}
w.write_all(Self::HUNKS_LINE_.as_bytes())?;
for (n, rec) in self.changes.iter().enumerate() {
write!(w, "\n{}. ", n + 1)?;
rec.write_old(changes, &hashes, &self.contents, &mut w)?
}
}
Ok(())
}
}
impl Change {
pub fn read_and_deps_old<
R: BufRead,
T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>,
>(
r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
txn: &T,
channel: &ChannelRef<T>,
) -> Result<Self, TextDeError> {
let (mut change, extra_dependencies) = Self::read_(r, updatables)?;
let (mut deps, extra) =
dependencies(txn, &channel.read(), change.hashed.changes.iter()).unwrap();
deps.extend(extra_dependencies.into_iter());
change.hashed.dependencies = deps;
change.hashed.extra_known = extra;
Ok(change)
}
pub fn read_old<R: BufRead>(
r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
) -> Result<Self, TextDeError> {
Ok(Self::read_(r, updatables)?.0)
}
fn read_<R: BufRead>(
mut r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
) -> Result<(Self, HashSet<Hash>), TextDeError> {
use self::text_changes_old::*;
let mut section = Section::Header(String::new());
let mut change = Change {
offsets: Offsets::default(),
hashed: Hashed {
version: VERSION,
header: ChangeHeader {
authors: Vec::new(),
message: String::new(),
description: None,
timestamp: chrono::Utc::now(),
},
dependencies: Vec::new(),
extra_known: Vec::new(),
metadata: Vec::new(),
changes: Vec::new(),
contents_hash: Hasher::default().finish(),
},
unhashed: None,
contents: Vec::new(),
};
let conclude_section = |change: &mut Change,
section: Section,
contents: &mut Vec<u8>|
-> Result<(), TextDeError> {
match section {
Section::Header(ref s) => {
debug!("header = {:?}", s);
change.header = toml::de::from_str(&s)?;
Ok(())
}
Section::Deps => Ok(()),
Section::Changes {
mut changes,
current,
..
} => {
if has_newvertices(¤t) {
contents.push(0)
}
if let Some(c) = current {
debug!("next action = {:?}", c);
changes.push(c)
}
change.changes = changes;
Ok(())
}
}
};
let mut h = String::new();
let mut contents = Vec::new();
let mut deps = HashMap::default();
let mut extra_dependencies = HashSet::default();
while r.read_line(&mut h)? > 0 {
debug!("h = {:?}", h);
if h == Self::DEPS_LINE_ {
let section = std::mem::replace(&mut section, Section::Deps);
conclude_section(&mut change, section, &mut contents)?;
} else if h == Self::HUNKS_LINE_ {
let section = std::mem::replace(
&mut section,
Section::Changes {
changes: Vec::new(),
current: None,
offsets: HashMap::default(),
},
);
conclude_section(&mut change, section, &mut contents)?;
} else {
use regex::Regex;
lazy_static! {
static ref DEPS: Regex = Regex::new(r#"\[(\d*|\*)\](\+| ) *(\S*)"#).unwrap();
static ref KNOWN: Regex = Regex::new(r#"(\S*)"#).unwrap();
}
match section {
Section::Header(ref mut s) => s.push_str(&h),
Section::Deps => {
if let Some(d) = DEPS.captures(&h) {
let hash = Hash::from_base32(d[3].as_bytes()).unwrap();
if let Ok(n) = d[1].parse() {
if &d[2] == " " {
change.hashed.dependencies.push(hash);
}
deps.insert(n, hash);
} else if &d[1] == "*" {
change.hashed.extra_known.push(hash);
} else {
extra_dependencies.insert(hash);
}
}
}
Section::Changes {
ref mut current,
ref mut changes,
ref mut offsets,
} => {
if let Some(next) =
Hunk::read(updatables, current, &mut contents, &deps, offsets, &h)?
{
debug!("next action = {:?}", next);
changes.push(next)
}
}
}
}
h.clear();
}
conclude_section(&mut change, section, &mut contents)?;
change.contents = contents;
change.contents_hash = {
let mut hasher = Hasher::default();
hasher.update(&change.contents);
hasher.finish()
};
Ok((change, extra_dependencies))
}
}
const BINARY_LABEL: &str = "binary";
struct Escaped<'a>(&'a str);
impl<'a> std::fmt::Display for Escaped<'a> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "\"")?;
for c in self.0.chars() {
if c == '"' {
write!(fmt, "\\{}", c)?
} else if c == '\\' {
write!(fmt, "\\\\")?
} else {
write!(fmt, "{}", c)?
}
}
write!(fmt, "\"")?;
Ok(())
}
}
fn unescape(s: &str) -> std::borrow::Cow<str> {
let mut b = 0;
let mut result = String::new();
let mut ch = s.chars();
while let Some(c) = ch.next() {
if c == '\\' {
if result.is_empty() {
result = s.split_at(b).0.to_string();
}
if let Some(c) = ch.next() {
result.push(c)
}
} else if !result.is_empty() {
result.push(c)
}
b += c.len_utf8()
}
if result.is_empty() {
s.into()
} else {
result.into()
}
}
impl Hunk<Option<Hash>, Local> {
fn write_old<W: WriteChangeLine, C: ChangeStore>(
&self,
changes: &C,
hashes: &HashMap<Hash, usize>,
change_contents: &[u8],
mut w: &mut W,
) -> Result<(), TextSerError<C::Error>> {
use self::text_changes_old::*;
match self {
Hunk::FileMove { del, add, path } => match add {
Atom::NewVertex(ref add) => {
let FileMetadata {
basename: name,
metadata: perms,
..
} = FileMetadata::read(&change_contents[add.start.0.into()..add.end.0.into()]);
write!(
w,
"Moved: {} {} {}",
Escaped(&path),
Escaped(&name),
if perms.0 & 0o1000 == 0o1000 {
"+dx "
} else if perms.0 & 0o100 == 0o100 {
"+x "
} else {
""
}
)?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &del)?;
write!(w, "up")?;
for c in add.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
write!(w, ", down")?;
for c in add.down_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
w.write_all(b"\n")?;
}
Atom::EdgeMap(_) => {
write!(w, "Moved: {:?} ", path)?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &add)?;
write_atom(&mut w, hashes, &del)?;
}
},
Hunk::FileDel {
del,
contents,
path,
encoding,
} => {
debug!("file del");
write!(w, "File deletion: {} ", Escaped(path))?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w, " {:?}", encoding_label(encoding))?;
write_atom(&mut w, hashes, &del)?;
if let Some(ref contents) = contents {
write_atom(&mut w, hashes, &contents)?;
writeln!(w)?;
print_change_contents(w, changes, contents, change_contents, encoding)?;
} else {
writeln!(w)?;
}
}
Hunk::FileUndel {
undel,
contents,
path,
encoding,
} => {
debug!("file undel");
write!(w, "File un-deletion: {} ", Escaped(path))?;
write_pos(&mut w, hashes, undel.inode())?;
writeln!(w, " {:?}", encoding_label(encoding))?;
write_atom(&mut w, hashes, &undel)?;
if let Some(ref contents) = contents {
write_atom(&mut w, hashes, &contents)?;
print_change_contents(w, changes, contents, change_contents, encoding)?;
} else {
writeln!(w)?;
}
}
Hunk::FileAdd {
add_name,
contents,
path,
encoding,
..
} => {
if let Atom::NewVertex(ref n) = add_name {
debug!("add_name {:?}", n);
let FileMetadata {
basename: name,
metadata: perms,
..
} = FileMetadata::read(&change_contents[n.start.0.into()..n.end.0.into()]);
let parent = if let Some(p) = crate::path::parent(&path) {
if p.is_empty() {
"/"
} else {
p
}
} else {
"/"
};
write!(
w,
"File addition: {} in {}{} \"{}\"\n up",
Escaped(name),
Escaped(parent),
if perms.0 & 0o1000 == 0o1000 {
" +dx"
} else if perms.0 & 0o100 == 0o100 {
" +x"
} else {
""
},
encoding_label(encoding)
)?;
assert!(n.down_context.is_empty());
for c in n.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
writeln!(w, ", new {}:{}", n.start.0, n.end.0)?;
}
if let Some(Atom::NewVertex(ref n)) = contents {
let c = &change_contents[n.start.us()..n.end.us()];
print_contents(w, "+", c, encoding)?;
if !c.ends_with(b"\n") {
writeln!(w, "\n\\")?
}
}
}
Hunk::Edit {
change,
local,
encoding,
} => {
debug!("edit");
write!(w, "Edit in {}:{} ", Escaped(&local.path), local.line)?;
write_pos(&mut w, hashes, change.inode())?;
write!(w, " {:?}", encoding_label(encoding))?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(w, changes, change, change_contents, encoding)?;
}
Hunk::Replacement {
change,
replacement,
local,
encoding,
} => {
debug!("replacement");
write!(w, "Replacement in {}:{} ", Escaped(&local.path), local.line)?;
write_pos(&mut w, hashes, change.inode())?;
write!(w, " {:?}", encoding_label(encoding))?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
write_atom(&mut w, hashes, &replacement)?;
print_change_contents(w, changes, change, change_contents, encoding)?;
print_change_contents(w, changes, replacement, change_contents, encoding)?;
}
Hunk::SolveNameConflict { name, path } => {
write!(w, "Solving a name conflict in {} ", Escaped(path))?;
write_pos(&mut w, hashes, name.inode())?;
write!(w, ": ")?;
write_deleted_names(&mut w, changes, name)?;
writeln!(w)?;
write_atom(&mut w, hashes, &name)?;
}
Hunk::UnsolveNameConflict { name, path } => {
write!(w, "Un-solving a name conflict in {} ", Escaped(path))?;
write_pos(&mut w, hashes, name.inode())?;
write!(w, ": ")?;
write_deleted_names(&mut w, changes, name)?;
writeln!(w)?;
write_atom(&mut w, hashes, &name)?;
}
Hunk::SolveOrderConflict { change, local } => {
debug!("solve order conflict");
write!(
w,
"Solving an order conflict in {}:{} ",
Escaped(&local.path),
local.line,
)?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(w, changes, change, change_contents, &None)?;
}
Hunk::UnsolveOrderConflict { change, local } => {
debug!("unsolve order conflict");
write!(
w,
"Un-solving an order conflict in {}:{} ",
Escaped(&local.path),
local.line,
)?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(w, changes, change, change_contents, &None)?;
}
Hunk::ResurrectZombies {
change,
local,
encoding,
} => {
debug!("resurrect zombies");
write!(
w,
"Resurrecting zombie lines in {}:{} ",
Escaped(&local.path),
local.line
)?;
write_pos(&mut w, hashes, change.inode())?;
write!(w, " \"{}\"", encoding_label(encoding))?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(w, changes, change, change_contents, encoding)?;
}
_ => {}
}
Ok(())
}
}
fn encoding_label(encoding: &Option<Encoding>) -> &str {
match encoding {
Some(encoding) => encoding.label(),
_ => BINARY_LABEL,
}
}
impl Hunk<Option<Hash>, Local> {
fn read(
updatables: &mut HashMap<usize, crate::InodeUpdate>,
current: &mut Option<Self>,
contents_: &mut Vec<u8>,
changes: &HashMap<usize, Hash>,
offsets: &mut HashMap<u64, ChangePosition>,
h: &str,
) -> Result<Option<Self>, TextDeError> {
use self::text_changes_old::*;
use regex::Regex;
lazy_static! {
static ref FILE_ADDITION: Regex =
Regex::new(r#"^(?P<n>\d+)\. File addition: "(?P<name>[^"]*)" in "(?P<parent>[^"]*)"(?P<perm> \S+)? "(?P<encoding>[^"]*)""#).unwrap();
static ref EDIT: Regex =
Regex::new(r#"^([0-9]+)\. Edit in "([^:]+)":(\d+) (\d+\.\d+) "(?P<encoding>[^"]*)""#).unwrap();
static ref REPLACEMENT: Regex =
Regex::new(r#"^([0-9]+)\. Replacement in "([^:]+)":(\d+) (\d+\.\d+) "(?P<encoding>[^"]*)""#).unwrap();
static ref FILE_DELETION: Regex =
Regex::new(r#"^([0-9]+)\. File deletion: "([^"]*)" (\d+\.\d+) "(?P<encoding>[^"]*)""#).unwrap();
static ref FILE_UNDELETION: Regex =
Regex::new(r#"^([0-9]+)\. File un-deletion: "([^"]*)" (\d+\.\d+) "(?P<encoding>[^"]*)""#).unwrap();
static ref MOVE: Regex =
Regex::new(r#"^([0-9]+)\. Moved: "(?P<former>[^"]*)" "(?P<new>[^"]*)" (?P<perm>[^ ]+ )?(?P<inode>.*)"#).unwrap();
static ref MOVE_: Regex = Regex::new(r#"^([0-9]+)\. Moved: "([^"]*)" (.*)"#).unwrap();
static ref NAME_CONFLICT: Regex = Regex::new(
r#"^([0-9]+)\. ((Solving)|(Un-solving)) a name conflict in "([^"]*)" (.*): .*"#
)
.unwrap();
static ref ORDER_CONFLICT: Regex = Regex::new(
r#"^([0-9]+)\. ((Solving)|(Un-solving)) an order conflict in (.*):(\d+) (\d+\.\d+)"#
)
.unwrap();
static ref ZOMBIE: Regex =
Regex::new(r#"^([0-9]+)\. Resurrecting zombie lines in (?P<path>"[^"]+"):(?P<line>\d+) (?P<inode>\d+\.\d+) "(?P<encoding>[^"]*)""#)
.unwrap();
static ref CONTEXT: Regex = Regex::new(
r#"up ((\d+\.\d+ )*\d+\.\d+)(, new (\d+):(\d+))?(, down ((\d+\.\d+ )*\d+\.\d+))?"#
)
.unwrap();
}
if let Some(cap) = FILE_ADDITION.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add_name = default_newvertex();
add_name.start = ChangePosition(contents_.len().into());
add_name.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let name = unescape(&cap.name("name").unwrap().as_str());
let path = {
let parent = cap.name("parent").unwrap().as_str();
(if parent == "/" {
String::new()
} else {
unescape(&parent).to_string() + "/"
}) + &name
};
debug!("cap = {:?}", cap);
let meta = if let Some(perm) = cap.name("perm") {
if perm.as_str() == " +dx" {
0o1100
} else if perm.as_str() == " +x" {
0o100
} else {
0
}
} else {
0
};
let n = cap.name("n").unwrap().as_str().parse().unwrap();
let encoding = encoding_from_label(cap);
let meta = FileMetadata {
metadata: InodeMetadata(meta),
basename: &name,
encoding: encoding.clone(),
};
meta.write(contents_);
add_name.end = ChangePosition(contents_.len().into());
let mut add_inode = default_newvertex();
add_inode.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
add_inode.up_context.push(Position {
change: None,
pos: ChangePosition(contents_.len().into()),
});
contents_.push(0);
add_inode.start = ChangePosition(contents_.len().into());
add_inode.end = ChangePosition(contents_.len().into());
contents_.push(0);
if let Entry::Occupied(mut e) = updatables.entry(n) {
if let crate::InodeUpdate::Add { ref mut pos, .. } = e.get_mut() {
offsets.insert(pos.0.into(), add_inode.start);
*pos = add_inode.start
}
}
Ok(std::mem::replace(
current,
Some(Hunk::FileAdd {
add_name: Atom::NewVertex(add_name),
add_inode: Atom::NewVertex(add_inode),
contents: None,
path,
encoding,
}),
))
} else if let Some(cap) = EDIT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut v = default_newvertex();
v.inode = parse_pos(changes, &cap[4])?;
v.flag = EdgeFlags::BLOCK;
Ok(std::mem::replace(
current,
Some(Hunk::Edit {
change: Atom::NewVertex(v),
local: Local {
path: unescape(&cap[2]).to_string(),
line: cap[3].parse().unwrap(),
},
encoding: encoding_from_label(cap),
}),
))
} else if let Some(cap) = REPLACEMENT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut v = default_newvertex();
v.inode = parse_pos(changes, &cap[4])?;
v.flag = EdgeFlags::BLOCK;
Ok(std::mem::replace(
current,
Some(Hunk::Replacement {
change: Atom::NewVertex(v.clone()),
replacement: Atom::NewVertex(v),
local: Local {
path: unescape(&cap[2]).to_string(),
line: cap[3].parse().unwrap(),
},
encoding: encoding_from_label(cap),
}),
))
} else if let Some(cap) = FILE_DELETION.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut del = default_edgemap();
del.inode = parse_pos(changes, &cap[3])?;
Ok(std::mem::replace(
current,
Some(Hunk::FileDel {
del: Atom::EdgeMap(del),
contents: None,
path: cap[2].to_string(),
encoding: encoding_from_label(cap),
}),
))
} else if let Some(cap) = FILE_UNDELETION.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut undel = default_edgemap();
undel.inode = parse_pos(changes, &cap[3])?;
Ok(std::mem::replace(
current,
Some(Hunk::FileUndel {
undel: Atom::EdgeMap(undel),
contents: None,
path: cap[2].to_string(),
encoding: encoding_from_label(cap),
}),
))
} else if let Some(cap) = NAME_CONFLICT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut name = default_edgemap();
debug!("cap = {:?}", cap);
name.inode = parse_pos(changes, &cap[6])?;
Ok(std::mem::replace(
current,
if &cap[2] == "Solving" {
Some(Hunk::SolveNameConflict {
name: Atom::EdgeMap(name),
path: cap[5].to_string(),
})
} else {
Some(Hunk::UnsolveNameConflict {
name: Atom::EdgeMap(name),
path: cap[5].to_string(),
})
},
))
} else if let Some(cap) = MOVE.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add = default_newvertex();
add.start = ChangePosition(contents_.len().into());
add.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let name = unescape(cap.name("new").unwrap().as_str());
let meta = if let Some(perm) = cap.name("perm") {
debug!("perm = {:?}", perm.as_str());
if perm.as_str() == "+dx " {
0o1100
} else if perm.as_str() == "+x " {
0o100
} else {
0
}
} else {
0
};
let meta = FileMetadata {
metadata: InodeMetadata(meta),
basename: &name,
encoding: None,
};
meta.write(contents_);
add.end = ChangePosition(contents_.len().into());
let mut del = default_edgemap();
del.inode = parse_pos(changes, cap.name("inode").unwrap().as_str())?;
Ok(std::mem::replace(
current,
Some(Hunk::FileMove {
del: Atom::EdgeMap(del),
add: Atom::NewVertex(add),
path: cap[2].to_string(),
}),
))
} else if let Some(cap) = MOVE_.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add = default_edgemap();
let mut del = default_edgemap();
add.inode = parse_pos(changes, &cap[3])?;
del.inode = add.inode;
Ok(std::mem::replace(
current,
Some(Hunk::FileMove {
del: Atom::EdgeMap(del),
add: Atom::EdgeMap(add),
path: cap[2].to_string(),
}),
))
} else if let Some(cap) = ORDER_CONFLICT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
Ok(std::mem::replace(
current,
Some(if &cap[2] == "Solving" {
let mut v = default_newvertex();
v.inode = parse_pos(changes, &cap[7])?;
Hunk::SolveOrderConflict {
change: Atom::NewVertex(v),
local: Local {
path: cap[5].to_string(),
line: cap[6].parse().unwrap(),
},
}
} else {
let mut v = default_edgemap();
v.inode = parse_pos(changes, &cap[7])?;
Hunk::UnsolveOrderConflict {
change: Atom::EdgeMap(v),
local: Local {
path: cap[5].to_string(),
line: cap[6].parse().unwrap(),
},
}
}),
))
} else if let Some(cap) = ZOMBIE.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut v = default_edgemap();
v.inode = parse_pos(changes, &cap.name("inode").unwrap().as_str())?;
Ok(std::mem::replace(
current,
Some(Hunk::ResurrectZombies {
change: Atom::EdgeMap(v),
local: Local {
path: cap.name("path").unwrap().as_str().parse().unwrap(),
line: cap.name("line").unwrap().as_str().parse().unwrap(),
},
encoding: encoding_from_label(cap),
}),
))
} else {
match current {
Some(Hunk::FileAdd {
ref mut contents,
ref mut add_name,
encoding,
..
}) => {
if h.starts_with('+') {
if contents.is_none() {
let mut v = default_newvertex();
let inode = Position {
change: None,
pos: ChangePosition((contents_.len() - 1).into()),
};
v.up_context.push(inode);
v.inode = inode;
v.flag = EdgeFlags::BLOCK;
v.start = ChangePosition(contents_.len().into());
*contents = Some(Atom::NewVertex(v));
}
if let Some(Atom::NewVertex(ref mut contents)) = contents {
if h.starts_with('+') {
text_changes_old::parse_line_add(h, contents, contents_, encoding)
}
}
} else if h.starts_with('\\') {
if let Some(Atom::NewVertex(mut c)) = contents.take() {
if c.end > c.start {
if contents_[c.end.us() - 1] == b'\n' {
assert_eq!(c.end.us(), contents_.len());
contents_.pop();
c.end.0 -= 1;
}
*contents = Some(Atom::NewVertex(c))
}
}
} else if let Some(cap) = CONTEXT.captures(h) {
if let Atom::NewVertex(ref mut name) = add_name {
name.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
if let (Some(new_start), Some(new_end)) = (cap.get(4), cap.get(5)) {
offsets.insert(new_start.as_str().parse().unwrap(), name.start);
offsets.insert(new_end.as_str().parse().unwrap(), name.end);
offsets.insert(
new_end.as_str().parse::<u64>().unwrap() + 1,
name.end + 1,
);
}
}
}
Ok(None)
}
Some(Hunk::FileDel {
ref mut del,
ref mut contents,
..
}) => {
if let Some(edges) = parse_edges(changes, h)? {
if let Atom::EdgeMap(ref mut e) = del {
if edges[0].flag.contains(EdgeFlags::FOLDER) {
*e = EdgeMap {
inode: e.inode,
edges,
}
} else {
*contents = Some(Atom::EdgeMap(EdgeMap {
inode: e.inode,
edges,
}))
}
}
}
Ok(None)
}
Some(Hunk::FileUndel {
ref mut undel,
ref mut contents,
..
}) => {
if let Some(edges) = parse_edges(changes, h)? {
if let Atom::EdgeMap(ref mut e) = undel {
if edges[0].flag.contains(EdgeFlags::FOLDER) {
*e = EdgeMap {
inode: e.inode,
edges,
}
} else {
*contents = Some(Atom::EdgeMap(EdgeMap {
inode: e.inode,
edges,
}))
}
}
}
Ok(None)
}
Some(Hunk::FileMove {
ref mut del,
ref mut add,
..
}) => {
if let Some(edges) = parse_edges(changes, h)? {
if edges[0].flag.contains(EdgeFlags::DELETED) {
*del = Atom::EdgeMap(EdgeMap {
inode: del.inode(),
edges,
});
return Ok(None);
} else if let Atom::EdgeMap(ref mut add) = add {
if add.edges.is_empty() {
*add = EdgeMap {
inode: add.inode,
edges,
};
return Ok(None);
}
}
} else if let Some(cap) = CONTEXT.captures(h) {
if let Atom::NewVertex(ref mut c) = add {
debug!("cap = {:?}", cap);
c.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
if let Some(cap) = cap.get(7) {
c.down_context = parse_pos_vec(changes, offsets, cap.as_str())?;
}
}
}
Ok(None)
}
Some(Hunk::Edit {
ref mut change,
encoding,
..
}) => {
debug!("edit {:?}", h);
if h.starts_with("+ ") {
if let Atom::NewVertex(ref mut change) = change {
if change.start == change.end {
change.start = ChangePosition(contents_.len().into());
}
text_changes_old::parse_line_add(h, change, contents_, encoding)
}
} else if h.starts_with('\\') {
if let Atom::NewVertex(ref mut change) = change {
if change.end > change.start && contents_[change.end.us() - 1] == b'\n'
{
assert_eq!(change.end.us(), contents_.len());
contents_.pop();
change.end.0 -= 1;
}
}
} else if let Some(cap) = CONTEXT.captures(h) {
if let Atom::NewVertex(ref mut c) = change {
debug!("cap = {:?}", cap);
c.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
if let Some(cap) = cap.get(7) {
c.down_context = parse_pos_vec(changes, offsets, cap.as_str())?;
}
}
} else if let Some(edges) = parse_edges(changes, h)? {
*change = Atom::EdgeMap(EdgeMap {
inode: change.inode(),
edges,
});
}
Ok(None)
}
Some(Hunk::Replacement {
ref mut change,
ref mut replacement,
encoding,
..
}) => {
if h.starts_with("+ ") {
if let Atom::NewVertex(ref mut repl) = replacement {
if repl.start == repl.end {
repl.start = ChangePosition(contents_.len().into());
}
text_changes_old::parse_line_add(h, repl, contents_, encoding)
}
} else if h.starts_with('\\') {
if let Atom::NewVertex(ref mut repl) = replacement {
if repl.end > repl.start && contents_[repl.end.us() - 1] == b'\n' {
assert_eq!(repl.end.us(), contents_.len());
contents_.pop();
repl.end.0 -= 1;
}
}
} else if let Some(cap) = CONTEXT.captures(h) {
debug!("cap = {:?}", cap);
if let Atom::NewVertex(ref mut repl) = replacement {
repl.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
if let Some(cap) = cap.get(7) {
repl.down_context = parse_pos_vec(changes, offsets, cap.as_str())?;
}
}
} else if let Some(edges) = parse_edges(changes, h)? {
*change = Atom::EdgeMap(EdgeMap {
inode: change.inode(),
edges,
});
}
Ok(None)
}
Some(Hunk::SolveNameConflict { ref mut name, .. })
| Some(Hunk::UnsolveNameConflict { ref mut name, .. }) => {
if let Some(edges) = parse_edges(changes, h)? {
*name = Atom::EdgeMap(EdgeMap {
edges,
inode: name.inode(),
})
}
Ok(None)
}
Some(Hunk::SolveOrderConflict { ref mut change, .. }) => {
if h.starts_with("+ ") {
if let Atom::NewVertex(ref mut change) = change {
if change.start == change.end {
change.start = ChangePosition(contents_.len().into());
}
text_changes_old::parse_line_add(h, change, contents_, &None)
}
} else if let Some(cap) = CONTEXT.captures(h) {
debug!("cap = {:?}", cap);
if let Atom::NewVertex(ref mut change) = change {
change.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
if let Some(cap) = cap.get(7) {
change.down_context =
parse_pos_vec(changes, offsets, cap.as_str())?;
}
if let (Some(new_start), Some(new_end)) = (cap.get(4), cap.get(5)) {
let new_start = new_start.as_str().parse::<u64>().unwrap();
let new_end = new_end.as_str().parse::<u64>().unwrap();
change.start = ChangePosition(contents_.len().into());
change.end = ChangePosition(
(contents_.len() as u64 + new_end - new_start).into(),
);
offsets.insert(new_end, change.end);
}
}
}
Ok(None)
}
Some(Hunk::UnsolveOrderConflict { ref mut change, .. }) => {
if let Some(edges) = parse_edges(changes, h)? {
if let Atom::EdgeMap(ref mut change) = change {
change.edges = edges
}
}
Ok(None)
}
Some(Hunk::ResurrectZombies { ref mut change, .. }) => {
if let Some(edges) = parse_edges(changes, h)? {
if let Atom::EdgeMap(ref mut change) = change {
change.edges = edges
}
}
Ok(None)
}
None => {
debug!("current = {:#?}", current);
debug!("h = {:?}", h);
Ok(None)
}
_ => {
unimplemented!()
},
}
}
}
}
fn encoding_from_label(cap: Captures) -> Option<Encoding> {
let encoding_label = cap.name("encoding").unwrap().as_str();
if encoding_label != BINARY_LABEL {
Some(Encoding::for_label(encoding_label))
} else {
None
}
}
lazy_static! {
static ref POS: regex::Regex = regex::Regex::new(r#"(\d+)\.(\d+)"#).unwrap();
static ref EDGE: regex::Regex =
regex::Regex::new(r#"\s*(?P<prev>[BFD]*):(?P<flag>[BFD]*)\s+(?P<up_c>\d+)\.(?P<up_l>\d+)\s*->\s*(?P<c>\d+)\.(?P<l0>\d+):(?P<l1>\d+)/(?P<intro>\d+)\s*"#).unwrap();
}
pub fn default_newvertex() -> NewVertex<Option<Hash>> {
NewVertex {
start: ChangePosition(L64(0)),
end: ChangePosition(L64(0)),
flag: EdgeFlags::empty(),
up_context: Vec::new(),
down_context: Vec::new(),
inode: Position {
change: Some(Hash::None),
pos: ChangePosition(L64(0)),
},
}
}
pub fn default_edgemap() -> EdgeMap<Option<Hash>> {
EdgeMap {
edges: Vec::new(),
inode: Position {
change: Some(Hash::None),
pos: ChangePosition(L64(0)),
},
}
}
pub fn has_newvertices<L>(current: &Option<Hunk<Option<Hash>, L>>) -> bool {
match current {
Some(Hunk::FileAdd { contents: None, .. }) | None => false,
Some(rec) => rec.iter().any(|e| matches!(e, Atom::NewVertex(_))),
}
}
pub fn parse_pos_vec(
changes: &HashMap<usize, Hash>,
offsets: &HashMap<u64, ChangePosition>,
s: &str,
) -> Result<Vec<Position<Option<Hash>>>, TextDeError> {
let mut v = Vec::new();
for pos in POS.captures_iter(s) {
let change: usize = (&pos[1]).parse().unwrap();
let pos: u64 = (&pos[2]).parse().unwrap();
let pos = if change == 0 {
if let Some(&pos) = offsets.get(&pos) {
pos
} else {
debug!("inconsistent change: {:?} {:?}", s, offsets);
return Err(TextDeError::MissingPosition(pos));
}
} else {
ChangePosition(L64(pos.to_le()))
};
v.push(Position {
change: change_ref(changes, change)?,
pos,
})
}
Ok(v)
}
fn change_ref(changes: &HashMap<usize, Hash>, change: usize) -> Result<Option<Hash>, TextDeError> {
debug!("change_ref {:?} {:?}", changes, change);
if change == 0 {
Ok(None)
} else if change == 1 {
Ok(Some(Hash::None))
} else if let Some(&c) = changes.get(&change) {
Ok(Some(c))
} else {
Err(TextDeError::MissingChange(change))
}
}
pub fn parse_pos(
changes: &HashMap<usize, Hash>,
s: &str,
) -> Result<Position<Option<Hash>>, TextDeError> {
let pos = POS.captures(s).unwrap();
let change: usize = (&pos[1]).parse().unwrap();
let pos: u64 = (&pos[2]).parse().unwrap();
Ok(Position {
change: change_ref(changes, change)?,
pos: ChangePosition(L64(pos.to_le())),
})
}
pub fn parse_edges(
changes: &HashMap<usize, Hash>,
s: &str,
) -> Result<Option<Vec<NewEdge<Option<Hash>>>>, TextDeError> {
debug!("parse_edges {:?}", s);
let mut result = Vec::new();
for edge in s.split(',') {
debug!("parse edge {:?}", edge);
if let Some(cap) = EDGE.captures(edge) {
let previous = read_flag(cap.name("prev").unwrap().as_str());
let flag = read_flag(cap.name("flag").unwrap().as_str());
let change0: usize = cap.name("up_c").unwrap().as_str().parse().unwrap();
let pos0: u64 = cap.name("up_l").unwrap().as_str().parse().unwrap();
let change1: usize = cap.name("c").unwrap().as_str().parse().unwrap();
let start1: u64 = cap.name("l0").unwrap().as_str().parse().unwrap();
let end1: u64 = cap.name("l1").unwrap().as_str().parse().unwrap();
let introduced_by: usize = cap.name("intro").unwrap().as_str().parse().unwrap();
result.push(NewEdge {
previous,
flag,
from: Position {
change: change_ref(changes, change0)?,
pos: ChangePosition(L64(pos0.to_le())),
},
to: Vertex {
change: change_ref(changes, change1)?,
start: ChangePosition(L64(start1.to_le())),
end: ChangePosition(L64(end1.to_le())),
},
introduced_by: change_ref(changes, introduced_by)?,
})
} else {
debug!("not parsed");
return Ok(None);
}
}
Ok(Some(result))
}
pub fn parse_line_add(
h: &str,
change: &mut NewVertex<Option<Hash>>,
contents_: &mut Vec<u8>,
encoding: &Option<Encoding>,
) {
let h = match encoding {
Some(encoding) => encoding.encode(h),
None => std::borrow::Cow::Borrowed(h.as_bytes()),
};
debug!("parse_line_add {:?} {:?}", change.end, change.start);
debug!("parse_line_add {:?}", h);
if h.len() > 2 {
let h = &h[2..h.len()];
contents_.extend(h);
} else if h.len() > 1 {
contents_.push(b'\n');
}
debug!("contents_.len() = {:?}", contents_.len());
trace!("contents_ = {:?}", contents_);
change.end = ChangePosition(contents_.len().into());
}
pub trait WriteChangeLine: std::io::Write {
fn write_change_line(&mut self, pref: &str, contents: &str) -> Result<(), std::io::Error> {
writeln!(self, "{} {}", pref, contents)
}
fn write_change_line_binary(
&mut self,
pref: &str,
contents: &[u8],
) -> Result<(), std::io::Error> {
write!(self, "{}b{}", pref, data_encoding::BASE64.encode(contents))
}
}
impl WriteChangeLine for &mut Vec<u8> {}
impl WriteChangeLine for &mut std::io::Stderr {}
impl WriteChangeLine for &mut std::io::Stdout {}
pub fn print_contents<W: WriteChangeLine>(
w: &mut W,
pref: &str,
contents: &[u8],
encoding: &Option<Encoding>,
) -> Result<(), std::io::Error> {
if let Some(encoding) = encoding {
let dec = encoding.decode(&contents);
let dec = if dec.ends_with("\n") {
&dec[..dec.len() - 1]
} else {
&dec
};
for a in dec.split('\n') {
w.write_change_line(pref, a)?
}
} else {
writeln!(w, "{}b{}", pref, data_encoding::BASE64.encode(contents))?
}
Ok(())
}
pub fn print_change_contents<W: WriteChangeLine, C: ChangeStore>(
w: &mut W,
changes: &C,
change: &Atom<Option<Hash>>,
change_contents: &[u8],
encoding: &Option<Encoding>,
) -> Result<(), TextSerError<C::Error>> {
debug!("print_change_contents {:?}", change);
match change {
Atom::NewVertex(ref n) => {
let c = &change_contents[n.start.us()..n.end.us()];
print_contents(w, "+", c, encoding)?;
if !c.ends_with(b"\n") {
debug!("print_change_contents {:?}", c);
writeln!(w, "\n\\")?
}
Ok(())
}
Atom::EdgeMap(ref n) if n.edges.is_empty() => return Err(TextSerError::InvalidChange),
Atom::EdgeMap(ref n) if n.edges[0].flag.contains(EdgeFlags::DELETED) => {
let mut buf = Vec::new();
let mut current = None;
for e in n.edges.iter() {
if Some(e.to) == current {
continue;
}
buf.clear();
changes
.get_contents_ext(e.to, &mut buf)
.map_err(TextSerError::C)?;
print_contents(w, "-", &buf[..], &encoding)?;
if !buf.ends_with(b"\n") {
debug!("print_change_contents {:?}", buf);
writeln!(w)?;
}
current = Some(e.to)
}
Ok(())
}
_ => Ok(()),
}
}
pub fn write_deleted_names<W: std::io::Write, C: ChangeStore>(
w: &mut W,
changes: &C,
del: &Atom<Option<Hash>>,
) -> Result<(), TextSerError<C::Error>> {
if let Atom::EdgeMap(ref e) = del {
let mut buf = Vec::new();
let mut is_first = true;
for d in e.edges.iter() {
buf.clear();
changes
.get_contents_ext(d.to, &mut buf)
.map_err(TextSerError::C)?;
if !buf.is_empty() {
let FileMetadata { basename: name, .. } = FileMetadata::read(&buf);
write!(w, "{}{:?}", if is_first { "" } else { ", " }, name)?;
is_first = false;
}
}
}
Ok(())
}
pub fn write_flag<W: std::io::Write>(mut w: W, flag: EdgeFlags) -> Result<(), std::io::Error> {
if flag.contains(EdgeFlags::BLOCK) {
w.write_all(b"B")?;
}
if flag.contains(EdgeFlags::FOLDER) {
w.write_all(b"F")?;
}
if flag.contains(EdgeFlags::DELETED) {
w.write_all(b"D")?;
}
assert!(!flag.contains(EdgeFlags::PARENT));
assert!(!flag.contains(EdgeFlags::PSEUDO));
Ok(())
}
pub fn read_flag(s: &str) -> EdgeFlags {
let mut f = EdgeFlags::empty();
for i in s.chars() {
match i {
'B' => f |= EdgeFlags::BLOCK,
'F' => f |= EdgeFlags::FOLDER,
'D' => f |= EdgeFlags::DELETED,
c => panic!("read_flag: {:?}", c),
}
}
f
}
pub fn write_pos<W: std::io::Write>(
mut w: W,
hashes: &HashMap<Hash, usize>,
pos: Position<Option<Hash>>,
) -> Result<(), std::io::Error> {
let change = if let Some(Hash::None) = pos.change {
1
} else if let Some(ref c) = pos.change {
*hashes.get(c).unwrap()
} else {
0
};
write!(w, "{}.{}", change, pos.pos.0)?;
Ok(())
}
pub fn write_atom<W: std::io::Write>(
w: &mut W,
hashes: &HashMap<Hash, usize>,
atom: &Atom<Option<Hash>>,
) -> Result<(), std::io::Error> {
match atom {
Atom::NewVertex(ref n) => write_newvertex(w, hashes, n),
Atom::EdgeMap(ref n) => write_edgemap(w, hashes, n),
}
}
pub fn write_newvertex<W: std::io::Write>(
mut w: W,
hashes: &HashMap<Hash, usize>,
n: &NewVertex<Option<Hash>>,
) -> Result<(), std::io::Error> {
write!(w, " up")?;
for c in n.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
write!(w, ", new {}:{}", n.start.0, n.end.0)?;
if !n.down_context.is_empty() {
write!(w, ", down")?;
for c in n.down_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
}
w.write_all(b"\n")?;
Ok(())
}
pub fn write_edgemap<W: std::io::Write>(
mut w: W,
hashes: &HashMap<Hash, usize>,
n: &EdgeMap<Option<Hash>>,
) -> Result<(), std::io::Error> {
let mut is_first = true;
for c in n.edges.iter() {
if !is_first {
write!(w, ", ")?;
}
is_first = false;
write_flag(&mut w, c.previous)?;
write!(w, ":")?;
write_flag(&mut w, c.flag)?;
write!(w, " ")?;
write_pos(&mut w, hashes, c.from)?;
write!(w, " -> ")?;
write_pos(&mut w, hashes, c.to.start_pos())?;
let h = if let Some(h) = hashes.get(c.introduced_by.as_ref().unwrap()) {
h
} else {
panic!("introduced_by = {:?}, not found", c.introduced_by);
};
write!(w, ":{}/{}", c.to.end.0, h)?;
}
writeln!(w)?;
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Section {
Header(String),
Deps,
Changes {
changes: Vec<Hunk<Option<Hash>, Local>>,
current: Option<Hunk<Option<Hash>, Local>>,
offsets: HashMap<u64, ChangePosition>,
},
}