Implement merge conflict handling for the noscript case #23

This commit is contained in:
Magnus Hoff 2017-11-20 16:07:33 +01:00
parent bf9716ccb8
commit b685139d5b
7 changed files with 134 additions and 41 deletions

View file

@ -29,6 +29,55 @@ impl<'a> MergeResult<&'a str> {
}
}
impl MergeResult<String> {
pub fn flatten(self) -> String {
match self {
MergeResult::Clean(x) => x,
MergeResult::Conflicted(x) => {
x.into_iter()
.flat_map(|out| match out {
Output::Conflict(a, _o, b) => {
let mut x: Vec<String> = vec![];
x.push("<<<<<<< Your changes:\n".into());
x.extend(a.into_iter().map(|x| format!("{}\n", x)));
x.push("======= Their changes:\n".into());
x.extend(b.into_iter().map(|x| format!("{}\n", x)));
x.push(">>>>>>> Conflict ends here\n".into());
x
},
Output::Resolved(x) =>
x.into_iter().map(|x| format!("{}\n", x)).collect(),
})
.collect()
}
}
}
}
impl MergeResult<char> {
pub fn flatten(self) -> String {
match self {
MergeResult::Clean(x) => x,
MergeResult::Conflicted(x) => {
x.into_iter()
.flat_map(|out| match out {
Output::Conflict(a, _o, b) => {
let mut x: Vec<char> = vec![];
x.push('<');
x.extend(a);
x.push('|');
x.extend(b);
x.push('>');
x
},
Output::Resolved(x) => x,
})
.collect()
}
}
}
}
pub fn merge_lines<'a>(a: &'a str, o: &'a str, b: &'a str) -> MergeResult<&'a str> {
let oa = diff::lines(o, a);
let ob = diff::lines(o, b);

View file

@ -29,7 +29,7 @@ impl ArticleRevision {
pub fn link(&self) -> &str { slug_link(&self.slug) }
}
#[derive(Debug, Queryable)]
#[derive(Debug, PartialEq, Queryable)]
pub struct ArticleRevisionStub {
pub sequence_number: i32,

View file

@ -10,16 +10,28 @@ use assets::ScriptJs;
use mimes::*;
use rendering::render_markdown;
use site::Layout;
use state::State;
use state::{State, UpdateResult, RebaseConflict};
use web::{Resource, ResponseFuture};
use super::changes_resource::QueryParameters;
pub struct ArticleResource {
state: State,
article_id: i32,
#[derive(BartDisplay)]
#[template="templates/article.html"]
struct Template<'a> {
revision: i32,
last_updated: Option<&'a str>,
edit: bool,
cancel_url: Option<&'a str>,
title: &'a str,
raw: &'a str,
rendered: String,
}
impl<'a> Template<'a> {
fn script_js_checksum(&self) -> &'static str {
ScriptJs::checksum()
}
}
#[derive(Deserialize)]
@ -29,6 +41,13 @@ struct UpdateArticle {
body: String,
}
pub struct ArticleResource {
state: State,
article_id: i32,
revision: i32,
edit: bool,
}
impl ArticleResource {
pub fn new(state: State, article_id: i32, revision: i32, edit: bool) -> Self {
Self { state, article_id, revision, edit }
@ -73,21 +92,6 @@ impl Resource for ArticleResource {
}
fn get(self: Box<Self>) -> ResponseFuture {
#[derive(BartDisplay)]
#[template="templates/article.html"]
struct Template<'a> {
revision: i32,
last_updated: Option<&'a str>,
edit: bool,
cancel_url: Option<&'a str>,
title: &'a str,
raw: &'a str,
rendered: String,
script_js_checksum: &'a str,
}
let data = self.state.get_article_revision(self.article_id, self.revision)
.map(|x| x.expect("Data model guarantees that this exists"));
let head = self.head();
@ -110,7 +114,6 @@ impl Resource for ArticleResource {
title: &data.title,
raw: &data.body,
rendered: render_markdown(&data.body),
script_js_checksum: ScriptJs::checksum(),
},
}.to_string()))
}))
@ -187,13 +190,41 @@ impl Resource for ArticleResource {
self.state.update_article(self.article_id, update.base_revision, update.title, update.body, identity)
})
.and_then(|updated| {
let updated = updated.unwrap();
futures::finished(Response::new()
.with_status(hyper::StatusCode::SeeOther)
.with_header(ContentType(TEXT_PLAIN.clone()))
.with_header(Location::new(updated.link().to_owned()))
.with_body("See other")
)
match updated {
UpdateResult::Success(updated) => Ok(Response::new()
.with_status(hyper::StatusCode::SeeOther)
.with_header(ContentType(TEXT_PLAIN.clone()))
.with_header(Location::new(updated.link().to_owned()))
.with_body("See other")
),
UpdateResult::RebaseConflict(RebaseConflict {
base_article, title, body
}) => {
let title = title.flatten();
let body = body.flatten();
Ok(Response::new()
.with_status(hyper::StatusCode::Ok)
.with_header(ContentType(TEXT_HTML.clone()))
.with_body(Layout {
base: None,
title: &title,
body: &Template {
revision: base_article.revision,
last_updated: Some(&last_updated(
base_article.article_id,
&Local.from_utc_datetime(&base_article.created),
base_article.author.as_ref().map(|x| &**x)
)),
edit: true,
cancel_url: Some(base_article.link()),
title: &title,
raw: &body,
rendered: render_markdown(&body),
},
}.to_string())
)
}
}
})
)
}

