Add three-way file root apply

This commit is contained in:
Eric Wendland 2026-05-22 14:41:09 +02:00
commit 606641dd4a
5 changed files with 602 additions and 27 deletions

View file

@ -1651,6 +1651,174 @@ fn cas_file_root_apply_writes_missing_files_and_records_conflicts() {
}
}
#[test]
fn cas_file_root_apply_three_way_updates_deletes_and_renames_safe_changes() {
let home = tempfile::tempdir().expect("tempdir");
let paths = geth_config::GethPaths::from_home(home.path());
let node = geth_node::init_node(&paths).expect("init node");
let target_path = home.path().join("target-root");
std::fs::create_dir_all(&target_path).expect("create target root");
std::fs::write(target_path.join("update.txt"), b"base").expect("write base update");
std::fs::write(target_path.join("delete.txt"), b"base delete").expect("write base delete");
std::fs::write(target_path.join("rename.txt"), b"base rename").expect("write base rename");
geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootAdd {
name: "shared".to_owned(),
path: target_path.clone(),
},
)
.expect("add local root");
geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootScan {
name: "shared".to_owned(),
},
)
.expect("scan local base");
let source_path = home.path().join("source-root");
std::fs::create_dir_all(&source_path).expect("create source root");
std::fs::write(source_path.join("update.txt"), b"remote").expect("write remote update");
std::fs::write(source_path.join("rename-new.txt"), b"base rename")
.expect("write remote rename");
std::fs::write(source_path.join("created.txt"), b"created").expect("write remote create");
geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootAdd {
name: "remote-node-peer-shared".to_owned(),
path: source_path,
},
)
.expect("add remote root");
geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootScan {
name: "remote-node-peer-shared".to_owned(),
},
)
.expect("scan remote root");
let response = geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootApply {
source: "remote-node-peer-shared".to_owned(),
target: target_path.clone(),
dry_run: false,
},
)
.expect("three-way apply");
match response {
geth_control::ControlResponse::CasRootApplied {
files_written,
conflicts,
note,
..
} => {
assert_eq!(files_written, 2);
assert!(conflicts.is_empty());
assert!(note.contains("three-way"));
}
other => panic!("unexpected response: {other:?}"),
}
assert_eq!(
std::fs::read(target_path.join("update.txt")).expect("read updated"),
b"remote"
);
assert!(
!target_path.join("delete.txt").exists(),
"safe remote delete should remove unchanged local base file"
);
assert!(
!target_path.join("rename.txt").exists(),
"safe remote rename should remove old path"
);
assert_eq!(
std::fs::read(target_path.join("rename-new.txt")).expect("read renamed"),
b"base rename"
);
assert_eq!(
std::fs::read(target_path.join("created.txt")).expect("read created"),
b"created"
);
}
#[test]
fn cas_file_root_apply_three_way_keeps_ambiguous_changes_as_conflicts() {
let home = tempfile::tempdir().expect("tempdir");
let paths = geth_config::GethPaths::from_home(home.path());
let node = geth_node::init_node(&paths).expect("init node");
let target_path = home.path().join("target-root");
std::fs::create_dir_all(&target_path).expect("create target root");
std::fs::write(target_path.join("note.txt"), b"base").expect("write base");
geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootAdd {
name: "shared".to_owned(),
path: target_path.clone(),
},
)
.expect("add local root");
geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootScan {
name: "shared".to_owned(),
},
)
.expect("scan local base");
std::fs::write(target_path.join("note.txt"), b"local edit").expect("write local edit");
let source_path = home.path().join("source-root");
std::fs::create_dir_all(&source_path).expect("create source root");
std::fs::write(source_path.join("note.txt"), b"remote edit").expect("write remote edit");
geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootAdd {
name: "remote-node-peer-shared".to_owned(),
path: source_path,
},
)
.expect("add remote root");
geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootScan {
name: "remote-node-peer-shared".to_owned(),
},
)
.expect("scan remote root");
let response = geth_node::handle_request(
&node,
geth_control::ControlRequest::CasRootApply {
source: "remote-node-peer-shared".to_owned(),
target: target_path.clone(),
dry_run: false,
},
)
.expect("three-way conflict apply");
match response {
geth_control::ControlResponse::CasRootApplied { conflicts, .. } => {
assert_eq!(conflicts.len(), 1);
assert_eq!(conflicts[0].path, "note.txt");
assert_eq!(
conflicts[0].kind,
geth_cas::FileConflictKind::ConcurrentEdit
);
assert!(conflicts[0].base_tree.is_some());
assert!(conflicts[0].local_tree.is_some());
assert!(conflicts[0].remote_tree.is_some());
}
other => panic!("unexpected response: {other:?}"),
}
assert_eq!(
std::fs::read(target_path.join("note.txt")).expect("read local after conflict"),
b"local edit"
);
}
#[test]
fn cas_file_conflict_record_list_and_resolve() {
let home = tempfile::tempdir().expect("tempdir");