make automatic conflict resolution work

This commit is contained in:
Felix Ableitner 2023-11-28 13:13:36 +01:00
parent cc8b1a9d54
commit d963f2257f
3 changed files with 62 additions and 26 deletions

View File

@ -108,7 +108,10 @@ impl DbConflict {
let ancestor = generate_article_version(&original_article.edits, &self.previous_version)?;
let patch = Patch::from_str(&self.diff)?;
if let Ok(new_text) = apply(&original_article.text, &patch) {
// apply self.diff to ancestor to get `ours`
let ours = apply(&ancestor, &patch)?;
match merge(&ancestor, &ours, &original_article.text) {
Ok(new_text) => {
// patch applies cleanly so we are done
// federate the change
submit_article_update(data, new_text, &original_article).await?;
@ -116,25 +119,19 @@ impl DbConflict {
let mut lock = data.conflicts.lock().unwrap();
lock.retain(|c| c.id != self.id);
Ok(None)
} else {
// there is a merge conflict, do three-way-merge
// apply self.diff to ancestor to get `ours`
let ours = apply(&ancestor, &patch)?;
// if it returns ok the merge was successful, which is impossible based on apply failing
// above. so we unconditionally read the three-way-merge string from err field
let merge = merge(&ancestor, &ours, &original_article.text)
.err()
.unwrap();
}
Err(three_way_merge) => {
// there is a merge conflict, user needs to do three-way-merge
Ok(Some(ApiConflict {
id: self.id,
three_way_merge: merge,
three_way_merge,
article_id: original_article.ap_id.clone(),
previous_version: original_article.latest_version,
}))
}
}
}
}
#[debug_handler]
async fn edit_article(

View File

@ -66,7 +66,7 @@ impl TestData {
}
}
pub const TEST_ARTICLE_DEFAULT_TEXT: &str = "empty\n";
pub const TEST_ARTICLE_DEFAULT_TEXT: &str = "some\nexample\ntext\n";
pub async fn create_article(hostname: &str, title: String) -> MyResult<DbArticle> {
let create_form = CreateArticleData {

View File

@ -246,7 +246,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
let edit_res = edit_article_with_conflict(data.hostname_alpha, &edit_form)
.await?
.unwrap();
assert_eq!("<<<<<<< ours\nIpsum Lorem\n||||||| original\nempty\n=======\nLorem Ipsum\n>>>>>>> theirs\n", edit_res.three_way_merge);
assert_eq!("<<<<<<< ours\nIpsum Lorem\n||||||| original\nsome\nexample\ntext\n=======\nLorem Ipsum\n>>>>>>> theirs\n", edit_res.three_way_merge);
let conflicts: Vec<ApiConflict> =
get_query(data.hostname_alpha, "edit_conflicts", None::<()>).await?;
@ -340,3 +340,42 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
data.stop()
}
#[tokio::test]
#[serial]
async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
let data = TestData::start();
// create new article
let title = "Manu_Chao".to_string();
let create_res = create_article(data.hostname_alpha, title.clone()).await?;
assert_eq!(title, create_res.title);
assert!(create_res.local);
// one user edits article
let edit_form = EditArticleData {
ap_id: create_res.ap_id.clone(),
new_text: "my\nexample\ntext\n".to_string(),
previous_version: create_res.latest_version.clone(),
resolve_conflict_id: None,
};
let edit_res = edit_article(data.hostname_alpha, &create_res.title, &edit_form).await?;
assert_eq!(edit_res.text, edit_form.new_text);
assert_eq!(2, edit_res.edits.len());
// another user edits article, without being aware of previous edit
let edit_form = EditArticleData {
ap_id: create_res.ap_id.clone(),
new_text: "some\nexample\narticle\n".to_string(),
previous_version: create_res.latest_version,
resolve_conflict_id: None,
};
let edit_res = edit_article(data.hostname_alpha, &title, &edit_form).await?;
let conflicts: Vec<ApiConflict> =
get_query(data.hostname_alpha, "edit_conflicts", None::<()>).await?;
assert_eq!(0, conflicts.len());
assert_eq!(3, edit_res.edits.len());
assert_eq!("my\nexample\narticle\n", edit_res.text);
data.stop()
}