use super::*;
use crate::working_copy::{WorkingCopy, WorkingCopyRead};
use std::io::Write;
#[test]
fn same_file_test() -> Result<(), anyhow::Error> {
same_file_("file", "alice", "bob")
}
#[test]
fn same_file_dirs_test() -> Result<(), anyhow::Error> {
same_file_("file", "alice/file", "bob/file")
}
fn same_file_(file: &str, alice: &str, bob: &str) -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
repo_alice.add_file(file, contents.to_vec());
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("alice").unwrap();
txn_alice.write().add_file(file, 0).unwrap();
let init_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let channel_bob = txn_bob.write().open_or_create_channel("bob").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &init_h).unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_alice.rename(file, alice)?;
txn_alice.write().move_file(file, alice, 0)?;
debug!("repo_bob = {:?}", repo_alice.list_files());
let alice_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
repo_bob.rename(file, bob)?;
txn_bob.write().move_file(file, bob, 0)?;
debug!("repo_bob = {:?}", repo_bob.list_files());
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_h)?;
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
match conflicts.iter().next().unwrap() {
Conflict::MultipleNames { .. } => {}
ref c => panic!("{:#?}", c),
}
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_h)?;
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
alice,
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("conflicts = {:#?}", conflicts);
}
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
bob,
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("conflicts = {:#?}", conflicts);
}
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
match conflicts.iter().next().unwrap() {
Conflict::MultipleNames { .. } => {}
ref c => panic!("{:#?}", c),
}
{
let txn_ = txn_bob.write();
let mut f = std::fs::File::create("/tmp/conflict0")?;
crate::pristine::debug(&*txn_, &txn_.graph(&*channel_bob.read()), &mut f)?;
}
info!("recording resolution");
let bob_solution = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
let txn_ = txn_bob.write();
let mut f = std::fs::File::create("/tmp/conflict1")?;
crate::pristine::debug(&*txn_, &txn_.graph(&*channel_bob.read()), &mut f)?;
panic!("conflicts = {:#?}", conflicts);
}
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_solution)?;
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("conflicts = {:#?}", conflicts);
}
debug!("repo_alice = {:?}", repo_alice.list_files());
debug!("repo_bob = {:?}", repo_bob.list_files());
debug_tree(&*txn_bob.read(), "debug_tree")?;
Ok(())
}
#[test]
fn same_name_test() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
repo_alice.add_file("file1", contents.to_vec());
repo_alice.add_file("file2", contents.to_vec());
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("alice")?;
txn_alice.write().add_file("file1", 0)?;
txn_alice.write().add_file("file2", 0)?;
info!("recording file additions");
debug!("working_copy = {:?}", repo_alice);
debug_tree(&*txn_alice.read(), "debug_tree")?;
let init_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let channel_bob = txn_bob.write().open_or_create_channel("bob").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &init_h).unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_alice.rename("file1", "file")?;
txn_alice.write().move_file("file1", "file", 0)?;
debug!("repo_bob = {:?}", repo_alice.list_files());
let alice_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
repo_bob.rename("file2", "file")?;
txn_bob.write().move_file("file2", "file", 0)?;
debug!("repo_bob = {:?}", repo_bob.list_files());
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_h)?;
output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_h)?;
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
assert!(!conflicts.is_empty());
let mut files_alice = repo_alice.list_files();
debug!("repo_alice = {:?}", files_alice);
assert_eq!(files_alice.len(), 2);
files_alice.sort();
assert_eq!(files_alice[0], "file");
assert!(files_alice[1].starts_with("file."));
txn_alice.write().move_file(&files_alice[1], "a1", 0)?;
repo_alice.rename(&files_alice[0], "file")?;
repo_alice.rename(&files_alice[1], "a1")?;
let solution_alice = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
let mut files_bob = repo_bob.list_files();
debug!("repo_bob = {:?}", files_bob);
assert_eq!(files_bob.len(), 2);
files_bob.sort();
assert_eq!(files_bob[0], "file");
assert!(files_bob[1].starts_with("file."));
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &solution_alice)?;
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("conflicts = {:#?}", conflicts);
}
let mut files_bob = repo_bob.list_files();
files_bob.sort();
assert_eq!(files_bob, vec!["a1", "file"]);
Ok(())
}
#[test]
fn file_conflicts_same_name_and_two_names() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let repo_alice = working_copy::memory::Memory::new();
let mut repo_bob = working_copy::memory::Memory::new();
let repo_charlie = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
repo_alice.add_file("file1", contents.to_vec());
repo_alice.add_file("file2", contents.to_vec());
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let mut txn_bob = env_bob.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("alice")?;
txn_alice.write().add_file("file1", 0)?;
txn_alice.write().add_file("file2", 0)?;
info!("recording file additions");
debug!("working_copy = {:?}", repo_alice);
debug_tree(&*txn_alice.read(), "debug_tree")?;
let init_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let mut channel_bob = txn_bob.write().open_or_create_channel("bob").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &init_h).unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_bob.rename("file2", "file")?;
txn_bob.write().move_file("file2", "file", 0)?;
debug!("repo_bob = {:?}", repo_bob.list_files());
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
repo_alice.rename("file1", "file")?;
txn_alice.write().move_file("file1", "file", 0)?;
debug!("repo_bob = {:?}", repo_alice.list_files());
let alice_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
let env_charlie = pristine::sanakirja::Pristine::new_anon()?;
let txn_charlie = env_charlie.arc_txn_begin().unwrap();
let channel_charlie = txn_charlie
.write()
.open_or_create_channel("charlie")
.unwrap();
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &init_h).unwrap();
output::output_repository_no_pending(
&repo_charlie,
&changes,
&txn_charlie,
&channel_charlie,
"",
true,
None,
1,
0,
)?;
repo_charlie.rename("file1", "file3")?;
txn_charlie.write().move_file("file1", "file3", 0)?;
let charlie_h =
record_all(&repo_charlie, &changes, &txn_charlie, &channel_charlie, "").unwrap();
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &bob_h)?;
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &alice_h)?;
{
let txn_ = txn_charlie.write();
let mut f = std::fs::File::create("/tmp/charlie0")?;
crate::pristine::debug(&*txn_, &txn_.graph(&*channel_charlie.read()), &mut f)?;
}
output::output_repository_no_pending(
&repo_charlie,
&changes,
&txn_charlie,
&channel_charlie,
"",
true,
None,
1,
0,
)?;
let mut files_charlie = repo_charlie.list_files();
files_charlie.sort();
assert_eq!(files_charlie[0], "file");
assert!(files_charlie[1] == "file3" || files_charlie[1].starts_with("file."));
debug!("files_charlie {:?}", files_charlie);
repo_charlie.rename(&files_charlie[1], "file3")?;
txn_charlie
.write()
.move_file(&files_charlie[1], "file3", 0)?;
let _charlie_solution =
record_all(&repo_charlie, &changes, &txn_charlie, &channel_charlie, "").unwrap();
{
let txn_ = txn_charlie.write();
let mut f = std::fs::File::create("/tmp/charlie1")?;
crate::pristine::debug(&*txn_, &txn_.graph(&*channel_charlie.read()), &mut f)?;
}
output::output_repository_no_pending(
&repo_charlie,
&changes,
&txn_charlie,
&channel_charlie,
"",
true,
None,
1,
0,
)?;
let mut files_charlie = repo_charlie.list_files();
files_charlie.sort();
assert_eq!(files_charlie, &["file", "file3"]);
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_h)?;
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &charlie_h)?;
output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
let mut files_alice = repo_alice.list_files();
files_alice.sort();
debug!("files_alice {:?}", files_alice);
repo_alice.remove_path(&files_alice[1], false).unwrap();
let _alice_solution =
record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_h)?;
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &charlie_h)?;
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
let files_bob = repo_bob.list_files();
debug!("files_bob {:?}", files_bob);
repo_bob.remove_path(&files_bob[1], false).unwrap();
let _bob_solution =
record_all(&mut repo_bob, &changes, &mut txn_bob, &mut channel_bob, "").unwrap();
Ok(())
}
#[test]
fn zombie_file_test() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let contents2 = b"a\nb\nc\nx\nd\ne\nf\n";
let repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
repo_alice.add_file("a/b/c/file", contents.to_vec());
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("alice").unwrap();
txn_alice.write().add_file("a/b/c/file", 0).unwrap();
let init_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let channel_bob = txn_bob.write().open_or_create_channel("bob").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &init_h).unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_alice.remove_path("a/b", true)?;
let alice_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
repo_bob
.write_file("a/b/c/file", Inode::ROOT)
.unwrap()
.write_all(contents2)?;
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_h)?;
debug!("alice2");
output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
let files_alice = repo_alice.list_files();
assert_eq!(files_alice, vec!["a", "a/b", "a/b/c", "a/b/c/file"]);
for x in txn_alice
.read()
.iter_tree(
&OwnedPathId {
parent_inode: Inode::ROOT,
basename: crate::small_string::SmallString::new(),
},
None,
)
.unwrap()
{
debug!("x = {:?}", x);
}
debug!("recording a solution");
let alice_solution = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_h)?;
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
debug!("repo_alice = {:?}", repo_alice.list_files());
debug!("repo_bob = {:?}", repo_bob.list_files());
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_solution)?;
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
let files_bob = repo_bob.list_files();
assert_eq!(files_bob, vec!["a", "a/b", "a/b/c", "a/b/c/file"]);
Ok(())
}
#[test]
fn rename_zombie_file() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let contents2 = b"a\nb\nc\nx\nd\ne\nf\n";
let mut repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
repo_alice.add_file("a/b/c/file", contents.to_vec());
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let mut txn_alice = env_alice.arc_txn_begin().unwrap();
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let mut channel_alice = txn_alice.write().open_or_create_channel("alice").unwrap();
txn_alice.write().add_file("a/b/c/file", 0).unwrap();
let init_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let channel_bob = txn_bob.write().open_or_create_channel("bob").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &init_h).unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_alice.remove_path("a/b", true)?;
let alice_h = record_all(
&mut repo_alice,
&changes,
&mut txn_alice,
&mut channel_alice,
"",
)
.unwrap();
repo_bob.rename("a/b/c/file", "a/b/c/file2")?;
repo_bob
.write_file("a/b/c/file2", Inode::ROOT)
.unwrap()
.write_all(contents2)?;
txn_bob.write().move_file("a/b/c/file", "a/b/c/file2", 0)?;
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_h)?;
debug!("alice2");
output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
let files_alice = repo_alice.list_files();
debug!("Alice records {:?}", files_alice);
repo_alice.rename("a/b/c/file", "a/b/c/file2").unwrap_or(());
txn_alice
.write()
.move_file("a/b/c/file", "a/b/c/file2", 0)
.unwrap_or(());
let alice_solution = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
debug!("Alice recorded {:?}", alice_solution);
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_h)?;
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
debug!("repo_alice = {:?}", repo_alice.list_files());
debug!("repo_bob = {:?}", repo_bob.list_files());
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_solution)?;
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
let files_bob = repo_bob.list_files();
assert!(["a", "a/b", "a/b/c", "a/b/c/file2"]
.iter()
.all(|n| files_bob.iter().any(|m| m == n)));
Ok(())
}
#[test]
fn rename_zombie_dir() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let contents2 = b"a\nb\nc\nx\nd\ne\nf\n";
let mut repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
repo_alice.add_file("a/b/c/file", contents.to_vec());
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let mut txn_alice = env_alice.arc_txn_begin().unwrap();
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let mut channel_alice = txn_alice.write().open_or_create_channel("alice").unwrap();
txn_alice.write().add_file("a/b/c/file", 0).unwrap();
let init_h = record_all(
&mut repo_alice,
&changes,
&mut txn_alice,
&mut channel_alice,
"",
)?;
let channel_bob = txn_bob.write().open_or_create_channel("bob").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &init_h).unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_alice.remove_path("a/b", true)?;
let alice_h = record_all(
&mut repo_alice,
&changes,
&mut txn_alice,
&mut channel_alice,
"",
)
.unwrap();
repo_bob.rename("a/b/c", "a/b/d")?;
repo_bob
.write_file("a/b/d/file", Inode::ROOT)
.unwrap()
.write_all(contents2)?;
txn_bob.write().move_file("a/b/c", "a/b/d", 0)?;
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_h)?;
debug!("alice2");
output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
let files_alice = repo_alice.list_files();
if files_alice.iter().any(|x| x == "a/b/d/file") {
let _ = txn_alice.write().add_file("a/b/d/file", 0);
} else {
assert!(files_alice.iter().any(|x| x == "a/b/c/file"));
txn_alice.write().move_file("a/b/c", "a/b/d", 0).unwrap();
repo_alice.rename("a/b/c", "a/b/d").unwrap();
}
debug!("Alice records");
let alice_solution = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
debug!("Alice recorded {:?}", alice_solution);
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_h)?;
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
debug!("repo_alice = {:?}", repo_alice.list_files());
debug!("repo_bob = {:?}", repo_bob.list_files());
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_solution)?;
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
let files_bob = repo_bob.list_files();
debug!("files_bob = {:?}", files_bob);
assert!(["a", "a/b", "a/b/d", "a/b/d/file"]
.iter()
.all(|n| files_bob.iter().any(|m| m == n)));
Ok(())
}
#[test]
fn double_zombie_file() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let contents2 = b"a\nb\nc\nx\nd\ne\nf\n";
let contents3 = b"a\nby\n\nc\nd\ne\nf\n";
let repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let repo_charlie = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let env_charlie = pristine::sanakirja::Pristine::new_anon()?;
let txn_charlie = env_charlie.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("alice").unwrap();
repo_alice.add_file("a/b/c/file", contents.to_vec());
txn_alice.write().add_file("a/b/c/file", 0).unwrap();
let init_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let channel_bob = txn_bob.write().open_or_create_channel("bob").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &init_h).unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
let channel_charlie = txn_charlie
.write()
.open_or_create_channel("charlie")
.unwrap();
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &init_h).unwrap();
let conflicts = output::output_repository_no_pending(
&repo_charlie,
&changes,
&txn_charlie,
&channel_charlie,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("charlie has conflicts: {:?}", conflicts);
}
repo_alice.remove_path("a/b", true)?;
let alice_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "").unwrap();
repo_bob
.write_file("a/b/c/file", Inode::ROOT)
.unwrap()
.write_all(contents2)?;
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
repo_charlie
.write_file("a/b/c/file", Inode::ROOT)
.unwrap()
.write_all(contents3)?;
let charlie_h =
record_all(&repo_charlie, &changes, &txn_charlie, &channel_charlie, "").unwrap();
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_h)?;
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &charlie_h)?;
debug!("alice2");
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?
.into_iter()
.collect::<Vec<_>>();
let files_alice = repo_alice.list_files();
assert_eq!(files_alice, vec!["a", "a/b", "a/b/c", "a/b/c/file"]);
let expected = [
Conflict::ZombieFile {
path: "a/b".to_string(),
},
Conflict::ZombieFile {
path: "a/b/c".to_string(),
},
Conflict::ZombieFile {
path: "a/b/c/file".to_string(),
},
Conflict::Zombie {
path: "a/b/c/file".to_string(),
line: 1,
},
];
assert_eq!(&conflicts[..], &expected[..]);
let mut buf = Vec::new();
repo_alice.read_file("a/b/c/file", &mut buf)?;
{
let mut w = repo_alice.write_file("a/b/c/file", Inode::ROOT).unwrap();
for l in std::str::from_utf8(&buf).unwrap().lines() {
if l.len() < 10 {
writeln!(w, "{}", l)?
}
}
}
let alice_solution = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_h)?;
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &charlie_h)?;
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?
.into_iter()
.collect::<Vec<_>>();
assert_eq!(&conflicts[..], &expected[..]);
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_solution)?;
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("bob has conflicts: {:?}", conflicts);
}
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &bob_h)?;
debug!("charlie applies Alice's change");
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &alice_h)?;
let conflicts = output::output_repository_no_pending(
&repo_charlie,
&changes,
&txn_charlie,
&channel_charlie,
"",
true,
None,
1,
0,
)?
.into_iter()
.collect::<Vec<_>>();
assert_eq!(&conflicts[..], &expected[..]);
debug!("charlie applies Alice's solution");
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &alice_solution)?;
let conflicts = output::output_repository_no_pending(
&repo_charlie,
&changes,
&txn_charlie,
&channel_charlie,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("charlie has conflicts: {:?}", conflicts);
}
Ok(())
}
#[test]
fn zombie_file_post_resolve() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let contents = b"a\nb\nc\nd\ne\nf\n";
let repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let repo_charlie = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let env_charlie = pristine::sanakirja::Pristine::new_anon()?;
let txn_charlie = env_charlie.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("alice").unwrap();
repo_alice.add_file("a/b/c/file", contents.to_vec());
txn_alice.write().add_file("a/b/c/file", 0).unwrap();
let init_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
repo_alice.rename("a/b/c/file", "a/b/c/alice")?;
txn_alice
.write()
.move_file("a/b/c/file", "a/b/c/alice", 0)?;
let alice_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let channel_bob = txn_bob.write().open_or_create_channel("bob").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &init_h).unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_bob.remove_path("a/b/c/file", false)?;
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &alice_h).unwrap();
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
debug!("conflicts = {:#?}", conflicts);
assert_eq!(conflicts.len(), 1);
match conflicts.iter().next().unwrap() {
Conflict::ZombieFile { ref path } => assert_eq!(path, "a/b/c/alice"),
ref c => panic!("unexpected conflict {:#?}", c),
}
debug!("Bob resolves");
let bob_resolution = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "").unwrap();
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("Bob has conflicts: {:?}", conflicts);
}
debug!("Alice applies Bob's resolution");
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_h).unwrap();
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &bob_resolution).unwrap();
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("Alice has conflicts: {:?}", conflicts);
}
let channel_charlie = txn_charlie
.write()
.open_or_create_channel("charlie")
.unwrap();
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &init_h).unwrap();
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &alice_h).unwrap();
let conflicts = output::output_repository_no_pending(
&repo_charlie,
&changes,
&txn_charlie,
&channel_charlie,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("charlie has conflicts: {:?}", conflicts);
}
debug!("Charlie applies Alice's move and deletes");
repo_charlie.remove_path("a/b/c/alice", false)?;
let charlie_h =
record_all(&repo_charlie, &changes, &txn_charlie, &channel_charlie, "").unwrap();
debug!("Charlie applies Bob's deletion");
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &bob_h).unwrap();
debug!("Charlie applies Bob's resolution");
apply::apply_change_arc(&changes, &txn_charlie, &channel_charlie, &bob_resolution).unwrap();
let conflicts = output::output_repository_no_pending(
&repo_charlie,
&changes,
&txn_charlie,
&channel_charlie,
"",
true,
None,
1,
0,
)?;
assert_eq!(conflicts.len(), 1);
match conflicts.iter().next().unwrap() {
Conflict::ZombieFile { ref path } => assert_eq!(path, "a/b/c/alice"),
ref c => panic!("unexpected conflict {:#?}", c),
}
debug!("Alice applies Charlie's deletion");
apply::apply_change_arc(&changes, &txn_alice, &channel_alice, &charlie_h).unwrap();
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
assert_eq!(conflicts.len(), 1);
match conflicts.iter().next().unwrap() {
Conflict::ZombieFile { ref path } => assert!(path == "a/b/c/file" || path == "a/b/c/alice"),
ref c => panic!("unexpected conflict {:#?}", c),
}
apply::apply_change_arc(&changes, &txn_bob, &channel_bob, &charlie_h).unwrap();
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
assert_eq!(conflicts.len(), 1);
match conflicts.iter().next().unwrap() {
Conflict::ZombieFile { ref path } => assert!(path == "a/b/c/file" || path == "a/b/c/alice"),
ref c => panic!("unexpected conflict {:#?}", c),
}
Ok(())
}
#[test]
fn move_vs_delete_test() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("main")?;
repo_alice.add_file("file", b"a\n".to_vec());
txn_alice.write().add_file("file", 0)?;
let init = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let channel_bob = txn_bob.write().open_or_create_channel("main")?;
txn_bob
.write()
.apply_change(&changes, &mut *channel_bob.write(), &init)
.unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_alice.rename("file", "alice/file").unwrap_or(());
txn_alice
.write()
.move_file("file", "alice/file", 0)
.unwrap_or(());
let alice_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
repo_bob.remove_path("file", false).unwrap_or(());
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "")?;
debug!("Bob applies Alice's change");
txn_bob
.write()
.apply_change(&changes, &mut *channel_bob.write(), &alice_h)
.unwrap();
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
debug!("conflicts = {:#?}", conflicts);
assert_eq!(conflicts.len(), 1);
match conflicts.iter().next().unwrap() {
Conflict::ZombieFile { ref path } => assert_eq!(path, "alice/file"),
ref c => panic!("unexpected conflict {:#?}", c),
}
let files = repo_bob.list_files();
if files.iter().any(|f| f == "alice/file") {
repo_bob.remove_path("bob", false).unwrap()
} else {
repo_bob.remove_path("alice", false).unwrap()
}
let resolution = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "")?;
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("Bob has conflicts: {:?}", conflicts);
}
debug!("Alice applies Bob's change");
txn_alice
.write()
.apply_change(&changes, &mut *channel_alice.write(), &bob_h)
.unwrap();
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
assert_eq!(conflicts.len(), 1);
match conflicts.iter().next().unwrap() {
Conflict::ZombieFile { ref path } => assert_eq!(path, "alice/file"),
ref c => panic!("unexpected conflict {:#?}", c),
}
debug!("Alice applies Bob's resolution");
txn_alice
.write()
.apply_change(&changes, &mut *channel_alice.write(), &resolution)
.unwrap();
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("Alice has conflicts: {:?}", conflicts);
}
Ok(())
}
#[test]
fn delete_zombie_test() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let repo_alice = working_copy::memory::Memory::new();
let repo_bob = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("main")?;
repo_alice.add_file("file", b"a\nb\nc\nd\n".to_vec());
txn_alice.write().add_file("file", 0)?;
let init = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let txn_bob = env_bob.arc_txn_begin().unwrap();
let channel_bob = txn_bob.write().open_or_create_channel("main")?;
txn_bob
.write()
.apply_change(&changes, &mut *channel_bob.write(), &init)
.unwrap();
output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
repo_alice
.write_file("file", Inode::ROOT)
.unwrap()
.write_all(b"a\nb\nx\nc\nd\n")?;
record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
repo_bob
.write_file("file", Inode::ROOT)
.unwrap()
.write_all(b"a\nd\n")?;
let bob_h1 = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "")?;
repo_bob.remove_path("file", false).unwrap_or(());
let bob_h2 = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "")?;
debug!("Alice applies Bob's change");
txn_alice
.write()
.apply_change(&changes, &mut *channel_alice.write(), &bob_h1)
.unwrap();
debug!("Applying bob_h2");
txn_alice
.write()
.apply_change(&changes, &mut *channel_alice.write(), &bob_h2)
.unwrap();
let (alive, reachable) = check_alive(&*txn_alice.read(), &channel_alice.read());
if !alive.is_empty() {
panic!("alive (bob0): {:?}", alive);
}
if !reachable.is_empty() {
panic!("reachable (bob0): {:?}", reachable);
}
crate::unrecord::unrecord(
&mut *txn_alice.write(),
&channel_alice,
&changes,
&bob_h2,
0,
)
.unwrap();
output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
let mut buf = Vec::new();
repo_alice.read_file("file", &mut buf)?;
debug!("file = {:?}", std::str::from_utf8(&buf));
Ok(())
}
#[test]
fn move_into_deleted_test() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let repo_alice = working_copy::memory::Memory::new();
let mut repo_bob = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("main")?;
repo_alice.add_file("file", b"a\n".to_vec());
repo_alice.add_dir("dir");
txn_alice.write().add_file("file", 0)?;
txn_alice.write().add_dir("dir", 0)?;
let init = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let env_bob = pristine::sanakirja::Pristine::new_anon()?;
let mut txn_bob = env_bob.arc_txn_begin().unwrap();
let mut channel_bob = txn_bob.write().open_or_create_channel("main")?;
txn_bob
.write()
.apply_change(&changes, &mut *channel_bob.write(), &init)
.unwrap();
output::output_repository_no_pending(
&mut repo_bob,
&changes,
&mut txn_bob,
&mut channel_bob,
"",
true,
None,
1,
0,
)?;
repo_alice.rename("file", "dir/file").unwrap_or(());
txn_alice
.write()
.move_file("file", "dir/file", 0)
.unwrap_or(());
let alice_h = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
repo_bob.remove_path("dir", true).unwrap_or(());
let bob_h = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "")?;
debug!("Bob applies Alice's change");
txn_bob
.write()
.apply_change(&changes, &mut *channel_bob.write(), &alice_h)
.unwrap();
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
debug!("conflicts = {:#?}", conflicts);
assert_eq!(conflicts.len(), 1);
match conflicts.iter().next().unwrap() {
Conflict::ZombieFile { ref path } => assert_eq!(path, "dir"),
ref c => panic!("unexpected conflict {:#?}", c),
}
let resolution = record_all(&repo_bob, &changes, &txn_bob, &channel_bob, "")?;
let conflicts = output::output_repository_no_pending(
&repo_bob,
&changes,
&txn_bob,
&channel_bob,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("Bob has conflicts: {:?}", conflicts);
}
debug!("Alice applies Bob's change");
txn_alice
.write()
.apply_change(&changes, &mut *channel_alice.write(), &bob_h)
.unwrap();
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
assert_eq!(conflicts.len(), 1);
match conflicts.iter().next().unwrap() {
Conflict::ZombieFile { ref path } => assert_eq!(path, "dir"),
ref c => panic!("unexpected conflict {:#?}", c),
}
debug!("Alice applies Bob's resolution");
txn_alice
.write()
.apply_change(&changes, &mut *channel_alice.write(), &resolution)
.unwrap();
let conflicts = output::output_repository_no_pending(
&repo_alice,
&changes,
&txn_alice,
&channel_alice,
"",
true,
None,
1,
0,
)?;
if !conflicts.is_empty() {
panic!("Alice has conflicts: {:?}", conflicts);
}
Ok(())
}
#[test]
fn move_back_noundel_test() -> Result<(), anyhow::Error> {
env_logger::try_init().unwrap_or(());
let repo_alice = working_copy::memory::Memory::new();
let changes = changestore::memory::Memory::new();
let env_alice = pristine::sanakirja::Pristine::new_anon()?;
let txn_alice = env_alice.arc_txn_begin().unwrap();
let channel_alice = txn_alice.write().open_or_create_channel("main")?;
repo_alice.add_file("a", b"a\n".to_vec());
txn_alice.write().add_file("a", 0)?;
let _init = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
repo_alice.rename("a", "b").unwrap_or(());
txn_alice.write().move_file("a", "b", 0)?;
let _mv = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
repo_alice.rename("b", "a").unwrap_or(());
txn_alice.write().move_file("b", "a", 0)?;
info!("MOVE BACK");
{
let txn_ = txn_alice.write();
let mut f = std::fs::File::create("/tmp/moveback")?;
crate::pristine::debug(&*txn_, &txn_.graph(&*channel_alice.read()), &mut f)?;
}
let back = record_all(&repo_alice, &changes, &txn_alice, &channel_alice, "")?;
let back = changes.get_change(&back).unwrap();
match back.hashed.changes[0] {
crate::change::Hunk::FileMove { .. } => {}
ref x => {
panic!("{:#?}", x);
}
}
Ok(())
}