libvctrl 0.3.0

A robust, content-addressed version control engine for arbitrary data, designed for embedding into applications.
Documentation
mod common;
use common::{encoder, hasher, put_blob, put_tree, setup_refs, setup_store};

use libvctrl::{
    Command, ConflictResolver, EntryKind, MergeCommand, Object, ObjectStore, ThreeWayMerger, Tree,
    TreeEntry, VctrlError,
};

struct SimpleResolver;
impl ConflictResolver for SimpleResolver {
    fn resolve(&self, _base: &[u8], _ours: &[u8], _theirs: &[u8]) -> Option<Vec<u8>> {
        None
    }
}

struct KeepOursResolver;
impl ConflictResolver for KeepOursResolver {
    fn resolve(&self, _base: &[u8], ours: &[u8], _theirs: &[u8]) -> Option<Vec<u8>> {
        Some(ours.to_vec())
    }
}

#[test]
fn merge_no_conflict() {
    let mut store = setup_store();
    let mut refs = setup_refs();
    let a1 = put_blob(&mut store, b"1");
    let b2 = put_blob(&mut store, b"2");

    let base_tree = Tree::new(vec![
        TreeEntry::new("a".into(), EntryKind::Blob, a1).unwrap(),
        TreeEntry::new("b".into(), EntryKind::Blob, b2).unwrap(),
    ])
    .unwrap();
    let base_hash = put_tree(&mut store, &base_tree);

    let b3 = put_blob(&mut store, b"3");
    let ours_tree = Tree::new(vec![
        TreeEntry::new("a".into(), EntryKind::Blob, a1).unwrap(),
        TreeEntry::new("b".into(), EntryKind::Blob, b3).unwrap(),
    ])
    .unwrap();
    let ours_hash = put_tree(&mut store, &ours_tree);

    let c4 = put_blob(&mut store, b"4");
    let theirs_tree = Tree::new(vec![
        TreeEntry::new("a".into(), EntryKind::Blob, a1).unwrap(),
        TreeEntry::new("b".into(), EntryKind::Blob, b2).unwrap(),
        TreeEntry::new("c".into(), EntryKind::Blob, c4).unwrap(),
    ])
    .unwrap();
    let theirs_hash = put_tree(&mut store, &theirs_tree);

    let cmd = MergeCommand {
        base: base_hash,
        ours: ours_hash,
        theirs: theirs_hash,
        merger: Box::new(ThreeWayMerger),
        resolver: Box::new(SimpleResolver),
        encoder: Box::new(encoder()),
        hasher: Box::new(hasher()),
    };
    let merged_hash = cmd.execute(&mut store, &mut refs).unwrap();
    let obj = store.get(&merged_hash).unwrap().unwrap();
    let tree = match obj {
        Object::Tree(t) => t,
        _ => panic!("expected tree"),
    };

    let mut entries = tree.into_entries();
    entries.sort_by(|a, b| a.name.cmp(&b.name));
    assert_eq!(entries.len(), 3);
    assert_eq!(entries[0].hash, a1);
    assert_eq!(entries[1].hash, b3);
    assert_eq!(entries[2].hash, c4);
}

#[test]
fn merge_conflict_blob() {
    let mut store = setup_store();
    let mut refs = setup_refs();
    let base_blob = put_blob(&mut store, b"base");
    let ours_blob = put_blob(&mut store, b"ours");
    let theirs_blob = put_blob(&mut store, b"theirs");

    let base_tree = Tree::new(vec![
        TreeEntry::new("f".into(), EntryKind::Blob, base_blob).unwrap(),
    ])
    .unwrap();
    let base_hash = put_tree(&mut store, &base_tree);
    let ours_tree = Tree::new(vec![
        TreeEntry::new("f".into(), EntryKind::Blob, ours_blob).unwrap(),
    ])
    .unwrap();
    let ours_hash = put_tree(&mut store, &ours_tree);
    let theirs_tree = Tree::new(vec![
        TreeEntry::new("f".into(), EntryKind::Blob, theirs_blob).unwrap(),
    ])
    .unwrap();
    let theirs_hash = put_tree(&mut store, &theirs_tree);

    let cmd = MergeCommand {
        base: base_hash,
        ours: ours_hash,
        theirs: theirs_hash,
        merger: Box::new(ThreeWayMerger),
        resolver: Box::new(SimpleResolver),
        encoder: Box::new(encoder()),
        hasher: Box::new(hasher()),
    };
    let err = cmd.execute(&mut store, &mut refs).unwrap_err();
    match err {
        VctrlError::MergeConflict { entry, .. } => assert_eq!(entry, "f"),
        _ => panic!("expected MergeConflict"),
    }
}

#[test]
fn merge_resolved() {
    let mut store = setup_store();
    let mut refs = setup_refs();
    let base_blob = put_blob(&mut store, b"base");
    let ours_blob = put_blob(&mut store, b"ours");
    let theirs_blob = put_blob(&mut store, b"theirs");

    let base_tree = Tree::new(vec![
        TreeEntry::new("f".into(), EntryKind::Blob, base_blob).unwrap(),
    ])
    .unwrap();
    let base_hash = put_tree(&mut store, &base_tree);
    let ours_tree = Tree::new(vec![
        TreeEntry::new("f".into(), EntryKind::Blob, ours_blob).unwrap(),
    ])
    .unwrap();
    let ours_hash = put_tree(&mut store, &ours_tree);
    let theirs_tree = Tree::new(vec![
        TreeEntry::new("f".into(), EntryKind::Blob, theirs_blob).unwrap(),
    ])
    .unwrap();
    let theirs_hash = put_tree(&mut store, &theirs_tree);

    let cmd = MergeCommand {
        base: base_hash,
        ours: ours_hash,
        theirs: theirs_hash,
        merger: Box::new(ThreeWayMerger),
        resolver: Box::new(KeepOursResolver),
        encoder: Box::new(encoder()),
        hasher: Box::new(hasher()),
    };
    let merged_hash = cmd.execute(&mut store, &mut refs).unwrap();
    let obj = store.get(&merged_hash).unwrap().unwrap();
    let tree = match obj {
        Object::Tree(t) => t,
        _ => panic!("expected tree"),
    };
    let entries = tree.into_entries();
    assert_eq!(entries.len(), 1);
    let blob = match store.get(&entries[0].hash).unwrap().unwrap() {
        Object::Blob(b) => b,
        _ => panic!("expected blob"),
    };
    assert_eq!(blob.into_bytes(), b"ours");
}