VO5OQW4W2656DIYYRNZ3PO7TQ4JOKQ3GVWE5ALUTYVMX3WMXJOYQC
Q45QHPO4HDTEZF2W4UDZSYYQ46BPEIWSW4GJILZR5HTJNLKXJABQC
62XVBWPYCBULZ2IUWF36JVHAPMKCGQC53PQQ53AAGZGC2KT7VQRQC
5DVRL6MFXQOCPOZMYSKBERMRRVUTYRL2SRGRTU2MH4IEOFCDKM3QC
LCERQSWMA2YKOPAPNZUSOQ7FLBGDHBZR33OHLYX7T4KTKULRMJDQC
GVQ7YSEDDCYYYWDJ5JUVFSBWA5EZVOZJI63KK6E46N6Y2B6LP72AC
ZXTHL45OYLOICXTXXEQ6AMNSLMQFZ6BFUJWMO6LZOSDK7ERROC5AC
DNQHXWRZF6EWII3RGQ6HPGFD47BOOFH4FCQHSCACBS3RLQO3DNIQC
3YDPHBANMNSK7Z5SCG745VLTP4BBQWIXCJTQOSDI6UJGYR6S45EQC
V2MDXX622MAXHXGLX7ERVKGYZVZDCYJIFET3TGGIK455XIADYJUAC
JP3BYVXXWFBVQ23MEHJ3LE36AN26P6OCZALKUXMNLHS2TSTM3NKAC
6T5ULULMRGU5GJ3JQTEH2QFQN5IMP53TYFFXUT5UE6FA6WWFFMFAC
4OCC6D42GZYRDLH3NSKXMJTRKXP7UZ6Z3YNGCNUT7NT6WBDBCBIAC
7A2TSC4PAKK3WOH3DMAJASCEC6D5JLJWNFWJTEEBE4CVS4K76PPQC
FYVZZNRQ4ZCJN7EJ7LRN4A6TYTWUG4YHRVMD4KAFYJALEBLZOBDQC
QNJBR73KCSCCF6FTHGZDF2K7PZQL3EFKZFOBT77KYZBMALTTW6OQC
SXEYMYF7P4RZMZ46WPL4IZUTSQ2ATBWYZX7QNVMS3SGOYXYOHAGQC
L4JXJHWXYNCL4QGJXNKKTOKKTAXKKXBJUUY7HFZGEUZ5A2V5H34QC
I52XSRUH5RVHQBFWVMAQPTUSPAJ4KNVID2RMI3UGCVKFLYUO6WZAC
KWAMD2KR5UYRHHPZWL7GY2KQKNXNVS4BYBVK3FXDI23NQMWA3U4QC
4H2XTVJ2BNXDNHQ3RQTMOG3I4NRGZT7JDLC2GRINS56TIYTYTO4QC
AEPEFS7O3YT7CRRFYQVJWUXUUSRGJ6K6XZQVK62B6N74UXOIFWYAC
WZVCLZKY34KQBQU6YBGJLQCDADBQ67LQVDNRVCMQVY3O3C3EIWSQC
R3H7D42UZ446V5TO2574BMAQQAYYJPEIMSZVDPAGVIYU2COJSWBAC
H23LO7U7MNB5GTLOUIFYAJ6DP57DM3VFBR4IBOAVPMHS356AYFPQC
7UPL3Y2A5QOBU6VIUHNWEFGSGQ5CMWGDGOVLRZ53UARXG3TDGLMAC
5BRU2RRWOQBMS2V3RQM7PRFR5UILYZ73GISHAKJA6KIZGC5M2MFAC
PJ7T2VFLV5PYG3CV23GC2GIQETXKGC6CO74JBGREV3JC3LG5OXUAC
74HX2XZDHFRE7FIQ6LQZALIIKED2ABSGGBQ34ULYZ5ZBKK7UTHQQC
BXD3IQYNMKMI5BTANCF6NZHZP4CKPWADGJMBT2R3WTMKVKONT5QAC
ERV3644QELKOHAVNMZGRWYCPPN3XELTAH4PPFR6QKGWW73XBOOKQC
KDF6FJRVF72L274BEUJCTUKRFMNL6BDZMTVKDPEYGFX4TC3YOVSQC
UNZXTNSJI4YRY3EQ3M4HMBKQDNYDTY6B7IZRBNYGDJXTA2UKYWRAC
KVBLRDOUFRYB6BPOQJDD7OVBYMTTPDAUX7CJ5DC3U7WFRI5OLWRAC
ISQJRA3OJJRDMYVQX7XNYGJNMR6CPWIVAIFOS2NCIZH2ZAPDTC5QC
OXMYGLW2563T6VA75532P7N7CZBHVJL4VI5CQBJ6X4MOX4RKL4GAC
43AJ37IXON6PMMYRZM6OB2MJXPYCNIWW2XBDVNBXIRH5DD7JDTVQC
OUWD436ATBTZJR53B6XDSI5SXRRNZV7YGRUEA5ACHZC2RUDP7G5QC
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use clap::Clap;
use libpijul::changestore::ChangeStore;
use libpijul::{Hash, MutTxnT, MutTxnTExt, TxnT, TxnTExt};
use crate::repository::Repository;
#[derive(Clap, Debug)]
pub struct Upgrade {
#[clap(long = "repository")]
repo_path: Option<PathBuf>,
}
impl Upgrade {
pub fn run(self) -> Result<(), anyhow::Error> {
let mut channels = HashMap::new();
{
let repo = Repository::find_root(self.repo_path.clone())?;
let txn = repo.pristine.txn_begin()?;
let mut hashes = HashSet::new();
for channel in txn.iter_channels("") {
let channel = channel.borrow();
let name = channel.name();
let e = channels.entry(name.to_string()).or_insert_with(Vec::new);
hashes.clear();
for (_, (h, _)) in txn.reverse_log(&channel, None) {
if !hashes.insert(h) {
continue;
}
let path = repo.changes.filename(&h);
let change = libpijul::change::v3::LocalChange3::deserialize(
path.to_str().unwrap(),
Some(&h),
)
.unwrap();
e.push((h, change))
}
}
std::fs::rename(repo.path.join(".pijul"), repo.path.join(".pijul.old"))?;
}
let repo2 = Repository::init(self.repo_path)?;
let mut txn2 = repo2.pristine.mut_txn_begin();
let mut translations = HashMap::new();
translations.insert(None, None);
translations.insert(Some(Hash::None), Some(Hash::None));
for (channel_name, mut changes) in channels {
let mut channel = txn2.open_or_create_channel(&channel_name)?;
while let Some((old_h, c)) = changes.pop() {
let h = repo2.changes.save_change(&c.to_v4(&translations))?;
translations.insert(Some(old_h), Some(h));
txn2.apply_change(&repo2.changes, &mut channel, h)?;
}
}
txn2.commit()?;
Ok(())
}
}
/// Set the repository where this command should run. Defaults to the first ancestor of the current directory that contains a `.pijul` directory.
Err(e) => match e.downcast() {
Ok(libpijul::Error::ChangeAlreadyOnChannel { hash }) => {
error!("change already on channel: {:?}", hash);
return Ok(txn.current_state(&channel.borrow()).unwrap());
}
Ok(e) => return Err(e.into()),
Err(e) => return Err(e),
},
Err(libpijul::LocalApplyError::ChangeAlreadyOnChannel { hash }) => {
error!("change already on channel: {:?}", hash);
return Ok(txn.current_state(&channel.borrow()).unwrap());
}
Err(e) => return Err(e.into()),
if let Err(e) = result {
match e.downcast() {
Ok(libpijul::Error::FileNotInRepo { .. }) => {}
Ok(e) => return Err(e.into()),
Err(e) => return Err(e),
use libpijul::working_copy::filesystem::*;
match result {
Ok(_) => {}
Err(Error::Add(AddError::Fs(FsError::NotFound(_)))) => {}
Err(Error::Add(AddError::Fs(FsError::AlreadyInRepo(_)))) => {}
Err(e) => {
error!("While adding {:?}: {}", p, e);
fn create_dir_all(&mut self, path: &str) -> Result<(), anyhow::Error>;
fn file_metadata(&self, file: &str) -> Result<InodeMetadata, anyhow::Error>;
fn read_file(&self, file: &str, buffer: &mut Vec<u8>) -> Result<(), anyhow::Error>;
fn modified_time(&self, file: &str) -> Result<std::time::SystemTime, anyhow::Error>;
fn remove_path(&mut self, name: &str) -> Result<(), anyhow::Error>;
fn rename(&mut self, former: &str, new: &str) -> Result<(), anyhow::Error>;
fn set_permissions(&mut self, name: &str, permissions: u16) -> Result<(), anyhow::Error>;
fn write_file<A, F: FnOnce(&mut dyn std::io::Write) -> Result<A, anyhow::Error>>(
type Error: std::error::Error;
fn create_dir_all(&mut self, path: &str) -> Result<(), Self::Error>;
fn file_metadata(&self, file: &str) -> Result<InodeMetadata, Self::Error>;
fn read_file(&self, file: &str, buffer: &mut Vec<u8>) -> Result<(), Self::Error>;
fn modified_time(&self, file: &str) -> Result<std::time::SystemTime, Self::Error>;
fn remove_path(&mut self, name: &str) -> Result<(), Self::Error>;
fn rename(&mut self, former: &str, new: &str) -> Result<(), Self::Error>;
fn set_permissions(&mut self, name: &str, permissions: u16) -> Result<(), Self::Error>;
fn write_file<A, E: std::error::Error, F: FnOnce(&mut dyn std::io::Write) -> Result<A, E>>(
fn output_line<F: FnOnce(&mut Vec<u8>) -> Result<(), anyhow::Error>>(
&mut self,
key: Vertex<ChangeId>,
contents: F,
) -> Result<(), anyhow::Error>;
fn output_line<E, F>(&mut self, key: Vertex<ChangeId>, contents: F) -> Result<(), E>
where
E: From<std::io::Error>,
F: FnOnce(&mut Vec<u8>) -> Result<(), E>;
fn output_conflict_marker(&mut self, s: &str) -> Result<(), anyhow::Error>;
fn begin_conflict(&mut self) -> Result<(), anyhow::Error> {
fn output_conflict_marker(&mut self, s: &str) -> Result<(), std::io::Error>;
fn begin_conflict(&mut self) -> Result<(), std::io::Error> {
fn output_line<C: FnOnce(&mut Vec<u8>) -> Result<(), anyhow::Error>>(
&mut self,
_: Vertex<ChangeId>,
c: C,
) -> Result<(), anyhow::Error> {
fn output_line<E, C>(&mut self, _: Vertex<ChangeId>, c: C) -> Result<(), E>
where
E: From<std::io::Error>,
C: FnOnce(&mut Vec<u8>) -> Result<(), E>,
{
fn output_line<C: FnOnce(&mut Vec<u8>) -> Result<(), anyhow::Error>>(
&mut self,
_: Vertex<ChangeId>,
c: C,
) -> Result<(), anyhow::Error> {
fn output_line<E, C>(&mut self, _: Vertex<ChangeId>, c: C) -> Result<(), E>
where
E: From<std::io::Error>,
C: FnOnce(&mut Vec<u8>) -> Result<(), E>,
{
put_tree_with_rev(txn, file_id.as_file_id(), inode)?;
put_inodes_with_rev(txn, inode, dest)?;
put_tree_with_rev(txn, file_id.as_file_id(), inode).map_err(super::UnrecordError::Txn)?;
put_inodes_with_rev(txn, inode, dest).map_err(super::UnrecordError::Txn)?;
#[derive(Debug, Error)]
pub enum UnrecordError<
ChangestoreError: std::error::Error + 'static,
TxnError: std::error::Error + 'static,
> {
#[error("Changestore error: {0}")]
Changestore(ChangestoreError),
#[error(transparent)]
Txn(TxnError),
#[error(transparent)]
Block(#[from] crate::pristine::BlockError),
#[error(transparent)]
InconsistentChange(#[from] crate::pristine::InconsistentChange),
#[error("Change not in channel: {}", hash.to_base32())]
ChangeNotInChannel { hash: ChangeId },
#[error("Change not in channel: {}", change_id.to_base32())]
ChangeIsDependedUpon { change_id: ChangeId },
#[error(transparent)]
Missing(#[from] crate::missing_context::MissingError<TxnError>),
#[error(transparent)]
LocalApply(#[from] crate::apply::LocalApplyError<TxnError>),
#[error(transparent)]
Apply(#[from] crate::apply::ApplyError<ChangestoreError, TxnError>),
}
while txn.del_dep(change_id, None)? {}
txn.del_external(change_id, None)?;
txn.del_internal(*hash, None)?;
while txn.del_dep(change_id, None).map_err(UnrecordError::Txn)? {}
txn.del_external(change_id, None)
.map_err(UnrecordError::Txn)?;
txn.del_internal(*hash, None).map_err(UnrecordError::Txn)?;
#[derive(Debug, Error)]
pub enum RecordError<
C: std::error::Error + 'static,
W: std::error::Error,
T: std::error::Error + 'static,
> {
#[error("Changestore error: {0}")]
Changestore(C),
#[error("Working copy error: {0}")]
WorkingCopy(W),
#[error("System time error: {0}")]
SystemTimeError(#[from] std::time::SystemTimeError),
#[error(transparent)]
Txn(T),
#[error(transparent)]
Diff(#[from] diff::DiffError<C>),
#[error("Path not in repository: {0}")]
PathNotInRepo(String),
#[error(transparent)]
Io(#[from] std::io::Error),
}
graph: self.txn.fork(&mut self.rng, &channel.graph)?,
changes: self.txn.fork(&mut self.rng, &channel.changes)?,
revchanges: self.txn.fork(&mut self.rng, &channel.revchanges)?,
states: self.txn.fork(&mut self.rng, &channel.states)?,
graph: self
.txn
.fork(&mut self.rng, &channel.graph)
.map_err(|e| ForkError::Txn(e.into()))?,
changes: self
.txn
.fork(&mut self.rng, &channel.changes)
.map_err(|e| ForkError::Txn(e.into()))?,
revchanges: self
.txn
.fork(&mut self.rng, &channel.revchanges)
.map_err(|e| ForkError::Txn(e.into()))?,
states: self
.txn
.fork(&mut self.rng, &channel.states)
.map_err(|e| ForkError::Txn(e.into()))?,
self.txn.del(
&mut self.rng,
&mut self.channels,
UnsafeSmallStr::from_small_str(channel.borrow().name.as_small_str()),
None,
)?;
self.txn
.del(
&mut self.rng,
&mut self.channels,
UnsafeSmallStr::from_small_str(channel.borrow().name.as_small_str()),
None,
)
.map_err(|e| ForkError::Txn(e.into()))?;
// org id Zm7WF9q4bHktcCg0LXmmKlQP7XVt4IkPEDuF9pREDw4=
#[derive(Debug, Error)]
pub enum HashPrefixError {
#[error("Failed to parse hash prefix: {0}")]
Parse(String),
#[error("Ambiguous hash prefix: {0}")]
Ambiguous(String),
#[error("Change not found: {0}")]
NotFound(String),
}
#[derive(Debug, Error)]
pub enum ForkError<T: std::error::Error + 'static> {
#[error("Channel name already exists: {0}")]
ChannelNameExists(String),
#[error(transparent)]
Txn(T),
}
put_inodes_with_rev(txn, inode, output_item.pos)?;
put_tree_with_rev(txn, file_id_, inode)?;
put_inodes_with_rev(txn, inode, output_item.pos)
.map_err(PristineOutputError::Txn)?;
put_tree_with_rev(txn, file_id_, inode).map_err(PristineOutputError::Txn)?;
put_inodes_with_rev(txn, inode, output_item.pos)?;
put_tree_with_rev(txn, file_id_, inode)?;
put_inodes_with_rev(txn, inode, output_item.pos).map_err(PristineOutputError::Txn)?;
put_tree_with_rev(txn, file_id_, inode).map_err(PristineOutputError::Txn)?;
put_inodes_with_rev(txn, inode, output_item.pos)?;
put_tree_with_rev(txn, file_id_, inode)?;
put_inodes_with_rev(txn, inode, output_item.pos).map_err(PristineOutputError::Txn)?;
put_tree_with_rev(txn, file_id_, inode).map_err(PristineOutputError::Txn)?;
#[derive(Debug, Error)]
pub enum OutputError<
ChangestoreError: std::error::Error + 'static,
Txn: std::error::Error + 'static,
W: std::error::Error + 'static,
> {
#[error("Working copy error: {0}")]
WorkingCopy(W),
#[error(transparent)]
Pristine(#[from] PristineOutputError<ChangestoreError, Txn>),
}
#[derive(Debug, Error)]
pub enum PristineOutputError<ChangestoreError: std::error::Error, Txn: std::error::Error + 'static>
{
#[error(transparent)]
Txn(Txn),
#[error("Changestore error: {0}")]
Changestore(ChangestoreError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Fs(#[from] crate::fs::FsError<Txn>),
}
use crate::working_copy::WriteError;
impl<C: std::error::Error, T: std::error::Error + 'static, W: std::error::Error>
OutputError<C, T, W>
{
fn from(e: WriteError<Self>) -> Self {
match e {
WriteError::Io(e) => OutputError::Pristine(PristineOutputError::Io(e)),
WriteError::E(e) => e,
}
}
}
#[derive(Debug, Error)]
pub enum FileError<ChangestoreError: std::error::Error + 'static> {
#[error(transparent)]
Changestore(ChangestoreError),
#[error(transparent)]
Io(#[from] std::io::Error),
}
impl<C: std::error::Error, T: std::error::Error + 'static> From<FileError<C>>
for PristineOutputError<C, T>
{
fn from(e: FileError<C>) -> Self {
match e {
FileError::Changestore(e) => PristineOutputError::Changestore(e),
FileError::Io(e) => PristineOutputError::Io(e),
}
}
}
}
#[derive(Debug, Error)]
pub enum ArchiveError<
P: std::error::Error + 'static,
T: std::error::Error + 'static,
A: std::error::Error + 'static,
> {
#[error(transparent)]
A(A),
#[error(transparent)]
Txn(T),
#[error(transparent)]
Unrecord(#[from] crate::unrecord::UnrecordError<P, T>),
#[error(transparent)]
Apply(#[from] crate::apply::ApplyError<P, T>),
#[error("State not found: {:?}", state)]
StateNotFound { state: crate::pristine::Merkle },
#[error(transparent)]
File(#[from] crate::output::FileError<P>),
#[error(transparent)]
Output(#[from] crate::output::PristineOutputError<P, T>),
let channel = pristine::MutTxnT::open_or_create_channel(self, name)?;
Ok(pristine::channel_dump::ChannelFromDump::new(self, channel))
let channel = pristine::MutTxnT::open_or_create_channel(self, name)
.map_err(ChannelDumpError::Txn)?;
Ok(ChannelFromDump::new(self, channel))
/// Find the inode corresponding to that path, or return an error if
/// there's no such inode.
pub fn find_inode<T: TxnT>(txn: &T, path: &str) -> Result<Inode, anyhow::Error> {
/// Find the inode corresponding to that path, if it exists.
pub fn find_inode<T: TxnT>(txn: &T, path: &str) -> Result<Inode, FsError<T::Error>> {
fn output_line<C: FnOnce(&mut Vec<u8>) -> Result<(), anyhow::Error>>(
&mut self,
v: crate::pristine::Vertex<ChangeId>,
c: C,
) -> Result<(), anyhow::Error> {
fn output_line<E, C>(&mut self, v: crate::pristine::Vertex<ChangeId>, c: C) -> Result<(), E>
where
E: From<std::io::Error>,
C: FnOnce(&mut Vec<u8>) -> Result<(), E>,
{
) -> Result<usize, anyhow::Error>;
fn get_dependencies(&self, hash: &Hash) -> Result<Vec<Hash>, anyhow::Error> {
) -> Result<usize, Self::Error>;
fn get_dependencies(&self, hash: &Hash) -> Result<Vec<Hash>, Self::Error> {
) -> Result<Vec<Hash>, anyhow::Error>;
fn save_change(&self, p: &Change) -> Result<Hash, anyhow::Error>;
fn del_change(&self, h: &Hash) -> Result<bool, anyhow::Error>;
fn get_change(&self, h: &Hash) -> Result<Change, anyhow::Error>;
) -> Result<Vec<Hash>, Self::Error>;
fn save_change(&self, p: &Change) -> Result<Hash, Self::Error>;
fn del_change(&self, h: &Hash) -> Result<bool, Self::Error>;
fn get_change(&self, h: &Hash) -> Result<Change, Self::Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Utf8(#[from] std::str::Utf8Error),
#[error(transparent)]
Change(#[from] crate::change::ChangeError),
#[error("Change not found: {:?}", hash)]
ChangeNotFound { hash: crate::Hash },
#[error(transparent)]
Bincode(#[from] bincode::Error),
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Utf8(#[from] std::str::Utf8Error),
#[error(transparent)]
ChangeFile(#[from] crate::change::ChangeError),
#[error(transparent)]
Persist(#[from] tempfile::PersistError),
#[cfg(feature = "text-changes")]
mod text_changes;
mod change_file;
pub use change_file::*;
#[derive(Debug, Error)]
pub enum ChangeError {
#[error("Version mismatch")]
VersionMismatch,
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Bincode(#[from] bincode::Error),
#[error(transparent)]
Zstd(#[from] zstd_seekable::Error),
#[error(transparent)]
TomlDe(#[from] toml::de::Error),
#[error(transparent)]
TomlSer(#[from] toml::ser::Error),
#[error("Missing contents for change {:?}", hash)]
MissingContents { hash: crate::pristine::Hash },
#[error("Change hash mismatch, claimed {:?}, computed {:?}", claimed, computed)]
ChangeHashMismatch {
claimed: crate::pristine::Hash,
computed: crate::pristine::Hash,
},
#[error(
"Change contents hash mismatch, claimed {:?}, computed {:?}",
claimed,
computed
)]
ContentsHashMismatch {
claimed: crate::pristine::Hash,
computed: crate::pristine::Hash,
},
}
// org id SY0PPKJcw1SQ8DEsOkicihzeXjmhMRxFEiBeypeSfTY=
use crate::changestore::*;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::io::BufRead;
#[cfg(feature = "text-changes")]
impl LocalChange<Local> {
const DEPS_LINE: &'static str = "# Dependencies\n";
const CHANGES_LINE: &'static str = "# Changes\n";
pub fn write_all_deps<F: FnMut(Hash) -> Result<(), anyhow::Error>>(
&self,
mut f: F,
) -> Result<(), anyhow::Error> {
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: Write, C: ChangeStore, F: FnMut(&Local, Position<Option<Hash>>) -> String>(
&self,
changes: &C,
hash: Option<Hash>,
mut file_name: F,
write_header: bool,
mut w: W,
) -> Result<(), anyhow::Error> {
if let Some(h) = hash {
// Check if we have the full contents
let mut hasher = Hasher::default();
hasher.update(&self.contents);
let hash = hasher.finish();
if hash != self.contents_hash {
return Err((Error::MissingContents {
hash: h.to_base32(),
})
.into());
}
}
if write_header {
w.write_all(toml::ser::to_string_pretty(&self.header)?.as_bytes())?;
w.write_all(b"\n")?;
}
let mut hashes = HashMap::new();
let mut i = 2;
let mut needs_newline = false;
if !self.dependencies.is_empty() {
w.write_all(Self::DEPS_LINE.as_bytes())?;
needs_newline = true;
for dep in self.dependencies.iter() {
hashes.insert(*dep, i);
writeln!(w, "[{}] {}", i, dep.to_base32())?;
i += 1;
}
}
self.write_all_deps(|change| {
if let Entry::Vacant(e) = hashes.entry(change) {
e.insert(i);
if !needs_newline {
w.write_all(Self::DEPS_LINE.as_bytes())?;
needs_newline = true;
}
writeln!(w, "[{}]+{}", i, change.to_base32())?;
i += 1;
}
Ok(())
})?;
if !self.extra_known.is_empty() {
needs_newline = true;
for dep in self.extra_known.iter() {
writeln!(w, "[*] {}", dep.to_base32())?;
i += 1;
}
}
if !self.changes.is_empty() {
if needs_newline {
w.write_all(b"\n")?
}
w.write_all(Self::CHANGES_LINE.as_bytes())?;
for (n, rec) in self.changes.iter().enumerate() {
write!(w, "\n{}. ", n + 1)?;
rec.write(changes, &mut file_name, &hashes, &self.contents, &mut w)?
}
}
Ok(())
}
}
impl Change {
pub fn read_and_deps<R: BufRead, T: TxnT>(
r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
txn: &T,
channel: &ChannelRef<T>,
) -> Result<Self, anyhow::Error> {
let (mut change, extra_dependencies) = Self::read_(r, updatables)?;
let (mut deps, extra) = dependencies(txn, channel, 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, anyhow::Error> {
Ok(Self::read_(r, updatables)?.0)
}
fn read_<R: BufRead>(
mut r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
) -> Result<(Self, HashSet<Hash>), anyhow::Error> {
use self::text_changes::*;
let mut section = Section::Header(String::new());
let mut change = Change {
offsets: Offsets::default(),
hashed: Hashed {
version: VERSION,
header: ChangeHeader {
authors: Vec::new(),
message: String::new(),
description: None,
timestamp: chrono::Utc::now(),
},
dependencies: Vec::new(),
extra_known: Vec::new(),
metadata: Vec::new(),
changes: Vec::new(),
contents_hash: Hasher::default().finish(),
},
unhashed: None,
contents: Vec::new(),
};
let conclude_section = |change: &mut Change,
section: Section,
contents: &mut Vec<u8>|
-> Result<(), anyhow::Error> {
match section {
Section::Header(ref s) => {
debug!("header = {:?}", s);
change.header = toml::de::from_str(&s)?;
Ok(())
}
Section::Deps => Ok(()),
Section::Changes {
mut changes,
current,
..
} => {
if has_newvertices(¤t) {
contents.push(0)
}
if let Some(c) = current {
debug!("next action = {:?}", c);
changes.push(c)
}
change.changes = changes;
Ok(())
}
}
};
let mut h = String::new();
let mut contents = Vec::new();
let mut deps = HashMap::new();
let mut extra_dependencies = HashSet::new();
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::CHANGES_LINE {
let section = std::mem::replace(
&mut section,
Section::Changes {
changes: Vec::new(),
current: None,
offsets: HashMap::new(),
},
);
conclude_section(&mut change, section, &mut contents)?;
} else {
use regex::Regex;
lazy_static! {
static ref DEPS: Regex = Regex::new(r#"\[(\d*|\*)\](\+| ) *(\S*)"#).unwrap();
static ref KNOWN: Regex = Regex::new(r#"(\S*)"#).unwrap();
}
match section {
Section::Header(ref mut s) => s.push_str(&h),
Section::Deps => {
if let Some(d) = DEPS.captures(&h) {
let hash = Hash::from_base32(d[3].as_bytes()).unwrap();
if let Ok(n) = d[1].parse() {
if &d[2] == " " {
change.hashed.dependencies.push(hash);
}
deps.insert(n, hash);
} else if &d[1] == "*" {
change.hashed.extra_known.push(hash);
} else {
extra_dependencies.insert(hash);
}
}
}
Section::Changes {
ref mut current,
ref mut changes,
ref mut offsets,
} => {
if let Some(next) =
Record::read(updatables, current, &mut contents, &deps, offsets, &h)?
{
debug!("next action = {:?}", next);
changes.push(next)
}
}
}
}
h.clear();
}
conclude_section(&mut change, section, &mut contents)?;
change.contents = contents;
change.contents_hash = {
let mut hasher = Hasher::default();
hasher.update(&change.contents);
hasher.finish()
};
Ok((change, extra_dependencies))
}
}
#[cfg(feature = "text-changes")]
impl Record<Option<Hash>, Local> {
fn write<
W: std::io::Write,
C: ChangeStore,
F: FnMut(&Local, Position<Option<Hash>>) -> String,
>(
&self,
changes: &C,
mut file_name: F,
hashes: &HashMap<Hash, usize>,
change_contents: &[u8],
mut w: W,
) -> Result<(), anyhow::Error> {
use self::text_changes::*;
match self {
Record::FileMove { del, add, path } => match add {
Atom::NewVertex(ref add) => {
let name = std::str::from_utf8(
&change_contents[add.start.0 as usize + 2..add.end.0 as usize],
)
.unwrap();
let perms = crate::pristine::InodeMetadata::from_basename(
&change_contents[add.start.0 as usize..add.start.0 as usize + 2],
);
write!(w, "Moved: {:?} {:?} {:o} ", path, name, perms.0)?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &del)?;
write!(w, "up")?;
for c in add.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
write!(w, ", down")?;
for c in add.down_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
w.write_all(b"\n")?;
}
Atom::EdgeMap(_) => {
write!(w, "Moved: {:?} ", path)?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &add)?;
write_atom(&mut w, hashes, &del)?;
}
},
Record::FileDel {
del,
contents,
path,
} => {
write!(w, "File deletion: {:?} ", path)?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &del)?;
if let Some(ref contents) = contents {
write_atom(&mut w, hashes, &contents)?;
writeln!(w)?;
print_change_contents(&mut w, changes, contents, change_contents)?;
} else {
writeln!(w)?;
}
}
Record::FileUndel {
undel,
contents,
path,
} => {
write!(w, "File un-deletion: {:?} ", path)?;
write_pos(&mut w, hashes, undel.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &undel)?;
if let Some(ref contents) = contents {
write_atom(&mut w, hashes, &contents)?;
print_change_contents(&mut w, changes, contents, change_contents)?;
} else {
writeln!(w)?;
}
}
Record::FileAdd {
add_name,
contents,
path,
..
} => {
if let Atom::NewVertex(ref n) = add_name {
let name = std::str::from_utf8(
&change_contents[n.start.0 as usize + 2..n.end.0 as usize],
)
.unwrap();
let perms = crate::pristine::InodeMetadata::from_basename(
&change_contents[n.start.0 as usize..n.start.0 as usize + 2],
);
let parent = if let Some(p) = crate::path::parent(&path) {
if p.is_empty() {
"/"
} else {
p
}
} else {
"/"
};
write!(
w,
"File addition: {:?} in {:?} {:o}\n up",
name, parent, perms.0
)?;
assert!(n.down_context.is_empty());
for c in n.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
writeln!(w, ", new {}:{}", n.start.0, n.end.0)?;
}
if let Some(Atom::NewVertex(ref n)) = contents {
let c = &change_contents[n.start.0 as usize..n.end.0 as usize];
print_contents(&mut w, "+", c)?;
if !c.ends_with(b"\n") {
writeln!(w, "\\")?
}
}
}
Record::Edit { change, local } => {
write!(w, "Edit in {} ", file_name(&local, change.inode()))?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(&mut w, changes, change, change_contents)?;
}
Record::Replacement {
change,
replacement,
local,
} => {
write!(w, "Replacement in {} ", file_name(&local, change.inode()))?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
write_atom(&mut w, hashes, &replacement)?;
print_change_contents(&mut w, changes, change, change_contents)?;
print_change_contents(&mut w, changes, replacement, change_contents)?;
}
Record::SolveNameConflict { name, path } => {
write!(w, "Solving a name conflict in {:?} ", 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)?;
}
Record::UnsolveNameConflict { name, path } => {
write!(w, "Un-solving a name conflict in {:?} ", 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)?;
}
Record::SolveOrderConflict { change, local } => {
write!(
w,
"Solving an order conflict in {} ",
file_name(&local, change.inode())
)?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(&mut w, changes, change, change_contents)?;
}
Record::UnsolveOrderConflict { change, local } => {
write!(
w,
"Un-solving an order conflict in {} ",
file_name(&local, change.inode())
)?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(&mut w, changes, change, change_contents)?;
}
Record::ResurrectZombies { change, local } => {
write!(
w,
"Resurrecting zombie lines in {:?}:{} ",
local.path, local.line
)?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(&mut w, changes, change, change_contents)?;
}
}
Ok(())
}
}
#[cfg(feature = "text-changes")]
impl Record<Option<Hash>, Local> {
fn read(
updatables: &mut HashMap<usize, crate::InodeUpdate>,
current: &mut Option<Self>,
mut contents_: &mut Vec<u8>,
changes: &HashMap<usize, Hash>,
offsets: &mut HashMap<u64, ChangePosition>,
h: &str,
) -> Result<Option<Self>, Error> {
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>\d+)"#).unwrap();
static ref EDIT: Regex =
Regex::new(r#"(\d+)\. Edit in ([^:]+):(\d+) (\d+\.\d+)"#).unwrap();
static ref REPLACEMENT: Regex =
Regex::new(r#"(\d+)\. Replacement in ([^:]+):(\d+) (\d+\.\d+)"#).unwrap();
static ref FILE_DELETION: Regex =
Regex::new(r#"(\d+)\. File deletion: "([^"]*)" (\d+\.\d+)"#).unwrap();
static ref FILE_UNDELETION: Regex =
Regex::new(r#"(\d+)\. File un-deletion: "([^"]*)" (\d+\.\d+)"#).unwrap();
static ref MOVE: Regex =
Regex::new(r#"(\d+)\. Moved: "(?P<former>[^"]*)" "(?P<new>[^"]*)" (?P<perm>\d+) (?P<inode>.*)"#).unwrap();
static ref MOVE_: Regex = Regex::new(r#"(\d+)\. Moved: "([^"]*)" (.*)"#).unwrap();
static ref NAME_CONFLICT: Regex = Regex::new(
r#"(\d+)\. ((Solving)|(Un-solving)) a name conflict in "([^"]*)" (.*): .*"#
)
.unwrap();
static ref ORDER_CONFLICT: Regex = Regex::new(
r#"(\d+)\. ((Solving)|(Un-solving)) an order conflict in (.*):(\d+) (\d+\.\d+)"#
)
.unwrap();
static ref ZOMBIE: Regex =
Regex::new(r#"(\d+)\. Resurrecting zombie lines in (?P<path>"[^"]+"):(?P<line>\d+) (?P<inode>\d+\.\d+)"#)
.unwrap();
static ref CONTEXT: Regex = Regex::new(
r#"up ((\d+\.\d+ )*\d+\.\d+)(, new (\d+):(\d+))?(, down ((\d+\.\d+ )*\d+\.\d+))?"#
)
.unwrap();
}
if let Some(cap) = FILE_ADDITION.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add_name = default_newvertex();
add_name.start = ChangePosition(contents_.len() as u64);
add_name.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let name = &cap.name("name").unwrap().as_str();
let path = {
let parent = cap.name("parent").unwrap().as_str();
(if parent == "/" {
String::new()
} else {
parent.to_string()
}) + name
};
let meta = cap
.name("perm")
.unwrap()
.as_str()
.chars()
.fold(0, |x, c| x * 8 + (c as u16 - b'0' as u16));
let meta = InodeMetadata(meta);
meta.write(&mut contents_).unwrap();
contents_.extend(name.as_bytes());
add_name.end = ChangePosition(contents_.len() as u64);
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() as u64),
});
contents_.push(0);
add_inode.start = ChangePosition(contents_.len() as u64);
add_inode.end = ChangePosition(contents_.len() as u64);
contents_.push(0);
let n = cap.name("n").unwrap().as_str().parse().unwrap();
if let Entry::Occupied(mut e) = updatables.entry(n) {
if let crate::InodeUpdate::Add { ref mut pos, .. } = e.get_mut() {
*pos = add_inode.start
}
}
Ok(std::mem::replace(
current,
Some(Record::FileAdd {
add_name: Atom::NewVertex(add_name),
add_inode: Atom::NewVertex(add_inode),
contents: None,
path,
}),
))
} else if let Some(cap) = EDIT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut v = default_newvertex();
v.inode = parse_pos(changes, &cap[4]);
Ok(std::mem::replace(
current,
Some(Record::Edit {
change: Atom::NewVertex(v),
local: Local {
path: cap[2].to_string(),
line: cap[3].parse().unwrap(),
},
}),
))
} 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]);
Ok(std::mem::replace(
current,
Some(Record::Replacement {
change: Atom::NewVertex(v.clone()),
replacement: Atom::NewVertex(v),
local: Local {
path: cap[2].to_string(),
line: cap[3].parse().unwrap(),
},
}),
))
} 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(Record::FileDel {
del: Atom::EdgeMap(del),
contents: None,
path: cap[2].to_string(),
}),
))
} 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(Record::FileUndel {
undel: Atom::EdgeMap(undel),
contents: None,
path: cap[2].to_string(),
}),
))
} 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(Record::SolveNameConflict {
name: Atom::EdgeMap(name),
path: cap[5].to_string(),
})
} else {
Some(Record::UnsolveNameConflict {
name: Atom::EdgeMap(name),
path: cap[5].to_string(),
})
},
))
} else if let Some(cap) = MOVE.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add = default_newvertex();
add.start = ChangePosition(contents_.len() as u64);
add.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let name = cap.name("new").unwrap().as_str();
let meta = cap
.name("perm")
.unwrap()
.as_str()
.chars()
.fold(0, |x, c| x * 8 + (c as u16 - b'0' as u16));
let meta = InodeMetadata(meta);
meta.write(&mut contents_).unwrap();
contents_.extend(name.as_bytes());
add.end = ChangePosition(contents_.len() as u64);
let mut del = default_edgemap();
del.inode = parse_pos(changes, cap.name("inode").unwrap().as_str());
Ok(std::mem::replace(
current,
Some(Record::FileMove {
del: Atom::EdgeMap(del),
add: Atom::NewVertex(add),
path: cap[2].to_string(),
}),
))
} else if let Some(cap) = MOVE_.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add = default_edgemap();
let mut del = default_edgemap();
add.inode = parse_pos(changes, &cap[3]);
del.inode = add.inode;
Ok(std::mem::replace(
current,
Some(Record::FileMove {
del: Atom::EdgeMap(del),
add: Atom::EdgeMap(add),
path: cap[2].to_string(),
}),
))
} else if let Some(cap) = ORDER_CONFLICT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
Ok(std::mem::replace(
current,
Some(if &cap[2] == "Solving" {
let mut v = default_newvertex();
v.inode = parse_pos(changes, &cap[7]);
Record::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]);
Record::UnsolveOrderConflict {
change: Atom::EdgeMap(v),
local: Local {
path: cap[5].to_string(),
line: cap[6].parse().unwrap(),
},
}
}),
))
} else if let Some(cap) = ZOMBIE.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut v = default_edgemap();
v.inode = parse_pos(changes, &cap.name("inode").unwrap().as_str());
Ok(std::mem::replace(
current,
Some(Record::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(),
},
}),
))
} else {
match current {
Some(Record::FileAdd {
ref mut contents,
ref mut add_name,
..
}) => {
if h.starts_with('+') {
if contents.is_none() {
let mut v = default_newvertex();
let inode = Position {
change: None,
pos: ChangePosition(contents_.len() as u64 - 1),
};
v.up_context.push(inode);
v.inode = inode;
v.start = ChangePosition(contents_.len() as u64);
*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_)
}
}
} else if h.starts_with('\\') {
if let Some(Atom::NewVertex(ref mut contents)) = contents {
if contents_[contents.end.0 as usize - 1] == b'\n' {
assert_eq!(contents.end.0 as usize, contents_.len());
contents_.pop();
contents.end.0 -= 1;
}
}
} 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(Record::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(Record::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(Record::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(Record::Edit { ref mut change, .. }) => {
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() as u64);
}
text_changes::parse_line_add(h, change, contents_)
}
} else if h.starts_with('\\') {
if let Atom::NewVertex(ref mut change) = change {
if contents_[change.end.0 as usize - 1] == b'\n' {
assert_eq!(change.end.0 as usize, 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(Record::Replacement {
ref mut change,
ref mut replacement,
..
}) => {
if h.starts_with("+ ") {
if let Atom::NewVertex(ref mut repl) = replacement {
if repl.start == repl.end {
repl.start = ChangePosition(contents_.len() as u64);
}
text_changes::parse_line_add(h, repl, contents_)
}
} else if h.starts_with('\\') {
if let Atom::NewVertex(ref mut repl) = replacement {
if contents_[repl.end.0 as usize - 1] == b'\n' {
assert_eq!(repl.end.0 as usize, 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(Record::SolveNameConflict { ref mut name, .. })
| Some(Record::UnsolveNameConflict { ref mut name, .. }) => {
if let Some(edges) = parse_edges(changes, h) {
*name = Atom::EdgeMap(EdgeMap {
edges,
inode: name.inode(),
})
}
Ok(None)
}
Some(Record::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() as u64);
}
text_changes::parse_line_add(h, change, contents_)
}
} 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() as u64);
change.end =
ChangePosition(contents_.len() as u64 + new_end - new_start);
offsets.insert(new_end, change.end);
}
}
}
Ok(None)
}
Some(Record::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(Record::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)
}
}
}
}
}
#[cfg(feature = "text-changes")]
mod text_changes {
use super::*;
lazy_static! {
static ref POS: regex::Regex = regex::Regex::new(r#"(\d+)\.(\d+)"#).unwrap();
static ref EDGE: regex::Regex =
regex::Regex::new(r#"\s*(?P<prev>[BFD]*):(?P<flag>[BFD]*)\s+(?P<up_c>\d+)\.(?P<up_l>\d+)\s*->\s*(?P<c>\d+)\.(?P<l0>\d+):(?P<l1>\d+)/(?P<intro>\d+)\s*"#).unwrap();
}
pub fn default_newvertex() -> NewVertex<Option<Hash>> {
NewVertex {
start: ChangePosition(0),
end: ChangePosition(0),
flag: EdgeFlags::empty(),
up_context: Vec::new(),
down_context: Vec::new(),
inode: Position {
change: Some(Hash::None),
pos: ChangePosition(0),
},
}
}
pub fn default_edgemap() -> EdgeMap<Option<Hash>> {
EdgeMap {
edges: Vec::new(),
inode: Position {
change: Some(Hash::None),
pos: ChangePosition(0),
},
}
}
pub fn has_newvertices<L>(current: &Option<Record<Option<Hash>, L>>) -> bool {
match current {
Some(Record::FileAdd { contents: None, .. }) | None => false,
Some(rec) => rec.iter().any(|e| matches!(e, Atom::NewVertex(_))),
}
}
pub fn parse_pos_vec(
changes: &HashMap<usize, Hash>,
offsets: &HashMap<u64, ChangePosition>,
s: &str,
) -> Result<Vec<Position<Option<Hash>>>, Error> {
let mut v = Vec::new();
for pos in POS.captures_iter(s) {
let change: usize = (&pos[1]).parse().unwrap();
let pos: u64 = (&pos[2]).parse().unwrap();
let pos = if change == 0 {
if let Some(&pos) = offsets.get(&pos) {
pos
} else {
debug!("inconsistent change: {:?} {:?}", s, offsets);
return Err(Error::InconsistentChange);
}
} else {
ChangePosition(pos)
};
v.push(Position {
change: change_ref(changes, change),
pos,
})
}
Ok(v)
}
fn change_ref(changes: &HashMap<usize, Hash>, change: usize) -> Option<Hash> {
debug!("change_ref {:?} {:?}", changes, change);
if change == 0 {
None
} else if change == 1 {
Some(Hash::None)
} else {
Some(*changes.get(&change).unwrap())
}
}
pub fn parse_pos(changes: &HashMap<usize, Hash>, s: &str) -> Position<Option<Hash>> {
let pos = POS.captures(s).unwrap();
let change: usize = (&pos[1]).parse().unwrap();
let pos: u64 = (&pos[2]).parse().unwrap();
Position {
change: change_ref(changes, change),
pos: ChangePosition(pos),
}
}
pub fn parse_edges(
changes: &HashMap<usize, Hash>,
s: &str,
) -> Option<Vec<NewEdge<Option<Hash>>>> {
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(pos0),
},
to: Vertex {
change: change_ref(changes, change1),
start: ChangePosition(start1),
end: ChangePosition(end1),
},
introduced_by: change_ref(changes, introduced_by),
})
} else {
debug!("not parsed");
return None;
}
}
Some(result)
}
pub fn parse_line_add(h: &str, change: &mut NewVertex<Option<Hash>>, contents_: &mut Vec<u8>) {
let h = 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() as u64);
}
pub fn print_contents<W: std::io::Write>(
w: &mut W,
pref: &str,
contents: &[u8],
) -> Result<(), anyhow::Error> {
if let Ok(mut contents) = std::str::from_utf8(&contents) {
while let Some(n) = contents.as_bytes().iter().position(|&c| c == b'\n') {
let (a, b) = contents.split_at(n + 1);
contents = b;
write!(w, "{} {}", pref, a)?;
}
if !contents.is_empty() {
writeln!(w, "{} {}", pref, contents)?;
}
} else {
writeln!(w, "{}b{}", pref, data_encoding::BASE64.encode(contents))?
}
Ok(())
}
pub fn print_change_contents<W: std::io::Write, C: ChangeStore>(
w: &mut W,
changes: &C,
change: &Atom<Option<Hash>>,
change_contents: &[u8],
) -> Result<(), anyhow::Error> {
match change {
Atom::NewVertex(ref n) => {
let c = &change_contents[n.start.0 as usize..n.end.0 as usize];
print_contents(w, "+", c)?;
if !c.ends_with(b"\n") {
writeln!(w, "\\")?
}
Ok(())
}
Atom::EdgeMap(ref n) if n.edges[0].flag.contains(EdgeFlags::DELETED) => {
let mut buf = Vec::new();
let mut current = None;
for e in n.edges.iter() {
if Some(e.to) == current {
continue;
}
buf.clear();
changes.get_contents_ext(e.to, &mut buf)?;
print_contents(w, "-", &buf[..])?;
current = Some(e.to)
}
Ok(())
}
_ => Ok(()),
}
}
pub fn write_deleted_names<W: std::io::Write, C: ChangeStore>(
w: &mut W,
changes: &C,
del: &Atom<Option<Hash>>,
) -> Result<(), anyhow::Error> {
if let Atom::EdgeMap(ref e) = del {
let mut buf = Vec::new();
let mut is_first = true;
for d in e.edges.iter() {
buf.clear();
changes.get_contents_ext(d.to, &mut buf)?;
if !buf.is_empty() {
let name = std::str::from_utf8(buf.split_at(2).1).unwrap();
write!(w, "{}{:?}", if is_first { "" } else { ", " }, name)?;
is_first = false;
}
}
}
Ok(())
}
pub fn write_flag<W: std::io::Write>(mut w: W, flag: EdgeFlags) -> Result<(), anyhow::Error> {
if flag.contains(EdgeFlags::BLOCK) {
w.write_all(b"B")?;
}
if flag.contains(EdgeFlags::FOLDER) {
w.write_all(b"F")?;
}
if flag.contains(EdgeFlags::DELETED) {
w.write_all(b"D")?;
}
assert!(!flag.contains(EdgeFlags::PARENT));
assert!(!flag.contains(EdgeFlags::PSEUDO));
Ok(())
}
pub fn read_flag(s: &str) -> EdgeFlags {
let mut f = EdgeFlags::empty();
for i in s.chars() {
match i {
'B' => f |= EdgeFlags::BLOCK,
'F' => f |= EdgeFlags::FOLDER,
'D' => f |= EdgeFlags::DELETED,
c => panic!("read_flag: {:?}", c),
}
}
f
}
pub fn write_pos<W: std::io::Write>(
mut w: W,
hashes: &HashMap<Hash, usize>,
pos: Position<Option<Hash>>,
) -> Result<(), anyhow::Error> {
let change = if let Some(Hash::None) = pos.change {
1
} else if let Some(ref c) = pos.change {
*hashes.get(c).unwrap()
} else {
0
};
write!(w, "{}.{}", change, pos.pos.0)?;
Ok(())
}
pub fn write_atom<W: std::io::Write>(
w: &mut W,
hashes: &HashMap<Hash, usize>,
atom: &Atom<Option<Hash>>,
) -> Result<(), anyhow::Error> {
match atom {
Atom::NewVertex(ref n) => write_newvertex(w, hashes, n),
Atom::EdgeMap(ref n) => write_edgemap(w, hashes, n),
}
}
pub fn write_newvertex<W: std::io::Write>(
mut w: W,
hashes: &HashMap<Hash, usize>,
n: &NewVertex<Option<Hash>>,
) -> Result<(), anyhow::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<(), anyhow::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(())
}
#[cfg(feature = "text-changes")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Section {
Header(String),
Deps,
Changes {
changes: Vec<Record<Option<Hash>, Local>>,
current: Option<Record<Option<Hash>, Local>>,
offsets: HashMap<u64, ChangePosition>,
},
}
}
// org id XDu/0rcMPSdaCWLiWWDiw32Rl45EX9uwvy9vprRXJbg=
/// An open, seekable change file.
#[cfg(feature = "zstd")]
pub struct ChangeFile<'a> {
s: Option<zstd_seekable::Seekable<'a, OffFile>>,
hashed: Hashed<Local>,
hash: Hash,
unhashed: Option<toml::Value>,
}
struct OffFile {
f: std::fs::File,
start: u64,
}
unsafe impl Send for OffFile {}
impl std::io::Read for OffFile {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
self.f.read(buf)
}
}
impl std::io::Seek for OffFile {
fn seek(&mut self, from: std::io::SeekFrom) -> Result<u64, std::io::Error> {
use std::io::SeekFrom;
let from = match from {
SeekFrom::Start(s) => SeekFrom::Start(s + self.start),
c => c,
};
self.f.seek(from)
}
}
#[cfg(feature = "zstd")]
impl<'a> ChangeFile<'a> {
/// Open a change file from a path.
pub fn open(hash: Hash, path: &str) -> Result<Self, anyhow::Error> {
use std::io::Read;
let mut r = std::fs::File::open(path)?;
let mut buf = Vec::new();
buf.resize(Change::OFFSETS_SIZE as usize, 0);
r.read_exact(&mut buf)?;
let offsets: Offsets = bincode::deserialize(&buf)?;
if offsets.version != VERSION {
return Err(Error::VersionMismatch.into());
}
buf.clear();
buf.resize((offsets.unhashed_off - Change::OFFSETS_SIZE) as usize, 0);
r.read_exact(&mut buf)?;
let mut buf2 = vec![0u8; offsets.hashed_len as usize];
let hashed: Hashed<Local> = {
let mut s = zstd_seekable::Seekable::init_buf(&buf)?;
s.decompress(&mut buf2, 0)?;
bincode::deserialize(&buf2)?
};
buf.resize((offsets.contents_off - offsets.unhashed_off) as usize, 0);
let unhashed = if buf.is_empty() {
None
} else {
r.read_exact(&mut buf)?;
let mut s = zstd_seekable::Seekable::init_buf(&buf)?;
buf2.resize(offsets.unhashed_len as usize, 0);
s.decompress(&mut buf2, 0)?;
Some(toml::de::from_slice(&buf2)?)
};
let m = r.metadata()?;
let s = if offsets.contents_off >= m.len() {
None
} else {
Some(zstd_seekable::Seekable::init(Box::new(OffFile {
f: r,
start: offsets.contents_off,
}))?)
};
Ok(ChangeFile {
s,
hashed,
hash,
unhashed,
})
}
pub fn has_contents(&self) -> bool {
self.s.is_some()
}
/// Reads the contents at an offset into `buf`, and returns the
/// number of bytes read. The bounds of the change's "contents"
/// section are not checked.
pub fn read_contents(&mut self, offset: u64, buf: &mut [u8]) -> Result<usize, anyhow::Error> {
debug!("read_contents {:?} {:?}", offset, buf.len());
if let Some(ref mut s) = self.s {
Ok(s.decompress(buf, offset)?)
} else {
Err((Error::MissingContents {
hash: self.hash.to_base32(),
})
.into())
}
}
pub fn hashed(&self) -> &Hashed<Local> {
&self.hashed
}
pub fn unhashed(&self) -> &Option<toml::Value> {
&self.unhashed
}
}
use super::*;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
enum Atom3<Change> {
NewVertex(NewVertex<Change>),
EdgeMap(EdgeMap3<Change>),
}
impl Atom3<Option<Hash>> {
fn to_v4(self, translation: &HashMap<Option<Hash>, Option<Hash>>) -> Atom<Option<Hash>> {
match self {
Atom3::NewVertex(n) => Atom::NewVertex(NewVertex {
up_context: n
.up_context
.into_iter()
.map(|x| Position {
change: *translation.get(&x.change).unwrap(),
..x
})
.collect(),
down_context: n
.down_context
.into_iter()
.map(|x| Position {
change: *translation.get(&x.change).unwrap(),
..x
})
.collect(),
flag: n.flag,
start: n.start,
end: n.end,
inode: Position {
change: *translation.get(&n.inode.change).unwrap(),
..n.inode
},
}),
Atom3::EdgeMap(e) => Atom::EdgeMap(e.to_v4(translation)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
struct EdgeMap3<Change> {
pub previous: EdgeFlags,
pub flag: EdgeFlags,
pub edges: Vec<NewEdge3<Change>>,
pub inode: Position<Change>,
}
impl EdgeMap3<Option<Hash>> {
fn to_v4(self, translations: &HashMap<Option<Hash>, Option<Hash>>) -> EdgeMap<Option<Hash>> {
let flag = self.flag;
let previous = self.previous;
EdgeMap {
inode: Position {
change: *translations.get(&self.inode.change).unwrap(),
..self.inode
},
edges: self
.edges
.into_iter()
.map(|e| NewEdge {
previous,
flag,
from: Position {
change: *translations.get(&e.from.change).unwrap(),
..e.from
},
to: Vertex {
change: *translations.get(&e.to.change).unwrap(),
..e.to
},
introduced_by: *translations.get(&e.introduced_by).unwrap(),
})
.collect(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
struct NewEdge3<Change> {
pub from: Position<Change>,
pub to: Vertex<Change>,
pub introduced_by: Change,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
enum Record3<Hash, Local> {
FileMove {
del: Atom3<Hash>,
add: Atom3<Hash>,
confirm: Option<Atom3<Hash>>,
path: String,
},
FileDel {
del: Atom3<Hash>,
confirm: Option<Atom3<Hash>>,
contents: Option<Atom3<Hash>>,
path: String,
},
FileUndel {
undel: Atom3<Hash>,
confirm: Option<Atom3<Hash>>,
contents: Option<Atom3<Hash>>,
path: String,
},
FileAdd {
add_name: Atom3<Hash>,
add_inode: Atom3<Hash>,
contents: Option<Atom3<Hash>>,
path: String,
},
SolveNameConflict {
name: Atom3<Hash>,
confirm: Option<Atom3<Hash>>,
path: String,
},
UnsolveNameConflict {
name: Atom3<Hash>,
confirm: Option<Atom3<Hash>>,
path: String,
},
Edit {
change: Atom3<Hash>,
local: Local,
},
Replacement {
change: Atom3<Hash>,
replacement: Atom3<Hash>,
local: Local,
},
SolveOrderConflict {
change: Atom3<Hash>,
local: Local,
},
UnsolveOrderConflict {
change: Atom3<Hash>,
local: Local,
},
ResurrectZombies {
alive: Atom3<Hash>,
dead: Atom3<Hash>,
local: Local,
},
}
impl Record3<Option<Hash>, Local> {
fn to_v4(
self,
translation: &HashMap<Option<Hash>, Option<Hash>>,
) -> Record<Option<Hash>, Local> {
match self {
Record3::FileMove {
del,
add,
confirm,
path,
} => {
let mut del = del.to_v4(translation);
if let Atom::EdgeMap(ref mut e) = del {
if let Some(Atom3::EdgeMap(c)) = confirm {
let c = c.to_v4(translation);
e.edges.extend(c.edges.into_iter())
}
}
let add = add.to_v4(translation);
Record::FileMove { del, add, path }
}
Record3::FileDel {
del,
confirm,
contents,
path,
} => {
let mut del = del.to_v4(translation);
if let Atom::EdgeMap(ref mut e) = del {
if let Some(Atom3::EdgeMap(c)) = confirm {
let c = c.to_v4(translation);
e.edges.extend(c.edges.into_iter())
}
}
let contents = contents.map(|c| c.to_v4(translation));
Record::FileDel {
del,
contents,
path,
}
}
Record3::FileUndel {
undel,
confirm,
contents,
path,
} => {
let mut undel = undel.to_v4(translation);
if let Atom::EdgeMap(ref mut e) = undel {
if let Some(Atom3::EdgeMap(c)) = confirm {
let c = c.to_v4(translation);
e.edges.extend(c.edges.into_iter())
}
}
let contents = contents.map(|c| c.to_v4(translation));
Record::FileUndel {
undel,
contents,
path,
}
}
Record3::FileAdd {
add_name,
add_inode,
contents,
path,
} => Record::FileAdd {
add_name: add_name.to_v4(translation),
add_inode: add_inode.to_v4(translation),
contents: contents.map(|c| c.to_v4(translation)),
path,
},
Record3::SolveNameConflict {
name,
confirm,
path,
} => {
let mut name = name.to_v4(translation);
if let Atom::EdgeMap(ref mut e) = name {
if let Some(Atom3::EdgeMap(c)) = confirm {
let c = c.to_v4(translation);
e.edges.extend(c.edges.into_iter())
}
}
Record::SolveNameConflict { name, path }
}
Record3::UnsolveNameConflict {
name,
confirm,
path,
} => {
let mut name = name.to_v4(translation);
if let Atom::EdgeMap(ref mut e) = name {
if let Some(Atom3::EdgeMap(c)) = confirm {
let c = c.to_v4(translation);
e.edges.extend(c.edges.into_iter())
}
}
Record::UnsolveNameConflict { name, path }
}
Record3::Edit { change, local } => Record::Edit {
change: change.to_v4(translation),
local,
},
Record3::Replacement {
change,
replacement,
local,
} => Record::Replacement {
change: change.to_v4(translation),
replacement: replacement.to_v4(translation),
local,
},
Record3::SolveOrderConflict { change, local } => Record::SolveOrderConflict {
change: change.to_v4(translation),
local,
},
Record3::UnsolveOrderConflict { change, local } => Record::UnsolveOrderConflict {
change: change.to_v4(translation),
local,
},
Record3::ResurrectZombies { alive, dead, local } => {
let mut change = alive.to_v4(translation);
if let Atom::EdgeMap(ref mut e) = change {
if let Atom3::EdgeMap(c) = dead {
let c = c.to_v4(translation);
e.edges.extend(c.edges.into_iter())
}
}
Record::ResurrectZombies { change, local }
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LocalChange3<Local> {
offsets: Offsets,
hashed: Hashed3<Local>,
unhashed: Option<toml::Value>,
contents: Vec<u8>,
}
const VERSION: u64 = 3;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct Hashed3<Local> {
version: u64,
header: ChangeHeader,
dependencies: BTreeSet<Hash>,
extra_known: BTreeSet<Hash>,
metadata: Vec<u8>,
changes: Vec<Record3<Option<Hash>, Local>>,
contents_hash: Hash,
}
impl Hashed3<Local> {
fn to_v4(self, translation: &HashMap<Option<Hash>, Option<Hash>>) -> Hashed<Local> {
Hashed {
version: 4,
header: self.header,
dependencies: self
.dependencies
.into_iter()
.map(|x| translation.get(&Some(x)).unwrap().unwrap())
.collect(),
extra_known: self
.extra_known
.into_iter()
.filter_map(|x| translation.get(&Some(x)).map(|x| x.unwrap()))
.collect(),
metadata: self.metadata,
contents_hash: self.contents_hash,
changes: self
.changes
.into_iter()
.map(|x| x.to_v4(translation))
.collect(),
}
}
}
impl LocalChange3<Local> {
const OFFSETS_SIZE: u64 = 56;
pub fn deserialize(file: &str, hash: Option<&Hash>) -> Result<Self, anyhow::Error> {
use std::io::Read;
let mut r = std::fs::File::open(file)?;
let mut buf = vec![0u8; Self::OFFSETS_SIZE as usize];
r.read_exact(&mut buf)?;
let offsets: Offsets = bincode::deserialize(&buf)?;
if offsets.version != VERSION {
return Err(Error::VersionMismatch.into());
}
debug!("offsets = {:?}", offsets);
buf.clear();
buf.resize((offsets.unhashed_off - Self::OFFSETS_SIZE) as usize, 0);
r.read_exact(&mut buf)?;
let hashed: Hashed3<Local> = {
let mut s = zstd_seekable::Seekable::init_buf(&buf[..])?;
let mut out = vec![0u8; offsets.hashed_len as usize];
s.decompress(&mut out[..], 0)?;
let mut hasher = Hasher::default();
hasher.update(&out);
let computed_hash = hasher.finish();
if let Some(hash) = hash {
if &computed_hash != hash {
return Err((Error::ChangeHashMismatch {
claimed: *hash,
computed: computed_hash,
})
.into());
}
}
bincode::deserialize_from(&out[..])?
};
buf.clear();
buf.resize((offsets.contents_off - offsets.unhashed_off) as usize, 0);
let unhashed = if buf.is_empty() {
None
} else {
r.read_exact(&mut buf)?;
let mut s = zstd_seekable::Seekable::init_buf(&buf[..])?;
let mut out = vec![0u8; offsets.unhashed_len as usize];
s.decompress(&mut out[..], 0)?;
Some(toml::de::from_slice(&out)?)
};
debug!("unhashed = {:?}", unhashed);
buf.clear();
buf.resize((offsets.total - offsets.contents_off) as usize, 0);
let contents = if r.read_exact(&mut buf).is_ok() {
let mut s = zstd_seekable::Seekable::init_buf(&buf[..])?;
let mut contents = vec![0u8; offsets.contents_len as usize];
s.decompress(&mut contents[..], 0)?;
contents
} else {
Vec::new()
};
debug!("contents = {:?}", contents);
Ok(LocalChange3 {
offsets,
hashed,
unhashed,
contents,
})
}
pub fn to_v4(self, translation: &HashMap<Option<Hash>, Option<Hash>>) -> LocalChange<Local> {
LocalChange {
offsets: self.offsets,
hashed: self.hashed.to_v4(translation),
unhashed: self.unhashed,
contents: self.contents,
}
}
}
use super::*;
use crate::changestore::*;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::io::BufRead;
#[derive(Debug, Error)]
pub enum TextDeError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
TomlDe(#[from] toml::de::Error),
#[error("Missing change [{0}]")]
MissingChange(usize),
}
#[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),
}
impl LocalChange<Local> {
const DEPS_LINE: &'static str = "# Dependencies\n";
const CHANGES_LINE: &'static str = "# Changes\n";
pub fn write_all_deps<F: FnMut(Hash) -> Result<(), ChangeError>>(
&self,
mut f: F,
) -> Result<(), ChangeError> {
for c in self.changes.iter() {
for c in c.iter() {
match *c {
Atom::NewVertex(ref n) => {
for change in n
.up_context
.iter()
.chain(n.down_context.iter())
.map(|c| c.change)
.chain(std::iter::once(n.inode.change))
{
if let Some(change) = change {
if let Hash::None = change {
continue;
}
f(change)?
}
}
}
Atom::EdgeMap(ref e) => {
for edge in e.edges.iter() {
for change in &[
edge.from.change,
edge.to.change,
edge.introduced_by,
e.inode.change,
] {
if let Some(change) = *change {
if let Hash::None = change {
continue;
}
f(change)?
}
}
}
}
}
}
}
Ok(())
}
pub fn write<W: Write, C: ChangeStore, F: FnMut(&Local, Position<Option<Hash>>) -> String>(
&self,
changes: &C,
hash: Option<Hash>,
mut file_name: F,
write_header: bool,
mut w: W,
) -> Result<(), TextSerError<C::Error>> {
if let Some(h) = hash {
// Check if we have the full contents
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 {
w.write_all(toml::ser::to_string_pretty(&self.header)?.as_bytes())?;
w.write_all(b"\n")?;
}
let mut hashes = HashMap::new();
let mut i = 2;
let mut needs_newline = false;
if !self.dependencies.is_empty() {
w.write_all(Self::DEPS_LINE.as_bytes())?;
needs_newline = true;
for dep in self.dependencies.iter() {
hashes.insert(*dep, i);
writeln!(w, "[{}] {}", i, dep.to_base32())?;
i += 1;
}
}
self.write_all_deps(|change| {
if let Entry::Vacant(e) = hashes.entry(change) {
e.insert(i);
if !needs_newline {
w.write_all(Self::DEPS_LINE.as_bytes())?;
needs_newline = true;
}
writeln!(w, "[{}]+{}", i, change.to_base32())?;
i += 1;
}
Ok(())
})?;
if !self.extra_known.is_empty() {
needs_newline = true;
for dep in self.extra_known.iter() {
writeln!(w, "[*] {}", dep.to_base32())?;
i += 1;
}
}
if !self.changes.is_empty() {
if needs_newline {
w.write_all(b"\n")?
}
w.write_all(Self::CHANGES_LINE.as_bytes())?;
for (n, rec) in self.changes.iter().enumerate() {
write!(w, "\n{}. ", n + 1)?;
rec.write(changes, &mut file_name, &hashes, &self.contents, &mut w)?
}
}
Ok(())
}
}
impl Change {
pub fn read_and_deps<R: BufRead, T: TxnT>(
r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
txn: &T,
channel: &ChannelRef<T>,
) -> Result<Self, TextDeError> {
let (mut change, extra_dependencies) = Self::read_(r, updatables)?;
let (mut deps, extra) = dependencies(txn, channel, 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_(r, updatables)?.0)
}
fn read_<R: BufRead>(
mut r: R,
updatables: &mut HashMap<usize, crate::InodeUpdate>,
) -> Result<(Self, HashSet<Hash>), TextDeError> {
use self::text_changes::*;
let mut section = Section::Header(String::new());
let mut change = Change {
offsets: Offsets::default(),
hashed: Hashed {
version: VERSION,
header: ChangeHeader {
authors: Vec::new(),
message: String::new(),
description: None,
timestamp: chrono::Utc::now(),
},
dependencies: Vec::new(),
extra_known: Vec::new(),
metadata: Vec::new(),
changes: Vec::new(),
contents_hash: Hasher::default().finish(),
},
unhashed: None,
contents: Vec::new(),
};
let conclude_section = |change: &mut Change,
section: Section,
contents: &mut Vec<u8>|
-> Result<(), TextDeError> {
match section {
Section::Header(ref s) => {
debug!("header = {:?}", s);
change.header = toml::de::from_str(&s)?;
Ok(())
}
Section::Deps => Ok(()),
Section::Changes {
mut changes,
current,
..
} => {
if has_newvertices(¤t) {
contents.push(0)
}
if let Some(c) = current {
debug!("next action = {:?}", c);
changes.push(c)
}
change.changes = changes;
Ok(())
}
}
};
let mut h = String::new();
let mut contents = Vec::new();
let mut deps = HashMap::new();
let mut extra_dependencies = HashSet::new();
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::CHANGES_LINE {
let section = std::mem::replace(
&mut section,
Section::Changes {
changes: Vec::new(),
current: None,
offsets: HashMap::new(),
},
);
conclude_section(&mut change, section, &mut contents)?;
} else {
use regex::Regex;
lazy_static! {
static ref DEPS: Regex = Regex::new(r#"\[(\d*|\*)\](\+| ) *(\S*)"#).unwrap();
static ref KNOWN: Regex = Regex::new(r#"(\S*)"#).unwrap();
}
match section {
Section::Header(ref mut s) => s.push_str(&h),
Section::Deps => {
if let Some(d) = DEPS.captures(&h) {
let hash = Hash::from_base32(d[3].as_bytes()).unwrap();
if let Ok(n) = d[1].parse() {
if &d[2] == " " {
change.hashed.dependencies.push(hash);
}
deps.insert(n, hash);
} else if &d[1] == "*" {
change.hashed.extra_known.push(hash);
} else {
extra_dependencies.insert(hash);
}
}
}
Section::Changes {
ref mut current,
ref mut changes,
ref mut offsets,
} => {
if let Some(next) =
Record::read(updatables, current, &mut contents, &deps, offsets, &h)?
{
debug!("next action = {:?}", next);
changes.push(next)
}
}
}
}
h.clear();
}
conclude_section(&mut change, section, &mut contents)?;
change.contents = contents;
change.contents_hash = {
let mut hasher = Hasher::default();
hasher.update(&change.contents);
hasher.finish()
};
Ok((change, extra_dependencies))
}
}
impl Record<Option<Hash>, Local> {
fn write<
W: std::io::Write,
C: ChangeStore,
F: FnMut(&Local, Position<Option<Hash>>) -> String,
>(
&self,
changes: &C,
mut file_name: F,
hashes: &HashMap<Hash, usize>,
change_contents: &[u8],
mut w: W,
) -> Result<(), TextSerError<C::Error>> {
use self::text_changes::*;
match self {
Record::FileMove { del, add, path } => match add {
Atom::NewVertex(ref add) => {
let name = std::str::from_utf8(
&change_contents[add.start.0 as usize + 2..add.end.0 as usize],
)
.unwrap();
let perms = crate::pristine::InodeMetadata::from_basename(
&change_contents[add.start.0 as usize..add.start.0 as usize + 2],
);
write!(w, "Moved: {:?} {:?} {:o} ", path, name, perms.0)?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &del)?;
write!(w, "up")?;
for c in add.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
write!(w, ", down")?;
for c in add.down_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
w.write_all(b"\n")?;
}
Atom::EdgeMap(_) => {
write!(w, "Moved: {:?} ", path)?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &add)?;
write_atom(&mut w, hashes, &del)?;
}
},
Record::FileDel {
del,
contents,
path,
} => {
write!(w, "File deletion: {:?} ", path)?;
write_pos(&mut w, hashes, del.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &del)?;
if let Some(ref contents) = contents {
write_atom(&mut w, hashes, &contents)?;
writeln!(w)?;
print_change_contents(&mut w, changes, contents, change_contents)?;
} else {
writeln!(w)?;
}
}
Record::FileUndel {
undel,
contents,
path,
} => {
write!(w, "File un-deletion: {:?} ", path)?;
write_pos(&mut w, hashes, undel.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &undel)?;
if let Some(ref contents) = contents {
write_atom(&mut w, hashes, &contents)?;
print_change_contents(&mut w, changes, contents, change_contents)?;
} else {
writeln!(w)?;
}
}
Record::FileAdd {
add_name,
contents,
path,
..
} => {
if let Atom::NewVertex(ref n) = add_name {
let name = std::str::from_utf8(
&change_contents[n.start.0 as usize + 2..n.end.0 as usize],
)
.unwrap();
let perms = crate::pristine::InodeMetadata::from_basename(
&change_contents[n.start.0 as usize..n.start.0 as usize + 2],
);
let parent = if let Some(p) = crate::path::parent(&path) {
if p.is_empty() {
"/"
} else {
p
}
} else {
"/"
};
write!(
w,
"File addition: {:?} in {:?} {:o}\n up",
name, parent, perms.0
)?;
assert!(n.down_context.is_empty());
for c in n.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
writeln!(w, ", new {}:{}", n.start.0, n.end.0)?;
}
if let Some(Atom::NewVertex(ref n)) = contents {
let c = &change_contents[n.start.0 as usize..n.end.0 as usize];
print_contents(&mut w, "+", c)?;
if !c.ends_with(b"\n") {
writeln!(w, "\\")?
}
}
}
Record::Edit { change, local } => {
write!(w, "Edit in {} ", file_name(&local, change.inode()))?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(&mut w, changes, change, change_contents)?;
}
Record::Replacement {
change,
replacement,
local,
} => {
write!(w, "Replacement in {} ", file_name(&local, change.inode()))?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
write_atom(&mut w, hashes, &replacement)?;
print_change_contents(&mut w, changes, change, change_contents)?;
print_change_contents(&mut w, changes, replacement, change_contents)?;
}
Record::SolveNameConflict { name, path } => {
write!(w, "Solving a name conflict in {:?} ", 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)?;
}
Record::UnsolveNameConflict { name, path } => {
write!(w, "Un-solving a name conflict in {:?} ", 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)?;
}
Record::SolveOrderConflict { change, local } => {
write!(
w,
"Solving an order conflict in {} ",
file_name(&local, change.inode())
)?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(&mut w, changes, change, change_contents)?;
}
Record::UnsolveOrderConflict { change, local } => {
write!(
w,
"Un-solving an order conflict in {} ",
file_name(&local, change.inode())
)?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(&mut w, changes, change, change_contents)?;
}
Record::ResurrectZombies { change, local } => {
write!(
w,
"Resurrecting zombie lines in {:?}:{} ",
local.path, local.line
)?;
write_pos(&mut w, hashes, change.inode())?;
writeln!(w)?;
write_atom(&mut w, hashes, &change)?;
print_change_contents(&mut w, changes, change, change_contents)?;
}
}
Ok(())
}
}
impl Record<Option<Hash>, Local> {
fn read(
updatables: &mut HashMap<usize, crate::InodeUpdate>,
current: &mut Option<Self>,
mut contents_: &mut Vec<u8>,
changes: &HashMap<usize, Hash>,
offsets: &mut HashMap<u64, ChangePosition>,
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>\d+)"#).unwrap();
static ref EDIT: Regex =
Regex::new(r#"(\d+)\. Edit in ([^:]+):(\d+) (\d+\.\d+)"#).unwrap();
static ref REPLACEMENT: Regex =
Regex::new(r#"(\d+)\. Replacement in ([^:]+):(\d+) (\d+\.\d+)"#).unwrap();
static ref FILE_DELETION: Regex =
Regex::new(r#"(\d+)\. File deletion: "([^"]*)" (\d+\.\d+)"#).unwrap();
static ref FILE_UNDELETION: Regex =
Regex::new(r#"(\d+)\. File un-deletion: "([^"]*)" (\d+\.\d+)"#).unwrap();
static ref MOVE: Regex =
Regex::new(r#"(\d+)\. Moved: "(?P<former>[^"]*)" "(?P<new>[^"]*)" (?P<perm>\d+) (?P<inode>.*)"#).unwrap();
static ref MOVE_: Regex = Regex::new(r#"(\d+)\. Moved: "([^"]*)" (.*)"#).unwrap();
static ref NAME_CONFLICT: Regex = Regex::new(
r#"(\d+)\. ((Solving)|(Un-solving)) a name conflict in "([^"]*)" (.*): .*"#
)
.unwrap();
static ref ORDER_CONFLICT: Regex = Regex::new(
r#"(\d+)\. ((Solving)|(Un-solving)) an order conflict in (.*):(\d+) (\d+\.\d+)"#
)
.unwrap();
static ref ZOMBIE: Regex =
Regex::new(r#"(\d+)\. Resurrecting zombie lines in (?P<path>"[^"]+"):(?P<line>\d+) (?P<inode>\d+\.\d+)"#)
.unwrap();
static ref CONTEXT: Regex = Regex::new(
r#"up ((\d+\.\d+ )*\d+\.\d+)(, new (\d+):(\d+))?(, down ((\d+\.\d+ )*\d+\.\d+))?"#
)
.unwrap();
}
if let Some(cap) = FILE_ADDITION.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add_name = default_newvertex();
add_name.start = ChangePosition(contents_.len() as u64);
add_name.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let name = &cap.name("name").unwrap().as_str();
let path = {
let parent = cap.name("parent").unwrap().as_str();
(if parent == "/" {
String::new()
} else {
parent.to_string()
}) + name
};
let meta = cap
.name("perm")
.unwrap()
.as_str()
.chars()
.fold(0, |x, c| x * 8 + (c as u16 - b'0' as u16));
let meta = InodeMetadata(meta);
meta.write(&mut contents_).unwrap();
contents_.extend(name.as_bytes());
add_name.end = ChangePosition(contents_.len() as u64);
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() as u64),
});
contents_.push(0);
add_inode.start = ChangePosition(contents_.len() as u64);
add_inode.end = ChangePosition(contents_.len() as u64);
contents_.push(0);
let n = cap.name("n").unwrap().as_str().parse().unwrap();
if let Entry::Occupied(mut e) = updatables.entry(n) {
if let crate::InodeUpdate::Add { ref mut pos, .. } = e.get_mut() {
*pos = add_inode.start
}
}
Ok(std::mem::replace(
current,
Some(Record::FileAdd {
add_name: Atom::NewVertex(add_name),
add_inode: Atom::NewVertex(add_inode),
contents: None,
path,
}),
))
} else if let Some(cap) = EDIT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut v = default_newvertex();
v.inode = parse_pos(changes, &cap[4]);
Ok(std::mem::replace(
current,
Some(Record::Edit {
change: Atom::NewVertex(v),
local: Local {
path: cap[2].to_string(),
line: cap[3].parse().unwrap(),
},
}),
))
} 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]);
Ok(std::mem::replace(
current,
Some(Record::Replacement {
change: Atom::NewVertex(v.clone()),
replacement: Atom::NewVertex(v),
local: Local {
path: cap[2].to_string(),
line: cap[3].parse().unwrap(),
},
}),
))
} 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(Record::FileDel {
del: Atom::EdgeMap(del),
contents: None,
path: cap[2].to_string(),
}),
))
} 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(Record::FileUndel {
undel: Atom::EdgeMap(undel),
contents: None,
path: cap[2].to_string(),
}),
))
} 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(Record::SolveNameConflict {
name: Atom::EdgeMap(name),
path: cap[5].to_string(),
})
} else {
Some(Record::UnsolveNameConflict {
name: Atom::EdgeMap(name),
path: cap[5].to_string(),
})
},
))
} else if let Some(cap) = MOVE.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add = default_newvertex();
add.start = ChangePosition(contents_.len() as u64);
add.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let name = cap.name("new").unwrap().as_str();
let meta = cap
.name("perm")
.unwrap()
.as_str()
.chars()
.fold(0, |x, c| x * 8 + (c as u16 - b'0' as u16));
let meta = InodeMetadata(meta);
meta.write(&mut contents_).unwrap();
contents_.extend(name.as_bytes());
add.end = ChangePosition(contents_.len() as u64);
let mut del = default_edgemap();
del.inode = parse_pos(changes, cap.name("inode").unwrap().as_str());
Ok(std::mem::replace(
current,
Some(Record::FileMove {
del: Atom::EdgeMap(del),
add: Atom::NewVertex(add),
path: cap[2].to_string(),
}),
))
} else if let Some(cap) = MOVE_.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut add = default_edgemap();
let mut del = default_edgemap();
add.inode = parse_pos(changes, &cap[3]);
del.inode = add.inode;
Ok(std::mem::replace(
current,
Some(Record::FileMove {
del: Atom::EdgeMap(del),
add: Atom::EdgeMap(add),
path: cap[2].to_string(),
}),
))
} else if let Some(cap) = ORDER_CONFLICT.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
Ok(std::mem::replace(
current,
Some(if &cap[2] == "Solving" {
let mut v = default_newvertex();
v.inode = parse_pos(changes, &cap[7]);
Record::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]);
Record::UnsolveOrderConflict {
change: Atom::EdgeMap(v),
local: Local {
path: cap[5].to_string(),
line: cap[6].parse().unwrap(),
},
}
}),
))
} else if let Some(cap) = ZOMBIE.captures(h) {
if has_newvertices(current) {
contents_.push(0)
}
let mut v = default_edgemap();
v.inode = parse_pos(changes, &cap.name("inode").unwrap().as_str());
Ok(std::mem::replace(
current,
Some(Record::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(),
},
}),
))
} else {
match current {
Some(Record::FileAdd {
ref mut contents,
ref mut add_name,
..
}) => {
if h.starts_with('+') {
if contents.is_none() {
let mut v = default_newvertex();
let inode = Position {
change: None,
pos: ChangePosition(contents_.len() as u64 - 1),
};
v.up_context.push(inode);
v.inode = inode;
v.start = ChangePosition(contents_.len() as u64);
*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_)
}
}
} else if h.starts_with('\\') {
if let Some(Atom::NewVertex(ref mut contents)) = contents {
if contents_[contents.end.0 as usize - 1] == b'\n' {
assert_eq!(contents.end.0 as usize, contents_.len());
contents_.pop();
contents.end.0 -= 1;
}
}
} 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(Record::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(Record::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(Record::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(Record::Edit { ref mut change, .. }) => {
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() as u64);
}
text_changes::parse_line_add(h, change, contents_)
}
} else if h.starts_with('\\') {
if let Atom::NewVertex(ref mut change) = change {
if contents_[change.end.0 as usize - 1] == b'\n' {
assert_eq!(change.end.0 as usize, 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(Record::Replacement {
ref mut change,
ref mut replacement,
..
}) => {
if h.starts_with("+ ") {
if let Atom::NewVertex(ref mut repl) = replacement {
if repl.start == repl.end {
repl.start = ChangePosition(contents_.len() as u64);
}
text_changes::parse_line_add(h, repl, contents_)
}
} else if h.starts_with('\\') {
if let Atom::NewVertex(ref mut repl) = replacement {
if contents_[repl.end.0 as usize - 1] == b'\n' {
assert_eq!(repl.end.0 as usize, 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(Record::SolveNameConflict { ref mut name, .. })
| Some(Record::UnsolveNameConflict { ref mut name, .. }) => {
if let Some(edges) = parse_edges(changes, h) {
*name = Atom::EdgeMap(EdgeMap {
edges,
inode: name.inode(),
})
}
Ok(None)
}
Some(Record::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() as u64);
}
text_changes::parse_line_add(h, change, contents_)
}
} 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() as u64);
change.end =
ChangePosition(contents_.len() as u64 + new_end - new_start);
offsets.insert(new_end, change.end);
}
}
}
Ok(None)
}
Some(Record::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(Record::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)
}
}
}
}
}
lazy_static! {
static ref POS: regex::Regex = regex::Regex::new(r#"(\d+)\.(\d+)"#).unwrap();
static ref EDGE: regex::Regex =
regex::Regex::new(r#"\s*(?P<prev>[BFD]*):(?P<flag>[BFD]*)\s+(?P<up_c>\d+)\.(?P<up_l>\d+)\s*->\s*(?P<c>\d+)\.(?P<l0>\d+):(?P<l1>\d+)/(?P<intro>\d+)\s*"#).unwrap();
}
pub fn default_newvertex() -> NewVertex<Option<Hash>> {
NewVertex {
start: ChangePosition(0),
end: ChangePosition(0),
flag: EdgeFlags::empty(),
up_context: Vec::new(),
down_context: Vec::new(),
inode: Position {
change: Some(Hash::None),
pos: ChangePosition(0),
},
}
}
pub fn default_edgemap() -> EdgeMap<Option<Hash>> {
EdgeMap {
edges: Vec::new(),
inode: Position {
change: Some(Hash::None),
pos: ChangePosition(0),
},
}
}
pub fn has_newvertices<L>(current: &Option<Record<Option<Hash>, L>>) -> bool {
match current {
Some(Record::FileAdd { contents: None, .. }) | None => false,
Some(rec) => rec.iter().any(|e| matches!(e, Atom::NewVertex(_))),
}
}
pub fn parse_pos_vec(
changes: &HashMap<usize, Hash>,
offsets: &HashMap<u64, ChangePosition>,
s: &str,
) -> Result<Vec<Position<Option<Hash>>>, TextDeError> {
let mut v = Vec::new();
for pos in POS.captures_iter(s) {
let change: usize = (&pos[1]).parse().unwrap();
let pos: u64 = (&pos[2]).parse().unwrap();
let pos = if change == 0 {
if let Some(&pos) = offsets.get(&pos) {
pos
} else {
debug!("inconsistent change: {:?} {:?}", s, offsets);
return Err(TextDeError::MissingChange(change));
}
} else {
ChangePosition(pos)
};
v.push(Position {
change: change_ref(changes, change),
pos,
})
}
Ok(v)
}
fn change_ref(changes: &HashMap<usize, Hash>, change: usize) -> Option<Hash> {
debug!("change_ref {:?} {:?}", changes, change);
if change == 0 {
None
} else if change == 1 {
Some(Hash::None)
} else {
Some(*changes.get(&change).unwrap())
}
}
pub fn parse_pos(changes: &HashMap<usize, Hash>, s: &str) -> Position<Option<Hash>> {
let pos = POS.captures(s).unwrap();
let change: usize = (&pos[1]).parse().unwrap();
let pos: u64 = (&pos[2]).parse().unwrap();
Position {
change: change_ref(changes, change),
pos: ChangePosition(pos),
}
}
pub fn parse_edges(changes: &HashMap<usize, Hash>, s: &str) -> Option<Vec<NewEdge<Option<Hash>>>> {
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(pos0),
},
to: Vertex {
change: change_ref(changes, change1),
start: ChangePosition(start1),
end: ChangePosition(end1),
},
introduced_by: change_ref(changes, introduced_by),
})
} else {
debug!("not parsed");
return None;
}
}
Some(result)
}
pub fn parse_line_add(h: &str, change: &mut NewVertex<Option<Hash>>, contents_: &mut Vec<u8>) {
let h = 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() as u64);
}
pub fn print_contents<W: std::io::Write>(
w: &mut W,
pref: &str,
contents: &[u8],
) -> Result<(), std::io::Error> {
if let Ok(mut contents) = std::str::from_utf8(&contents) {
while let Some(n) = contents.as_bytes().iter().position(|&c| c == b'\n') {
let (a, b) = contents.split_at(n + 1);
contents = b;
write!(w, "{} {}", pref, a)?;
}
if !contents.is_empty() {
writeln!(w, "{} {}", pref, contents)?;
}
} else {
writeln!(w, "{}b{}", pref, data_encoding::BASE64.encode(contents))?
}
Ok(())
}
pub fn print_change_contents<W: std::io::Write, C: ChangeStore>(
w: &mut W,
changes: &C,
change: &Atom<Option<Hash>>,
change_contents: &[u8],
) -> Result<(), TextSerError<C::Error>> {
match change {
Atom::NewVertex(ref n) => {
let c = &change_contents[n.start.0 as usize..n.end.0 as usize];
print_contents(w, "+", c)?;
if !c.ends_with(b"\n") {
writeln!(w, "\\")?
}
Ok(())
}
Atom::EdgeMap(ref n) if n.edges[0].flag.contains(EdgeFlags::DELETED) => {
let mut buf = Vec::new();
let mut current = None;
for e in n.edges.iter() {
if Some(e.to) == current {
continue;
}
buf.clear();
changes
.get_contents_ext(e.to, &mut buf)
.map_err(TextSerError::C)?;
print_contents(w, "-", &buf[..])?;
current = Some(e.to)
}
Ok(())
}
_ => Ok(()),
}
}
pub fn write_deleted_names<W: std::io::Write, C: ChangeStore>(
w: &mut W,
changes: &C,
del: &Atom<Option<Hash>>,
) -> Result<(), TextSerError<C::Error>> {
if let Atom::EdgeMap(ref e) = del {
let mut buf = Vec::new();
let mut is_first = true;
for d in e.edges.iter() {
buf.clear();
changes
.get_contents_ext(d.to, &mut buf)
.map_err(TextSerError::C)?;
if !buf.is_empty() {
let name = std::str::from_utf8(buf.split_at(2).1).unwrap();
write!(w, "{}{:?}", if is_first { "" } else { ", " }, name)?;
is_first = false;
}
}
}
Ok(())
}
pub fn write_flag<W: std::io::Write>(mut w: W, flag: EdgeFlags) -> Result<(), std::io::Error> {
if flag.contains(EdgeFlags::BLOCK) {
w.write_all(b"B")?;
}
if flag.contains(EdgeFlags::FOLDER) {
w.write_all(b"F")?;
}
if flag.contains(EdgeFlags::DELETED) {
w.write_all(b"D")?;
}
assert!(!flag.contains(EdgeFlags::PARENT));
assert!(!flag.contains(EdgeFlags::PSEUDO));
Ok(())
}
pub fn read_flag(s: &str) -> EdgeFlags {
let mut f = EdgeFlags::empty();
for i in s.chars() {
match i {
'B' => f |= EdgeFlags::BLOCK,
'F' => f |= EdgeFlags::FOLDER,
'D' => f |= EdgeFlags::DELETED,
c => panic!("read_flag: {:?}", c),
}
}
f
}
pub fn write_pos<W: std::io::Write>(
mut w: W,
hashes: &HashMap<Hash, usize>,
pos: Position<Option<Hash>>,
) -> Result<(), std::io::Error> {
let change = if let Some(Hash::None) = pos.change {
1
} else if let Some(ref c) = pos.change {
*hashes.get(c).unwrap()
} else {
0
};
write!(w, "{}.{}", change, pos.pos.0)?;
Ok(())
}
pub fn write_atom<W: std::io::Write>(
w: &mut W,
hashes: &HashMap<Hash, usize>,
atom: &Atom<Option<Hash>>,
) -> Result<(), std::io::Error> {
match atom {
Atom::NewVertex(ref n) => write_newvertex(w, hashes, n),
Atom::EdgeMap(ref n) => write_edgemap(w, hashes, n),
}
}
pub fn write_newvertex<W: std::io::Write>(
mut w: W,
hashes: &HashMap<Hash, usize>,
n: &NewVertex<Option<Hash>>,
) -> Result<(), std::io::Error> {
write!(w, " up")?;
for c in n.up_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
write!(w, ", new {}:{}", n.start.0, n.end.0)?;
if !n.down_context.is_empty() {
write!(w, ", down")?;
for c in n.down_context.iter() {
write!(w, " ")?;
write_pos(&mut w, hashes, *c)?
}
}
w.write_all(b"\n")?;
Ok(())
}
pub fn write_edgemap<W: std::io::Write>(
mut w: W,
hashes: &HashMap<Hash, usize>,
n: &EdgeMap<Option<Hash>>,
) -> Result<(), std::io::Error> {
let mut is_first = true;
for c in n.edges.iter() {
if !is_first {
write!(w, ", ")?;
}
is_first = false;
write_flag(&mut w, c.previous)?;
write!(w, ":")?;
write_flag(&mut w, c.flag)?;
write!(w, " ")?;
write_pos(&mut w, hashes, c.from)?;
write!(w, " -> ")?;
write_pos(&mut w, hashes, c.to.start_pos())?;
let h = if let Some(h) = hashes.get(c.introduced_by.as_ref().unwrap()) {
h
} else {
panic!("introduced_by = {:?}, not found", c.introduced_by);
};
write!(w, ":{}/{}", c.to.end.0, h)?;
}
writeln!(w)?;
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Section {
Header(String),
Deps,
Changes {
changes: Vec<Record<Option<Hash>, Local>>,
current: Option<Record<Option<Hash>, Local>>,
offsets: HashMap<u64, ChangePosition>,
},
}
use super::*;
/// An open, seekable change file.
#[cfg(feature = "zstd")]
pub struct ChangeFile<'a> {
s: Option<zstd_seekable::Seekable<'a, OffFile>>,
hashed: Hashed<Local>,
hash: Hash,
unhashed: Option<toml::Value>,
}
struct OffFile {
f: std::fs::File,
start: u64,
}
unsafe impl Send for OffFile {}
impl std::io::Read for OffFile {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
self.f.read(buf)
}
}
impl std::io::Seek for OffFile {
fn seek(&mut self, from: std::io::SeekFrom) -> Result<u64, std::io::Error> {
use std::io::SeekFrom;
let from = match from {
SeekFrom::Start(s) => SeekFrom::Start(s + self.start),
c => c,
};
self.f.seek(from)
}
}
#[cfg(feature = "zstd")]
impl<'a> ChangeFile<'a> {
/// Open a change file from a path.
pub fn open(hash: Hash, path: &str) -> Result<Self, ChangeError> {
use std::io::Read;
let mut r = std::fs::File::open(path)?;
let mut buf = Vec::new();
buf.resize(Change::OFFSETS_SIZE as usize, 0);
r.read_exact(&mut buf)?;
let offsets: Offsets = bincode::deserialize(&buf)?;
if offsets.version != VERSION {
return Err(ChangeError::VersionMismatch);
}
buf.clear();
buf.resize((offsets.unhashed_off - Change::OFFSETS_SIZE) as usize, 0);
r.read_exact(&mut buf)?;
let mut buf2 = vec![0u8; offsets.hashed_len as usize];
let hashed: Hashed<Local> = {
let mut s = zstd_seekable::Seekable::init_buf(&buf)?;
s.decompress(&mut buf2, 0)?;
bincode::deserialize(&buf2)?
};
buf.resize((offsets.contents_off - offsets.unhashed_off) as usize, 0);
let unhashed = if buf.is_empty() {
None
} else {
r.read_exact(&mut buf)?;
let mut s = zstd_seekable::Seekable::init_buf(&buf)?;
buf2.resize(offsets.unhashed_len as usize, 0);
s.decompress(&mut buf2, 0)?;
Some(toml::de::from_slice(&buf2)?)
};
let m = r.metadata()?;
let s = if offsets.contents_off >= m.len() {
None
} else {
Some(zstd_seekable::Seekable::init(Box::new(OffFile {
f: r,
start: offsets.contents_off,
}))?)
};
Ok(ChangeFile {
s,
hashed,
hash,
unhashed,
})
}
pub fn has_contents(&self) -> bool {
self.s.is_some()
}
/// Reads the contents at an offset into `buf`, and returns the
/// number of bytes read. The bounds of the change's "contents"
/// section are not checked.
pub fn read_contents(&mut self, offset: u64, buf: &mut [u8]) -> Result<usize, ChangeError> {
debug!("read_contents {:?} {:?}", offset, buf.len());
if let Some(ref mut s) = self.s {
Ok(s.decompress(buf, offset)?)
} else {
Err(ChangeError::MissingContents { hash: self.hash })
}
}
pub fn hashed(&self) -> &Hashed<Local> {
&self.hashed
}
pub fn unhashed(&self) -> &Option<toml::Value> {
&self.unhashed
}
}
// org id Fn0IzU5GRCA4xymOneQzYPhm7xvcbR8viQt+xz5AzSU=
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ApplyError<ChangestoreError: std::error::Error, TxnError: std::error::Error + 'static> {
#[error("Changestore error: {0}")]
Changestore(ChangestoreError),
#[error("Local change error: {err}")]
LocalChange {
#[from]
err: LocalApplyError<TxnError>,
},
}
#[derive(Debug, Error)]
pub enum LocalApplyError<TxnError: std::error::Error> {
#[error("Dependency missing: {:?}", hash)]
DependencyMissing { hash: crate::pristine::Hash },
#[error("Change already on channel: {:?}", hash)]
ChangeAlreadyOnChannel { hash: crate::pristine::Hash },
#[error("Transaction error: {0}")]
Txn(TxnError),
#[error(transparent)]
Block(#[from] crate::pristine::BlockError),
#[error(transparent)]
InconsistentChange(#[from] crate::pristine::InconsistentChange),
}
impl<TxnError: std::error::Error> LocalApplyError<TxnError> {
fn from_missing(err: MissingError<TxnError>) -> Self {
match err {
MissingError::Txn(e) => LocalApplyError::Txn(e),
MissingError::Block(e) => LocalApplyError::Block(e),
MissingError::Inconsistent(e) => LocalApplyError::InconsistentChange(e),
}
}
}
let merkle =
if let Some(m) = txn.put_changes(channel, change_id, channel.apply_counter, hash)? {
m
} else {
return Err((Error::ChangeAlreadyOnChannel { hash: *hash }).into());
};
let merkle = if let Some(m) = txn
.put_changes(channel, change_id, channel.apply_counter, hash)
.map_err(LocalApplyError::Txn)?
{
m
} else {
return Err(LocalApplyError::ChangeAlreadyOnChannel { hash: *hash });
};
channel.last_modified = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)?
.as_secs();
if let Ok(duration) =
std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH)
{
channel.last_modified = duration.as_secs();
}
F: Fn(
&mut T,
&mut Channel<T>,
Vertex<ChangeId>,
Vertex<ChangeId>,
) -> Result<bool, anyhow::Error>,
E: From<LocalApplyError<T::Error>>,
F: Fn(&mut T, &mut Channel<T>, Vertex<ChangeId>, Vertex<ChangeId>) -> Result<bool, E>,
changes.get_contents(|p| txn.get_external(p), vertex, buf)?;
Ok(())
let now = std::time::Instant::now();
let result = changes
.get_contents(|p| txn.get_external(p), vertex, buf)
.map(|_| ())
.map_err(FileError::Changestore);
crate::TIMERS.lock().unwrap().alive_contents += now.elapsed();
result