#[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: &'static 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::fs::WorkingCopyIterator;
pub use crate::output::{Archive, Conflict};
pub use crate::pristine::{
Base32, ChangeId, Channel, ChannelRef, EdgeFlags, Hash, Inode, Merkle, MutTxnT, OwnedPathId,
RemoteRef, 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), anyhow::Error> {
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<(), anyhow::Error> {
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), anyhow::Error> {
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<(), anyhow::Error> {
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<(), anyhow::Error> {
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), anyhow::Error> {
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), anyhow::Error> {
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<(), anyhow::Error> {
builder.record(self, diff_algorithm, channel, 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, anyhow::Error> {
let mut builder = crate::record::Builder::new();
builder.record(self, diff_algorithm, channel, 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, anyhow::Error> {
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))
.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)?;
apply::apply_local_change(self, channel, &change, hash.clone(), &recorded.updatables)?;
Ok(hash)
}
fn unrecord<C: changestore::ChangeStore>(
&mut self,
changes: &C,
channel: &mut pristine::ChannelRef<Self>,
hash: &pristine::Hash,
) -> Result<bool, anyhow::Error> {
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>,
prefix: &str,
output_name_conflicts: bool,
) -> Result<Vec<output::Conflict>, anyhow::Error> {
output::output_repository_no_pending(
repo,
changes,
self,
channel,
prefix,
output_name_conflicts,
)
}
fn add_file(&mut self, path: &str) -> Result<(), anyhow::Error> {
fs::add_inode(self, None, path, false)
}
fn add_dir(&mut self, path: &str) -> Result<(), anyhow::Error> {
fs::add_inode(self, None, path, true)
}
fn add(&mut self, path: &str, is_dir: bool) -> Result<(), anyhow::Error> {
fs::add_inode(self, None, path, is_dir)
}
fn move_file(&mut self, a: &str, b: &str) -> Result<(), anyhow::Error> {
fs::move_file(self, a, b)
}
fn remove_file(&mut self, a: &str) -> Result<(), anyhow::Error> {
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>, anyhow::Error> {
if self.load_channel(name).is_none() {
let channel = pristine::MutTxnT::open_or_create_channel(self, name)?;
Ok(pristine::channel_dump::ChannelFromDump::new(self, channel))
} else {
Err((Error::ChannelNameExists {
name: name.to_string(),
})
.into())
}
}
fn archive_with_state<'a, 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>, anyhow::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>, anyhow::Error> {
let mut unrecord = Vec::new();
let mut found = false;
for (_, (h, m)) in pristine::changeid_rev_log(self, &channel.borrow(), None) {
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((Error::StateNotFound { state }).into())
}
}
}
pub trait TxnTExt: pristine::TxnT {
fn is_directory(&self, inode: pristine::Inode) -> bool {
fs::is_directory(self, inode)
}
fn is_tracked(&self, path: &str) -> bool {
fs::is_tracked(self, path)
}
fn iter_working_copy<'txn>(&'txn self) -> WorkingCopyIterator<'txn, Self> {
fs::iter_working_copy(self, pristine::Inode::ROOT)
}
fn has_change(
&self,
channel: &pristine::ChannelRef<Self>,
hash: pristine::Hash,
) -> Option<u64> {
let cid = if let Some(c) = pristine::TxnT::get_internal(self, hash) {
c
} else {
return None;
};
self.get_changeset(&channel.borrow().changes, cid, None)
}
fn is_alive(
&self,
channel: &pristine::Channel<Self>,
a: pristine::Vertex<pristine::ChangeId>,
) -> bool {
pristine::is_alive(self, channel, a)
}
fn current_state(&self, channel: &pristine::Channel<Self>) -> Option<pristine::Merkle> {
pristine::current_state(self, channel)
}
fn log<'channel, 'txn>(
&'txn self,
channel: &'channel pristine::Channel<Self>,
from: u64,
) -> Log<'txn, Self> {
Log {
txn: self,
iter: pristine::changeid_log(self, channel, from),
}
}
fn log_for_path<'channel, 'txn>(
&'txn self,
channel: &'channel pristine::Channel<Self>,
pos: pristine::Position<pristine::ChangeId>,
from: u64,
) -> pristine::PathChangeset<'channel, 'txn, Self> {
pristine::log_for_path(self, channel, pos, from)
}
fn rev_log_for_path<'channel, 'txn>(
&'txn self,
channel: &'channel pristine::Channel<Self>,
pos: pristine::Position<pristine::ChangeId>,
from: u64,
) -> pristine::RevPathChangeset<'channel, 'txn, Self> {
pristine::rev_log_for_path(self, channel, pos, from)
}
fn reverse_log<'channel, 'txn>(
&'txn self,
channel: &'channel pristine::Channel<Self>,
from: Option<u64>,
) -> RevLog<'txn, Self> {
RevLog {
txn: self,
iter: pristine::changeid_rev_log(self, channel, from),
}
}
fn changeid_reverse_log<'txn>(
&'txn self,
channel: &pristine::Channel<Self>,
from: Option<u64>,
) -> pristine::RevCursor<
Self,
&'txn Self,
Self::RevchangesetCursor,
u64,
(pristine::ChangeId, pristine::Merkle),
> {
pristine::changeid_rev_log(self, channel, from)
}
fn get_changes(
&self,
channel: &pristine::ChannelRef<Self>,
n: u64,
) -> Option<(pristine::Hash, pristine::Merkle)> {
self.get_revchangeset(&channel.borrow().revchanges, n, None)
.map(|(h, m)| (self.get_external(h).unwrap(), m))
}
fn get_revchanges(
&self,
channel: &pristine::ChannelRef<Self>,
h: pristine::Hash,
) -> Option<u64> {
if let Some(h) = pristine::TxnT::get_internal(self, h) {
self.get_changeset(&channel.borrow().changes, h, None)
} else {
None
}
}
fn touched_files<'txn>(&'txn self, h: pristine::Hash) -> Option<Touched<'txn, Self>> {
if let Some(id) = pristine::TxnT::get_internal(self, h) {
Some(Touched {
txn: self,
iter: self.iter_rev_touched_files(id, None),
id,
})
} else {
None
}
}
fn find_oldest_path<C: changestore::ChangeStore>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
position: pristine::Position<pristine::Hash>,
) -> Result<(String, bool), anyhow::Error> {
let position = pristine::Position {
change: pristine::TxnT::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), anyhow::Error> {
let position = pristine::Position {
change: pristine::TxnT::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), anyhow::Error> {
fs::follow_oldest_path(changes, self, channel, 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<(), anyhow::Error> {
let mut forward = Vec::new();
let channel = channel.borrow();
let mut graph = alive::retrieve(self, &channel, v0);
alive::output_graph(changes, self, &channel, out, &mut graph, &mut forward)?;
Ok(())
}
fn archive<'a, C: changestore::ChangeStore, A: Archive>(
&self,
changes: &C,
channel: &pristine::ChannelRef<Self>,
arch: &mut A,
) -> Result<Vec<output::Conflict>, anyhow::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>, anyhow::Error> {
output::archive(changes, self, channel, prefix, arch)
}
fn iter_adjacent<'db, 'txn: 'db>(
&'txn self,
channel: &'db pristine::Channel<Self>,
key: Vertex<pristine::ChangeId>,
min_flag: pristine::EdgeFlags,
max_flag: pristine::EdgeFlags,
) -> pristine::AdjacentIterator<'txn, Self> {
pristine::iter_adjacent(self, channel, key, min_flag, max_flag)
}
}
pub struct Log<'txn, T: pristine::TxnT> {
txn: &'txn T,
iter: pristine::Cursor<
T,
&'txn T,
T::RevchangesetCursor,
u64,
(pristine::ChangeId, pristine::Merkle),
>,
}
impl<'txn, T: pristine::TxnT> Iterator for Log<'txn, T> {
type Item = (u64, (pristine::Hash, pristine::Merkle));
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(|(n, (c, m))| (n, (self.txn.get_external(c).unwrap(), m)))
}
}
pub struct RevLog<'txn, T: pristine::TxnT> {
txn: &'txn T,
iter: pristine::RevCursor<
T,
&'txn T,
T::RevchangesetCursor,
u64,
(pristine::ChangeId, pristine::Merkle),
>,
}
impl<'txn, T: pristine::TxnT> Iterator for RevLog<'txn, T> {
type Item = (u64, (pristine::Hash, pristine::Merkle));
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(|(n, (c, m))| (n, (self.txn.get_external(c).unwrap(), m)))
}
}
pub struct Touched<'txn, T: pristine::TxnT> {
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::TxnT> Iterator for Touched<'txn, T> {
type Item = pristine::Position<pristine::Hash>;
fn next(&mut self) -> Option<Self::Item> {
while let Some((cid, file)) = self.iter.next() {
if cid > self.id {
return None;
} else if cid == self.id {
let change = self.txn.get_external(file.change).unwrap();
return Some(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()
}