View file

@ -67,8 +67,11 @@ impl Resource for NewArticleResource {
title: &'a str,
raw: &'a str,
rendered: &'a str,
script_js_checksum: &'a str,
}
impl<'a> Template<'a> {
fn script_js_checksum(&self) -> &'static str {
ScriptJs::checksum()
}
}
let title = self.slug.as_ref()
@ -92,7 +95,6 @@ impl Resource for NewArticleResource {
title: &title,
raw: "",
rendered: EMPTY_ARTICLE_MESSAGE,
script_js_checksum: ScriptJs::checksum(),
},
}.to_string()))
}))

View file

@ -48,10 +48,10 @@ impl Resource for TemporaryRedirectResource {
}
fn put(self: Box<Self>, _body: hyper::Body, _identity: Option<String>) -> ResponseFuture {
Box::new(self.head()
.and_then(move |head| {
Ok(head
.with_body(format!("Moved to {}", self.location)))
}))
self.get()
}
fn post(self: Box<Self>, _body: hyper::Body, _identity: Option<String>) -> ResponseFuture {
self.get()
}
}

View file

@ -42,9 +42,9 @@ struct NewRevision<'a> {
#[derive(Debug, PartialEq)]
pub struct RebaseConflict {
base_revision: i32,
title: merge::MergeResult<char>,
body: merge::MergeResult<String>,
pub base_article: models::ArticleRevisionStub,
pub title: merge::MergeResult<char>,
pub body: merge::MergeResult<String>,
}
#[derive(Debug, PartialEq)]
@ -164,6 +164,17 @@ impl<'a> SyncState<'a> {
)
}
fn get_article_revision_stub(&self, article_id: i32, revision: i32) -> Result<Option<models::ArticleRevisionStub>, Error> {
use schema::article_revisions;
Ok(self.query_article_revision_stubs(move |query| {
query
.filter(article_revisions::article_id.eq(article_id))
.filter(article_revisions::revision.eq(revision))
.limit(1)
})?.pop())
}
pub fn lookup_slug(&self, slug: String) -> Result<SlugLookup, Error> {
#[derive(Queryable)]
struct ArticleRevisionStub {
@ -233,7 +244,7 @@ impl<'a> SyncState<'a> {
(Clean(title), Clean(body)) => (title, body),
(title_merge, body_merge) => {
return Ok(RebaseResult::Conflict(RebaseConflict {
base_revision: revision,
base_article: self.get_article_revision_stub(article_id, revision+1)?.expect("Application layer guarantee"),
title: title_merge,
body: body_merge.to_strings(),
}));

View file

@ -1,4 +1,4 @@
<script src="_assets/script-{{script_js_checksum}}.js" defer></script>
<script src="_assets/script-{{script_js_checksum()}}.js" defer></script>
<div class="container {{#edit?}}edit{{/edit}}">
<div class="rendered">