mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-02 19:45:57 +01:00
(refs #73)Add Wiki conflict detection and some fix.
This commit is contained in:
@@ -1,13 +1,14 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import service._
|
import service._
|
||||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, JGitUtil, StringUtil}
|
import util._
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
import util.ControlUtil._
|
import util.ControlUtil._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.api.errors.PatchApplyException
|
|
||||||
import org.scalatra.FlashMapSupport
|
import org.scalatra.FlashMapSupport
|
||||||
|
import service.WikiService.WikiPageInfo
|
||||||
|
import scala.Some
|
||||||
|
|
||||||
class WikiController extends WikiControllerBase
|
class WikiController extends WikiControllerBase
|
||||||
with WikiService with RepositoryService with AccountService with ActivityService
|
with WikiService with RepositoryService with AccountService with ActivityService
|
||||||
@@ -17,20 +18,22 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport {
|
|||||||
self: WikiService with RepositoryService with ActivityService
|
self: WikiService with RepositoryService with ActivityService
|
||||||
with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
||||||
|
|
||||||
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String)
|
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String)
|
||||||
|
|
||||||
val newForm = mapping(
|
val newForm = mapping(
|
||||||
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename, unique))),
|
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename, unique))),
|
||||||
"content" -> trim(label("Content" , text(required))),
|
"content" -> trim(label("Content" , text(required, conflictForNew))),
|
||||||
"message" -> trim(label("Message" , optional(text()))),
|
"message" -> trim(label("Message" , optional(text()))),
|
||||||
"currentPageName" -> trim(label("Current page name" , text()))
|
"currentPageName" -> trim(label("Current page name" , text())),
|
||||||
|
"id" -> trim(label("Latest commit id" , text()))
|
||||||
)(WikiPageEditForm.apply)
|
)(WikiPageEditForm.apply)
|
||||||
|
|
||||||
val editForm = mapping(
|
val editForm = mapping(
|
||||||
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename))),
|
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename))),
|
||||||
"content" -> trim(label("Content" , text(required))),
|
"content" -> trim(label("Content" , text(required, conflictForEdit))),
|
||||||
"message" -> trim(label("Message" , optional(text()))),
|
"message" -> trim(label("Message" , optional(text()))),
|
||||||
"currentPageName" -> trim(label("Current page name" , text(required)))
|
"currentPageName" -> trim(label("Current page name" , text(required))),
|
||||||
|
"id" -> trim(label("Latest commit id" , text(required)))
|
||||||
)(WikiPageEditForm.apply)
|
)(WikiPageEditForm.apply)
|
||||||
|
|
||||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||||
@@ -60,45 +63,43 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport {
|
|||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
defining(params("commitId").split("\\.\\.\\.")){ case Array(from, to) =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
||||||
wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
||||||
defining(params("commitId").split("\\.\\.\\.")){ case Array(from, to) =>
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
|
||||||
wiki.html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
wiki.html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
||||||
}
|
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_revert/:commitId")(collaboratorsOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
defining(params("commitId").split("\\.\\.\\.")){ case Array(from, to) =>
|
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))){
|
||||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))){
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
} else {
|
||||||
} else {
|
flash += "info" -> "This patch was not able to be reversed."
|
||||||
flash += "info" -> "This patch was not able to be reversed."
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}")
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_revert/:commitId")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/_revert/:commitId")(collaboratorsOnly { repository =>
|
||||||
defining(params("commitId").split("\\.\\.\\.")){ case Array(from, to) =>
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/}")
|
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){
|
||||||
} else {
|
redirect(s"/${repository.owner}/${repository.name}/wiki/}")
|
||||||
flash += "info" -> "This patch was not able to be reversed."
|
} else {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
flash += "info" -> "This patch was not able to be reversed."
|
||||||
}
|
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport {
|
|||||||
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
||||||
form.content, loginAccount, form.message.getOrElse("")).map { commitId =>
|
form.content, loginAccount, form.message.getOrElse(""), Some(form.id)).map { commitId =>
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
||||||
}
|
}
|
||||||
@@ -125,7 +126,7 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport {
|
|||||||
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
||||||
form.content, loginAccount, form.message.getOrElse(""))
|
form.content, loginAccount, form.message.getOrElse(""), None)
|
||||||
|
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||||
@@ -160,9 +161,16 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
||||||
getFileContent(repository.owner, repository.name, multiParams("splat").head).map { content =>
|
val path = multiParams("splat").head
|
||||||
contentType = "application/octet-stream"
|
|
||||||
content
|
getFileContent(repository.owner, repository.name, path).map { bytes =>
|
||||||
|
val mimeType = FileUtil.getMimeType(path)
|
||||||
|
contentType = if(mimeType == "application/octet-stream" && FileUtil.isText(bytes)){
|
||||||
|
"text/plain"
|
||||||
|
} else {
|
||||||
|
mimeType
|
||||||
|
}
|
||||||
|
bytes
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -182,5 +190,22 @@ trait WikiControllerBase extends ControllerBase with FlashMapSupport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def conflictForNew: Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String): Option[String] = {
|
||||||
|
optionIf(targetWikiPage.nonEmpty){
|
||||||
|
Some("Someone has created the wiki since you started. Please reload this page and re-apply your changes.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def conflictForEdit: Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String): Option[String] = {
|
||||||
|
optionIf(targetWikiPage.map(_.id != params("id")).getOrElse(true)){
|
||||||
|
Some("Someone has edited the wiki since you started. Please reload this page and re-apply your changes.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,9 @@ object WikiService {
|
|||||||
* @param content the page content
|
* @param content the page content
|
||||||
* @param committer the last committer
|
* @param committer the last committer
|
||||||
* @param time the last modified time
|
* @param time the last modified time
|
||||||
|
* @param id the latest commit id
|
||||||
*/
|
*/
|
||||||
case class WikiPageInfo(name: String, content: String, committer: String, time: Date)
|
case class WikiPageInfo(name: String, content: String, committer: String, time: Date, id: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The model for wiki page history.
|
* The model for wiki page history.
|
||||||
@@ -43,7 +44,7 @@ trait WikiService {
|
|||||||
if(!dir.exists){
|
if(!dir.exists){
|
||||||
try {
|
try {
|
||||||
JGitUtil.initRepository(dir)
|
JGitUtil.initRepository(dir)
|
||||||
saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit")
|
saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None)
|
||||||
} finally {
|
} finally {
|
||||||
// once delete cloned repository because initial cloned repository does not have 'branch.master.merge'
|
// once delete cloned repository because initial cloned repository does not have 'branch.master.merge'
|
||||||
FileUtils.deleteDirectory(Directory.getWikiWorkDir(owner, repository))
|
FileUtils.deleteDirectory(Directory.getWikiWorkDir(owner, repository))
|
||||||
@@ -59,7 +60,7 @@ trait WikiService {
|
|||||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||||
optionIf(!JGitUtil.isEmpty(git)){
|
optionIf(!JGitUtil.isEmpty(git)){
|
||||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
||||||
WikiPageInfo(file.name, new String(git.getRepository.open(file.id).getBytes, "UTF-8"), file.committer, file.time)
|
WikiPageInfo(file.name, new String(git.getRepository.open(file.id).getBytes, "UTF-8"), file.committer, file.time, file.commitId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +149,7 @@ trait WikiService {
|
|||||||
* Save the wiki page.
|
* Save the wiki page.
|
||||||
*/
|
*/
|
||||||
def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String,
|
def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String,
|
||||||
content: String, committer: model.Account, message: String): Option[String] = {
|
content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = {
|
||||||
|
|
||||||
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
||||||
defining(Directory.getWikiWorkDir(owner, repository)){ workDir =>
|
defining(Directory.getWikiWorkDir(owner, repository)){ workDir =>
|
||||||
@@ -158,6 +159,7 @@ trait WikiService {
|
|||||||
// write as file
|
// write as file
|
||||||
using(Git.open(workDir)){ git =>
|
using(Git.open(workDir)){ git =>
|
||||||
defining(new File(workDir, newPageName + ".md")){ file =>
|
defining(new File(workDir, newPageName + ".md")){ file =>
|
||||||
|
// new page
|
||||||
val created = !file.exists
|
val created = !file.exists
|
||||||
|
|
||||||
// created or updated
|
// created or updated
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
@helper.html.preview(repository, page.map(_.content).getOrElse(""), true, false, "width: 900px; height: 400px;", "")
|
@helper.html.preview(repository, page.map(_.content).getOrElse(""), true, false, "width: 900px; height: 400px;", "")
|
||||||
<input type="text" name="message" value="" style="width: 900px;" placeholder="Write a small message here explaining this change. (Optional)"/>
|
<input type="text" name="message" value="" style="width: 900px;" placeholder="Write a small message here explaining this change. (Optional)"/>
|
||||||
<input type="hidden" name="currentPageName" value="@pageName"/>
|
<input type="hidden" name="currentPageName" value="@pageName"/>
|
||||||
|
<input type="hidden" name="id" value="@page.map(_.id)"/>
|
||||||
<input type="submit" value="Save" class="btn btn-success">
|
<input type="submit" value="Save" class="btn btn-success">
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user