#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate pijul_macros;
#[macro_use]
extern crate thiserror;
#[macro_use]
extern crate lazy_static;
pub mod alive;
mod apply;
pub mod change;
pub mod changestore;
mod diff;
mod find_alive;
pub mod fs;
mod missing_context;
pub mod output;
pub mod path;
pub mod pristine;
pub mod record;
pub mod small_string;
mod unrecord;
mod vector2;
pub mod vertex_buffer;
pub mod working_copy;
#[cfg(test)]
mod tests;
pub const DOT_DIR: &str = ".pijul";
#[derive(Debug, Error)]
pub enum Error {
#[error("File {:?} already in repository", path)]
FileAlreadyInRepo { path: String },
#[error("File {:?} not in repository", path)]
FileNotInRepo { path: String },
#[error("Cannot record from unrecorded prefix {:?}", path)]
UnrecordedPath { path: String },
#[error("File {:?} not found", path)]
FileNotFound { path: String },
#[error("Change not on channel {:?}", change_id)]
ChangeNotOnChannel { change_id: pristine::ChangeId },
#[error("Change is depended upon {:?}", change_id)]
ChangeIsDependedUpon { change_id: pristine::ChangeId },
#[error("Change not found: {:?}", hash)]
ChangeNotFound { hash: String },
#[error("State not found: {:?}", state)]
StateNotFound { state: pristine::Merkle },
#[error("Change hash mismatch: {:?} != {:?}", claimed, computed)]
ChangeHashMismatch {
claimed: pristine::Hash,
computed: pristine::Hash,
},
#[error("Contents hash mismatch: {:?} != {:?}", claimed, computed)]
ContentsHashMismatch {
claimed: pristine::Hash,
computed: pristine::Hash,
},
#[error("Change already on channel: {:?}", hash)]
ChangeAlreadyOnChannel { hash: pristine::Hash },
#[error("Dependency missing: {:?}", hash)]
DependencyMissing { hash: pristine::Hash },
#[error("Channel name already taken: {:?}", name)]
ChannelNameExists { name: String },
#[error("Parse error: {:?}", s)]
ParseError { s: String },
#[error("Verify error, public key = {:?}", pk)]
VerifyError { pk: String },
#[error("Ambiguous hash prefix: {}", prefix)]
AmbiguousHashPrefix { prefix: String },
#[error("Inconsistent references in change")]
InconsistentChange,
#[error("Missing change contents: {}", hash)]
MissingContents { hash: String },
#[error("Wrong block: {:?}", block)]
WrongBlock {
block: pristine::Position<pristine::ChangeId>,
},
#[error("Pristine corrupt")]
PristineCorrupt,
#[error("Change version mismatch, please run `pijul upgrade`.")]
VersionMismatch,
#[error("The repository is locked by another process.")]
PristineLocked,
}
pub use crate::apply::Workspace as ApplyWorkspace;
pub use crate::apply::{ApplyError, LocalApplyError};
pub use crate::fs::{FsError, WorkingCopyIterator};
pub use crate::output::{Archive, Conflict};
pub use crate::pristine::{
Base32, ChangeId, ChannelRef, ChannelTxnT, DepsTxnT, EdgeFlags, GraphTxnT, Hash, Inode, Merkle,
MutTxnT, OwnedPathId, RemoteRef, TreeTxnT, TxnT, Vertex,
};
pub use crate::record::Builder as RecordBuilder;
pub use crate::record::{Algorithm, InodeUpdate};
use std::collections::HashMap;
impl MutTxnTExt for pristine::sanakirja::MutTxn<()> {}
impl TxnTExt for pristine::sanakirja::MutTxn<()> {}
impl TxnTExt for pristine::sanakirja::Txn {}
pub trait MutTxnTExt: pristine::MutTxnT {
fn apply_change_ws<C: changestore::ChangeStore>(
&mut self,
changes: &C,
channel: &mut pristine::ChannelRef<Self>,
hash: crate::pristine::Hash,
workspace: &mut ApplyWorkspace,
) -> Result<(u64, pristine::Merkle), crate::apply::ApplyError<C::Error, Self::GraphError>> {
crate::apply::apply_change_ws(changes, self, channel, hash, workspace)
}
fn apply_change_rec_ws<C: changestore::ChangeStore>(
&mut self,
changes: &C,
channel: &mut pristine::ChannelRef<Self>,
hash: crate::pristine::Hash,
workspace: &mut ApplyWorkspace,
) -> Result<(), crate::apply::ApplyError<C::Error, Self::GraphError>> {
crate::apply::apply_change_rec_ws(changes, self, channel, hash, workspace, false)
}
fn apply_change<C: changestore::ChangeStore>(
&mut self,
changes: &C,
channel: &mut pristine::ChannelRef<Self>,
hash: pristine::Hash,
) -> Result<(u64, pristine::Merkle), crate::apply::ApplyError<C::Error, Self::GraphError>> {
crate::apply::apply_change(changes, self, channel, hash)
}
fn apply_change_rec<C: changestore::ChangeStore>(
&mut self,
changes: &C,
channel: &mut pristine::ChannelRef<Self>,
hash: pristine::Hash,
) -> Result<(), crate::apply::ApplyError<C::Error, Self::GraphError>> {
crate::apply::apply_change_rec(changes, self, channel, hash, false)
}
fn apply_deps_rec<C: changestore::ChangeStore>(
&mut self,
changes: &C,
channel: &mut pristine::ChannelRef<Self>,
hash: pristine::Hash,
) -> Result<(), crate::apply::ApplyError<C::Error, Self::GraphError>> {
crate::apply::apply_change_rec(changes, self, channel, hash, true)
}
fn apply_local_change_ws(
&mut self,
channel: &mut pristine::ChannelRef<Self>,
change: &change::Change,
hash: pristine::Hash,
inode_updates: &HashMap<usize, InodeUpdate>,
workspace: &mut ApplyWorkspace,
) -> Result<(u64, pristine::Merkle), crate::apply::LocalApplyError<Self::GraphError>> {
crate::apply::apply_local_change_ws(self, channel, change, hash, inode_updates, workspace)
}
fn apply_local_change(
&mut self,
channel: &mut crate::pristine::ChannelRef<Self>,
change: &crate::change::Change,
hash: pristine::Hash,
inode_updates: &HashMap<usize, InodeUpdate>,
) -> Result<(u64, pristine::Merkle), crate::apply::LocalApplyError<Self::GraphError>> {
crate::apply::apply_local_change(self, channel, change, hash, inode_updates)
}
fn record<W: crate::working_copy::WorkingCopy, C: crate::changestore::ChangeStore>(
&mut self,
builder: &mut RecordBuilder,
diff_algorithm: Algorithm,
channel: &mut pristine::ChannelRef<Self>,
working_copy: &mut W,
changes: &C,
prefix: &str,
) -> Result<(), crate::record::RecordError<C::Error, W::Error, Self::GraphError>>
where
<W as crate::working_copy::WorkingCopy>::Error: 'static,
{
builder.record(
self,
diff_algorithm,
&mut channel.borrow_mut(),
working_copy,
changes,
prefix,
)
}
fn record_all<W: crate::working_copy::WorkingCopy, C: crate::changestore::ChangeStore>(
&mut self,
diff_algorithm: Algorithm,
channel: &mut pristine::ChannelRef<Self>,
working_copy: &mut W,
changes: &C,
prefix: &str,
) -> Result<record::Recorded, crate::record::RecordError<C::Error, W::Error, Self::GraphError>>
where
<W as crate::working_copy::WorkingCopy>::Error: 'static,
{
let mut builder = crate::record::Builder::new();
builder.record(
self,
diff_algorithm,
&mut channel.borrow_mut(),
working_copy,
changes,
prefix,
)?;
Ok(builder.finish())
}
fn apply_recorded<C: changestore::ChangeStore>(
&mut self,
channel: &mut pristine::ChannelRef<Self>,
recorded: record::Recorded,
changestore: &C,
) -> Result<pristine::Hash, crate::apply::ApplyError<C::Error, Self::GraphError>> {
let contents_hash = {
let mut hasher = pristine::Hasher::default();
hasher.update(&recorded.contents);
hasher.finish()
};
let change = change::LocalChange {
offsets: change::Offsets::default(),
hashed: change::Hashed {
version: change::VERSION,
contents_hash,
changes: recorded
.actions
.into_iter()
.map(|rec| rec.globalize(self).unwrap())
.collect(),
metadata: Vec::new(),
dependencies: Vec::new(),
extra_known: Vec::new(),
header: change::ChangeHeader::default(),
},
unhashed: None,
contents: recorded.contents,
};
let hash = changestore
.save_change(&change)
.map_err(apply::ApplyError::Changestore)?;
apply::apply_local_change(self, channel, &change, hash, &recorded.updatables)?;
Ok(hash)
}
fn unrecord<C: changestore::ChangeStore>(
&mut self,
changes: &C,
channel: &mut pristine::ChannelRef<Self>,
hash: &pristine::Hash,
) -> Result<bool, unrecord::UnrecordError<C::Error, Self::GraphError>> {
unrecord::unrecord(self, channel, changes, hash)
}
fn output_repository_no_pending<R: working_copy::WorkingCopy, C: changestore::ChangeStore>(
&mut self,
repo: &mut R,
changes: &C,
channel: &mut pristine::ChannelRef<Self>,
done: &mut HashMap<pristine::Position<ChangeId>, Vertex<ChangeId>>,
prefix: &str,
output_name_conflicts: bool,
if_modified_since: Option<std::time::SystemTime>,
) -> Result<Vec<output::Conflict>, output::OutputError<C::Error, Self::GraphError, R::Error>>
{
output::output_repository_no_pending(
repo,
changes,
self,
channel,
done,
prefix,
output_name_conflicts,
if_modified_since,
)
}
fn add_file(&mut self, path: &str) -> Result<(), fs::FsError<Self::GraphError>> {
fs::add_inode(self, None, path, false)
}
fn add_dir(&mut self, path: &str) -> Result<(), fs::FsError<Self::GraphError>> {
fs::add_inode(self, None, path, true)
}
fn add(&mut self, path: &str, is_dir: bool) -> Result<(), fs::FsError<Self::GraphError>> {
fs::add_inode(self, None, path, is_dir)
}
fn move_file(&mut self, a: &str, b: &str) -> Result<(), fs::FsError<Self::GraphError>> {
fs::move_file(self, a, b)
}
fn remove_file(&mut self, a: &str) -> Result<(), fs::FsError<Self::GraphError>> {
fs::remove_file(self, a)
}
#[cfg(feature = "dump")]
fn channel_from_dump<'a>(
&'a mut self,
name: &str,
) -> Result<
pristine::channel_dump::ChannelFromDump<'a, Self>,
pristine::channel_dump::ChannelDumpError<Self::GraphError>,
> {
use pristine::channel_dump::*;
if self.load_channel(name)?.is_none() {
let channel = pristine::MutTxnT::open_or_create_channel(self, name)
.map_err(ChannelDumpError::Txn)?;
Ok(ChannelFromDump::new(self, channel))
} else {
Err(ChannelDumpError::ChannelNameExists(name.to_string()))
}
}
fn archive_with_state<P: changestore::ChangeStore, A: Archive>(
&mut self,
changes: &P,
channel: &mut pristine::ChannelRef<Self>,
state: pristine::Merkle,
extra: &[pristine::Hash],
arch: &mut A,
) -> Result<Vec<output::Conflict>, output::ArchiveError<P::Error, Self::GraphError, A::Error>>
{
self.archive_prefix_with_state(
changes,
channel,
state,
extra,
&mut std::iter::empty(),
arch,
)
}
fn archive_prefix_with_state<
'a,
P: changestore::ChangeStore,
A: Archive,
I: Iterator<Item = &'a str>,
>(
&mut self,
changes: &P,
channel: &mut pristine::ChannelRef<Self>,
state: pristine::Merkle,
extra: &[pristine::Hash],
prefix: &mut I,
arch: &mut A,
) -> Result<Vec<output::Conflict>, output::ArchiveError<P::Error, Self::GraphError, A::Error>>
{
let mut unrecord = Vec::new();
let mut found = false;
for x in pristine::changeid_rev_log(self, &channel.borrow(), None)? {
let (_, (h, m)) = x?;
if m == state {
found = true;
break;
} else {
unrecord.push(h)
}
}
debug!("unrecord = {:?}", unrecord);
if found {
for h in unrecord.drain(..) {
let h = self.get_external(h)?.unwrap();
self.unrecord(changes, channel, &h)?;
}
for app in extra.iter() {
self.apply_change_rec(changes, channel, *app)?
}
output::archive(changes, self, channel, prefix, arch)
} else {
Err(output::ArchiveError::StateNotFound { state })
}
}
}
pub trait TxnTExt: pristine::TxnT {
fn is_directory(&self, inode: pristine::Inode) -> Result<bool, Self::TreeError> {
fs::is_directory(self, inode).map_err(|e| e.0)
}
fn is_tracked(&self, path: &str) -> Result<bool, Self::TreeError> {
fs::is_tracked(self, path).map_err(|e| e.0)
}
fn iter_working_copy(&self) -> WorkingCopyIterator<Self> {
fs::iter_working_copy(self, pristine::Inode::ROOT)
}
fn has_change(
&self,
channel: &pristine::ChannelRef<Self>,
hash: pristine::Hash,
) -> Result<Option<u64>, Self::GraphError> {
if let Some(cid) = pristine::GraphTxnT::get_internal(self, hash).map_err(|e| e.0)? {
self.get_changeset(Self::changes(&channel.borrow()), cid)
.map_err(|e| e.0)
} else {
Ok(None)
}
}
fn is_alive(
&self,
channel: &Self::Channel,
a: pristine::Vertex<pristine::ChangeId>,
) -> Result<bool, Self::GraphError> {
pristine::is_alive(self, Self::graph(channel), a).map_err(|e| e.0)
}
fn current_state(&self, channel: &Self::Channel) -> Result<pristine::Merkle, Self::GraphError> {
pristine::current_state(self, channel).map_err(|e| e.0)
}
fn log<'channel, 'txn>(
&'txn self,
channel: &'channel Self::Channel,
from: u64,
) -> Result<Log<'txn, Self>, Self::GraphError> {
Ok(Log {
txn: self,
iter: pristine::changeid_log(self, channel, from).map_err(|e| e.0)?,
})
}
fn log_for_path<'channel, 'txn>(
&'txn self,
channel: &'channel Self::Channel,
pos: pristine::Position<pristine::ChangeId>,
from: u64,
) -> Result<pristine::PathChangeset<'channel, 'txn, Self>, Self::GraphError> {
pristine::log_for_path(self, channel, pos, from).map_err(|e| e.0)
}
fn rev_log_for_path<'channel, 'txn>(
&'txn self,
channel: &'channel Self::Channel,
pos: pristine::Position<pristine::ChangeId>,
from: u64,
) -> Result<pristine::RevPathChangeset<'channel, 'txn, Self>, Self::DepsError> {
pristine::rev_log_for_path(self, channel, pos, from).map_err(|e| e.0)
}
fn reverse_log<'channel, 'txn>(
&'txn self,
channel: &'channel Self::Channel,
from: Option<u64>,
) -> Result<RevLog<'txn, Self>, Self::GraphError> {
Ok(RevLog {
txn: self,
iter: pristine::changeid_rev_log(self, channel, from).map_err(|e| e.0)?,
})
}
fn changeid_reverse_log<'txn>(
&'txn self,
channel: &Self::Channel,
from: Option<u64>,
) -> Result<
pristine::RevCursor<
Self,
&'txn Self,
Self::RevchangesetCursor,
u64,
(pristine::ChangeId, pristine::Merkle),
>,
Self::GraphError,
> {
pristine::changeid_rev_log(self, channel, from).map_err(|e| e.0)
}
fn get_changes(
&self,
channel: &pristine::ChannelRef<Self>,
n: u64,
) -> Result<Option<(pristine::Hash, pristine::Merkle)>, Self::GraphError> {
if let Some((h, m)) = self
.get_revchangeset(Self::rev_changes(&channel.borrow()), n)
.map_err(|e| e.0)?
{
Ok(Some((self.get_external(h).map_err(|e| e.0)?.unwrap(), m)))
} else {
Ok(None)
}
}
fn get_revchanges(
&self,
channel: &pristine::ChannelRef<Self>,
h: pristine::Hash,
) -> Result<Option<u64>, Self::GraphError> {
if let Some(h) = pristine::GraphTxnT::get_internal(self, h).map_err(|e| e.0)? {
self.get_changeset(Self::changes(&channel.borrow()), h)
.map_err(|e| e.0)
} else {
Ok(None)
}
}
fn touched_files(&self, h: pristine::Hash) -> Result<Option<Touched<Self>>, Self::DepsError> {
if let Some(id) = pristine::GraphTxnT::get_internal(self, h).map_err(|e| e.0)? {
Ok(Some(Touched {
txn: self,
iter: self.iter_rev_touched_files(id, None).map_err(|e| e.0)?,
id,
}))
} else {
Ok(None)
}
}
fn find_oldest_path<C: changestore::ChangeStore>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
position: pristine::Position<pristine::Hash>,
) -> Result<(String, bool), output::FileError<C::Error, Self::GraphError>> {
let position = pristine::Position {
change: pristine::GraphTxnT::get_internal(self, position.change)?.unwrap(),
pos: position.pos,
};
fs::find_path(changes, self, &channel.borrow(), false, position)
}
fn find_youngest_path<C: changestore::ChangeStore>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
position: pristine::Position<pristine::Hash>,
) -> Result<(String, bool), output::FileError<C::Error, Self::GraphError>> {
let position = pristine::Position {
change: pristine::GraphTxnT::get_internal(self, position.change)?.unwrap(),
pos: position.pos,
};
fs::find_path(changes, self, &channel.borrow(), true, position)
}
fn follow_oldest_path<C: changestore::ChangeStore>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
path: &str,
) -> Result<
(pristine::Position<pristine::ChangeId>, bool),
fs::FsErrorC<C::Error, Self::GraphError>,
> {
fs::follow_oldest_path(changes, self, &channel.borrow(), path)
}
fn output_file<C: changestore::ChangeStore, V: vertex_buffer::VertexBuffer>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
v0: pristine::Position<pristine::ChangeId>,
out: &mut V,
) -> Result<(), output::FileError<C::Error, Self::GraphError>> {
let mut forward = Vec::new();
let channel = channel.borrow();
let mut graph = alive::retrieve(self, Self::graph(&channel), v0)?;
alive::output_graph(changes, self, &channel, out, &mut graph, &mut forward)?;
Ok(())
}
fn archive<C: changestore::ChangeStore, A: Archive>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
arch: &mut A,
) -> Result<Vec<output::Conflict>, output::ArchiveError<C::Error, Self::GraphError, A::Error>>
{
output::archive(changes, self, channel, &mut std::iter::empty(), arch)
}
fn archive_prefix<'a, C: changestore::ChangeStore, I: Iterator<Item = &'a str>, A: Archive>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
prefix: &mut I,
arch: &mut A,
) -> Result<Vec<output::Conflict>, output::ArchiveError<C::Error, Self::GraphError, A::Error>>
{
output::archive(changes, self, channel, prefix, arch)
}
fn iter_adjacent<'txn>(
&'txn self,
graph: &'txn Self::Channel,
key: Vertex<pristine::ChangeId>,
min_flag: pristine::EdgeFlags,
max_flag: pristine::EdgeFlags,
) -> Result<pristine::AdjacentIterator<'txn, Self>, pristine::TxnErr<Self::GraphError>> {
pristine::iter_adjacent(self, Self::graph(graph), key, min_flag, max_flag)
}
}
pub struct Log<'txn, T: pristine::ChannelTxnT> {
txn: &'txn T,
iter: pristine::Cursor<
T,
&'txn T,
T::RevchangesetCursor,
u64,
(pristine::ChangeId, pristine::Merkle),
>,
}
impl<'txn, T: pristine::ChannelTxnT> Iterator for Log<'txn, T> {
type Item = Result<(u64, (pristine::Hash, pristine::Merkle)), T::GraphError>;
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next() {
Some(Ok((n, (c, m)))) => {
let ext = match self.txn.get_external(c) {
Err(pristine::TxnErr(e)) => return Some(Err(e)),
Ok(Some(ext)) => ext,
Ok(None) => panic!("Unknown change {:?}", c),
};
Some(Ok((n, (ext, m))))
}
None => None,
Some(Err(e)) => Some(Err(e.0)),
}
}
}
pub struct RevLog<'txn, T: pristine::ChannelTxnT> {
txn: &'txn T,
iter: pristine::RevCursor<
T,
&'txn T,
T::RevchangesetCursor,
u64,
(pristine::ChangeId, pristine::Merkle),
>,
}
impl<'txn, T: pristine::ChannelTxnT> Iterator for RevLog<'txn, T> {
type Item = Result<(u64, (pristine::Hash, pristine::Merkle)), T::GraphError>;
fn next(&mut self) -> Option<Self::Item> {
match self.iter.next() {
Some(Ok((n, (c, m)))) => match self.txn.get_external(c) {
Ok(Some(ext)) => Some(Ok((n, (ext, m)))),
Err(e) => Some(Err(e.0)),
Ok(None) => panic!("Unknown change {:?}", c),
},
None => None,
Some(Err(e)) => Some(Err(e.0)),
}
}
}
pub struct Touched<'txn, T: pristine::DepsTxnT> {
txn: &'txn T,
iter: pristine::Cursor<
T,
&'txn T,
T::Rev_touched_filesCursor,
pristine::ChangeId,
pristine::Position<pristine::ChangeId>,
>,
id: pristine::ChangeId,
}
impl<
'txn,
T: pristine::DepsTxnT + pristine::GraphTxnT<GraphError = <T as pristine::DepsTxnT>::DepsError>,
> Iterator for Touched<'txn, T>
{
type Item = Result<pristine::Position<pristine::Hash>, T::DepsError>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(x) = self.iter.next() {
let (cid, file) = match x {
Ok(x) => x,
Err(e) => return Some(Err(e.0)),
};
if cid > self.id {
return None;
} else if cid == self.id {
let change = match self.txn.get_external(file.change) {
Ok(ext) => ext.unwrap(),
Err(e) => return Some(Err(e.0)),
};
return Some(Ok(pristine::Position {
change,
pos: file.pos,
}));
}
}
None
}
}
#[doc(hidden)]
#[derive(Debug, Default, Clone)]
pub struct Timers {
pub alive_output: std::time::Duration,
pub alive_graph: std::time::Duration,
pub alive_retrieve: std::time::Duration,
pub alive_contents: std::time::Duration,
pub alive_write: std::time::Duration,
pub record: std::time::Duration,
pub apply: std::time::Duration,
pub repair_context: std::time::Duration,
pub check_cyclic_paths: std::time::Duration,
pub find_alive: std::time::Duration,
}
use std::sync::Mutex;
lazy_static! {
pub static ref TIMERS: Mutex<Timers> = Mutex::new(Timers {
alive_output: std::time::Duration::from_secs(0),
alive_graph: std::time::Duration::from_secs(0),
alive_retrieve: std::time::Duration::from_secs(0),
alive_contents: std::time::Duration::from_secs(0),
alive_write: std::time::Duration::from_secs(0),
record: std::time::Duration::from_secs(0),
apply: std::time::Duration::from_secs(0),
repair_context: std::time::Duration::from_secs(0),
check_cyclic_paths: std::time::Duration::from_secs(0),
find_alive: std::time::Duration::from_secs(0),
});
}
#[doc(hidden)]
pub fn reset_timers() {
*TIMERS.lock().unwrap() = Timers::default();
}
#[doc(hidden)]
pub fn get_timers() -> Timers {
TIMERS.lock().unwrap().clone()
}