mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-25 15:21:10 +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
21
src/api.rs
21
src/api.rs
|
@ -108,7 +108,10 @@ impl DbConflict {
|
||||||
let ancestor = generate_article_version(&original_article.edits, &self.previous_version)?;
|
let ancestor = generate_article_version(&original_article.edits, &self.previous_version)?;
|
||||||
|
|
||||||
let patch = Patch::from_str(&self.diff)?;
|
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
|
// patch applies cleanly so we are done
|
||||||
// federate the change
|
// federate the change
|
||||||
submit_article_update(data, new_text, &original_article).await?;
|
submit_article_update(data, new_text, &original_article).await?;
|
||||||
|
@ -116,24 +119,18 @@ impl DbConflict {
|
||||||
let mut lock = data.conflicts.lock().unwrap();
|
let mut lock = data.conflicts.lock().unwrap();
|
||||||
lock.retain(|c| c.id != self.id);
|
lock.retain(|c| c.id != self.id);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
}
|
||||||
// there is a merge conflict, do three-way-merge
|
Err(three_way_merge) => {
|
||||||
// apply self.diff to ancestor to get `ours`
|
// there is a merge conflict, user needs to do three-way-merge
|
||||||
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 {
|
Ok(Some(ApiConflict {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
three_way_merge: merge,
|
three_way_merge,
|
||||||
article_id: original_article.ap_id.clone(),
|
article_id: original_article.ap_id.clone(),
|
||||||
previous_version: original_article.latest_version,
|
previous_version: original_article.latest_version,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
|
|
|
@ -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> {
|
pub async fn create_article(hostname: &str, title: String) -> MyResult<DbArticle> {
|
||||||
let create_form = CreateArticleData {
|
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)
|
let edit_res = edit_article_with_conflict(data.hostname_alpha, &edit_form)
|
||||||
.await?
|
.await?
|
||||||
.unwrap();
|
.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> =
|
let conflicts: Vec<ApiConflict> =
|
||||||
get_query(data.hostname_alpha, "edit_conflicts", None::<()>).await?;
|
get_query(data.hostname_alpha, "edit_conflicts", None::<()>).await?;
|
||||||
|
@ -340,3 +340,42 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
|
|
||||||
data.stop()
|
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