libpijul_compat/
lib.rs

1//! This crate contains the core API to access Pijul repositories.
2//!
3//! The key object is a `Repository`, on which `Txn` (immutable
4//! transactions) and `MutTxn` (mutable transactions) can be started,
5//! to perform a variety of operations.
6//!
7//! Another important object is a `Patch`, which encodes two different pieces of information:
8//!
9//! - Information about deleted and inserted lines between two versions of a file.
10//!
11//! - Information about file moves, additions and deletions.
12//!
13//! The standard layout of a repository is defined in module
14//! `fs_representation`, and mainly consists of a directory called
15//! `.pijul` at the root of the repository, containing:
16//!
17//! - a directory called `pristine`, containing a Sanakirja database
18//! storing most of the repository information.
19//!
20//! - a directory called `patches`, actually containing the patches,
21//! where each patch is a gzipped compression of the bincode encoding
22//! of the `patch::Patch` type.
23//!
24//! At the moment, users of this library, such as the Pijul
25//! command-line tool, may use other files in the `.pijul` directory,
26//! such as user preferences, or information about remote branches and
27//! repositories.
28#![recursion_limit = "128"]
29#[macro_use]
30extern crate bitflags;
31extern crate chrono;
32#[macro_use]
33extern crate log;
34
35extern crate base64;
36extern crate bincode;
37extern crate bs58;
38extern crate byteorder;
39#[macro_use]
40extern crate error_chain;
41extern crate flate2;
42extern crate hex;
43extern crate ignore;
44extern crate openssl;
45extern crate rand;
46extern crate sanakirja;
47extern crate serde;
48#[macro_use]
49extern crate serde_derive;
50extern crate serde_json;
51extern crate tempdir;
52extern crate thrussh_keys;
53
54use std::path::Path;
55use std::collections::HashSet;
56use std::io::Write;
57use std::path::PathBuf;
58pub use sanakirja::Transaction;
59
60error_chain! {
61    foreign_links {
62        IO(std::io::Error);
63        Sanakirja(sanakirja::Error);
64        Bincode(bincode::Error);
65        Utf8(std::str::Utf8Error);
66        Serde(serde_json::Error);
67        OpenSSL(openssl::error::Error);
68        OpenSSLStack(openssl::error::ErrorStack);
69        Keys(thrussh_keys::Error);
70        Base58Decode(bs58::decode::DecodeError);
71    }
72    errors {
73        AlreadyAdded {}
74        FileNotInRepo(path: PathBuf) {
75            description("File not tracked")
76                display("File {:?} not tracked", path.display())
77        }
78        NoDb(root: backend::Root) {}
79        WrongHash {}
80        EOF {}
81        WrongPatchSignature {
82            description("Wrong patch signature")
83                display("Wrong patch signature")
84        }
85        BranchNameAlreadyExists(name: String) {
86            description("Branch name already exists")
87                display("Branch name {:?} already exists", name)
88        }
89        WrongFileHeader(node: Key<PatchId>) {
90            description("Wrong file header (possible branch corruption)")
91                display("Wrong file header (possible branch corruption): {:?}", node)
92        }
93        FileNameCount(node: Key<PatchId>) {
94            description("A file name doesn't have exactly one child")
95                display("A file name doesn't have exactly one child: {:?}", node)
96        }
97    }
98}
99
100impl Error {
101    pub fn lacks_space(&self) -> bool {
102        match self.0 {
103            ErrorKind::Sanakirja(sanakirja::Error::NotEnoughSpace) => true,
104            _ => false,
105        }
106    }
107}
108
109#[macro_use]
110mod backend;
111pub mod fs_representation;
112mod file_operations;
113
114pub mod patch;
115
116mod conflict;
117pub mod graph;
118mod optimal_diff;
119mod record;
120pub mod apply;
121mod output;
122mod unrecord;
123
124pub use backend::{ApplyTimestamp, Branch, Edge, EdgeFlags, FileId, FileMetadata, FileStatus,
125                  GenericTxn, Hash, HashRef, Inode, Key, LineId, MutTxn, OwnedFileId, PatchId,
126                  Repository, SmallStr, SmallString, Txn, DEFAULT_BRANCH, ROOT_INODE, ROOT_KEY};
127
128pub use record::{InodeUpdate, RecordState};
129pub use patch::{Patch, PatchHeader};
130pub use sanakirja::value::Value;
131pub use output::{Prefixes, ToPrefixes};
132use fs_representation::ID_LENGTH;
133use std::io::Read;
134use rand::Rng;
135
136impl<'env, T: rand::Rng> backend::MutTxn<'env, T> {
137    pub fn output_changes_file<P: AsRef<Path>>(&mut self, branch: &Branch, path: P) -> Result<()> {
138        let changes_file =
139            fs_representation::branch_changes_file(path.as_ref(), branch.name.as_str());
140        let mut branch_id: Vec<u8> = vec![b'\n'; ID_LENGTH + 1];
141        {
142            if let Ok(mut file) = std::fs::File::open(&changes_file) {
143                file.read_exact(&mut branch_id)?;
144            }
145        }
146        let mut branch_id = if let Ok(s) = String::from_utf8(branch_id) {
147            s
148        } else {
149            "\n".to_string()
150        };
151        if branch_id.as_bytes()[0] == b'\n' {
152            branch_id.truncate(0);
153            let mut rng = rand::thread_rng();
154            branch_id.extend(rng.gen_ascii_chars().take(ID_LENGTH));
155            branch_id.push('\n');
156        }
157
158        let mut file = std::fs::File::create(&changes_file)?;
159        file.write_all(&branch_id.as_bytes())?;
160        for (s, hash) in self.iter_applied(&branch, None) {
161            let hash_ext = self.get_external(hash).unwrap();
162            writeln!(file, "{}:{}", hash_ext.to_base58(), s)?
163        }
164        Ok(())
165    }
166
167    pub fn branch_patches(&mut self, branch: &Branch) -> HashSet<(backend::Hash, ApplyTimestamp)> {
168        self.iter_patches(branch, None)
169            .map(|(patch, time)| (self.external_hash(patch).to_owned(), time))
170            .collect()
171    }
172
173    pub fn fork(&mut self, branch: &Branch, new_name: &str) -> Result<Branch> {
174        if branch.name.as_str() == new_name {
175            Err(ErrorKind::BranchNameAlreadyExists(new_name.to_string()).into())
176        } else {
177            Ok(Branch {
178                db: self.txn.fork(&mut self.rng, &branch.db)?,
179                patches: self.txn.fork(&mut self.rng, &branch.patches)?,
180                revpatches: self.txn.fork(&mut self.rng, &branch.revpatches)?,
181                name: SmallString::from_str(new_name),
182                apply_counter: branch.apply_counter,
183            })
184        }
185    }
186    pub fn add_file<P: AsRef<Path>>(&mut self, path: P, is_dir: bool) -> Result<()> {
187        self.add_inode(None, path.as_ref(), is_dir)
188    }
189
190    fn file_nodes_fold_<A, F: FnMut(A, Key<PatchId>) -> A>(
191        &self,
192        branch: &Branch,
193        root: Key<PatchId>,
194        level: usize,
195        mut init: A,
196        f: &mut F,
197    ) -> Result<A> {
198        for (k, v) in self.iter_nodes(&branch, Some((root, None)))
199            .take_while(|&(k, v)| {
200                k.is_root() && v.flag.contains(EdgeFlags::FOLDER_EDGE)
201                    && !v.flag.contains(EdgeFlags::PARENT_EDGE)
202            }) {
203            debug!("file_nodes_fold_: {:?} {:?}", k, v);
204            if level & 1 == 0 && level > 0 {
205                init = f(init, k)
206            }
207            init = self.file_nodes_fold_(branch, v.dest, level + 1, init, f)?
208        }
209        Ok(init)
210    }
211
212    pub fn file_nodes_fold<A, F: FnMut(A, Key<PatchId>) -> A>(
213        &self,
214        branch: &Branch,
215        init: A,
216        mut f: F,
217    ) -> Result<A> {
218        self.file_nodes_fold_(branch, ROOT_KEY, 0, init, &mut f)
219    }
220}
221
222impl<T: Transaction, R> backend::GenericTxn<T, R> {
223    /// Tells whether a `key` is alive in `branch`, i.e. is either the
224    /// root, or all its ingoing edges are alive.
225    pub fn is_alive(&self, branch: &Branch, key: Key<PatchId>) -> bool {
226        debug!("is_alive {:?}?", key);
227        let mut alive = key == ROOT_KEY;
228        let e = Edge::zero(EdgeFlags::PARENT_EDGE);
229        for (k, v) in self.iter_nodes(&branch, Some((key, Some(&e)))) {
230            if k != key {
231                break;
232            }
233            alive = alive
234                || (!v.flag.contains(EdgeFlags::DELETED_EDGE)
235                    && !v.flag.contains(EdgeFlags::PSEUDO_EDGE))
236        }
237        alive
238    }
239
240    /// Tells whether a `key` is alive or zombie in `branch`, i.e. is
241    /// either the root, or has at least one of its incoming alive
242    /// edge is alive.
243    pub fn is_alive_or_zombie(&self, branch: &Branch, key: Key<PatchId>) -> bool {
244        debug!("is_alive_or_zombie {:?}?", key);
245        if key == ROOT_KEY {
246            return true;
247        }
248        let e = Edge::zero(EdgeFlags::PARENT_EDGE);
249        for (k, v) in self.iter_nodes(&branch, Some((key, Some(&e)))) {
250            if k != key {
251                break;
252            }
253            debug!("{:?}", v);
254            if v.flag.contains(EdgeFlags::PARENT_EDGE) && !v.flag.contains(EdgeFlags::DELETED_EDGE)
255            {
256                return true;
257            }
258        }
259        false
260    }
261
262    /// Test whether `key` has a neighbor with flag `flag0`. If
263    /// `include_pseudo`, this includes pseudo-neighbors.
264    pub fn has_edge(
265        &self,
266        branch: &Branch,
267        key: Key<PatchId>,
268        min: EdgeFlags,
269        max: EdgeFlags,
270    ) -> bool {
271        let e = Edge::zero(min);
272        if let Some((k, v)) = self.iter_nodes(&branch, Some((key, Some(&e)))).next() {
273            debug!("has_edge {:?}", v.flag);
274            k == key && (v.flag <= max)
275        } else {
276            false
277        }
278    }
279
280    /// Tells which paths (of folder nodes) a key is in.
281    pub fn get_file<'a>(&'a self, branch: &Branch, key: Key<PatchId>) -> Vec<Key<PatchId>> {
282        let mut stack = vec![key.to_owned()];
283        let mut seen = HashSet::new();
284        let mut names = Vec::new();
285        loop {
286            match stack.pop() {
287                None => break,
288                Some(key) if !seen.contains(&key) => {
289                    debug!("key {:?}, None", key);
290                    seen.insert(key.clone());
291                    let e = Edge::zero(EdgeFlags::PARENT_EDGE);
292                    for (_, v) in self.iter_nodes(branch, Some((key, None)))
293                        .take_while(|&(k, _)| k == key)
294                    {
295                        debug!("all_edges: {:?}", v);
296                    }
297                    for (_, v) in self.iter_nodes(branch, Some((key, Some(&e))))
298                        .take_while(|&(k, _)| k == key)
299                    {
300                        debug!("get_file {:?}", v);
301                        if v.flag | EdgeFlags::PSEUDO_EDGE
302                            == EdgeFlags::PARENT_EDGE | EdgeFlags::PSEUDO_EDGE
303                        {
304                            debug!("push!");
305                            stack.push(v.dest.clone())
306                        } else if v.flag
307                            .contains(EdgeFlags::PARENT_EDGE | EdgeFlags::FOLDER_EDGE)
308                        {
309                            names.push(key);
310                        }
311                    }
312                }
313                _ => {}
314            }
315        }
316        debug!("get_file returning {:?}", names);
317        names
318    }
319
320    pub fn get_file_names<'a>(
321        &'a self,
322        branch: &Branch,
323        key: Key<PatchId>,
324    ) -> Vec<(Key<PatchId>, Vec<&'a str>)> {
325        let mut names = vec![(key, Vec::new())];
326        debug!("inode: {:?}", names);
327        // Go back to the root.
328        let mut next_names = Vec::new();
329        let mut only_roots = false;
330        let mut inodes = HashSet::new();
331        while !only_roots {
332            next_names.clear();
333            only_roots = true;
334            for (inode, names) in names.drain(..) {
335                if !inodes.contains(&inode) {
336                    inodes.insert(inode.clone());
337
338                    if inode != ROOT_KEY {
339                        only_roots = false;
340                    }
341                    let names_ = self.file_names(branch, inode);
342                    if names_.is_empty() {
343                        next_names.push((inode, names));
344                        break;
345                    } else {
346                        debug!("names_ = {:?}", names_);
347                        for (inode_, _, base) in names_ {
348                            let mut names = names.clone();
349                            names.push(base);
350                            next_names.push((inode_, names))
351                        }
352                    }
353                }
354            }
355            std::mem::swap(&mut names, &mut next_names)
356        }
357        debug!("end: {:?}", names);
358        for &mut (_, ref mut name) in names.iter_mut() {
359            name.reverse()
360        }
361        names
362    }
363}
364
365fn make_remote<'a, I: Iterator<Item = &'a Hash>>(
366    target: &Path,
367    remote: I,
368) -> Result<(Vec<(Hash, Patch)>, usize)> {
369    use fs_representation::*;
370    use std::io::BufReader;
371    use std::fs::File;
372    let mut patches = Vec::new();
373    let mut patches_dir = patches_dir(target).to_path_buf();
374    let mut size_increase = 0;
375
376    for h in remote {
377        patches_dir.push(&patch_file_name(h.as_ref()));
378
379        debug!("opening {:?}", patches_dir);
380        let file = try!(File::open(&patches_dir));
381        let mut file = BufReader::new(file);
382        let (h, _, patch) = Patch::from_reader_compressed(&mut file)?;
383
384        size_increase += patch.size_upper_bound();
385        patches.push((h.clone(), patch));
386
387        patches_dir.pop();
388    }
389    Ok((patches, size_increase))
390}
391
392/// Apply a number of patches, guessing the new repository size.  If
393/// this fails, the repository size is guaranteed to have been
394/// increased by at least some pages, and it is safe to call this
395/// function again.
396///
397/// Also, this function takes a file lock on the repository.
398pub fn apply_resize<'a, I, F, P: output::ToPrefixes>(
399    target: &Path,
400    branch_name: &str,
401    remote: I,
402    partial_paths: P,
403    apply_cb: F,
404) -> Result<()>
405where
406    I: Iterator<Item = &'a Hash>,
407    F: FnMut(usize, &Hash),
408{
409    let (patches, size_increase) = make_remote(target, remote)?;
410    apply_resize_patches(
411        target,
412        branch_name,
413        &patches,
414        size_increase,
415        partial_paths,
416        apply_cb,
417    )
418}
419
420/// A version of `apply_resize` with the patches list already loaded.
421pub fn apply_resize_patches<'a, F, P: output::ToPrefixes>(
422    target: &Path,
423    branch_name: &str,
424    patches: &[(Hash, Patch)],
425    size_increase: usize,
426    partial_paths: P,
427    apply_cb: F,
428) -> Result<()>
429where
430    F: FnMut(usize, &Hash),
431{
432    use fs_representation::*;
433    info!("applying patches with size_increase {:?}", size_increase);
434    let pristine_dir = pristine_dir(target).to_path_buf();
435    let repo = Repository::open(pristine_dir, Some(size_increase as u64))?;
436    let mut txn = repo.mut_txn_begin(rand::thread_rng())?;
437    let mut branch = txn.open_branch(branch_name)?;
438    txn.apply_patches(&mut branch, target, &patches, partial_paths, apply_cb)?;
439    txn.commit_branch(branch)?;
440    txn.commit().map_err(Into::into)
441}
442
443/// Apply a number of patches, guessing the new repository size.  If
444/// this fails, the repository size is guaranteed to have been
445/// increased by at least some pages, and it is safe to call this
446/// function again.
447///
448/// Also, this function takes a file lock on the repository.
449pub fn apply_resize_no_output<'a, F, I>(
450    target: &Path,
451    branch_name: &str,
452    remote: I,
453    apply_cb: F,
454) -> Result<()>
455where
456    I: Iterator<Item = &'a Hash>,
457    F: FnMut(usize, &Hash),
458{
459    let (patches, size_increase) = make_remote(target, remote)?;
460    apply_resize_patches_no_output(target, branch_name, &patches, size_increase, apply_cb)
461}
462
463pub fn apply_resize_patches_no_output<'a, F>(
464    target: &Path,
465    branch_name: &str,
466    patches: &[(Hash, Patch)],
467    size_increase: usize,
468    mut apply_cb: F,
469) -> Result<()>
470where
471    F: FnMut(usize, &Hash),
472{
473    use fs_representation::*;
474    debug!("apply_resize_no_output: patches = {:?}", patches);
475    let pristine_dir = pristine_dir(target).to_path_buf();
476    let repo = try!(Repository::open(pristine_dir, Some(size_increase as u64)));
477    let mut txn = try!(repo.mut_txn_begin(rand::thread_rng()));
478    let mut branch = txn.open_branch(branch_name)?;
479    let mut new_patches_count = 0;
480    for &(ref p, ref patch) in patches.iter() {
481        debug!("apply_patches: {:?}", p);
482        txn.apply_patches_rec(&mut branch, &patches, p, patch, &mut new_patches_count)?;
483        apply_cb(new_patches_count, p);
484    }
485    info!("branch: {:?}", branch);
486    txn.commit_branch(branch)?;
487    txn.commit()?;
488    Ok(())
489}
490
491/// Open the repository, and unrecord the patch, without increasing
492/// the size. If this fails, the repository file is guaranteed to have
493/// been increased by `increase` bytes.
494pub fn unrecord_no_resize(
495    repo_dir: &Path,
496    repo_root: &Path,
497    branch_name: &str,
498    selected: &mut Vec<(Hash, Patch)>,
499    increase: u64,
500) -> Result<()> {
501    debug!("unrecord_no_resize: {:?}", repo_dir);
502    let repo = try!(Repository::open(repo_dir, Some(increase)));
503
504    let mut txn = try!(repo.mut_txn_begin(rand::thread_rng()));
505    let mut branch = txn.open_branch(branch_name)?;
506    let mut timestamps = Vec::new();
507    while let Some((hash, patch)) = selected.pop() {
508        let internal = txn.get_internal(hash.as_ref()).unwrap().to_owned();
509        debug!("Unrecording {:?}", hash);
510        if let Some(ts) = txn.get_patch(&branch.patches, internal) {
511            timestamps.push(ts);
512        }
513        txn.unrecord(&mut branch, internal, &patch)?;
514        debug!("Done unrecording {:?}", hash);
515    }
516
517    if let Err(e) = txn.output_changes_file(&branch, repo_root) {
518        error!("no changes file: {:?}", e)
519    }
520    try!(txn.commit_branch(branch));
521    try!(txn.commit());
522    Ok(())
523}