use crate::HashMap;
use std::collections::hash_map::Entry;
use std::io::BufRead;
use super::*;
use crate::change::parse::*;
use crate::change::printable::*;
use crate::changestore::*;
#[derive(Debug, Error)]
pub enum TextDeError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
TomlDe(#[from] toml::de::Error),
#[error(transparent)]
Nom(#[from] nom::Err<nom::error::Error<String>>),
#[error("Missing dependency [{0}]")]
MissingChange(usize),
#[error("Byte position {0} from this change missing")]
MissingPosition(u64),
}
#[derive(Debug, Error)]
pub enum TextDeErrorDeps<T: GraphTxnT> {
#[error(transparent)]
TextDe(#[from] TextDeError),
#[error(transparent)]
MakeChange(#[from] MakeChangeError<T>),
}
#[derive(Debug, Error)]
pub enum TextSerError<C: std::error::Error + 'static> {
#[error(transparent)]
C(C),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
TomlSer(#[from] toml::ser::Error),
#[error("Missing contents in change {:?}", h)]
MissingContents { h: Hash },
#[error(transparent)]
Change(#[from] ChangeError),
#[error("Invalid change")]
InvalidChange,
}
impl LocalChange<Hunk<Option<Hash>, Local>, Author> {
const DEPS_LINE: &'static str = "# Dependencies\n";
const HUNKS_LINE: &'static str = "# Hunks\n";
pub fn write_all_deps_old<F: FnMut(Hash) -> Result<(), ChangeError>>(
&self,
mut f: F,
) -> Result<(), ChangeError> {
for c in self.changes.iter() {
for c in c.iter() {
match *c {
Atom::NewVertex(ref n) => {
for change in n
.up_context
.iter()
.chain(n.down_context.iter())
.map(|c| c.change)
.chain(std::iter::once(n.inode.change))
{
if let Some(change) = change {
if let Hash::None = change {
continue;
}
f(change)?
}
}
}
Atom::EdgeMap(ref e) => {
for edge in e.edges.iter() {
for change in &[
edge.from.change,
edge.to.change,
edge.introduced_by,
e.inode.change,
] {
if let Some(change) = *change {
if let Hash::None = change {
continue;
}
f(change)?
}
}
}
}
}
}
}
Ok(())
}
pub fn write<W: WriteChangeLine, C: ChangeStore>(
&self,
changes: &C,
hash: Option<Hash>,
write_header: bool,
mut w: W,
) -> Result<(), TextSerError<C::Error>> {
fn change_message<C: ChangeStore>(changes: &C, change: &Hash) -> String {
match changes.get_header(change) {
Ok(h) => h.message.lines().next().unwrap_or("").to_string(),
Err(e) => format!("[couldn't get change description: {}]", e),
}
}
if let Some(h) = hash {
let mut hasher = Hasher::default();
hasher.update(&self.contents);
let hash = hasher.finish();
if hash != self.contents_hash {
return Err((TextSerError::MissingContents { h }).into());
}
}
if write_header {
let s = toml::ser::to_string_pretty(&self.header)?;
writeln!(w, "{}", s)?;
}
let mut hashes = HashMap::default();
let mut i = 2;
let mut needs_newline = false;
if !self.dependencies.is_empty() {
w.write_all(Self::DEPS_LINE.as_bytes())?;
needs_newline = true;
for dep in self.dependencies.iter() {
hashes.insert(*dep, i);
writeln!(
w,
"[{}] {} # {}",
i,
dep.to_base32(),
change_message(changes, dep)
)?;
i += 1;
}
}
self.write_all_deps(|change| {
if let Entry::Vacant(e) = hashes.entry(change) {
e.insert(i);
if !needs_newline {
w.write_all(Self::DEPS_LINE.as_bytes())?;
needs_newline = true;
}
writeln!(
w,
"[{}]+{} # {}",
i,
change.to_base32(),
change_message(changes, &change)
)?;
i += 1;
}
Ok(())
})?;
if !self.extra_known.is_empty() {
needs_newline = true;
for dep in self.extra_known.iter() {
writeln!(
w,
"[*] {} # {}",
dep.to_base32(),
change_message(changes, dep)
)?;
i += 1;
}
}
if !self.changes.is_empty() {
if needs_newline {
w.write_all(b"\n")?
}
w.write_all(Self::HUNKS_LINE.as_bytes())?;
for (n, rec) in self.changes.iter().enumerate() {
write!(w, "\n{}. ", n + 1)?;
rec.write(changes, &hashes, &self.contents, &mut w)?
}
}
Ok(())
}
}
impl Change {
pub fn read_and_deps<
R: BufRead,
T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>,
>(
r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
txn: &T,
channel: &ChannelRef<T>,
) -> Result<Self, TextDeErrorDeps<T>> {
let (mut change, extra_dependencies) = Self::read_impl(r, updatables)?;
let (mut deps, extra) = dependencies(txn, &channel.read(), change.hashed.changes.iter())?;
deps.extend(extra_dependencies.into_iter());
change.hashed.dependencies = deps;
change.hashed.extra_known = extra;
Ok(change)
}
pub fn read<R: BufRead>(
r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
) -> Result<Self, TextDeError> {
Ok(Self::read_impl(r, updatables)?.0)
}
fn read_impl<R: BufRead>(
mut r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
) -> Result<(Self, HashSet<Hash>), TextDeError> {
let mut s = String::new();
r.read_to_string(&mut s)?;
let i = &s;
let (i, m_header) = parse_header(i).map_err(|e| e.to_owned())?;
let header = m_header?;
let (i, deps) = parse_dependencies(i).map_err(|e| e.to_owned())?;
let (_, hunks) = parse_hunks(i).map_err(|e| e.to_owned())?;
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> {
let mut change = Change {
offsets: Offsets::default(),
hashed: Hashed {
version: VERSION,
header,
dependencies: Vec::new(),
extra_known: Vec::new(),
metadata: Vec::new(),
changes: Vec::new(),
contents_hash: Hasher::default().finish(),
},
unhashed: None,
contents: Vec::new(),
};
let mut deps = HashMap::default();
let mut extra_dependencies = HashSet::default();
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);
}
DepType::Numbered(n, true) => {
deps.insert(n, hash);
}
DepType::ExtraKnown => {
change.hashed.extra_known.push(hash);
}
DepType::ExtraUnknown => {
extra_dependencies.insert(hash);
}
}
}
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))?;
debug!("res = {:?}", res);
change.hashed.changes.push(res);
}
change.contents = contents;
change.contents_hash = {
let mut hasher = Hasher::default();
hasher.update(&change.contents);
hasher.finish()
};
Ok((change, extra_dependencies))
}
}
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")
}
}
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")
}
}
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.as_u64(),
end: new_vertex.end.0.as_u64(),
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.as_u64(),
introduced_by: *hashes.get(&c.introduced_by.unwrap()).unwrap_or_else(|| {
panic!("introduced_by = {:?}, not found", c.introduced_by)
}),
})
.collect(),
),
}
}
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.to_le())),
},
introduced_by: change_ref(changes, edge.introduced_by)?,
})
}
Ok(res)
}
impl Hunk<Option<Hash>, Local> {
fn write<W: WriteChangeLine, C: ChangeStore>(
&self,
changes: &C,
hashes: &HashMap<Hash, usize>,
change_contents: &[u8],
w: &mut W,
) -> Result<(), TextSerError<C::Error>> {
use self::text_changes::*;
debug!("write {:?}", self);
match self {
Hunk::FileMove { del, add, path } => match add {
Atom::NewVertex(ref add) => {
let FileMetadata {
basename: name,
metadata,
..
} = FileMetadata::read(&change_contents[add.start.0.into()..add.end.0.into()]);
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(_) => 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),
},
},
Hunk::FileDel {
del,
contents,
path,
encoding,
} => {
debug!("file del");
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())
};
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,
}
}
Hunk::FileUndel {
undel,
contents,
path,
encoding,
} => {
debug!("file undel");
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())
};
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,
}
}
Hunk::FileAdd {
add_name,
contents,
path,
encoding,
..
} => {
if let Atom::NewVertex(ref n) = add_name {
debug!("add_name {:?}", n);
let (name, metadata) = if n.start == n.end {
("", InodeMetadata::DIR)
} else {
let FileMetadata {
basename: name,
metadata: perms,
..
} = FileMetadata::read(&change_contents[n.start.0.into()..n.end.0.into()]);
(name, perms)
};
let contents = if let Some(Atom::NewVertex(ref n)) = contents {
change_contents[n.start.us()..n.end.us()].to_vec()
} else {
Vec::new()
};
assert!(n.down_context.is_empty());
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.as_u64(),
end: n.end.0.as_u64(),
contents,
}
} else {
panic!("Invalid Hunk::FileAdd field add_name: {:?}", add_name);
}
}
Hunk::Edit {
change,
local,
encoding,
} => {
debug!("edit");
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)?,
}
}
Hunk::Replacement {
change,
replacement,
local,
encoding,
} => {
debug!("replacement");
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),
},
Hunk::SolveOrderConflict { change, local } => {
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)?,
}
}
Hunk::UnsolveOrderConflict { change, local } => {
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)?,
}
}
Hunk::ResurrectZombies {
change,
local,
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)?,
},
Hunk::AddRoot { name, .. } => {
if let Atom::NewVertex(ref n) = name {
PrintableHunk::AddRoot {
start: n.start.0.as_u64(),
}
} else {
unreachable!()
}
}
Hunk::DelRoot { inode, name } => PrintableHunk::DelRoot {
name: to_printable_edge_map(name, hashes),
inode: to_printable_edge_map(inode, hashes),
},
}
.write(w)?;
Ok(())
}
}
impl Hunk<Option<Hash>, Local> {
fn from_printable(
updatables: &mut HashMap<usize, crate::InodeUpdate>,
contents_: &mut Vec<u8>,
changes: &HashMap<usize, Hash>,
offsets: &mut HashMap<u64, ChangePosition>,
(hunk_id, hunk): (u64, PrintableHunk),
) -> Result<Self, TextDeError> {
debug!("from_printable {:?}", hunk);
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 {
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)?;
contents_.push(0);
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,
})
}
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 mut 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
};
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
}
}
add_name.up_context =
from_printable_pos_vec_offsets(changes, offsets, &up_context)?;
offsets.insert(start, add_name.start);
offsets.insert(end, add_name.end);
offsets.insert(end + 1, add_name.end + 1);
let contents = if contents.len() > 0 {
let mut x = default_newvertex();
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());
Some(Atom::NewVertex(x))
} else {
None
};
contents_.push(0);
Ok(Hunk::FileAdd {
add_name: Atom::NewVertex(add_name),
add_inode: Atom::NewVertex(add_inode),
contents,
path: if parent == "" {
name
} else {
parent + "/" + &name
},
encoding,
})
}
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)?,
}),
contents: if content_edges.is_empty() {
None
} else {
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)?,
}),
contents: if content_edges.is_empty() {
None
} else {
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) => {
assert!(!contents.is_empty());
let mut x = default_newvertex();
x.inode = inode;
x.flag = EdgeFlags::BLOCK;
x.up_context = from_printable_pos_vec_offsets(
changes,
offsets,
&new_vertex.up_context,
)?;
x.down_context = from_printable_pos_vec_offsets(
changes,
offsets,
&new_vertex.down_context,
)?;
x.start = ChangePosition(contents_.len().into());
contents_.extend(&contents);
x.end = ChangePosition(contents_.len().into());
contents_.push(0);
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,
})
}
PrintableHunk::Replace {
path,
line,
pos,
encoding,
change,
replacement,
change_contents: _,
replacement_contents,
} => {
let inode = from_printable_pos(changes, pos)?;
let replacement = {
let mut x = default_newvertex();
x.inode = inode;
x.flag = EdgeFlags::BLOCK;
x.up_context =
from_printable_pos_vec_offsets(changes, offsets, &replacement.up_context)?;
x.down_context = from_printable_pos_vec_offsets(
changes,
offsets,
&replacement.down_context,
)?;
x.start = ChangePosition(contents_.len().into());
contents_.extend(&replacement_contents);
x.end = ChangePosition(contents_.len().into());
Atom::NewVertex(x)
};
contents_.push(0);
Ok(Hunk::Replacement {
change: Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&change, changes)?,
inode: inode,
}),
replacement,
local: Local { path, line },
encoding,
})
}
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)?,
}),
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,
} => {
let mut c = default_newvertex();
c.inode = from_printable_pos(changes, pos)?;
c.up_context =
from_printable_pos_vec_offsets(changes, offsets, &change.up_context)?;
c.down_context =
from_printable_pos_vec_offsets(changes, offsets, &change.down_context)?;
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());
contents_.push(0);
Ok(Hunk::SolveOrderConflict {
change: Atom::NewVertex(c),
local: Local { path, line },
})
}
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)?,
}),
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,
}),
PrintableHunk::AddRoot { start } => {
contents_.push(0);
let root_inode = Position {
change: Some(Hash::None),
pos: ChangePosition(contents_.len().into()),
};
contents_.push(0);
let inode = contents_.len();
contents_.push(0);
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(), ChangePosition((start + 1).into()));
*pos = ChangePosition((start + 1).into())
}
}
Ok(Hunk::AddRoot {
name: Atom::NewVertex(NewVertex {
up_context: vec![root_inode],
down_context: Vec::new(),
start: ChangePosition(start.into()),
end: ChangePosition(start.into()),
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
inode: root_inode,
}),
inode: Atom::NewVertex(NewVertex {
up_context: vec![Position {
change: None,
pos: ChangePosition(start.into()),
}],
down_context: Vec::new(),
start: ChangePosition(inode.into()),
end: ChangePosition(inode.into()),
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
inode: root_inode,
}),
})
}
PrintableHunk::DelRoot { name, inode } => {
let root_inode = PrintablePos(1, 0);
Ok(Hunk::DelRoot {
name: Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&name, changes)?,
inode: from_printable_pos(changes, root_inode)?,
}),
inode: Atom::EdgeMap(EdgeMap {
edges: from_printable_edge_map(&inode, changes)?,
inode: from_printable_pos(changes, root_inode)?,
}),
})
}
}
}
}
pub fn default_newvertex() -> NewVertex<Option<Hash>> {
NewVertex {
start: ChangePosition(L64(0)),
end: ChangePosition(L64(0)),
flag: EdgeFlags::empty(),
up_context: Vec::new(),
down_context: Vec::new(),
inode: Position {
change: Some(Hash::None),
pos: ChangePosition(L64(0)),
},
}
}
pub fn from_printable_pos_vec_offsets(
changes: &HashMap<usize, Hash>,
offsets: &HashMap<u64, ChangePosition>,
s: &[PrintablePos],
) -> Result<Vec<Position<Option<Hash>>>, TextDeError> {
let mut v = Vec::new();
for PrintablePos(change, pos) in s {
let pos = if *change == 0 {
if let Some(&pos) = offsets.get(&pos) {
pos
} else {
debug!("inconsistent change: {:?} {:?}", s, offsets);
return Err(TextDeError::MissingPosition(*pos));
}
} else {
ChangePosition(L64(pos.to_le()))
};
v.push(Position {
change: change_ref(changes, *change)?,
pos,
})
}
Ok(v)
}
fn change_ref(changes: &HashMap<usize, Hash>, change: usize) -> Result<Option<Hash>, TextDeError> {
debug!("change_ref {:?} {:?}", changes, change);
if change == 0 {
Ok(None)
} else if change == 1 {
Ok(Some(Hash::None))
} else if let Some(&c) = changes.get(&change) {
Ok(Some(c))
} else {
Err(TextDeError::MissingChange(change))
}
}
pub fn from_printable_pos(
changes: &HashMap<usize, Hash>,
pos: PrintablePos,
) -> Result<Position<Option<Hash>>, TextDeError> {
Ok(Position {
change: change_ref(changes, pos.0)?,
pos: ChangePosition(L64(pos.1.to_le())),
})
}
pub trait WriteChangeLine: std::io::Write {
fn write_change_line(&mut self, pref: &str, contents: &str) -> Result<(), std::io::Error> {
writeln!(self, "{} {}", pref, contents)
}
fn write_change_line_binary(
&mut self,
pref: &str,
contents: &[u8],
) -> Result<(), std::io::Error> {
writeln!(self, "{}b{}", pref, data_encoding::BASE64.encode(contents))
}
}
impl WriteChangeLine for &mut Vec<u8> {}
impl WriteChangeLine for &mut std::io::Stderr {}
impl WriteChangeLine for &mut std::io::Stdout {}
pub fn get_change_contents<C: ChangeStore>(
changes: &C,
change: &Atom<Option<Hash>>,
change_contents: &[u8],
) -> Result<Vec<u8>, TextSerError<C::Error>> {
debug!("get_change_contents {:?}", change);
match change {
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),
Atom::EdgeMap(ref n) if n.edges[0].flag.contains(EdgeFlags::DELETED) => {
let mut buf = Vec::new();
let mut z = 0;
let mut current = None;
for e in n.edges.iter() {
if Some(e.to) == current {
continue;
}
let sz = e.to.end - e.to.start;
buf.resize(z + sz, 0);
changes
.get_contents_ext(e.to, &mut buf[z..])
.map_err(TextSerError::C)?;
z += sz;
current = Some(e.to)
}
Ok(buf)
}
_ => Ok(Vec::new()),
}
}
pub fn get_deleted_names<C: ChangeStore>(
changes: &C,
del: &Atom<Option<Hash>>,
) -> Result<Vec<String>, TextSerError<C::Error>> {
let mut res = Vec::new();
let mut h = HashSet::new();
if let Atom::EdgeMap(ref e) = del {
let mut tmp = Vec::new();
for d in e.edges.iter() {
if !h.insert(d.to) {
continue;
}
tmp.resize(d.to.end - d.to.start, 0);
changes
.get_contents_ext(d.to, &mut tmp)
.map_err(TextSerError::C)?;
if !tmp.is_empty() {
let FileMetadata { basename: name, .. } = FileMetadata::read(&tmp);
res.push(name.to_string());
}
}
}
Ok(res)
}
pub fn to_printable_pos(
hashes: &HashMap<Hash, usize>,
pos: Position<Option<Hash>>,
) -> PrintablePos {
let change = if let Some(Hash::None) = pos.change {
1
} else if let Some(ref c) = pos.change {
*hashes.get(c).unwrap()
} else {
0
};
PrintablePos(change, pos.pos.0.as_u64())
}
pub fn to_printable_pos_vec(
hashes: &HashMap<Hash, usize>,
pos: &[Position<Option<Hash>>],
) -> Vec<PrintablePos> {
pos.iter().map(|c| to_printable_pos(hashes, *c)).collect()
}