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);
#[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>,
},
AddRoot {
start: u64,
},
DelRoot {
name: Vec<PrintableEdge>,
inode: Vec<PrintableEdge>,
},
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct PrintableDep {
pub type_: DepType,
pub hash: String,
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum DepType {
Numbered(usize, bool), 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),
}
}
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()))?;
if !content_edges.is_empty() {
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()))?;
if !content_edges.is_empty() {
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)?;
let sign = if let PrintableAtom::Edges(ref e) = change {
if e.is_empty() || e[0].flag.deleted {
"-"
} else {
"+"
}
} else {
"+"
};
print_contents(w, sign, 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)?;
}
AddRoot { start } => {
writeln!(
w,
"Root add\n up {}, new {}:{}",
PrintablePos(1, 0),
start,
start,
)?;
}
DelRoot { name, inode } => {
writeln!(w, "Root del",)?;
writeln!(w, "{}", PrintableAtom::Edges(name.to_vec()))?;
writeln!(w, "{}", PrintableAtom::Edges(inode.to_vec()))?;
}
};
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
}
}
fn print_contents<W: WriteChangeLine>(
w: &mut W,
prefix: &str,
contents: &[u8],
encoding: &Option<Encoding>,
) -> Result<(), std::io::Error> {
if contents.is_empty() {
return Ok(());
}
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 if contents.len() <= 4096 {
writeln!(w, "{}b{}", prefix, data_encoding::BASE64.encode(contents))
} else {
Ok(())
}
}
#[cfg(test)]
#[rustfmt::skip]
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),
},
AddRoot {
start: f(g),
},
DelRoot {
name: f(g), inode: f(g)
},
])
.unwrap().clone())
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
use std::iter::empty;
match self.clone() {
_ => Box::new(empty()),
}
}
}
#[cfg(test)]
fn fix_encoding(mut hunk: PrintableHunk) -> PrintableHunk {
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,
},
))
}
}