mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-25 11:11:09 +00:00
make automatic conflict resolution work
This commit is contained in:
parent
cc8b1a9d54
commit
d963f2257f
3 changed files with 62 additions and 26 deletions
45
src/api.rs
45
src/api.rs
|
@ -108,30 +108,27 @@ 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) {
|
||||
// patch applies cleanly so we are done
|
||||
// federate the change
|
||||
submit_article_update(data, new_text, &original_article).await?;
|
||||
// remove conflict from db
|
||||
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();
|
||||
|
||||
Ok(Some(ApiConflict {
|
||||
id: self.id,
|
||||
three_way_merge: merge,
|
||||
article_id: original_article.ap_id.clone(),
|
||||
previous_version: original_article.latest_version,
|
||||
}))
|
||||
// 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?;
|
||||
// remove conflict from db
|
||||
let mut lock = data.conflicts.lock().unwrap();
|
||||
lock.retain(|c| c.id != self.id);
|
||||
Ok(None)
|
||||
}
|
||||
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,
|
||||
article_id: original_article.ap_id.clone(),
|
||||
previous_version: original_article.latest_version,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue