5FI6SBEZ6RERERUAIWQJVAY66BEZ7YQOYOUNK2DPOLRGS2X326RAC
RUBSM5DRA4O7SGD7NO7QYFGDQ2CQL6K7SZTIQVGRWAQHGSFUBGCQC
YDMAIJ5VGDZFON7VD76EHNERLCJQL3OK6EPSNU552JKQWMDVEW7AC
V4T4SC7OL6WEZNV4XSFBSXY5HPB7VXPSXWSK4Z63QXKQD4JSFNCQC
LWBBN2IBFFW2UIQIP7XL3J2QOK76EJI3P4MO5D543UAATAYW66XAC
IUH7IMWES3KQTHVWA5UNHAO7QWVCC5PQJ6VLK3RC3T4F2MS74P3AC
6CZYYOG7C7ULY5Q4WSRVVC3QVSYACV3KFEYXDCH63OXXX2QFFFBAC
SGXOEWHUET7RVS5HANSYE7QRQAG56NIKTC56MLPZ5XBCXODEWUXQC
SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC
246V5TYIUL7CFN7G5Y7A35EEM6IJPN532ROEYVSM7Q4HCQSWPDBQC
IACED7RWM2ZQIPN3YZATA6SXTRO2C6OUGT3HSOU3LIDZ7YRMLRXAC
SHSJ3Y5332WHVUDDQZ2P7GH6VMC7XIX5L7BDJUUMF3QCDRZP7NLQC
FXEDPLRI7PXLDXV634ZA6D5Q3ZWG3ESTKJTMRPJ4MAHI7PKU3M6AC
IIV3EL2XYI2X7HZWKXEXQFAE3R3KC2Q7SGOT3Q332HSENMYVF32QC
YN63NUZO4LVJ7XPMURDULTXBVJKW5MVCTZ24R7Z52QMHO3HPDUVQC
VO5OQW4W2656DIYYRNZ3PO7TQ4JOKQ3GVWE5ALUTYVMX3WMXJOYQC
I24UEJQLCH2SOXA4UHIYWTRDCHSOPU7AFTRUOTX7HZIAV4AZKYEQC
XR7MNOMU5PMOOEY2EPPUABZ7NOP432RDCWUET23ONPXTT3JQIFIAC
VMOYG7MKEWTUEEY2EOL256RWCVPGRD63IFOSKXHBGJ6VSRITLMOAC
73NW2X2MI767RYNTKS67ZB5QUWYEAA4SCORLD52K36ZU3JAK67AQC
CCLLB7OIFNFYJZTG3UCI7536TOCWSCSXR67VELSB466R24WLJSDAC
CCFJ7VO3I73FE3MZRS5RSDRYNZVW7AXC345P4BXS7JIL2TU3LQJQC
NYOF5766GLBTWQV2KTVRAJMGVJNJ37Z5BLJMFPZA3HG7X2Q2RXPAC
SFJ3XRTFUNG6KNYDLIYKHCENZ6Y3PG33GNGDW6444LAVBMCSH2FAC
ZRUPLBBTT4S6S7A3LOAHG4ONYEGPA5CFO4L2XBCNFKK45MWX3BDAC
3S6LU2U5TIU43WRE5RQS3IOLVJLVNCDL4W44FVK2HR3NAXZ7IDUAC
ZSF3YFZTDOXMCOC3HOT5C6MQLYLWOR7QJAOUDS2M2Z4SC2YW3GRAC
X243Z3Y54ULINQMMRIKLHRV5T237B7VUOAHVJ7DMPOQ6A6GQXY2AC
CIEUBH465IFZXO3YDG7XYHP54NJ4TGVQD47SKNW6P5XM4X7IVNBAC
KL5737GRIOFVXKSINVO5DLLWWV4EMHF7ECXMX6ZZLAZZPWDJPB2QC
XSEODPNEN2Y2THBRO7L5QFPAEQVSQTLAFZFWCRMBGZ3YSRZB2UJAC
Q3GU26WDEYE2HXMM4WWNEGVMSXLQAKRJBSB2EJZA6JTHPKQHXCXQC
UM5DLRPBCQZQBEDJDLDPKODOKLACUHZD6YL6S4JRNKW6JLPNUVSAC
BZCGXVS77ZS3N4QPLIHNWZ3YFVV7H4PXQD3U6RN5ZFVOC7QL7MBQC
TVVW53HZGYPODAXEQ4BFZNSPBOFG6JEDVOKIYIDZMWFAMOBKOR2QC
KJDQ2WOMIUTVDEEQ7NMJYBZAVUZ3NIVOVJ6MUCZPRAWIEWOV6TWQC
YTQS4ES362EJ27OE45CE5HLY7ZU57YLVKRMDRJ2OXT623VM5WOBQC
CFNFIUJVWV2PHHZKAPBY6GLW4TCX2N66DYH2QCFU5X7D7KE5D4AQC
NG3Z3DOKDZQDQ7UNQEOMXWX2NJKKLM7DKRWW3KIUD7QEECTNQIZQC
6YMDOZIB5LVYLFIDGN2WNT5JTHEAMS4TFPVDEZ3OWXWOKJOC5QDAC
EQLDTLXVCARE36EJE3S6SNEVTW2JJY4EYD36EX7WSIFLG2XMKKQAC
65S67T3EKKLFRBCU73Z542V7A4JSMGP37OJINON6N563UIBQAITAC
4DNDMC7IUZNYLDEQQYYF5K3G2QWWXGQENTEWPNTM6XKQEFPW7L3QC
VYHHOEYHO67JNJEODX5L3CQFIV3DAXZBBIQUOMCWJDYF3VWICDNQC
6HNRL5RT76NH5YNSUN7B4FHNRZXKNLX4DROFGMO4R5P2U7JWOL2QC
EAZ45JTF44727P34UQAMIFQQTSXFKABIG4PLBGC7C7KQ74GUSDPAC
FYUDBQ3C5GWIFKITBAKEXTK4AFZXZOH7DHI7QFXQOQ3HYUIYDEVAC
TNN56XYKX4QRHA4FWCF5F3JVG52FIAC76EEYYANDKEE4IAWQKPEQC
QJXNUQFJOAPQT3GUXRDTVKMJZCKFONSXUZMAZB7VC7OHDCGAVCOQC
use crate::change::*;
use crate::changestore::*;
use crate::pristine::*;
use crate::record::*;
use crate::working_copy::*;
use crate::*;
use std::io::Write;
use super::*;
fn hash_mismatch(change: &Change) -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
use crate::change::*;
let mut buf = tempfile::NamedTempFile::new()?;
let mut h = change.serialize(&mut buf)?;
match h {
crate::pristine::Hash::Blake3(ref mut h) => h[0] = h[0].wrapping_add(1),
_ => unreachable!(),
}
match Change::deserialize(buf.path().to_str().unwrap(), Some(&h)) {
Err(ChangeError::ChangeHashMismatch { .. }) => {}
_ => unreachable!(),
}
let f = ChangeFile::open(h, buf.path().to_str().unwrap())?;
assert_eq!(f.hashed(), &change.hashed);
Ok(())
}
#[test]
fn hash_mism() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let repo = working_copy::memory::Memory::new();
let store = changestore::memory::Memory::new();
repo.add_file("file", contents.to_vec());
repo.add_file("file2", contents.to_vec());
let env = pristine::sanakirja::Pristine::new_anon()?;
let txn = env.arc_txn_begin().unwrap();
let mut channel = txn.write().open_or_create_channel("main")?;
txn.write().add_file("file", 0)?;
txn.write().add_file("file2", 0)?;
let mut state = Builder::new();
state
.record(
txn.clone(),
Algorithm::Myers,
channel.clone(),
&repo,
&store,
"",
0,
)
.unwrap();
let rec = state.finish();
let changes: Vec<_> = rec
.actions
.into_iter()
.map(|rec| rec.globalize(&*txn.read()).unwrap())
.collect();
info!("changes = {:?}", changes);
let change0 = crate::change::Change::make_change(
&*txn.read(),
&channel,
changes,
std::mem::take(&mut *rec.contents.lock()),
crate::change::ChangeHeader {
message: "test".to_string(),
authors: vec![],
description: None,
timestamp: chrono::Utc::now(),
},
Vec::new(),
)
.unwrap();
let hash0 = store.save_change(&change0)?;
apply::apply_local_change(
&mut *txn.write(),
&mut channel,
&change0,
&hash0,
&rec.updatables,
)?;
hash_mismatch(&change0)?;
Ok(())
}
#[cfg(feature = "text-changes")]
#[test]
fn text_changes() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let repo = working_copy::memory::Memory::new();
let store = changestore::memory::Memory::new();
repo.add_file("file", contents.to_vec());
repo.add_file("file2", contents.to_vec());
let env = pristine::sanakirja::Pristine::new_anon()?;
let txn = env.arc_txn_begin().unwrap();
let channel = txn.write().open_or_create_channel("main")?;
txn.write().add_file("file", 0)?;
txn.write().add_file("file2", 0)?;
let h0 = record_all(&repo, &store, &txn, &channel, "")?;
let change0 = store.get_change(&h0).unwrap();
text_test(&store, &change0, h0);
write!(repo.write_file("file")?, "a\nx\nc\ne\ny\nf\n")?;
let h1 = record_all(&repo, &store, &txn, &channel, "")?;
let change1 = store.get_change(&h1).unwrap();
text_test(&store, &change1, h1);
repo.remove_path("file2", false)?;
let h2 = record_all(&repo, &store, &txn, &channel, "")?;
let change2 = store.get_change(&h2).unwrap();
text_test(&store, &change2, h2);
repo.rename("file", "file3")?;
txn.write().move_file("file", "file3", 0)?;
let h3 = record_all(&repo, &store, &txn, &channel, "")?;
let change3 = store.get_change(&h3).unwrap();
text_test(&store, &change3, h3);
// name conflicts
let env2 = pristine::sanakirja::Pristine::new_anon()?;
let txn2 = env2.arc_txn_begin().unwrap();
let channel2 = txn2.write().open_or_create_channel("main")?;
let repo2 = working_copy::memory::Memory::new();
apply::apply_change(&store, &mut *txn2.write(), &mut *channel2.write(), &h0)?;
apply::apply_change(&store, &mut *txn2.write(), &mut *channel2.write(), &h1)?;
apply::apply_change(&store, &mut *txn2.write(), &mut *channel2.write(), &h2)?;
output::output_repository_no_pending(&repo2, &store, &txn2, &channel2, "", true, None, 1, 0)?;
repo2.rename("file", "file4")?;
txn2.write().move_file("file", "file4", 0)?;
record_all(&repo2, &store, &txn2, &channel2, "")?;
apply::apply_change(&store, &mut *txn2.write(), &mut *channel2.write(), &h3)?;
output::output_repository_no_pending(&repo2, &store, &txn2, &channel2, "", true, None, 1, 0)?;
let h = record_all(&repo2, &store, &txn2, &channel2, "")?;
let solution = store.get_change(&h).unwrap();
text_test(&store, &solution, h);
Ok(())
}
fn text_test<C: ChangeStore>(c: &C, change0: &Change, h: Hash) {
let mut v = Vec::new();
// let channel = channel.borrow();
change0.write(c, Some(h), true, &mut v).unwrap();
println!("{}", String::from_utf8_lossy(&v));
for i in std::str::from_utf8(&v).unwrap().lines() {
debug!("{}", i);
}
let change1 = Change::read(std::io::Cursor::new(&v[..]), &mut HashMap::default()).unwrap();
if change0.header != change1.header {
error!("header: {:#?} != {:#?}", change0.header, change1.header);
}
if change0.dependencies != change1.dependencies {
error!(
"deps: {:#?} != {:#?}",
change0.dependencies, change1.dependencies
);
}
if change0.extra_known != change1.extra_known {
error!(
"extra: {:#?} != {:#?}",
change0.extra_known, change1.extra_known
);
}
if change0.metadata != change1.metadata {
error!("meta: {:#?} != {:#?}", change0.metadata, change1.metadata);
}
if change0.changes != change1.changes {
if change0.changes.len() != change1.changes.len() {
trace!("change0.changes = {:#?}", change0.changes);
trace!("change1.changes = {:#?}", change1.changes);
} else {
for (a, b) in change0.changes.iter().zip(change1.changes.iter()) {
trace!("change0: {:#?}", a);
trace!("change1: {:#?}", b);
for (a, b) in a.iter().zip(b.iter()) {
if a != b {
error!("change0 -> {:#?}", a);
error!("change1 -> {:#?}", b);
}
}
}
}
}
if change0.contents != change1.contents {
error!("change0.contents = {:?}", change0.contents);
error!("change1.contents = {:?}", change1.contents);
}
assert_eq!(change0, &change1);
}
quickcheck! {
fn string_roundtrip(s1: String) -> () {
let mut w = Vec::new();
write!(&mut w, "{}", Escaped(&s1)).unwrap();
let utf = std::str::from_utf8(&w).unwrap();
let (_, s2) = parse_string(&utf).unwrap();
assert_eq!(s1, s2);
}
}
#[test]
#[cfg(feature = "text-changes")]
fn hunk_roundtrip_test() {
fn go(hunk: PrintableHunk) {
let mut w = Vec::new();
hunk.write(&mut &mut w).unwrap();
let s = std::str::from_utf8(&w).unwrap();
match parse_hunk(&s) {
Ok((_, hunk2)) => {
if hunk != hunk2 {
eprintln!("## Hunk: \n\n{}", s);
eprintln!("In: {:?}\n\nOut: {:?}\n", hunk, hunk2);
panic!("Hunks don't match.");
}
}
Err(e) => {
eprintln!("Hunk: \n\n{}", s);
panic!("Can't parse hunk. Error: {:?}", e);
}
}
}
// Create a new thread with custom stack size
std::thread::Builder::new()
// frequently blown by shrinking :(
// You can disable shrinking by commenting out the shrink function
// for PrintableHunk
.stack_size(20 * 1024 * 1024)
.spawn(move || {
quickcheck::QuickCheck::new()
.tests(1000)
.gen(quickcheck::Gen::new(3))
.max_tests(100000000)
.quickcheck(go as fn(PrintableHunk) -> ());
})
.unwrap()
.join()
.unwrap();
}
}
/// Get a valid encoding. This is checked by round-tripping the encoding on `buffer`.
/// TODO: This is a hack. Make it better. Make it faster.
pub fn get_valid(
&self,
tld: Option<&[u8]>,
allow_utf8: bool,
buffer: &[u8],
) -> Option<&'static Encoding> {
if let (encoding, true) = self.guess_assess(tld, allow_utf8) {
if let (s, e, false) = encoding.decode(buffer) {
if encoding.encode(&s).0 == buffer {
return Some(e);
}
}
}
None
use self::text_changes::*;
let mut section = Section::Header(String::new());
// read the data
// TODO: make this streaming
let mut s = String::new();
r.read_to_string(&mut s)?;
let i = &s;
// parse header
let (i, m_header) = parse_header(i).map_err(|e| e.to_owned())?;
let header = m_header?;
dbg!(&header);
// parse dependencies
let (i, deps) = parse_dependencies(i).map_err(|e| e.to_owned())?;
dbg!(&deps);
// parse hunks
let (_, hunks) = parse_hunks(i).map_err(|e| e.to_owned())?;
dbg!(&hunks);
Change::update(header, deps, hunks, updatables)
}
fn update(
header: ChangeHeader,
dependencies: Vec<PrintableDep>,
hunks: Vec<(u64, PrintableHunk)>,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
) -> Result<(Self, HashSet<Hash>), TextDeError> {
// TODO: get rid of this whole default change if possible
};
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(())
}
}
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();
for dep in dependencies {
let hash = Hash::from_base32(dep.hash.as_bytes()).unwrap();
match dep.type_ {
DepType::Numbered(n, false) => {
change.hashed.dependencies.push(hash);
deps.insert(n, hash);
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)
}
}
DepType::Numbered(n, true) => {
deps.insert(n, hash);
}
DepType::ExtraKnown => {
change.hashed.extra_known.push(hash);
}
DepType::ExtraUnknown => {
extra_dependencies.insert(hash);
h.clear();
}
// process hunks
let mut contents = Vec::new();
let mut offsets = HashMap::default();
for (n, hunk) in hunks {
let res =
Hunk::from_printable(updatables, &mut contents, &deps, &mut offsets, (n, hunk))?;
change.hashed.changes.push(res);
const BINARY_LABEL: &str = "binary";
pub fn to_printable_new_vertex(
atom: &Atom<Option<Hash>>,
hashes: &HashMap<Hash, usize>,
) -> PrintableNewVertex {
if let PrintableAtom::NewVertex(v) = to_printable_atom(atom, hashes) {
v
} else {
panic!("PrintableAtom::NewVertex expected here")
}
}
struct Escaped<'a>(&'a str);
pub fn to_printable_edge_map(
atom: &Atom<Option<Hash>>,
hashes: &HashMap<Hash, usize>,
) -> Vec<PrintableEdge> {
if let PrintableAtom::Edges(v) = to_printable_atom(atom, hashes) {
v
} else {
panic!("PrintableAtom::Edges expected here")
}
}
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(())
/// Panics if the Atom is not an EdgeMap
fn to_printable_atom(atom: &Atom<Option<Hash>>, hashes: &HashMap<Hash, usize>) -> PrintableAtom {
match atom {
Atom::NewVertex(ref new_vertex) => PrintableAtom::NewVertex(PrintableNewVertex {
up_context: new_vertex
.up_context
.iter()
.map(|c| to_printable_pos(hashes, *c))
.collect(),
start: new_vertex.start.0 .0,
end: new_vertex.end.0 .0,
down_context: new_vertex
.down_context
.iter()
.map(|c| to_printable_pos(hashes, *c))
.collect(),
}),
Atom::EdgeMap(ref edge_map) => PrintableAtom::Edges(
edge_map
.edges
.iter()
.map(|c| PrintableEdge {
previous: PrintableEdgeFlags::from(c.previous),
flag: PrintableEdgeFlags::from(c.flag),
from: to_printable_pos(hashes, c.from),
to_start: to_printable_pos(hashes, c.to.start_pos()),
to_end: c.to.end.0 .0,
introduced_by: *hashes.get(&c.introduced_by.unwrap()).unwrap_or_else(|| {
panic!("introduced_by = {:?}, not found", c.introduced_by)
}),
})
.collect(),
),
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()
fn from_printable_edge_map(
edges: &[PrintableEdge],
changes: &HashMap<usize, Hash>,
) -> Result<Vec<NewEdge<Option<Hash>>>, TextDeError> {
let mut res = Vec::new();
for edge in edges {
let Position { change, pos } = from_printable_pos(changes, edge.to_start)?;
res.push(NewEdge {
previous: edge.previous.to(),
flag: edge.flag.to(),
from: from_printable_pos(changes, edge.from)?,
to: Vertex {
change,
start: pos,
end: ChangePosition(L64(edge.to_end)),
},
introduced_by: change_ref(changes, edge.introduced_by)?,
})
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)?
PrintableHunk::FileMoveV {
path: path.to_string(),
name: name.to_string(),
perms: PrintablePerms::from_metadata(metadata),
pos: to_printable_pos(hashes, del.inode()),
up_context: to_printable_pos_vec(hashes, &add.up_context),
down_context: to_printable_pos_vec(hashes, &add.down_context),
del: to_printable_edge_map(del, hashes),
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)?;
}
Atom::EdgeMap(_) => PrintableHunk::FileMoveE {
path: path.to_string(),
pos: to_printable_pos(hashes, del.inode()),
add: to_printable_edge_map(add, hashes),
del: to_printable_edge_map(del, hashes),
},
write!(w, "File deletion: {} ", Escaped(path))?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w, " {:?}", encoding_label(encoding))?;
let (contents_data, content_edges) = if let Some(ref c) = contents {
(
get_change_contents(changes, c, change_contents)?,
to_printable_edge_map(c, hashes),
)
} else {
(Vec::new(), Vec::new())
};
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)?;
PrintableHunk::FileDel {
path: path.to_string(),
pos: to_printable_pos(hashes, del.inode()),
encoding: encoding.clone(),
del_edges: to_printable_edge_map(del, hashes),
content_edges: content_edges,
contents: contents_data,
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)?;
let (contents_data, content_edges) = if let Some(ref c) = contents {
(
get_change_contents(changes, c, change_contents)?,
to_printable_edge_map(c, hashes),
)
writeln!(w)?;
(Vec::new(), Vec::new())
};
PrintableHunk::FileUndel {
path: path.to_string(),
pos: to_printable_pos(hashes, undel.inode()),
encoding: encoding.clone(),
undel_edges: to_printable_edge_map(undel, hashes),
content_edges: content_edges,
contents: contents_data,
let parent = if let Some(p) = crate::path::parent(&path) {
if p.is_empty() {
"/"
} else {
p
}
let contents = if let Some(Atom::NewVertex(ref n)) = contents {
change_contents[n.start.us()..n.end.us()].to_vec()
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)
)?;
for c in n.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
PrintableHunk::FileAddition {
name: name.to_string(),
parent: crate::path::parent(&path).unwrap_or("").to_string(),
perms: PrintablePerms::from_metadata(metadata),
encoding: encoding.clone(),
up_context: to_printable_pos_vec(hashes, &n.up_context),
start: n.start.0 .0,
end: n.end.0 .0,
contents,
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\\")?
}
} else {
// TODO: should this panic?
panic!("Invalid Hunk::FileAdd field add_name: {:?}", add_name);
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)?;
PrintableHunk::Edit {
path: local.path.clone(),
line: local.line,
pos: to_printable_pos(hashes, change.inode()),
encoding: encoding.clone(),
change: to_printable_atom(change, hashes),
contents: get_change_contents(changes, change, change_contents)?,
}
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)?;
PrintableHunk::Replace {
path: local.path.clone(),
line: local.line,
pos: to_printable_pos(hashes, change.inode()),
encoding: encoding.clone(),
change: to_printable_edge_map(change, hashes),
replacement: to_printable_new_vertex(replacement, hashes),
change_contents: get_change_contents(changes, change, change_contents)?,
replacement_contents: get_change_contents(
changes,
replacement,
change_contents,
)?,
}
Hunk::SolveNameConflict { name, path } => PrintableHunk::SolveNameConflict {
path: path.clone(),
pos: to_printable_pos(hashes, name.inode()),
names: get_deleted_names(changes, name)?,
edges: to_printable_edge_map(name, hashes),
},
Hunk::UnsolveNameConflict { name, path } => PrintableHunk::UnsolveNameConflict {
path: path.clone(),
pos: to_printable_pos(hashes, name.inode()),
names: get_deleted_names(changes, name)?,
edges: to_printable_edge_map(name, hashes),
},
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)?;
// TODO: pass in the encoding
let contents = get_change_contents(changes, change, change_contents)?;
let encoding = get_encoding(&contents);
PrintableHunk::SolveOrderConflict {
path: local.path.clone(),
line: local.line,
pos: to_printable_pos(hashes, change.inode()),
encoding: encoding.clone(),
change: to_printable_new_vertex(change, hashes),
contents: get_change_contents(changes, change, change_contents)?,
}
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)?;
// TODO: pass in the encoding
let contents = get_change_contents(changes, change, change_contents)?;
let encoding = get_encoding(&contents);
PrintableHunk::UnsolveOrderConflict {
path: local.path.clone(),
line: local.line,
pos: to_printable_pos(hashes, change.inode()),
encoding: encoding.clone(),
change: to_printable_edge_map(change, hashes),
contents: get_change_contents(changes, change, change_contents)?,
}
} => {
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)?;
}
} => PrintableHunk::ResurrectZombies {
path: local.path.clone(),
line: local.line,
pos: to_printable_pos(hashes, change.inode()),
encoding: encoding.clone(),
change: to_printable_edge_map(change, hashes),
contents: get_change_contents(changes, change, change_contents)?,
},
h: &str,
) -> Result<Option<Self>, TextDeError> {
use self::text_changes::*;
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)
(hunk_id, hunk): (u64, PrintableHunk),
) -> Result<Self, TextDeError> {
// TODO: should we push, or not?
contents_.push(0);
match hunk {
PrintableHunk::FileMoveV {
path,
name,
perms,
pos,
up_context,
down_context,
del,
} => {
let mut add = default_newvertex();
add.start = ChangePosition(contents_.len().into());
add.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let meta = FileMetadata {
metadata: InodeMetadata(match perms {
// TODO: deduplicate
PrintablePerms::IsDir => 0o1100,
PrintablePerms::IsExecutable => 0o100,
PrintablePerms::IsFile => 0,
}),
basename: &name,
encoding: None,
};
meta.write(contents_);
add.end = ChangePosition(contents_.len().into());
add.up_context = from_printable_pos_vec_offsets(changes, offsets, &up_context)?;
add.down_context = from_printable_pos_vec_offsets(changes, offsets, &down_context)?;
Ok(Hunk::FileMove {
add: Atom::NewVertex(add),
del: Atom::EdgeMap(EdgeMap {
inode: from_printable_pos(changes, pos)?,
edges: from_printable_edge_map(&del, changes)?,
}),
path,
})
}
PrintableHunk::FileMoveE {
path,
pos,
add,
del,
} => {
let inode = from_printable_pos(changes, pos)?;
Ok(Hunk::FileMove {
add: Atom::EdgeMap(EdgeMap {
inode,
edges: from_printable_edge_map(&add, changes)?,
}),
del: Atom::EdgeMap(EdgeMap {
inode,
edges: from_printable_edge_map(&del, changes)?,
}),
path,
})
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
PrintableHunk::FileAddition {
name,
parent,
perms,
encoding,
up_context,
start,
end,
contents,
} => {
let meta = FileMetadata {
metadata: InodeMetadata(match perms {
PrintablePerms::IsDir => 0o1100,
PrintablePerms::IsExecutable => 0o100,
PrintablePerms::IsFile => 0,
}),
basename: &name,
encoding: encoding.clone(),
};
let add_name = {
let mut x = default_newvertex();
x.start = ChangePosition(contents_.len().into());
meta.write(contents_);
x.end = ChangePosition(contents_.len().into());
x.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
x.up_context = from_printable_pos_vec_offsets(changes, offsets, &up_context)?;
x
};
let add_inode = {
let mut x = default_newvertex();
x.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
x.up_context.push(Position {
change: None,
pos: ChangePosition(contents_.len().into()),
});
contents_.push(0);
x.start = ChangePosition(contents_.len().into());
x.end = ChangePosition(contents_.len().into());
contents_.push(0);
x
};
if let Entry::Occupied(mut e) = updatables.entry(hunk_id as usize) {
if let crate::InodeUpdate::Add { ref mut pos, .. } = e.get_mut() {
offsets.insert(pos.0.into(), add_inode.start);
*pos = add_inode.start
}
} 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()),
});
// context
offsets.insert(start, add_name.start);
offsets.insert(end, add_name.end);
offsets.insert(end + 1, add_name.end + 1);
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);
// contents
let contents_res = {
let mut x = default_newvertex();
// The `-1` here comes from the extra 0
// padding bytes pushed onto `contents_`.
// TODO: verify this is correct
let inode = Position {
change: None,
pos: ChangePosition((contents_.len() - 1).into()),
};
x.up_context.push(inode);
x.inode = inode;
x.flag = EdgeFlags::BLOCK;
x.start = ChangePosition(contents_.len().into());
contents_.extend(&contents);
x.end = ChangePosition(contents_.len().into());
x
};
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: cap[2].to_string(),
line: cap[3].parse().unwrap(),
},
encoding: encoding_from_label(cap),
PrintableHunk::FileDel {
path,
pos,
encoding,
del_edges,
content_edges,
contents: _,
} => Ok(Hunk::FileDel {
del: Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&del_edges, changes)?,
inode: from_printable_pos(changes, pos)?,
))
} 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: 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),
contents: Some(Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&content_edges, changes)?,
inode: from_printable_pos(changes, pos)?,
})),
path,
encoding,
}),
PrintableHunk::FileUndel {
path,
pos,
encoding,
undel_edges,
content_edges,
contents: _,
} => Ok(Hunk::FileUndel {
undel: Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&undel_edges, changes)?,
inode: from_printable_pos(changes, pos)?,
))
} 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)
contents: Some(Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&content_edges, changes)?,
inode: from_printable_pos(changes, pos)?,
})),
path,
encoding,
}),
PrintableHunk::Edit {
path,
line,
pos,
encoding,
change,
contents,
} => {
let inode = from_printable_pos(changes, pos)?;
let change = match change {
PrintableAtom::NewVertex(new_vertex) => {
let mut x = default_newvertex();
x.inode = inode;
x.flag = EdgeFlags::BLOCK;
x.up_context = from_printable_pos_vec(changes, &new_vertex.up_context)?;
x.down_context = from_printable_pos_vec(changes, &new_vertex.down_context)?;
x.start = ChangePosition(contents_.len().into());
contents_.extend(&contents);
x.end = ChangePosition(contents_.len().into());
Atom::NewVertex(x)
}
PrintableAtom::Edges(edges) => Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&edges, changes)?,
inode: inode,
}),
};
Ok(Hunk::Edit {
change,
local: Local { path, line },
encoding,
})
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());
PrintableHunk::Replace {
path,
line,
pos,
encoding,
change,
replacement,
change_contents: _,
replacement_contents,
} => {
let inode = from_printable_pos(changes, pos)?;
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 replacement = {
let mut x = default_newvertex();
x.inode = inode;
x.flag = EdgeFlags::BLOCK;
x.up_context = from_printable_pos_vec(changes, &replacement.up_context)?;
x.down_context = from_printable_pos_vec(changes, &replacement.down_context)?;
x.start = ChangePosition(contents_.len().into());
contents_.extend(&replacement_contents);
x.end = ChangePosition(contents_.len().into());
Atom::NewVertex(x)
};
Ok(Hunk::Replacement {
change: Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&change, changes)?,
inode: inode,
}),
replacement,
local: Local { path, line },
encoding,
})
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(),
PrintableHunk::SolveNameConflict {
path,
pos,
names: _,
edges,
} => Ok(Hunk::SolveNameConflict {
name: Atom::EdgeMap(EdgeMap {
inode: from_printable_pos(changes, pos)?,
edges: from_printable_edge_map(&edges, changes)?,
))
} else if let Some(cap) = ORDER_CONFLICT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
path,
}),
PrintableHunk::UnsolveNameConflict {
path,
pos,
names: _,
edges,
} => Ok(Hunk::UnsolveNameConflict {
name: Atom::EdgeMap(EdgeMap {
inode: from_printable_pos(changes, pos)?,
edges: from_printable_edge_map(&edges, changes)?,
}),
path,
}),
PrintableHunk::SolveOrderConflict {
path,
line,
pos,
encoding: _,
change,
contents,
} => {
// TODO: this code block looks suspect. Check the correctness.
let mut c = default_newvertex();
c.inode = from_printable_pos(changes, pos)?;
c.up_context = from_printable_pos_vec(changes, &change.up_context)?;
c.down_context = from_printable_pos_vec(changes, &change.down_context)?;
// TODO: this maths is probably unnecessarily complicated
c.start = ChangePosition(contents_.len().into());
c.end = ChangePosition((contents_.len() as u64 + change.end - change.start).into());
offsets.insert(change.end, c.end);
c.start = ChangePosition(contents_.len().into());
contents_.extend(&contents);
c.end = ChangePosition(contents_.len().into());
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)
Ok(Hunk::SolveOrderConflict {
change: Atom::NewVertex(c),
local: Local { path, line },
})
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),
PrintableHunk::UnsolveOrderConflict {
path,
line,
pos,
encoding: _,
change,
contents: _,
} => Ok(Hunk::UnsolveOrderConflict {
change: Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&change, changes)?,
inode: from_printable_pos(changes, pos)?,
))
} 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();
// The `-1` here comes from the extra 0
// padding bytes pushed onto `contents_`.
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::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::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::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());
}
// TODO encoding
text_changes::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)
}
}
local: Local { path, line },
}),
PrintableHunk::ResurrectZombies {
path,
line,
pos,
encoding,
change,
contents: _,
} => Ok(Hunk::ResurrectZombies {
change: Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&change, changes)?,
inode: from_printable_pos(changes, pos)?,
}),
local: Local { path, line },
encoding,
}),
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 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(
// TODO: rename
pub fn from_printable_pos_vec_offsets(
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);
}
pos: &[PrintablePos],
) -> Result<Vec<Position<Option<Hash>>>, TextDeError> {
let mut buf = Vec::new();
for p in pos {
buf.push(from_printable_pos(changes, *p)?);
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 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(())
}
encoding: &Option<Encoding>,
) -> Result<(), TextSerError<C::Error>> {
debug!("print_change_contents {:?}", change);
) -> Result<Vec<u8>, TextSerError<C::Error>> {
debug!("get_change_contents {:?}", 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::NewVertex(ref n) => Ok(change_contents[n.start.us()..n.end.us()].to_vec()),
Atom::EdgeMap(ref n) if n.edges.is_empty() => Err(TextSerError::InvalidChange),
print_contents(w, "-", &buf[..], &encoding)?;
if !buf.ends_with(b"\n") {
debug!("print_change_contents {:?}", buf);
writeln!(w)?;
}
buf.extend_from_slice(&tmp);
if !buf.is_empty() {
let FileMetadata { basename: name, .. } = FileMetadata::read(&buf);
write!(w, "{}{:?}", if is_first { "" } else { ", " }, name)?;
is_first = false;
if !tmp.is_empty() {
let FileMetadata { basename: name, .. } = FileMetadata::read(&tmp);
res.push(name.to_string());
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,
pub fn to_printable_pos(
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),
}
pos: &[Position<Option<Hash>>],
) -> Vec<PrintablePos> {
pos.iter().map(|c| to_printable_pos(hashes, *c)).collect()
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>,
},
}
use std::fmt;
#[cfg(test)]
use quickcheck::{Arbitrary, Gen};
#[cfg(test)]
use PrintableHunk::*;
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StringFragment<'a> {
Literal(&'a str),
EscapedChar(char),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PrintablePerms {
IsDir,
IsExecutable,
IsFile,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct PrintableEdgeFlags {
pub block: bool,
pub folder: bool,
pub deleted: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PrintableEdge {
pub previous: PrintableEdgeFlags,
pub flag: PrintableEdgeFlags,
pub from: PrintablePos,
pub to_start: PrintablePos,
pub to_end: u64,
pub introduced_by: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PrintableNewVertex {
pub up_context: Vec<PrintablePos>,
pub start: u64,
pub end: u64,
pub down_context: Vec<PrintablePos>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PrintableAtom {
NewVertex(PrintableNewVertex),
Edges(Vec<PrintableEdge>),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct PrintablePos(pub usize, pub u64);
// TODO: Some of these are untested: I didn't know how to create them from the
// command line
// NOTE: contents must be valid under the chosen encoding
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum PrintableHunk {
FileMoveV {
path: String,
name: String,
perms: PrintablePerms,
pos: PrintablePos,
up_context: Vec<PrintablePos>,
down_context: Vec<PrintablePos>,
del: Vec<PrintableEdge>,
},
FileMoveE {
path: String,
pos: PrintablePos,
add: Vec<PrintableEdge>,
del: Vec<PrintableEdge>,
},
FileAddition {
name: String,
parent: String,
perms: PrintablePerms,
encoding: Option<Encoding>,
up_context: Vec<PrintablePos>,
start: u64,
end: u64,
contents: Vec<u8>,
},
FileDel {
path: String,
pos: PrintablePos,
encoding: Option<Encoding>,
del_edges: Vec<PrintableEdge>,
content_edges: Vec<PrintableEdge>,
contents: Vec<u8>,
},
FileUndel {
path: String,
pos: PrintablePos,
encoding: Option<Encoding>,
undel_edges: Vec<PrintableEdge>,
content_edges: Vec<PrintableEdge>,
contents: Vec<u8>,
},
Edit {
path: String,
line: usize,
pos: PrintablePos,
encoding: Option<Encoding>,
change: PrintableAtom,
contents: Vec<u8>,
},
Replace {
path: String,
line: usize,
pos: PrintablePos,
encoding: Option<Encoding>,
change: Vec<PrintableEdge>,
replacement: PrintableNewVertex,
change_contents: Vec<u8>,
replacement_contents: Vec<u8>,
},
SolveNameConflict {
path: String,
pos: PrintablePos,
names: Vec<String>,
edges: Vec<PrintableEdge>,
},
UnsolveNameConflict {
path: String,
pos: PrintablePos,
names: Vec<String>,
edges: Vec<PrintableEdge>,
},
SolveOrderConflict {
path: String,
line: usize,
pos: PrintablePos,
encoding: Option<Encoding>,
change: PrintableNewVertex,
contents: Vec<u8>,
},
UnsolveOrderConflict {
path: String,
line: usize,
pos: PrintablePos,
encoding: Option<Encoding>,
change: Vec<PrintableEdge>,
contents: Vec<u8>,
},
ResurrectZombies {
path: String,
line: usize,
pos: PrintablePos,
encoding: Option<Encoding>,
change: Vec<PrintableEdge>,
contents: Vec<u8>,
},
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct PrintableDep {
pub type_: DepType,
pub hash: String,
}
// TODO: make names more precise. I don't know what these should be named.
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum DepType {
Numbered(usize, bool), // (number, plus-sign)
ExtraKnown,
ExtraUnknown,
}
pub struct Escaped<'a>(pub &'a str);
impl<'a> fmt::Display for Escaped<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "\"")?;
for c in self.0.chars() {
if c == '\n' {
write!(fmt, "\\n")?
} else if c == '\r' {
write!(fmt, "\\r")?
} else if c == '\t' {
write!(fmt, "\\t")?
} else if c == '\u{08}' {
write!(fmt, "\\b")?
} else if c == '\u{0C}' {
write!(fmt, "\\f")?
} else if c == '\\' {
write!(fmt, "\\\\")?
} else if c == '"' {
write!(fmt, "\\\"")?
} else {
write!(fmt, "{}", c)?
}
}
write!(fmt, "\"")?;
Ok(())
}
}
impl PrintablePerms {
pub fn from_metadata(perms: InodeMetadata) -> Self {
if perms.0 & 0o1000 == 0o1000 {
PrintablePerms::IsDir
} else if perms.0 & 0o100 == 0o100 {
PrintablePerms::IsExecutable
} else {
PrintablePerms::IsFile
}
}
pub fn to_metadata(self) -> InodeMetadata {
InodeMetadata(match self {
PrintablePerms::IsDir => 0o1100,
PrintablePerms::IsExecutable => 0o100,
PrintablePerms::IsFile => 0o0,
})
}
}
impl fmt::Display for PrintablePos {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}.{}", self.0, self.1)
}
}
impl fmt::Display for PrintablePerms {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(
fmt,
"{}",
match self {
PrintablePerms::IsDir => " +dx",
PrintablePerms::IsExecutable => " +x",
PrintablePerms::IsFile => "",
}
)
}
}
impl PrintableEdgeFlags {
pub fn from(ef: EdgeFlags) -> Self {
assert!(!ef.contains(EdgeFlags::PARENT));
assert!(!ef.contains(EdgeFlags::PSEUDO));
Self {
block: ef.contains(EdgeFlags::BLOCK),
folder: ef.contains(EdgeFlags::FOLDER),
deleted: ef.contains(EdgeFlags::DELETED),
}
}
// TODO: make this nicer
pub fn to(self) -> EdgeFlags {
let mut f = EdgeFlags::empty();
if self.block {
f |= EdgeFlags::BLOCK;
}
if self.folder {
f |= EdgeFlags::FOLDER;
}
if self.deleted {
f |= EdgeFlags::DELETED;
}
f
}
}
impl fmt::Display for PrintableEdgeFlags {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if self.block {
write!(fmt, "B")?;
}
if self.folder {
write!(fmt, "F")?;
}
if self.deleted {
write!(fmt, "D")?;
}
Ok(())
}
}
impl fmt::Display for PrintableEdge {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(
fmt,
"{}:{} {} -> {}:{}/{}",
self.previous, self.flag, self.from, self.to_start, self.to_end, self.introduced_by
)
}
}
impl fmt::Display for PrintableNewVertex {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, " up")?;
for c in self.up_context.iter() {
write!(fmt, " {}", c)?
}
write!(fmt, ", new {}:{}, down", self.start, self.end)?;
for c in self.down_context.iter() {
write!(fmt, " {}", c)?;
}
Ok(())
}
}
impl fmt::Display for PrintableAtom {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
PrintableAtom::NewVertex(x) => write!(fmt, "{}", x),
PrintableAtom::Edges(x) => {
for (i, edge) in x.iter().enumerate() {
if i > 0 {
write!(fmt, ", ")?;
}
write!(fmt, "{}", edge)?;
}
Ok(())
}
}
}
}
impl PrintableHunk {
pub fn write<W: WriteChangeLine>(&self, w: &mut W) -> Result<(), std::io::Error> {
use PrintableHunk::*;
match self {
FileMoveV {
path,
name,
perms,
pos,
up_context,
down_context,
del,
} => {
writeln!(
w,
"Moved: {} {} {} {}",
Escaped(path),
Escaped(name),
perms,
pos,
)?;
writeln!(w, "{}", PrintableAtom::Edges(del.to_vec()))?;
write!(w, "up")?;
for c in up_context.iter() {
write!(w, " {}", c)?
}
write!(w, ", down")?;
for c in down_context.iter() {
write!(w, " {}", c)?
}
writeln!(w)?;
}
FileMoveE {
path,
pos,
add,
del,
} => {
writeln!(w, "Moved: {} {}", Escaped(path), pos)?;
writeln!(w, "{}", PrintableAtom::Edges(add.to_vec()))?;
writeln!(w, "{}", PrintableAtom::Edges(del.to_vec()))?;
}
FileAddition {
name,
parent,
perms,
encoding,
up_context,
start,
end,
contents,
} => {
write!(
w,
"File addition: {} in {}{} {}\n up",
Escaped(name),
Escaped(parent),
perms,
Escaped(encoding_label(encoding)),
)?;
for c in up_context.iter() {
write!(w, " {}", c)?
}
writeln!(w, ", new {}:{}", start, end)?;
print_contents(w, "+", contents, encoding)?;
}
FileDel {
path,
pos,
encoding,
del_edges,
content_edges,
contents,
} => {
writeln!(
w,
"File deletion: {} {} {}",
Escaped(path),
pos,
Escaped(encoding_label(encoding)),
)?;
writeln!(w, "{}", PrintableAtom::Edges(del_edges.to_vec()))?;
writeln!(w, "{}", PrintableAtom::Edges(content_edges.to_vec()))?;
print_contents(w, "-", contents, encoding)?;
}
FileUndel {
path,
pos,
encoding,
undel_edges,
content_edges,
contents,
} => {
writeln!(
w,
"File un-deletion: {} {} {}",
Escaped(path),
pos,
Escaped(encoding_label(encoding)),
)?;
writeln!(w, "{}", PrintableAtom::Edges(undel_edges.to_vec()))?;
writeln!(w, "{}", PrintableAtom::Edges(content_edges.to_vec()))?;
print_contents(w, "+", contents, encoding)?;
}
Edit {
path,
line,
pos,
encoding,
change,
contents,
} => {
writeln!(
w,
"Edit in {}:{} {} {}",
Escaped(&path),
line,
pos,
Escaped(encoding_label(encoding))
)?;
writeln!(w, "{}", change)?;
print_contents(w, "+", contents, encoding)?;
}
Replace {
path,
line,
pos,
encoding,
change,
replacement,
change_contents,
replacement_contents,
} => {
writeln!(
w,
"Replacement in {}:{} {} {}",
Escaped(&path),
line,
pos,
Escaped(encoding_label(encoding))
)?;
writeln!(w, "{}", PrintableAtom::Edges(change.clone()))?;
writeln!(w, "{}", PrintableAtom::NewVertex(replacement.clone()))?;
print_contents(w, "-", change_contents, encoding)?;
print_contents(w, "+", replacement_contents, encoding)?;
}
SolveNameConflict {
path,
pos,
names,
edges,
} => {
write!(w, "Solving a name conflict in {} {}: ", Escaped(path), pos,)?;
write_names(w, names)?;
writeln!(w)?;
writeln!(w, "{}", PrintableAtom::Edges(edges.clone()))?;
}
UnsolveNameConflict {
path,
pos,
names,
edges,
} => {
write!(
w,
"Un-solving a name conflict in {} {}: ",
Escaped(path),
pos,
)?;
write_names(w, names)?;
writeln!(w)?;
writeln!(w, "{}", PrintableAtom::Edges(edges.clone()))?;
}
SolveOrderConflict {
path,
line,
pos,
encoding,
change,
contents,
} => {
writeln!(
w,
"Solving an order conflict in {}:{} {} {}",
Escaped(path),
line,
pos,
Escaped(encoding_label(encoding)),
)?;
writeln!(w, "{}", change)?;
print_contents(w, "+", contents, encoding)?;
}
UnsolveOrderConflict {
path,
line,
pos,
encoding,
change,
contents,
} => {
writeln!(
w,
"Un-solving an order conflict in {}:{} {} {}",
Escaped(path),
line,
pos,
Escaped(encoding_label(encoding))
)?;
writeln!(w, "{}", PrintableAtom::Edges(change.clone()))?;
print_contents(w, "-", contents, encoding)?;
}
ResurrectZombies {
path,
line,
pos,
encoding,
change,
contents,
} => {
writeln!(
w,
"Resurrecting zombie lines in {}:{} {} {}",
Escaped(path),
line,
pos,
Escaped(encoding_label(encoding))
)?;
writeln!(w, "{}", PrintableAtom::Edges(change.clone()))?;
print_contents(w, "+", contents, encoding)?;
}
};
Ok(())
}
}
pub fn write_names<W: std::io::Write>(w: &mut W, names: &[String]) -> Result<(), std::io::Error> {
for (i, name) in names.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
write!(w, "{}", Escaped(name))?;
}
Ok(())
}
pub fn get_encoding(contents: &[u8]) -> Option<Encoding> {
let mut detector = crate::chardetng::EncodingDetector::new();
detector.feed(contents, true);
if let Some(e) = detector.get_valid(None, true, &contents) {
Some(Encoding(e))
} else {
None
}
// let (encoding_guess, may_be_right) = detector.guess_assess(None, true);
// if may_be_right {
// Some(Encoding(encoding_guess))
// } else {
// None
// }
}
fn print_contents<W: WriteChangeLine>(
w: &mut W,
prefix: &str,
contents: &[u8],
encoding: &Option<Encoding>,
) -> Result<(), std::io::Error> {
if let Some(encoding) = encoding {
let dec = encoding.decode(&contents);
let ends_with_newline = dec.ends_with("\n");
let dec = if ends_with_newline {
&dec[..dec.len() - 1]
} else {
&dec
};
for a in dec.split('\n') {
writeln!(w, "{} {}", prefix, a)?;
}
if !ends_with_newline {
writeln!(w, "\\")?;
}
Ok(())
} else {
writeln!(w, "{}b{}", prefix, data_encoding::BASE64.encode(contents))
}
}
// QuickCheck instances
#[cfg(test)]
#[rustfmt::skip]
// This may be nicer if it was generated by a macro
impl Arbitrary for PrintableHunk {
fn arbitrary(g: &mut Gen) -> Self {
fn f<A: Arbitrary>(g: &mut Gen) -> A {
Arbitrary::arbitrary(g)
}
fix_encoding(Gen::new(g.size()).choose(&[
FileMoveV {
path: f(g), name: f(g), perms: f(g), pos: f(g), up_context: f(g), down_context: f(g), del: f(g),
},
FileMoveE {
path: f(g), pos: f(g), add: f(g), del: f(g),
},
FileAddition {
name: f(g), parent: f(g), perms: f(g), encoding: f(g), up_context: f(g), start: f(g), end: f(g), contents: f(g),
},
FileDel {
path: f(g), pos: f(g), encoding: f(g), del_edges: f(g), content_edges: f(g), contents: f(g),
},
FileUndel {
path: f(g), pos: f(g), encoding: f(g), undel_edges: f(g), content_edges: f(g), contents: f(g),
},
Edit {
path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), contents: f(g),
},
Replace {
path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), replacement: f(g), change_contents: f(g), replacement_contents: f(g),
},
SolveNameConflict {
path: f(g), pos: f(g), names: f(g), edges: f(g),
},
UnsolveNameConflict {
path: f(g), pos: f(g), names: f(g), edges: f(g),
},
SolveOrderConflict {
path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), contents: f(g),
},
UnsolveOrderConflict {
path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), contents: f(g),
},
ResurrectZombies {
path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), contents: f(g),
},
])
.unwrap().clone())
}
// Shrinking frequently blows stack. Investigate how to fix it.
// You can disable shrinking by commenting out this function.
// This may be best solved by switching to proptest crate
/*
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
match self.clone() {
FileMoveV { path, name, perms, pos, up_context, down_context, del } =>
Box::new((path, name, perms, pos, up_context, down_context, del)
.shrink().map(|(path, name, perms, pos, up_context, down_context, del)|
fix_encoding(FileMoveV { path, name, perms, pos, up_context, down_context, del }))),
FileMoveE { path, pos, add, del } =>
Box::new((path, pos, add, del)
.shrink().map(|(path, pos, add, del)|
fix_encoding(FileMoveE { path, pos, add, del }))),
FileAddition { name, parent, perms, encoding, up_context, start, end, contents } =>
Box::new((name, parent, perms, encoding, up_context, start, end, contents)
.shrink().map(|(name, parent, perms, encoding, up_context, start, end, contents)|
fix_encoding(FileAddition { name, parent, perms, encoding, up_context, start, end, contents }))),
FileDel { path, pos, encoding, del_edges, content_edges, contents } =>
Box::new((path, pos, encoding, del_edges, content_edges, contents)
.shrink().map(|(path, pos, encoding, del_edges, content_edges, contents)|
fix_encoding(FileDel { path, pos, encoding, del_edges, content_edges, contents }))),
FileUndel { path, pos, encoding, undel_edges, content_edges, contents } =>
Box::new((path, pos, encoding, undel_edges, content_edges, contents)
.shrink().map(|(path, pos, encoding, undel_edges, content_edges, contents)|
fix_encoding(FileUndel { path, pos, encoding, undel_edges, content_edges, contents }))),
Edit { path, line, pos, encoding, change, contents } =>
Box::new((path, line, pos, encoding, change, contents)
.shrink().map(|(path, line, pos, encoding, change, contents)|
fix_encoding(Edit { path, line, pos, encoding, change, contents }))),
Replace { path, line, pos, encoding, change, replacement, change_contents, replacement_contents } =>
Box::new((path, line, pos, encoding, change, replacement, change_contents, replacement_contents)
.shrink().map(|(path, line, pos, encoding, change, replacement, change_contents, replacement_contents)|
fix_encoding(Replace { path, line, pos, encoding, change, replacement, change_contents, replacement_contents }))),
SolveNameConflict { path, pos, names, edges } =>
Box::new((path, pos, names, edges)
.shrink().map(|(path, pos, names, edges)|
fix_encoding(SolveNameConflict { path, pos, names, edges }))),
UnsolveNameConflict { path, pos, names, edges } =>
Box::new((path, pos, names, edges)
.shrink().map(|(path, pos, names, edges)|
fix_encoding(UnsolveNameConflict { path, pos, names, edges }))),
SolveOrderConflict { path, line, pos, encoding, change, contents } =>
Box::new((path, line, pos, encoding, change, contents)
.shrink().map(|(path, line, pos, encoding, change, contents)|
fix_encoding(SolveOrderConflict { path, line, pos, encoding, change, contents }))),
UnsolveOrderConflict { path, line, pos, encoding, change, contents } =>
Box::new((path, line, pos, encoding, change, contents)
.shrink().map(|(path, line, pos, encoding, change, contents)|
fix_encoding(UnsolveOrderConflict { path, line, pos, encoding, change, contents }))),
ResurrectZombies { path, line, pos, encoding, change, contents } =>
Box::new((path, line, pos, encoding, change, contents)
.shrink().map(|(path, line, pos, encoding, change, contents)|
fix_encoding(ResurrectZombies { path, line, pos, encoding, change, contents }))),
}
}
*/
}
#[cfg(test)]
/// This is the one thing that is not normalized on PrintableHunk: encoding
/// content must be valid, given the encoding. This function ensures that.
fn fix_encoding(mut hunk: PrintableHunk) -> PrintableHunk {
// let mut h = hunk.clone();
match &mut hunk {
FileAddition {
encoding, contents, ..
} => *encoding = get_encoding(contents),
FileDel {
encoding, contents, ..
} => *encoding = get_encoding(contents),
FileUndel {
encoding, contents, ..
} => *encoding = get_encoding(contents),
Edit {
encoding, contents, ..
} => *encoding = get_encoding(contents),
Replace { encoding, .. } => *encoding = None,
SolveOrderConflict {
encoding, contents, ..
} => *encoding = get_encoding(contents),
UnsolveOrderConflict {
encoding, contents, ..
} => *encoding = get_encoding(contents),
ResurrectZombies {
encoding, contents, ..
} => *encoding = get_encoding(contents),
_ => (),
};
hunk
}
#[cfg(test)]
impl Arbitrary for PrintablePerms {
fn arbitrary(g: &mut Gen) -> Self {
*g.choose(&[
PrintablePerms::IsDir,
PrintablePerms::IsExecutable,
PrintablePerms::IsFile,
])
.unwrap()
}
}
#[cfg(test)]
impl Arbitrary for PrintablePos {
fn arbitrary(g: &mut Gen) -> Self {
PrintablePos(usize::arbitrary(g), u64::arbitrary(g))
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new(
self.0
.shrink()
.zip(self.1.shrink())
.map(|(a, b)| PrintablePos(a, b)),
)
}
}
#[cfg(test)]
impl Arbitrary for PrintableEdge {
fn arbitrary(g: &mut Gen) -> Self {
Self {
previous: Arbitrary::arbitrary(g),
flag: Arbitrary::arbitrary(g),
from: Arbitrary::arbitrary(g),
to_start: Arbitrary::arbitrary(g),
to_end: Arbitrary::arbitrary(g),
introduced_by: Arbitrary::arbitrary(g),
}
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let Self {
previous,
flag,
from,
to_start,
to_end,
introduced_by,
} = self.clone();
Box::new(
(previous, flag, from, to_start, to_end, introduced_by)
.shrink()
.map(
|(previous, flag, from, to_start, to_end, introduced_by)| Self {
previous,
flag,
from,
to_start,
to_end,
introduced_by,
},
),
)
}
}
#[cfg(test)]
impl Arbitrary for PrintableNewVertex {
fn arbitrary(g: &mut Gen) -> Self {
Self {
up_context: Arbitrary::arbitrary(g),
start: Arbitrary::arbitrary(g),
end: Arbitrary::arbitrary(g),
down_context: Arbitrary::arbitrary(g),
}
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
let Self {
up_context,
start,
end,
down_context,
} = self.clone();
Box::new((up_context, start, end, down_context).shrink().map(
|(up_context, start, end, down_context)| Self {
up_context,
start,
end,
down_context,
},
))
}
}
#[cfg(test)]
impl Arbitrary for PrintableAtom {
fn arbitrary(g: &mut Gen) -> Self {
Gen::new(g.size())
.choose(&[
PrintableAtom::NewVertex(Arbitrary::arbitrary(g)),
PrintableAtom::Edges(Arbitrary::arbitrary(g)),
])
.unwrap()
.clone()
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
match self {
PrintableAtom::NewVertex(x) => {
Box::new(x.shrink().map(|x| PrintableAtom::NewVertex(x)))
}
PrintableAtom::Edges(x) => Box::new(x.shrink().map(|x| PrintableAtom::Edges(x))),
}
}
}
#[cfg(test)]
impl Arbitrary for PrintableEdgeFlags {
fn arbitrary(g: &mut Gen) -> Self {
Self {
block: Arbitrary::arbitrary(g),
folder: Arbitrary::arbitrary(g),
deleted: Arbitrary::arbitrary(g),
}
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
Box::new((self.block, self.folder, self.deleted).shrink().map(
|(block, folder, deleted)| Self {
block,
folder,
deleted,
},
))
}
}
use nom::branch::alt;
use nom::bytes::complete::*;
use nom::character::complete::*;
use nom::combinator::*;
use nom::error::ParseError;
use nom::multi::*;
use nom::sequence::*;
use nom::*;
use crate::change::printable::*;
use PrintableHunk::*;
use super::*;
fn parse_file_move_v_hunk(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(delimited(space0, tag("Moved:"), space0), parse_string)(i)?;
let (i, name) = preceded(space0, parse_string)(i)?;
let (i, perms) = preceded(space0, parse_perms)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, del) = parse_edges(i)?;
let (i, up_context) = preceded(pair(space0, tag("up")), parse_context)(i)?;
let (i, down_context) = preceded(pair(space0, tag(", down")), parse_context)(i)?;
let (i, _) = newline(i)?;
Ok((
i,
FileMoveV {
path,
name,
perms,
pos,
up_context,
down_context,
del,
},
))
}
fn parse_file_move_e_hunk(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(delimited(space0, tag("Moved:"), space0), parse_string)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, add) = parse_edges(i)?;
let (i, del) = parse_edges(i)?;
Ok((
i,
FileMoveE {
path,
pos,
add,
del,
},
))
}
fn parse_file_del_hunk(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(
delimited(space0, tag("File deletion:"), space0),
parse_string,
)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, encoding) = preceded(space0, parse_encoding)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, del_edges) = parse_edges(i)?;
let (i, content_edges) = parse_edges(i)?;
let (i, contents) = parse_contents('-', encoding.clone(), i)?;
Ok((
i,
FileDel {
path,
pos,
encoding,
del_edges,
content_edges,
contents,
},
))
}
fn parse_file_undel_hunk(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(
delimited(space0, tag("File un-deletion:"), space0),
parse_string,
)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, encoding) = preceded(space0, parse_encoding)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, undel_edges) = parse_edges(i)?;
let (i, content_edges) = parse_edges(i)?;
let (i, contents) = parse_contents('+', encoding.clone(), i)?;
Ok((
i,
FileUndel {
path,
pos,
encoding,
undel_edges,
content_edges,
contents,
},
))
}
fn parse_file_addition_hunk(i: &str) -> IResult<&str, PrintableHunk> {
let (i, name) = preceded(
delimited(space0, tag("File addition:"), space0),
parse_string,
)(i)?;
let (i, parent) = preceded(delimited(space1, tag("in"), space1), parse_string)(i)?;
let (i, perms) = preceded(space0, parse_perms)(i)?;
let (i, encoding) = preceded(space0, parse_encoding)(i)?;
let (i, _) = tuple((space0, newline, multispace0))(i)?;
let (i, up_context) = preceded(tag("up"), parse_context)(i)?;
let (i, (start, end)) = delimited(space0, parse_start_end, pair(space0, newline))(i)?;
let (i, contents) = parse_contents('+', encoding.clone(), i)?;
Ok((
i,
FileAddition {
name,
parent,
perms,
encoding,
up_context,
start,
end,
contents,
},
))
}
/// Parse a hunk header string
fn parse_edit_hunk(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(delimited(space0, tag("Edit in"), space0), parse_string)(i)?;
let (i, line) = preceded(char(':'), u64)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, encoding) = preceded(space0, parse_encoding)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, change) = parse_atom(i)?;
let (i, contents) = parse_contents('+', encoding.clone(), i)?;
Ok((
i,
Edit {
path,
line: line as usize,
pos,
encoding,
change,
contents,
},
))
}
fn parse_replace_hunk(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(
delimited(space0, tag("Replacement in"), space0),
parse_string,
)(i)?;
let (i, line) = preceded(char(':'), u64)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, encoding) = preceded(space0, parse_encoding)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
// TODO: allow newlines in between these lines
let (i, change) = parse_edges(i)?;
let (i, replacement) = parse_new_vertex(i)?;
let (i, change_contents) = parse_contents('-', encoding.clone(), i)?;
let (i, replacement_contents) = parse_contents('+', encoding.clone(), i)?;
Ok((
i,
Replace {
path,
line: line as usize,
pos,
encoding,
change,
replacement,
change_contents,
replacement_contents,
},
))
}
fn parse_solve_name_conflict(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(
delimited(space0, tag("Solving a name conflict in"), space0),
parse_string,
)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, _) = tuple((space0, char(':'), space0))(i)?;
let (i, names) = separated_list0(tuple((space0, char(','), space0)), parse_string)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, edges) = parse_edges(i)?;
Ok((
i,
SolveNameConflict {
path,
pos,
names,
edges,
},
))
}
fn parse_unsolve_name_conflict(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(
delimited(space0, tag("Un-solving a name conflict in"), space0),
parse_string,
)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, _) = tuple((space0, char(':'), space0))(i)?;
let (i, names) = separated_list0(tuple((space0, char(','), space0)), parse_string)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, edges) = parse_edges(i)?;
Ok((
i,
UnsolveNameConflict {
path,
pos,
names,
edges,
},
))
}
fn parse_solve_order_conflict(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(
delimited(space0, tag("Solving an order conflict in"), space0),
parse_string,
)(i)?;
let (i, line) = preceded(char(':'), u64)(i)?;
let (i, pos) = preceded(space1, parse_printable_pos)(i)?;
let (i, encoding) = preceded(space0, parse_encoding)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, change) = parse_new_vertex(i)?;
let (i, contents) = parse_contents('+', encoding.clone(), i)?;
Ok((
i,
SolveOrderConflict {
path,
line: line as usize,
pos,
encoding,
change,
contents,
},
))
}
fn parse_unsolve_order_conflict(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(
delimited(space0, tag("Un-solving an order conflict in"), space0),
parse_string,
)(i)?;
let (i, line) = preceded(char(':'), u64)(i)?;
let (i, pos) = preceded(space1, parse_printable_pos)(i)?;
let (i, encoding) = preceded(space0, parse_encoding)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, change) = parse_edges(i)?;
let (i, contents) = parse_contents('-', encoding.clone(), i)?;
Ok((
i,
UnsolveOrderConflict {
path,
line: line as usize,
pos,
encoding,
change,
contents,
},
))
}
fn parse_resurrect_zombies(i: &str) -> IResult<&str, PrintableHunk> {
let (i, path) = preceded(
delimited(space0, tag("Resurrecting zombie lines in"), space0),
parse_string,
)(i)?;
let (i, line) = preceded(char(':'), u64)(i)?;
let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
let (i, encoding) = preceded(space0, parse_encoding)(i)?;
let (i, _) = tuple((space0, newline))(i)?;
let (i, change) = parse_edges(i)?;
let (i, contents) = parse_contents('+', encoding.clone(), i)?;
Ok((
i,
ResurrectZombies {
path,
line: line as usize,
pos,
encoding,
change,
contents,
},
))
}
fn parse_content_line(leading_char: char, input: &str) -> IResult<&str, String> {
preceded(
char(leading_char),
alt((
map(
delimited(tag(" "), take_till(|c| c == '\n'), newline),
|s: &str| s.to_string() + "\n",
),
map(
delimited(tag("b"), take_till(|c| c == '\n'), newline),
|s: &str| s.to_string(),
),
)),
)(input)
}
// TODO: better error handling
fn parse_contents(
leading_char: char,
encoding: Option<Encoding>,
i: &str,
) -> IResult<&str, Vec<u8>> {
let (i, res) = fold_many0(
complete(|i| parse_content_line(leading_char, i)),
String::new,
|s, r| s + r.as_str(),
)(i)?;
let (i, backslash) = opt(complete(tag("\\\n")))(i)?;
if let Ok(mut vec) = encode(encoding, &res) {
if backslash.is_some() && vec[vec.len() - 1] == b'\n' {
vec.pop();
}
Ok((i, vec))
} else {
Err(nom::Err::Error(nom::error::Error::new(
i,
nom::error::ErrorKind::Verify,
)))
}
}
fn encode(encoding: Option<Encoding>, contents: &str) -> Result<Vec<u8>, String> {
if let Some(encoding) = encoding {
Ok(encoding.encode(contents).to_vec())
} else {
data_encoding::BASE64
.decode(contents.as_bytes())
.map_err(|e| e.to_string())
}
}
fn parse_encoding(input: &str) -> IResult<&str, Option<Encoding>> {
map(parse_string, |e| {
if e != BINARY_LABEL {
Some(Encoding::for_label(&e))
} else {
None
}
})(input)
}
pub fn parse_numbered_hunk(input: &str) -> IResult<&str, (u64, PrintableHunk)> {
tuple((terminated(u64, char('.')), parse_hunk))(input)
}
pub fn parse_hunk(input: &str) -> IResult<&str, PrintableHunk> {
alt((
parse_file_move_v_hunk,
parse_file_move_e_hunk,
parse_file_del_hunk,
parse_file_undel_hunk,
parse_file_addition_hunk,
parse_edit_hunk,
parse_replace_hunk,
parse_solve_name_conflict,
parse_unsolve_name_conflict,
parse_solve_order_conflict,
parse_unsolve_order_conflict,
parse_resurrect_zombies,
))(input)
}
pub fn parse_hunks(input: &str) -> IResult<&str, Vec<(u64, PrintableHunk)>> {
preceded(
tuple((tag("# Hunks"), space0, newline, multispace0)),
many0(complete(terminated(parse_numbered_hunk, multispace0))),
)(input)
}
pub const BINARY_LABEL: &str = "binary";
pub fn encoding_label(encoding: &Option<Encoding>) -> &str {
match encoding {
Some(encoding) => encoding.label(),
_ => BINARY_LABEL,
}
}
/// Parse an escaped character: \n, \t, \r, etc.
fn parse_escaped_char(input: &str) -> IResult<&str, char> {
preceded(
char('\\'),
alt((
value('\n', char('n')),
value('\r', char('r')),
value('\t', char('t')),
value('\u{08}', char('b')),
value('\u{0C}', char('f')),
value('\\', char('\\')),
value('"', char('"')),
)),
)(input)
}
/// Parse a non-empty block of text that doesn't include \ or "
fn parse_literal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
let not_quote_slash = is_not("\"\\");
verify(not_quote_slash, |s: &str| !s.is_empty())(input)
}
/// Combine parse_literal and parse_escaped_char into a StringFragment.
fn parse_fragment(input: &str) -> IResult<&str, StringFragment> {
alt((
map(parse_literal, StringFragment::Literal),
map(parse_escaped_char, StringFragment::EscapedChar),
))(input)
}
/// Parse a string. Use a loop of parse_fragment and push all of the fragments
/// into an output string.
pub fn parse_string(input: &str) -> IResult<&str, String> {
let build_string = fold_many0(parse_fragment, String::new, |mut string, fragment| {
match fragment {
StringFragment::Literal(s) => string.push_str(s),
StringFragment::EscapedChar(c) => string.push(c),
}
string
});
delimited(char('"'), build_string, char('"'))(input)
}
fn parse_perms(input: &str) -> IResult<&str, PrintablePerms> {
alt((
value(PrintablePerms::IsDir, tag("+dx")),
value(PrintablePerms::IsExecutable, tag("+x")),
value(PrintablePerms::IsFile, tag("")),
))(input)
}
fn parse_printable_pos(input: &str) -> IResult<&str, PrintablePos> {
map(separated_pair(u64, char('.'), u64), |(a, b)| {
PrintablePos(a as usize, b)
})(input)
}
fn parse_context(input: &str) -> IResult<&str, Vec<PrintablePos>> {
delimited(space0, separated_list0(space1, parse_printable_pos), space0)(input)
}
fn parse_start_end(input: &str) -> IResult<&str, (u64, u64)> {
preceded(
pair(tag(", new"), space1),
separated_pair(u64, char(':'), u64),
)(input)
}
fn parse_edge_flags(i: &str) -> IResult<&str, PrintableEdgeFlags> {
let (i, block) = map(opt(char('B')), |x| x.is_some())(i)?;
let (i, folder) = map(opt(char('F')), |x| x.is_some())(i)?;
let (i, deleted) = map(opt(char('D')), |x| x.is_some())(i)?;
Ok((
i,
PrintableEdgeFlags {
block,
folder,
deleted,
},
))
}
fn parse_new_vertex(i: &str) -> IResult<&str, PrintableNewVertex> {
map(
tuple((
space0,
preceded(tag("up"), parse_context),
terminated(tag(", new"), space0),
terminated(u64, char(':')),
terminated(u64, space0),
preceded(tag(", down"), parse_context),
newline,
)),
|(_, up_context, _, start, end, down_context, _)| PrintableNewVertex {
up_context,
start,
end,
down_context,
},
)(i)
}
fn parse_edge(i: &str) -> IResult<&str, PrintableEdge> {
map(
tuple((
terminated(parse_edge_flags, char(':')),
terminated(parse_edge_flags, char(' ')),
terminated(parse_printable_pos, tag(" -> ")),
terminated(parse_printable_pos, tag(":")),
terminated(u64, tag("/")),
terminated(u64, space0),
)),
|(previous, flag, from, to_start, to_end, introduced_by)| PrintableEdge {
previous,
flag,
from,
to_start,
to_end,
introduced_by: introduced_by as usize,
},
)(i)
}
fn parse_atom(i: &str) -> IResult<&str, PrintableAtom> {
alt((
map(parse_new_vertex, PrintableAtom::NewVertex),
map(parse_edges, PrintableAtom::Edges),
))(i)
}
fn parse_edges(input: &str) -> IResult<&str, Vec<PrintableEdge>> {
terminated(
separated_list0(delimited(space0, char(','), space0), complete(parse_edge)),
pair(space0, newline),
)(input)
}
pub fn parse_header(input: &str) -> IResult<&str, Result<ChangeHeader, toml::de::Error>> {
map(
alt((take_until("# Dependencies"), take_until("# Hunks"))),
|s| toml::de::from_str(s),
)(input)
}
pub fn parse_dependency(i: &str) -> IResult<&str, PrintableDep> {
let (i, mut type_) = delimited(
char('['),
alt((
map(u64, |n| DepType::Numbered(n as usize, false)),
value(DepType::ExtraKnown, char('*')),
value(DepType::ExtraUnknown, take_till(|c| c != ']')),
)),
char(']'),
)(i)?;
let (i, plus) = terminated(
alt((value(true, char('+')), value(false, char(' ')))),
space0,
)(i)?;
// TODO: get rid of this confusing mutation
type_ = if let DepType::Numbered(n, _) = type_ {
DepType::Numbered(n, plus)
} else {
type_
};
let (i, hash) = delimited(
space0,
take_while(|c: char| c.is_ascii_alphanumeric()),
pair(space0, char('\n')),
)(i)?;
Ok((
i,
PrintableDep {
type_,
hash: hash.to_string(),
},
))
}
pub fn parse_dependencies(input: &str) -> IResult<&str, Vec<PrintableDep>> {
alt((
preceded(
tuple((tag("# Dependencies"), space0, char('\n'), multispace0)),
many0(terminated(parse_dependency, multispace0)),
),
value(Vec::new(), multispace0),
))(input)
}
quickcheck = "1"
quickcheck_macros = "1"