Rebase title edits, #23

This commit is contained in:
Magnus Hoff 2017-11-20 13:12:36 +01:00
parent b862ad8c73
commit bfca5d6e78
2 changed files with 76 additions and 21 deletions

View file

@ -43,6 +43,30 @@ pub fn merge_lines<'a>(a: &'a str, o: &'a str, b: &'a str) -> MergeResult<&'a st
}
}
pub fn merge_chars<'a>(a: &'a str, o: &'a str, b: &'a str) -> MergeResult<char> {
let oa = diff::chars(o, a);
let ob = diff::chars(o, b);
let chunks = ChunkIterator::new(&oa, &ob);
let hunks: Vec<_> = chunks.map(resolve).collect();
let clean = hunks.iter().all(|x| match x { &Resolved(..) => true, _ => false });
if clean {
MergeResult::Clean(
hunks
.into_iter()
.flat_map(|x| match x {
Resolved(y) => y.into_iter(),
_ => unreachable!()
})
.collect()
)
} else {
MergeResult::Conflicted(hunks)
}
}
#[cfg(test)]
mod test {
use diff;
@ -51,16 +75,16 @@ mod test {
use super::output::*;
use super::output::Output::*;
fn merge_chars(a: &str, o: &str, b: &str) -> Vec<Output<char>> {
let oa = diff::chars(o, a);
let ob = diff::chars(o, b);
let chunks = super::chunk_iterator::ChunkIterator::new(&oa, &ob);
chunks.map(resolve).collect()
}
#[test]
fn simple_case() {
fn merge_chars(a: &str, o: &str, b: &str) -> Vec<Output<char>> {
let oa = diff::chars(o, a);
let ob = diff::chars(o, b);
let chunks = super::chunk_iterator::ChunkIterator::new(&oa, &ob);
chunks.map(resolve).collect()
}
assert_eq!(vec![
Resolved("aaa".chars().collect()),
Resolved("xxx".chars().collect()),
@ -89,6 +113,15 @@ mod test {
));
}
#[test]
fn clean_case_chars() {
assert_eq!(MergeResult::Clean("Title".into()), merge_chars(
"Titlle",
"titlle",
"title",
));
}
#[test]
fn false_conflict() {
assert_eq!(MergeResult::Clean("\

View file

@ -174,12 +174,11 @@ impl<'a> SyncState<'a> {
})
}
fn rebase_update(&self, article_id: i32, target_base_revision: i32, existing_base_revision: i32, _title: &str, body: String)
-> Result<String, Error>
fn rebase_update(&self, article_id: i32, target_base_revision: i32, existing_base_revision: i32, title: String, body: String)
-> Result<(String, String), Error>
{
// TODO Also rebase title
let mut a = body;
let mut title_a = title;
let mut body_a = body;
for revision in existing_base_revision..target_base_revision {
let mut stored = article_revisions::table
@ -187,19 +186,27 @@ impl<'a> SyncState<'a> {
.filter(article_revisions::revision.ge(revision))
.filter(article_revisions::revision.le(revision+1))
.order(article_revisions::revision.asc())
.select((article_revisions::body))
.load::<(String)>(self.db_connection)?;
.select((
article_revisions::title,
article_revisions::body,
))
.load::<(String, String)>(self.db_connection)?;
let b = stored.pop().expect("Application layer guarantee");
let o = stored.pop().expect("Application layer guarantee");
let (title_b, body_b) = stored.pop().expect("Application layer guarantee");
let (title_o, body_o) = stored.pop().expect("Application layer guarantee");
a = match merge::merge_lines(&a, &o, &b) {
title_a = match merge::merge_chars(&title_a, &title_o, &title_b) {
merge::MergeResult::Clean(merged) => merged,
_ => unimplemented!("Missing handling of merge conflicts"),
}
};
body_a = match merge::merge_lines(&body_a, &body_o, &body_b) {
merge::MergeResult::Clean(merged) => merged,
_ => unimplemented!("Missing handling of merge conflicts"),
};
}
Ok(a)
Ok((title_a, body_a))
}
pub fn update_article(&self, article_id: i32, base_revision: i32, title: String, body: String, author: Option<String>)
@ -229,7 +236,7 @@ impl<'a> SyncState<'a> {
Err("This edit is based on a future version of the article")?;
}
let body = self.rebase_update(article_id, latest_revision, base_revision, &title, body)?;
let (title, body) = self.rebase_update(article_id, latest_revision, base_revision, title, body)?;
let new_revision = latest_revision + 1;
@ -517,4 +524,19 @@ mod test {
assert_eq!("a\nx1\nx2\nx3\nb\ny\nc\n", rebase_edit.body);
}
#[test]
fn update_article_when_title_edit_conflict_then_merge_title() {
init!(state);
let article = state.create_article(None, "titlle".into(), "".into(), None).unwrap();
let first_edit = state.update_article(article.article_id, article.revision, "Titlle".into(), article.body.clone(), None).unwrap();
let second_edit = state.update_article(article.article_id, article.revision, "title".into(), article.body.clone(), None).unwrap();
assert!(article.revision < first_edit.revision);
assert!(first_edit.revision < second_edit.revision);
assert_eq!("Title", second_edit.title);
}
}