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> { pub fn merge_lines<'a>(a: &'a str, o: &'a str, b: &'a str) -> MergeResult<&'a str> {
let oa = diff::lines(o, a); let oa = diff::lines(o, a);
let ob = diff::lines(o, b); let ob = diff::lines(o, b);

View file

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

View file

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

View file

@ -48,10 +48,10 @@ impl Resource for TemporaryRedirectResource {
} }
fn put(self: Box<Self>, _body: hyper::Body, _identity: Option<String>) -> ResponseFuture { fn put(self: Box<Self>, _body: hyper::Body, _identity: Option<String>) -> ResponseFuture {
Box::new(self.head() self.get()
.and_then(move |head| { }
Ok(head
.with_body(format!("Moved to {}", self.location))) 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)] #[derive(Debug, PartialEq)]
pub struct RebaseConflict { pub struct RebaseConflict {
base_revision: i32, pub base_article: models::ArticleRevisionStub,
title: merge::MergeResult<char>, pub title: merge::MergeResult<char>,
body: merge::MergeResult<String>, pub body: merge::MergeResult<String>,
} }
#[derive(Debug, PartialEq)] #[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> { pub fn lookup_slug(&self, slug: String) -> Result<SlugLookup, Error> {
#[derive(Queryable)] #[derive(Queryable)]
struct ArticleRevisionStub { struct ArticleRevisionStub {
@ -233,7 +244,7 @@ impl<'a> SyncState<'a> {
(Clean(title), Clean(body)) => (title, body), (Clean(title), Clean(body)) => (title, body),
(title_merge, body_merge) => { (title_merge, body_merge) => {
return Ok(RebaseResult::Conflict(RebaseConflict { 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, title: title_merge,
body: body_merge.to_strings(), 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="container {{#edit?}}edit{{/edit}}">
<div class="rendered"> <div class="rendered">