mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 22:45:51 +01:00
Merge branch 'master' into slick2
Conflicts: project/build.scala src/main/scala/app/IndexController.scala src/main/scala/app/RepositorySettingsController.scala src/main/scala/model/Account.scala src/main/scala/model/BasicTemplate.scala src/main/scala/model/Issue.scala src/main/scala/model/IssueComment.scala src/main/scala/model/package.scala src/main/scala/service/IssuesService.scala src/main/scala/service/PullRequestService.scala src/main/scala/service/RepositoryService.scala src/main/scala/service/WikiService.scala src/main/scala/servlet/TransactionFilter.scala src/main/scala/util/Notifier.scala
This commit is contained in:
@@ -1,193 +1,209 @@
|
||||
package servlet
|
||||
|
||||
import java.io.File
|
||||
import java.sql.{DriverManager, Connection}
|
||||
import org.apache.commons.io.FileUtils
|
||||
import javax.servlet.{ServletContext, ServletContextListener, ServletContextEvent}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import util.Directory
|
||||
|
||||
object AutoUpdate {
|
||||
|
||||
/**
|
||||
* Version of GitBucket
|
||||
*
|
||||
* @param majorVersion the major version
|
||||
* @param minorVersion the minor version
|
||||
*/
|
||||
case class Version(majorVersion: Int, minorVersion: Int){
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[servlet.AutoUpdate.Version])
|
||||
|
||||
/**
|
||||
* Execute update/MAJOR_MINOR.sql to update schema to this version.
|
||||
* If corresponding SQL file does not exist, this method do nothing.
|
||||
*/
|
||||
def update(conn: Connection): Unit = {
|
||||
val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
|
||||
|
||||
using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in =>
|
||||
if(in != null){
|
||||
val sql = IOUtils.toString(in, "UTF-8")
|
||||
using(conn.createStatement()){ stmt =>
|
||||
logger.debug(sqlPath + "=" + sql)
|
||||
stmt.executeUpdate(sql)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MAJOR.MINOR
|
||||
*/
|
||||
val versionString = s"${majorVersion}.${minorVersion}"
|
||||
}
|
||||
|
||||
/**
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(2, 0){
|
||||
override def update(conn: Connection): Unit = {
|
||||
import eu.medsea.mimeutil.{MimeUtil2, MimeType}
|
||||
|
||||
val mimeUtil = new MimeUtil2()
|
||||
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
|
||||
|
||||
super.update(conn)
|
||||
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
|
||||
while(rs.next){
|
||||
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
|
||||
if(dir.exists && dir.isDirectory){
|
||||
dir.listFiles.foreach { file =>
|
||||
if(file.getName.indexOf('.') < 0){
|
||||
val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
|
||||
if(mimeType.startsWith("image/")){
|
||||
file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Version(1, 13),
|
||||
Version(1, 12),
|
||||
Version(1, 11),
|
||||
Version(1, 10),
|
||||
Version(1, 9),
|
||||
Version(1, 8),
|
||||
Version(1, 7),
|
||||
Version(1, 6),
|
||||
Version(1, 5),
|
||||
Version(1, 4),
|
||||
new Version(1, 3){
|
||||
override def update(conn: Connection): Unit = {
|
||||
super.update(conn)
|
||||
// Fix wiki repository configuration
|
||||
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
|
||||
while(rs.next){
|
||||
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
||||
defining(git.getRepository.getConfig){ config =>
|
||||
if(!config.getBoolean("http", "receivepack", false)){
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Version(1, 2),
|
||||
Version(1, 1),
|
||||
Version(1, 0),
|
||||
Version(0, 0)
|
||||
)
|
||||
|
||||
/**
|
||||
* The head version of BitBucket.
|
||||
*/
|
||||
val headVersion = versions.head
|
||||
|
||||
/**
|
||||
* The version file (GITBUCKET_HOME/version).
|
||||
*/
|
||||
lazy val versionFile = new File(GitBucketHome, "version")
|
||||
|
||||
/**
|
||||
* Returns the current version from the version file.
|
||||
*/
|
||||
def getCurrentVersion(): Version = {
|
||||
if(versionFile.exists){
|
||||
FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match {
|
||||
case Array(majorVersion, minorVersion) => {
|
||||
versions.find { v =>
|
||||
v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt
|
||||
}.getOrElse(Version(0, 0))
|
||||
}
|
||||
case _ => Version(0, 0)
|
||||
}
|
||||
} else Version(0, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update database schema automatically in the context initializing.
|
||||
*/
|
||||
class AutoUpdateListener extends ServletContextListener {
|
||||
import AutoUpdate._
|
||||
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
|
||||
|
||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||
val datadir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||
if(datadir != null){
|
||||
System.setProperty("gitbucket.home", datadir)
|
||||
}
|
||||
org.h2.Driver.load()
|
||||
event.getServletContext.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true")
|
||||
|
||||
logger.debug("Start schema update")
|
||||
defining(getConnection(event.getServletContext)){ conn =>
|
||||
try {
|
||||
defining(getCurrentVersion()){ currentVersion =>
|
||||
if(currentVersion == headVersion){
|
||||
logger.debug("No update")
|
||||
} else if(!versions.contains(currentVersion)){
|
||||
logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.")
|
||||
} else {
|
||||
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
|
||||
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
||||
conn.commit()
|
||||
logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex: Throwable => {
|
||||
logger.error("Failed to schema update", ex)
|
||||
ex.printStackTrace()
|
||||
conn.rollback()
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.debug("End schema update")
|
||||
}
|
||||
|
||||
def contextDestroyed(sce: ServletContextEvent): Unit = {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
private def getConnection(servletContext: ServletContext): Connection =
|
||||
DriverManager.getConnection(
|
||||
servletContext.getInitParameter("db.url"),
|
||||
servletContext.getInitParameter("db.user"),
|
||||
servletContext.getInitParameter("db.password"))
|
||||
|
||||
}
|
||||
package servlet
|
||||
|
||||
import java.io.File
|
||||
import java.sql.{DriverManager, Connection}
|
||||
import org.apache.commons.io.FileUtils
|
||||
import javax.servlet.{ServletContext, ServletContextListener, ServletContextEvent}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import util.Directory
|
||||
import plugin.PluginUpdateJob
|
||||
|
||||
object AutoUpdate {
|
||||
|
||||
/**
|
||||
* Version of GitBucket
|
||||
*
|
||||
* @param majorVersion the major version
|
||||
* @param minorVersion the minor version
|
||||
*/
|
||||
case class Version(majorVersion: Int, minorVersion: Int){
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[servlet.AutoUpdate.Version])
|
||||
|
||||
/**
|
||||
* Execute update/MAJOR_MINOR.sql to update schema to this version.
|
||||
* If corresponding SQL file does not exist, this method do nothing.
|
||||
*/
|
||||
def update(conn: Connection): Unit = {
|
||||
val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
|
||||
|
||||
using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in =>
|
||||
if(in != null){
|
||||
val sql = IOUtils.toString(in, "UTF-8")
|
||||
using(conn.createStatement()){ stmt =>
|
||||
logger.debug(sqlPath + "=" + sql)
|
||||
stmt.executeUpdate(sql)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MAJOR.MINOR
|
||||
*/
|
||||
val versionString = s"${majorVersion}.${minorVersion}"
|
||||
}
|
||||
|
||||
/**
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(2, 0){
|
||||
override def update(conn: Connection): Unit = {
|
||||
import eu.medsea.mimeutil.{MimeUtil2, MimeType}
|
||||
|
||||
val mimeUtil = new MimeUtil2()
|
||||
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
|
||||
|
||||
super.update(conn)
|
||||
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
|
||||
while(rs.next){
|
||||
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
|
||||
if(dir.exists && dir.isDirectory){
|
||||
dir.listFiles.foreach { file =>
|
||||
if(file.getName.indexOf('.') < 0){
|
||||
val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
|
||||
if(mimeType.startsWith("image/")){
|
||||
file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Version(1, 13),
|
||||
Version(1, 12),
|
||||
Version(1, 11),
|
||||
Version(1, 10),
|
||||
Version(1, 9),
|
||||
Version(1, 8),
|
||||
Version(1, 7),
|
||||
Version(1, 6),
|
||||
Version(1, 5),
|
||||
Version(1, 4),
|
||||
new Version(1, 3){
|
||||
override def update(conn: Connection): Unit = {
|
||||
super.update(conn)
|
||||
// Fix wiki repository configuration
|
||||
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
|
||||
while(rs.next){
|
||||
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
||||
defining(git.getRepository.getConfig){ config =>
|
||||
if(!config.getBoolean("http", "receivepack", false)){
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Version(1, 2),
|
||||
Version(1, 1),
|
||||
Version(1, 0),
|
||||
Version(0, 0)
|
||||
)
|
||||
|
||||
/**
|
||||
* The head version of BitBucket.
|
||||
*/
|
||||
val headVersion = versions.head
|
||||
|
||||
/**
|
||||
* The version file (GITBUCKET_HOME/version).
|
||||
*/
|
||||
lazy val versionFile = new File(GitBucketHome, "version")
|
||||
|
||||
/**
|
||||
* Returns the current version from the version file.
|
||||
*/
|
||||
def getCurrentVersion(): Version = {
|
||||
if(versionFile.exists){
|
||||
FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match {
|
||||
case Array(majorVersion, minorVersion) => {
|
||||
versions.find { v =>
|
||||
v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt
|
||||
}.getOrElse(Version(0, 0))
|
||||
}
|
||||
case _ => Version(0, 0)
|
||||
}
|
||||
} else Version(0, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update database schema automatically in the context initializing.
|
||||
*/
|
||||
class AutoUpdateListener extends ServletContextListener {
|
||||
import org.quartz.impl.StdSchedulerFactory
|
||||
import org.quartz.JobBuilder._
|
||||
import org.quartz.TriggerBuilder._
|
||||
import org.quartz.SimpleScheduleBuilder._
|
||||
import AutoUpdate._
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
|
||||
private val scheduler = StdSchedulerFactory.getDefaultScheduler
|
||||
|
||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||
val datadir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||
if(datadir != null){
|
||||
System.setProperty("gitbucket.home", datadir)
|
||||
}
|
||||
org.h2.Driver.load()
|
||||
event.getServletContext.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true")
|
||||
|
||||
logger.debug("Start schema update")
|
||||
defining(getConnection(event.getServletContext)){ conn =>
|
||||
try {
|
||||
defining(getCurrentVersion()){ currentVersion =>
|
||||
if(currentVersion == headVersion){
|
||||
logger.debug("No update")
|
||||
} else if(!versions.contains(currentVersion)){
|
||||
logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.")
|
||||
} else {
|
||||
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
|
||||
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
||||
conn.commit()
|
||||
logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex: Throwable => {
|
||||
logger.error("Failed to schema update", ex)
|
||||
ex.printStackTrace()
|
||||
conn.rollback()
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.debug("End schema update")
|
||||
|
||||
logger.debug("Starting plugin system...")
|
||||
plugin.PluginSystem.init()
|
||||
|
||||
scheduler.start()
|
||||
PluginUpdateJob.schedule(scheduler)
|
||||
logger.debug("PluginUpdateJob is started.")
|
||||
|
||||
logger.debug("Plugin system is initialized.")
|
||||
}
|
||||
|
||||
def contextDestroyed(sce: ServletContextEvent): Unit = {
|
||||
scheduler.shutdown()
|
||||
}
|
||||
|
||||
private def getConnection(servletContext: ServletContext): Connection =
|
||||
DriverManager.getConnection(
|
||||
servletContext.getInitParameter("db.url"),
|
||||
servletContext.getInitParameter("db.user"),
|
||||
servletContext.getInitParameter("db.password"))
|
||||
|
||||
}
|
||||
|
||||
81
src/main/scala/servlet/PluginActionInvokeFilter.scala
Normal file
81
src/main/scala/servlet/PluginActionInvokeFilter.scala
Normal file
@@ -0,0 +1,81 @@
|
||||
package servlet
|
||||
|
||||
import javax.servlet._
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import twirl.api.Html
|
||||
import service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
import model.Account
|
||||
import util.{JGitUtil, Keys}
|
||||
|
||||
class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService {
|
||||
|
||||
def init(config: FilterConfig) = {}
|
||||
|
||||
def destroy(): Unit = {}
|
||||
|
||||
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
||||
(req, res) match {
|
||||
case (request: HttpServletRequest, response: HttpServletResponse) => {
|
||||
Database(req.getServletContext) withTransaction { implicit session =>
|
||||
val path = req.asInstanceOf[HttpServletRequest].getRequestURI
|
||||
if(!processGlobalAction(path, request, response) && !processRepositoryAction(path, request, response)){
|
||||
chain.doFilter(req, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse): Boolean = {
|
||||
plugin.PluginSystem.globalActions.find(_.path == path).map { action =>
|
||||
val result = action.function(request, response)
|
||||
result match {
|
||||
case x: String => {
|
||||
response.setContentType("text/html; charset=UTF-8")
|
||||
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||
implicit val context = app.Context(loadSystemSettings(), Option(loginAccount), request)
|
||||
val html = _root_.html.main("GitBucket", None)(Html(x))
|
||||
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
|
||||
}
|
||||
case x => {
|
||||
// TODO returns as JSON?
|
||||
response.setContentType("application/json; charset=UTF-8")
|
||||
|
||||
}
|
||||
}
|
||||
true
|
||||
} getOrElse false
|
||||
}
|
||||
|
||||
private def processRepositoryAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
|
||||
(implicit session: model.simple.Session): Boolean = {
|
||||
val elements = path.split("/")
|
||||
if(elements.length > 3){
|
||||
val owner = elements(1)
|
||||
val name = elements(2)
|
||||
val remain = elements.drop(3).mkString("/", "/", "")
|
||||
getRepository(owner, name, "").flatMap { repository => // TODO fill baseUrl
|
||||
plugin.PluginSystem.repositoryActions.find(_.path == remain).map { action =>
|
||||
val result = action.function(request, response)
|
||||
result match {
|
||||
case x: String => {
|
||||
response.setContentType("text/html; charset=UTF-8")
|
||||
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||
implicit val context = app.Context(loadSystemSettings(), Option(loginAccount), request)
|
||||
val html = _root_.html.main("GitBucket", None)(_root_.html.menu("", repository)(Html(x))) // TODO specify active side menu
|
||||
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
|
||||
}
|
||||
case x => {
|
||||
// TODO returns as JSON?
|
||||
response.setContentType("application/json; charset=UTF-8")
|
||||
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
} getOrElse false
|
||||
} else false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class TransactionFilter extends Filter {
|
||||
}
|
||||
|
||||
object Database {
|
||||
|
||||
def apply(context: ServletContext): slick.jdbc.JdbcBackend.Database =
|
||||
slick.jdbc.JdbcBackend.Database.forURL(context.getInitParameter("db.url"),
|
||||
context.getInitParameter("db.user"),
|
||||
|
||||
Reference in New Issue
Block a user