mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 07:46:16 +02:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5674f0e980 | ||
|
|
b9ade60eb2 | ||
|
|
96303723fa | ||
|
|
0f5dbc5788 | ||
|
|
8df0c3a439 | ||
|
|
ca6a86816a | ||
|
|
3ea939798f | ||
|
|
d947410e3c | ||
|
|
db59bc08ac | ||
|
|
95a8649f79 | ||
|
|
ffd10122ed | ||
|
|
c4c39f36e9 | ||
|
|
96900c3cbf | ||
|
|
69fa370d12 | ||
|
|
7496437d11 | ||
|
|
33b7d09af7 | ||
|
|
53d0974760 | ||
|
|
a87399f223 | ||
|
|
975dfb17e1 | ||
|
|
8b8bd0289b | ||
|
|
3bb69c623b | ||
|
|
dd427bdbef | ||
|
|
b40657a14a | ||
|
|
21ca5b2eec | ||
|
|
b78d584d8a | ||
|
|
e6b666a66a | ||
|
|
bab93ea4f5 | ||
|
|
7fe98253ae | ||
|
|
13385cbced | ||
|
|
3f20cec7b2 | ||
|
|
a0e4b020ca | ||
|
|
ea5d898b27 | ||
|
|
4e652b5ccd | ||
|
|
dd809896c8 | ||
|
|
93536d3365 | ||
|
|
098b18fe6d | ||
|
|
66efdac757 | ||
|
|
45545d3815 | ||
|
|
b65d41731b | ||
|
|
be19e97518 | ||
|
|
2ebf2b99bd | ||
|
|
193a312b22 | ||
|
|
6a2d2ebfd1 | ||
|
|
6d200aa340 | ||
|
|
a0fbb90048 | ||
|
|
08e29e7077 | ||
|
|
3bef71f5f2 |
@@ -80,6 +80,14 @@ Run the following commands in `Terminal` to
|
||||
|
||||
Release Notes
|
||||
--------
|
||||
### 2.3 - 1 Sep 2014
|
||||
- Scala based plugin system
|
||||
- Embedded Jetty war extraction directory moved to `GITBUCKET_HOME/tmp`
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.2.1 - 5 Aug 2014
|
||||
- Bug fix
|
||||
|
||||
### 2.2 - 4 Aug 2014
|
||||
- Plug-in system is available
|
||||
- Move to Scala 2.11, Scalatra 2.3 and Slick 2.1
|
||||
|
||||
13
contrib/README.md
Normal file
13
contrib/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Contrib Notes #
|
||||
|
||||
The configuration script adapts according to the OS.
|
||||
The `linux` directory contains scripts for Ubuntu and RedHat.
|
||||
The Mac scripts have been folded in as well.
|
||||
Common scripts are in this directory.
|
||||
|
||||
This version of scripts has so far only been tested on Ubuntu and Mac. Someone else will have to test on RedHat.
|
||||
|
||||
To run:
|
||||
1. Edit `gitbucket.conf` to suit.
|
||||
2. Type: `install`
|
||||
|
||||
62
contrib/gitbucket.conf
Normal file
62
contrib/gitbucket.conf
Normal file
@@ -0,0 +1,62 @@
|
||||
# Configuration section is below. Ignore this part
|
||||
|
||||
function isUbuntu {
|
||||
if [ -f /etc/lsb-release ]; then
|
||||
grep -i ubuntu /etc/lsb-release | head -n 1 | cut -d \ -f 1 | cut -d = -f 2
|
||||
fi
|
||||
}
|
||||
|
||||
function isRedHat {
|
||||
if [ -d "/etc/rc.d/init.d" ]; then echo yes; fi
|
||||
}
|
||||
|
||||
function isMac {
|
||||
if [[ "$(uname -a | cut -d \ -f 1 )" == "Darwin" ]]; then echo yes; fi
|
||||
}
|
||||
|
||||
#
|
||||
# Configuration section start
|
||||
#
|
||||
|
||||
# Bind host
|
||||
GITBUCKET_HOST=0.0.0.0
|
||||
|
||||
# Other Java option
|
||||
GITBUCKET_JVM_OPTS=-Dmail.smtp.starttls.enable=true
|
||||
|
||||
# Data directory, holds repositories
|
||||
GITBUCKET_HOME=/var/lib/gitbucket
|
||||
|
||||
GITBUCKET_LOG_DIR=/var/log/gitbucket
|
||||
|
||||
# Server port
|
||||
GITBUCKET_PORT=8080
|
||||
|
||||
# URL prefix for the GitBucket page (http://<host>:<port>/<prefix>/)
|
||||
GITBUCKET_PREFIX=
|
||||
|
||||
# Directory where GitBucket is installed
|
||||
# Configuration is stored here:
|
||||
GITBUCKET_DIR=/usr/share/gitbucket
|
||||
GITBUCKET_WAR_DIR=$GITBUCKET_DIR/lib
|
||||
|
||||
# Path to the WAR file
|
||||
GITBUCKET_WAR_FILE=$GITBUCKET_WAR_DIR/gitbucket.war
|
||||
|
||||
# GitBucket version to fetch when installing
|
||||
GITBUCKET_VERSION=2.1
|
||||
|
||||
#
|
||||
# End of configuration section. Ignore this part
|
||||
#
|
||||
if [ `isUbuntu` ]; then
|
||||
GITBUCKET_SERVICE=/etc/init.d/gitbucket
|
||||
elif [ `isRedHat` ]; then
|
||||
GITBUCKET_SERVICE=/etc/rc.d/init.d
|
||||
elif [ `isMac` ]; then
|
||||
GITBUCKET_SERVICE=/Library/StartupItems/GitBucket/GitBucket
|
||||
else
|
||||
echo "Don't know how to install onto this OS"
|
||||
exit -2
|
||||
fi
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# /etc/rc.d/init.d/gitbucket
|
||||
# RedHat: /etc/rc.d/init.d/gitbucket
|
||||
# Ubuntu: /etc/init.d/gitbucket
|
||||
# Mac OS/X: /Library/StartupItems/GitBucket
|
||||
#
|
||||
# Starts the GitBucket server
|
||||
#
|
||||
@@ -8,28 +10,44 @@
|
||||
# description: Run GitBucket server
|
||||
# processname: java
|
||||
|
||||
# Source function library
|
||||
. /etc/rc.d/init.d/functions
|
||||
set -e
|
||||
|
||||
[ -f /etc/rc.d/init.d/functions ] && source /etc/rc.d/init.d/functions # RedHat
|
||||
[ -f /etc/rc.common ] && source /etc/rc.common # Mac OS/X
|
||||
|
||||
# Default values
|
||||
GITBUCKET_HOME=/var/lib/gitbucket
|
||||
GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
|
||||
|
||||
# Pull in cq settings
|
||||
[ -f /etc/sysconfig/gitbucket ] && . /etc/sysconfig/gitbucket
|
||||
[ -f /etc/sysconfig/gitbucket ] && source /etc/sysconfig/gitbucket # RedHat
|
||||
[ -f gitbucket.conf ] && source gitbucket.conf # For all systems
|
||||
|
||||
# Location of the log and PID file
|
||||
LOG_FILE=/var/log/gitbucket/run.log
|
||||
LOG_FILE=$GITBUCKET_LOG_DIR/run.log
|
||||
PID_FILE=/var/run/gitbucket.pid
|
||||
|
||||
# Default return value
|
||||
RETVAL=0
|
||||
RED='\033[1m\E[37;41m'
|
||||
GREEN='\033[1m\E[37;42m'
|
||||
OFF='\E[0m'
|
||||
|
||||
if [ -z "$(which success)" ]; then
|
||||
function success {
|
||||
printf "%b\n" "$GREEN $* $OFF"
|
||||
}
|
||||
fi
|
||||
if [ -z "$(which failure)" ]; then
|
||||
function failure {
|
||||
printf "%b\n" "$RED $* $OFF"
|
||||
}
|
||||
fi
|
||||
|
||||
RETVAL=0
|
||||
|
||||
start() {
|
||||
echo -n $"Starting GitBucket server: "
|
||||
|
||||
# Compile statup parameters
|
||||
START_OPTS=
|
||||
if [ $GITBUCKET_PORT ]; then
|
||||
START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
|
||||
fi
|
||||
@@ -40,17 +58,15 @@ start() {
|
||||
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
|
||||
fi
|
||||
|
||||
# Run the Java process
|
||||
GITBUCKET_HOME="${GITBUCKET_HOME}" java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
|
||||
RETVAL=$?
|
||||
|
||||
# Store PID of the Java process into a file
|
||||
echo $! > $PID_FILE
|
||||
|
||||
if [ $RETVAL -eq 0 ] ; then
|
||||
success "GitBucket startup"
|
||||
success "Success"
|
||||
else
|
||||
failure "GitBucket startup"
|
||||
failure "Exit code $RETVAL"
|
||||
fi
|
||||
|
||||
echo
|
||||
@@ -82,25 +98,41 @@ restart() {
|
||||
start
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
## MacOS proxies for System V service hooks:
|
||||
StartService() {
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
}
|
||||
|
||||
StopService() {
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
}
|
||||
|
||||
RestartService() {
|
||||
restart
|
||||
;;
|
||||
status)
|
||||
status -p $PID_FILE java
|
||||
RETVAL=$?
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 [start|stop|restart|status]"
|
||||
RETVAL=2
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
exit $RETVAL
|
||||
if [ `isMac` ]; then
|
||||
RunService "$1"
|
||||
else
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
status)
|
||||
status -p $PID_FILE java
|
||||
RETVAL=$?
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 [start|stop|restart|status]"
|
||||
RETVAL=2
|
||||
esac
|
||||
exit $RETVAL
|
||||
fi
|
||||
|
||||
69
contrib/install
Executable file
69
contrib/install
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Only tested on Ubuntu 14.04
|
||||
|
||||
# Uses information stored in GitBucket git repo on GitHub as defaults.
|
||||
# Edit gitbucket.conf before running this
|
||||
|
||||
set -e
|
||||
|
||||
GITBUCKET_VERSION=2.1
|
||||
|
||||
if [ ! -f gitbucket.conf ]; then
|
||||
echo "gitbucket.conf not found, aborting"
|
||||
exit -3
|
||||
fi
|
||||
source gitbucket.conf
|
||||
|
||||
function createDir {
|
||||
if [ ! -d "$1" ]; then
|
||||
echo "Making $1 directory."
|
||||
sudo mkdir -p "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$(which iptables)" ]; then
|
||||
echo "Opening port $GITBUCKET_PORT in firewall."
|
||||
sudo iptables -A INPUT -p tcp --dport $GITBUCKET_PORT -j ACCEPT
|
||||
echo "Please use iptables-persistent:"
|
||||
echo " sudo apt-get install iptables-persistent"
|
||||
echo "After installed, you can save/reload iptables rules anytime:"
|
||||
echo " sudo /etc/init.d/iptables-persistent save"
|
||||
echo " sudo /etc/init.d/iptables-persistent reload"
|
||||
fi
|
||||
|
||||
createDir "$GITBUCKET_HOME"
|
||||
createDir "$GITBUCKET_WAR_DIR"
|
||||
createDir "$GITBUCKET_DIR"
|
||||
createDir "$GITBUCKET_LOG_DIR"
|
||||
|
||||
echo "Fetching GitBucket v$GITBUCKET_VERSION and saving as $GITBUCKET_WAR_FILE"
|
||||
sudo wget -qO "$GITBUCKET_WAR_FILE" https://github.com/takezoe/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
|
||||
|
||||
sudo rm -f "$GITBUCKET_LOG_DIR/run.log"
|
||||
|
||||
echo "Copying gitbucket.conf to $GITBUCKET_DIR"
|
||||
sudo cp gitbucket.conf $GITBUCKET_DIR
|
||||
if [ `isUbuntu` ] || [ `isRedHat` ]; then
|
||||
sudo cp gitbucket.init "$GITBUCKET_SERVICE"
|
||||
# Install gitbucket as a service that starts when system boots
|
||||
sudo chown root:root $GITBUCKET_SERVICE
|
||||
sudo chmod 755 $GITBUCKET_SERVICE
|
||||
sudo update-rc.d "$(basename $GITBUCKET_SERVICE)" defaults 98 02
|
||||
echo "Starting GitBucket service"
|
||||
sudo $GITBUCKET_SERVICE start
|
||||
elif [ `isMac` ]; then
|
||||
sudo macosx/makePlist
|
||||
echo "Starting GitBucket service"
|
||||
sudo cp gitbucket.conf "$GITBUCKET_SERVICE"
|
||||
sudo cp gitbucket.init "$GITBUCKET_SERVICE"
|
||||
sudo chmod a+x "$GITBUCKET_SERVICE"
|
||||
sudo "$GITBUCKET_SERVICE" start
|
||||
else
|
||||
echo "Don't know how to install this OS"
|
||||
exit -2
|
||||
fi
|
||||
|
||||
if [ $? != 0 ]; then
|
||||
less "$GITBUCKET_LOG_DIR/run.log"
|
||||
fi
|
||||
14
contrib/macosx/gitbucket.plist → contrib/macosx/makePlist
Normal file → Executable file
14
contrib/macosx/gitbucket.plist → contrib/macosx/makePlist
Normal file → Executable file
@@ -1,3 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
# From http://docstore.mik.ua/orelly/unix3/mac/ch02_02.htm
|
||||
source gitbucket.conf
|
||||
GITBUCKET_SERVICE_DIR=`dirname "$GITBUCKET_SERVICE"`
|
||||
mkdir -p "$GITBUCKET_SERVICE_DIR"
|
||||
cat << EOF > "$GITBUCKET_SERVICE_DIR/gitbucket.plist"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
@@ -7,14 +14,15 @@
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/bin/java</string>
|
||||
<string>-Dmail.smtp.starttls.enable=true</string>
|
||||
<string>$GITBUCKET_JVM_OPTS</string>
|
||||
<string>-jar</string>
|
||||
<string>gitbucket.war</string>
|
||||
<string>--host=127.0.0.1</string>
|
||||
<string>--port=8080</string>
|
||||
<string>--host=$GITBUCKET_HOST</string>
|
||||
<string>--port=$GITBUCKET_PORT</string>
|
||||
<string>--https=true</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
@@ -1,17 +0,0 @@
|
||||
# Bind host
|
||||
#GITBUCKET_HOST=0.0.0.0
|
||||
|
||||
# Server port
|
||||
#GITBUCKET_PORT=8080
|
||||
|
||||
# Data directory (GITBUCKET_HOME/gitbucket)
|
||||
#GITBUCKET_HOME=/var/lib/gitbucket
|
||||
|
||||
# Path to the WAR file
|
||||
#GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
|
||||
|
||||
# URL prefix for the GitBucket page (http://<host>:<port>/<prefix>/)
|
||||
#GITBUCKET_PREFIX=
|
||||
|
||||
# Other Java option
|
||||
#GITBUCKET_JVM_OPTS=
|
||||
@@ -1 +1 @@
|
||||
sbt.version=0.13.1
|
||||
sbt.version=0.13.5
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import sbt._
|
||||
import Keys._
|
||||
import org.scalatra.sbt._
|
||||
import twirl.sbt.TwirlPlugin._
|
||||
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
||||
import play.twirl.sbt.SbtTwirl
|
||||
import play.twirl.sbt.Import.TwirlKeys._
|
||||
|
||||
object MyBuild extends Build {
|
||||
val Organization = "jp.sf.amateras"
|
||||
@@ -13,46 +14,47 @@ object MyBuild extends Build {
|
||||
|
||||
lazy val project = Project (
|
||||
"gitbucket",
|
||||
file("."),
|
||||
settings = Defaults.defaultSettings ++ ScalatraPlugin.scalatraWithJRebel ++ Seq(
|
||||
sourcesInBase := false,
|
||||
organization := Organization,
|
||||
name := Name,
|
||||
version := Version,
|
||||
scalaVersion := ScalaVersion,
|
||||
resolvers ++= Seq(
|
||||
Classpaths.typesafeReleases,
|
||||
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
|
||||
),
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.1.201406201815-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.1.201406201815-r",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.2.10",
|
||||
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
|
||||
"commons-io" % "commons-io" % "2.4",
|
||||
"org.pegdown" % "pegdown" % "1.4.1",
|
||||
"org.apache.commons" % "commons-compress" % "1.5",
|
||||
"org.apache.commons" % "commons-email" % "1.3.1",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.3",
|
||||
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
||||
"com.typesafe.slick" %% "slick" % "2.1.0-RC3",
|
||||
"org.mozilla" % "rhino" % "1.7R4",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"org.quartz-scheduler" % "quartz" % "2.2.1",
|
||||
"com.h2database" % "h2" % "1.4.180",
|
||||
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
|
||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
||||
"junit" % "junit" % "4.11" % "test"
|
||||
),
|
||||
EclipseKeys.withSource := true,
|
||||
javacOptions in compile ++= Seq("-target", "6", "-source", "6"),
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
||||
packageOptions += Package.MainClass("JettyLauncher")
|
||||
) ++ seq(Twirl.settings: _*)
|
||||
file(".")
|
||||
)
|
||||
.settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
||||
.settings(
|
||||
sourcesInBase := false,
|
||||
organization := Organization,
|
||||
name := Name,
|
||||
version := Version,
|
||||
scalaVersion := ScalaVersion,
|
||||
resolvers ++= Seq(
|
||||
Classpaths.typesafeReleases,
|
||||
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
|
||||
),
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.1.201406201815-r",
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.1.201406201815-r",
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.2.10",
|
||||
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
|
||||
"commons-io" % "commons-io" % "2.4",
|
||||
"org.pegdown" % "pegdown" % "1.4.1",
|
||||
"org.apache.commons" % "commons-compress" % "1.5",
|
||||
"org.apache.commons" % "commons-email" % "1.3.1",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.3",
|
||||
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
||||
"com.typesafe.slick" %% "slick" % "2.1.0-RC3",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"org.quartz-scheduler" % "quartz" % "2.2.1",
|
||||
"com.h2database" % "h2" % "1.4.180",
|
||||
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
|
||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
||||
"junit" % "junit" % "4.11" % "test",
|
||||
"com.typesafe.play" %% "twirl-compiler" % "1.0.2"
|
||||
),
|
||||
EclipseKeys.withSource := true,
|
||||
javacOptions in compile ++= Seq("-target", "6", "-source", "6"),
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
||||
packageOptions += Package.MainClass("JettyLauncher")
|
||||
).enablePlugins(SbtTwirl)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
|
||||
|
||||
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
|
||||
|
||||
resolvers += "spray repo" at "http://repo.spray.io"
|
||||
|
||||
addSbtPlugin("io.spray" % "sbt-twirl" % "0.7.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
|
||||
|
||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
|
||||
|
||||
Binary file not shown.
2
sbt.bat
2
sbt.bat
@@ -1,2 +1,2 @@
|
||||
set SCRIPT_DIR=%~dp0
|
||||
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.1.jar" %*
|
||||
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.5.jar" %*
|
||||
|
||||
2
sbt.sh
2
sbt.sh
@@ -1 +1 @@
|
||||
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.1.jar "$@"
|
||||
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.5.jar "$@"
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
@@ -44,6 +42,14 @@ public class JettyLauncher {
|
||||
server.addConnector(connector);
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
|
||||
File tmpDir = new File(getGitBucketHome(), "tmp");
|
||||
if(tmpDir.exists()){
|
||||
deleteDirectory(tmpDir);
|
||||
}
|
||||
tmpDir.mkdirs();
|
||||
context.setTempDirectory(tmpDir);
|
||||
|
||||
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
||||
URL location = domain.getCodeSource().getLocation();
|
||||
|
||||
@@ -59,4 +65,27 @@ public class JettyLauncher {
|
||||
server.start();
|
||||
server.join();
|
||||
}
|
||||
|
||||
private static File getGitBucketHome(){
|
||||
String home = System.getProperty("gitbucket.home");
|
||||
if(home != null && home.length() > 0){
|
||||
return new File(home);
|
||||
}
|
||||
home = System.getenv("GITBUCKET_HOME");
|
||||
if(home != null && home.length() > 0){
|
||||
return new File(home);
|
||||
}
|
||||
return new File(System.getProperty("user.home"), ".gitbucket");
|
||||
}
|
||||
|
||||
private static void deleteDirectory(File dir){
|
||||
for(File file: dir.listFiles()){
|
||||
if(file.isFile()){
|
||||
file.delete();
|
||||
} else if(file.isDirectory()){
|
||||
deleteDirectory(file);
|
||||
}
|
||||
}
|
||||
dir.delete();
|
||||
}
|
||||
}
|
||||
|
||||
6
src/main/resources/update/2_3.sql
Normal file
6
src/main/resources/update/2_3.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE PLUGIN (
|
||||
PLUGIN_ID VARCHAR(100) NOT NULL,
|
||||
VERSION VARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE PLUGIN ADD CONSTRAINT IDX_PLUGIN_PK PRIMARY KEY (PLUGIN_ID);
|
||||
@@ -335,7 +335,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
builder.finish()
|
||||
|
||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||
loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
|
||||
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
import org.scalatra.Ok
|
||||
import model.Issue
|
||||
import plugin.PluginSystem
|
||||
|
||||
class IssuesController extends IssuesControllerBase
|
||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
||||
|
||||
@@ -191,6 +191,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
val lastModifiedCommit = JGitUtil.getLastModifiedCommit(git, revCommit, path)
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
if(raw){
|
||||
// Download
|
||||
@@ -200,7 +201,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
} else {
|
||||
repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
|
||||
new JGitUtil.CommitInfo(revCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
}
|
||||
} getOrElse NotFound
|
||||
}
|
||||
@@ -311,10 +312,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repo.html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} else {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
//val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head)
|
||||
// get specified commit
|
||||
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
|
||||
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
|
||||
val lastModifiedCommit = if(path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path)
|
||||
// get files
|
||||
val files = JGitUtil.getFileList(git, revision, path)
|
||||
val parentPath = if (path == ".") Nil else path.split("/").toList
|
||||
@@ -329,7 +330,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
repo.html.files(revision, repository,
|
||||
if(path == ".") Nil else path.split("/").toList, // current path
|
||||
new JGitUtil.CommitInfo(revCommit), // latest commit
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
}
|
||||
} getOrElse NotFound
|
||||
@@ -350,7 +351,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headName = s"refs/heads/${branch}"
|
||||
val headTip = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val headTip = git.getRepository.resolve(headName)
|
||||
|
||||
JGitUtil.processTree(git, headTip){ (path, tree) =>
|
||||
if(!newPath.exists(_ == path) && !oldPath.exists(_ == path)){
|
||||
@@ -365,7 +366,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
builder.finish()
|
||||
|
||||
val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter),
|
||||
loginAccount.fullName, loginAccount.mailAddress, message)
|
||||
headName, loginAccount.fullName, loginAccount.mailAddress, message)
|
||||
|
||||
inserter.flush()
|
||||
inserter.release()
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.apache.commons.io.FileUtils
|
||||
import java.io.FileInputStream
|
||||
import plugin.{Plugin, PluginSystem}
|
||||
import org.scalatra.Ok
|
||||
import util.Implicits._
|
||||
|
||||
class SystemSettingsController extends SystemSettingsControllerBase
|
||||
with AccountService with AdminAuthenticator
|
||||
@@ -111,15 +112,15 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
||||
redirect("/admin/plugins")
|
||||
})
|
||||
|
||||
// get("/admin/plugins/console")(adminOnly {
|
||||
// admin.plugins.html.console()
|
||||
// })
|
||||
//
|
||||
// post("/admin/plugins/console")(adminOnly {
|
||||
// val script = request.getParameter("script")
|
||||
// val result = plugin.JavaScriptPlugin.evaluateJavaScript(script)
|
||||
// Ok(result)
|
||||
// })
|
||||
get("/admin/plugins/console")(adminOnly {
|
||||
admin.plugins.html.console()
|
||||
})
|
||||
|
||||
post("/admin/plugins/console")(adminOnly {
|
||||
val script = request.getParameter("script")
|
||||
val result = plugin.ScalaPlugin.eval(script)
|
||||
Ok()
|
||||
})
|
||||
|
||||
// TODO Move these methods to PluginSystem or Service?
|
||||
private def deletePlugins(pluginIds: List[String]): Unit = {
|
||||
@@ -138,9 +139,10 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
||||
val installedPlugins = plugin.PluginSystem.plugins
|
||||
getAvailablePlugins(installedPlugins).filter(x => pluginIds.contains(x.id)).foreach { plugin =>
|
||||
val pluginDir = new java.io.File(PluginHome, plugin.id)
|
||||
if(!pluginDir.exists){
|
||||
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
|
||||
if(pluginDir.exists){
|
||||
FileUtils.deleteDirectory(pluginDir)
|
||||
}
|
||||
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
|
||||
PluginSystem.installPlugin(plugin.id)
|
||||
}
|
||||
}
|
||||
|
||||
19
src/main/scala/model/Plugin.scala
Normal file
19
src/main/scala/model/Plugin.scala
Normal file
@@ -0,0 +1,19 @@
|
||||
package model
|
||||
|
||||
trait PluginComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val Plugins = TableQuery[Plugins]
|
||||
|
||||
class Plugins(tag: Tag) extends Table[Plugin](tag, "PLUGIN"){
|
||||
val pluginId = column[String]("PLUGIN_ID", O PrimaryKey)
|
||||
val version = column[String]("VERSION")
|
||||
def * = (pluginId, version) <> (Plugin.tupled, Plugin.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
case class Plugin(
|
||||
pluginId: String,
|
||||
version: String
|
||||
)
|
||||
@@ -31,7 +31,8 @@ object Profile extends {
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with WebHookComponent with Profile {
|
||||
with WebHookComponent
|
||||
with PluginComponent with Profile {
|
||||
|
||||
/**
|
||||
* Returns system date.
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
package plugin
|
||||
|
||||
import org.mozilla.javascript.{Context => JsContext}
|
||||
import org.mozilla.javascript.{Function => JsFunction}
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import plugin.PluginSystem._
|
||||
import util.ControlUtil._
|
||||
import plugin.PluginSystem.GlobalMenu
|
||||
import plugin.PluginSystem.RepositoryAction
|
||||
import plugin.PluginSystem.Action
|
||||
import plugin.PluginSystem.RepositoryMenu
|
||||
|
||||
class JavaScriptPlugin(val id: String, val version: String,
|
||||
val author: String, val url: String, val description: String) extends Plugin {
|
||||
|
||||
private val repositoryMenuList = ListBuffer[RepositoryMenu]()
|
||||
private val globalMenuList = ListBuffer[GlobalMenu]()
|
||||
private val repositoryActionList = ListBuffer[RepositoryAction]()
|
||||
private val globalActionList = ListBuffer[Action]()
|
||||
|
||||
def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList
|
||||
def globalMenus : List[GlobalMenu] = globalMenuList.toList
|
||||
def repositoryActions : List[RepositoryAction] = repositoryActionList.toList
|
||||
def globalActions : List[Action] = globalActionList.toList
|
||||
|
||||
def addRepositoryMenu(label: String, name: String, url: String, icon: String, condition: JsFunction): Unit = {
|
||||
repositoryMenuList += RepositoryMenu(label, name, url, icon, (context) => {
|
||||
val context = JsContext.enter()
|
||||
try {
|
||||
condition.call(context, condition, condition, Array(context)).asInstanceOf[Boolean]
|
||||
} finally {
|
||||
JsContext.exit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def addGlobalMenu(label: String, url: String, icon: String, condition: JsFunction): Unit = {
|
||||
globalMenuList += GlobalMenu(label, url, icon, (context) => {
|
||||
val context = JsContext.enter()
|
||||
try {
|
||||
condition.call(context, condition, condition, Array(context)).asInstanceOf[Boolean]
|
||||
} finally {
|
||||
JsContext.exit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def addGlobalAction(path: String, function: JsFunction): Unit = {
|
||||
globalActionList += Action(path, (request, response) => {
|
||||
val context = JsContext.enter()
|
||||
try {
|
||||
function.call(context, function, function, Array(request, response))
|
||||
} finally {
|
||||
JsContext.exit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def addRepositoryAction(path: String, function: JsFunction): Unit = {
|
||||
repositoryActionList += RepositoryAction(path, (request, response, repository) => {
|
||||
val context = JsContext.enter()
|
||||
try {
|
||||
function.call(context, function, function, Array(request, response, repository))
|
||||
} finally {
|
||||
JsContext.exit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
object db {
|
||||
// TODO Use JavaScript Map instead of java.util.Map
|
||||
def select(sql: String): Array[java.util.Map[String, String]] = {
|
||||
defining(PluginConnectionHolder.threadLocal.get){ conn =>
|
||||
using(conn.prepareStatement(sql)){ stmt =>
|
||||
using(stmt.executeQuery()){ rs =>
|
||||
val list = new java.util.ArrayList[java.util.Map[String, String]]()
|
||||
while(rs.next){
|
||||
defining(rs.getMetaData){ meta =>
|
||||
val map = new java.util.HashMap[String, String]()
|
||||
Range(1, meta.getColumnCount).map { i =>
|
||||
val name = meta.getColumnName(i)
|
||||
map.put(name, rs.getString(name))
|
||||
}
|
||||
list.add(map)
|
||||
}
|
||||
}
|
||||
list.toArray(new Array[java.util.Map[String, String]](list.size))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object JavaScriptPlugin {
|
||||
|
||||
def define(id: String, version: String, author: String, url: String, description: String)
|
||||
= new JavaScriptPlugin(id, version, author, url, description)
|
||||
|
||||
def evaluateJavaScript(script: String, vars: Map[String, Any] = Map.empty): Any = {
|
||||
val context = JsContext.enter()
|
||||
try {
|
||||
val scope = context.initStandardObjects()
|
||||
scope.put("PluginSystem", scope, PluginSystem)
|
||||
scope.put("JavaScriptPlugin", scope, this)
|
||||
vars.foreach { case (key, value) =>
|
||||
scope.put(key, scope, value)
|
||||
}
|
||||
val result = context.evaluateString(scope, script, "<cmd>", 1, null)
|
||||
result
|
||||
} finally {
|
||||
JsContext.exit
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,10 +10,11 @@ trait Plugin {
|
||||
val url: String
|
||||
val description: String
|
||||
|
||||
def repositoryMenus : List[RepositoryMenu]
|
||||
def globalMenus : List[GlobalMenu]
|
||||
def repositoryActions : List[RepositoryAction]
|
||||
def globalActions : List[Action]
|
||||
def repositoryMenus : List[RepositoryMenu]
|
||||
def globalMenus : List[GlobalMenu]
|
||||
def repositoryActions : List[RepositoryAction]
|
||||
def globalActions : List[Action]
|
||||
def javaScripts : List[JavaScript]
|
||||
}
|
||||
|
||||
object PluginConnectionHolder {
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
package plugin
|
||||
|
||||
import app.Context
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import util.JGitUtil
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.apache.commons.io.{IOUtils, FileUtils}
|
||||
import Security._
|
||||
import service.PluginService
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import java.io.FileInputStream
|
||||
import java.sql.Connection
|
||||
import app.Context
|
||||
import service.RepositoryService.RepositoryInfo
|
||||
|
||||
/**
|
||||
* Provides extension points to plug-ins.
|
||||
*/
|
||||
object PluginSystem {
|
||||
object PluginSystem extends PluginService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(PluginSystem.getClass)
|
||||
|
||||
@@ -28,8 +32,21 @@ object PluginSystem {
|
||||
|
||||
def plugins: List[Plugin] = pluginsMap.values.toList
|
||||
|
||||
def uninstall(id: String): Unit = {
|
||||
def uninstall(id: String)(implicit session: Session): Unit = {
|
||||
pluginsMap.remove(id)
|
||||
|
||||
// Delete from PLUGIN table
|
||||
deletePlugin(id)
|
||||
|
||||
// Drop tables
|
||||
val pluginDir = new java.io.File(PluginHome)
|
||||
val sqlFile = new java.io.File(pluginDir, s"${id}/sql/drop.sql")
|
||||
if(sqlFile.exists){
|
||||
val sql = IOUtils.toString(new FileInputStream(sqlFile), "UTF-8")
|
||||
using(session.conn.createStatement()){ stmt =>
|
||||
stmt.executeUpdate(sql)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def repositories: List[PluginRepository] = repositoriesList.toList
|
||||
@@ -37,7 +54,7 @@ object PluginSystem {
|
||||
/**
|
||||
* Initializes the plugin system. Load scripts from GITBUCKET_HOME/plugins.
|
||||
*/
|
||||
def init(): Unit = {
|
||||
def init()(implicit session: Session): Unit = {
|
||||
if(initialized.compareAndSet(false, true)){
|
||||
// Load installed plugins
|
||||
val pluginDir = new java.io.File(PluginHome)
|
||||
@@ -52,42 +69,107 @@ object PluginSystem {
|
||||
}
|
||||
|
||||
// TODO Method name seems to not so good.
|
||||
def installPlugin(id: String): Unit = {
|
||||
val pluginDir = new java.io.File(PluginHome)
|
||||
val javaScriptFile = new java.io.File(pluginDir, id + "/plugin.js")
|
||||
def installPlugin(id: String)(implicit session: Session): Unit = {
|
||||
val pluginHome = new java.io.File(PluginHome)
|
||||
val pluginDir = new java.io.File(pluginHome, id)
|
||||
|
||||
if(javaScriptFile.exists && javaScriptFile.isFile){
|
||||
val scalaFile = new java.io.File(pluginDir, "plugin.scala")
|
||||
if(scalaFile.exists && scalaFile.isFile){
|
||||
val properties = new java.util.Properties()
|
||||
using(new java.io.FileInputStream(new java.io.File(pluginDir, id + "/plugin.properties"))){ in =>
|
||||
using(new java.io.FileInputStream(new java.io.File(pluginDir, "plugin.properties"))){ in =>
|
||||
properties.load(in)
|
||||
}
|
||||
|
||||
val script = FileUtils.readFileToString(javaScriptFile, "UTF-8")
|
||||
val pluginId = properties.getProperty("id")
|
||||
val version = properties.getProperty("version")
|
||||
val author = properties.getProperty("author")
|
||||
val url = properties.getProperty("url")
|
||||
val description = properties.getProperty("description")
|
||||
|
||||
val source = s"""
|
||||
|val id = "${pluginId}"
|
||||
|val version = "${version}"
|
||||
|val author = "${author}"
|
||||
|val url = "${url}"
|
||||
|val description = "${description}"
|
||||
""".stripMargin + FileUtils.readFileToString(scalaFile, "UTF-8")
|
||||
|
||||
try {
|
||||
JavaScriptPlugin.evaluateJavaScript(script, Map(
|
||||
"id" -> properties.getProperty("id"),
|
||||
"version" -> properties.getProperty("version"),
|
||||
"author" -> properties.getProperty("author"),
|
||||
"url" -> properties.getProperty("url"),
|
||||
"description" -> properties.getProperty("description")
|
||||
))
|
||||
// Compile and eval Scala source code
|
||||
ScalaPlugin.eval(pluginDir.listFiles.filter(_.getName.endsWith(".scala.html")).map { file =>
|
||||
ScalaPlugin.compileTemplate(
|
||||
id.replaceAll("-", ""),
|
||||
file.getName.replaceAll("\\.scala\\.html$", ""),
|
||||
IOUtils.toString(new FileInputStream(file)))
|
||||
}.mkString("\n") + source)
|
||||
|
||||
// Migrate database
|
||||
val plugin = getPlugin(pluginId)
|
||||
if(plugin.isEmpty){
|
||||
registerPlugin(model.Plugin(pluginId, version))
|
||||
migrate(session.conn, pluginId, "0.0")
|
||||
} else {
|
||||
updatePlugin(model.Plugin(pluginId, version))
|
||||
migrate(session.conn, pluginId, plugin.get.version)
|
||||
}
|
||||
} catch {
|
||||
case e: Exception => logger.warn(s"Error in plugin loading for ${javaScriptFile.getAbsolutePath}", e)
|
||||
case e: Throwable => logger.warn(s"Error in plugin loading for ${scalaFile.getAbsolutePath}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def repositoryMenus : List[RepositoryMenu] = pluginsMap.values.flatMap(_.repositoryMenus).toList
|
||||
def globalMenus : List[GlobalMenu] = pluginsMap.values.flatMap(_.globalMenus).toList
|
||||
def repositoryActions : List[RepositoryAction] = pluginsMap.values.flatMap(_.repositoryActions).toList
|
||||
def globalActions : List[Action] = pluginsMap.values.flatMap(_.globalActions).toList
|
||||
// TODO Should PluginSystem provide a way to migrate resources other than H2?
|
||||
private def migrate(conn: Connection, pluginId: String, current: String): Unit = {
|
||||
val pluginDir = new java.io.File(PluginHome)
|
||||
|
||||
// TODO Is ot possible to use this migration system in GitBucket migration?
|
||||
val dim = current.split("\\.")
|
||||
val currentVersion = Version(dim(0).toInt, dim(1).toInt)
|
||||
|
||||
val sqlDir = new java.io.File(pluginDir, s"${pluginId}/sql")
|
||||
if(sqlDir.exists && sqlDir.isDirectory){
|
||||
sqlDir.listFiles.filter(_.getName.endsWith(".sql")).map { file =>
|
||||
val array = file.getName.replaceFirst("\\.sql", "").split("_")
|
||||
Version(array(0).toInt, array(1).toInt)
|
||||
}
|
||||
.sorted.reverse.takeWhile(_ > currentVersion)
|
||||
.reverse.foreach { version =>
|
||||
val sqlFile = new java.io.File(pluginDir, s"${pluginId}/sql/${version.major}_${version.minor}.sql")
|
||||
val sql = IOUtils.toString(new FileInputStream(sqlFile), "UTF-8")
|
||||
using(conn.createStatement()){ stmt =>
|
||||
stmt.executeUpdate(sql)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class Version(major: Int, minor: Int) extends Ordered[Version] {
|
||||
|
||||
override def compare(that: Version): Int = {
|
||||
if(major != that.major){
|
||||
major.compare(that.major)
|
||||
} else{
|
||||
minor.compare(that.minor)
|
||||
}
|
||||
}
|
||||
|
||||
def displayString: String = major + "." + minor
|
||||
}
|
||||
|
||||
def repositoryMenus : List[RepositoryMenu] = pluginsMap.values.flatMap(_.repositoryMenus).toList
|
||||
def globalMenus : List[GlobalMenu] = pluginsMap.values.flatMap(_.globalMenus).toList
|
||||
def repositoryActions : List[RepositoryAction] = pluginsMap.values.flatMap(_.repositoryActions).toList
|
||||
def globalActions : List[Action] = pluginsMap.values.flatMap(_.globalActions).toList
|
||||
def javaScripts : List[JavaScript] = pluginsMap.values.flatMap(_.javaScripts).toList
|
||||
|
||||
// Case classes to hold plug-ins information internally in GitBucket
|
||||
case class PluginRepository(id: String, url: String)
|
||||
case class GlobalMenu(label: String, url: String, icon: String, condition: Context => Boolean)
|
||||
case class RepositoryMenu(label: String, name: String, url: String, icon: String, condition: Context => Boolean)
|
||||
case class Action(path: String, function: (HttpServletRequest, HttpServletResponse) => Any)
|
||||
case class RepositoryAction(path: String, function: (HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any)
|
||||
case class Action(method: String, path: String, security: Security, function: (HttpServletRequest, HttpServletResponse, Context) => Any)
|
||||
case class RepositoryAction(method: String, path: String, security: Security, function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any)
|
||||
case class Button(label: String, href: String)
|
||||
case class JavaScript(filter: String => Boolean, script: String)
|
||||
|
||||
/**
|
||||
* Checks whether the plugin is updatable.
|
||||
@@ -109,17 +191,4 @@ object PluginSystem {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO This is a test
|
||||
// addGlobalMenu("Google", "http://www.google.co.jp/", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEvwAABL8BkeKJvAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIgSURBVEiJtdZNiI1hFAfw36ORhSFFPgYLszOKJAsWRLGzks1gYyFZKFs7C7K2Y2XDRiwmq9kIJWQjJR9Tk48xRtTIRwjH4p473nm99yLNqdNTz/mf//+555x7ektEmEmbNaPs6OkUKKX0YBmWp6/IE8bwIs8xjEfEt0aiiJBl6sEuXMRLfEf8pX/PnIvJ0TPFWxE4+w+Ef/Kzbd5qDx5l8H8tkku7LG17gH7sxWatevdhEUoXsjda5RnDTZzH6jagtMe0lHIa23AJw3iOiSRZlmJ9mfcyfTzFl2AldmI3rkbEkbrAYKrX7S1eVRyWVnxhQ87eiLjQ+o2/mtyve+PuYy3W4+EfsP2/TVGKTHRI+Iz9Fdx8XOmAnZjGWRMYqoF/4ESW4hpOYk1iZ2WsLjDUTeBYBfgeuyux2XiNT5hXud+DD5W8Y90EtifoSfultfjx7MVtrKzcr8No5m7vJtCLx1hQJ8/4IZzClpyoy5ibsYUYQW81Z9o2jYgPeKr15+poEXE9+1XF9WIkOaasaV2P4k4pZUdDbEm+VEQcjIgtEfGxlLIVd/Gs6TX1MhzQquU3HK1t23f4IsuS94fxNXMO/MbXIDBg+tidw5yMbcCmylSdqWEH/kagYLKWeAt9Fcxi3KhhJuXq6SqQBMO15NDalvswmLWux4cbuToIbMS9BpJOfg8bm7imtmmTlVJWaa3hpnU9nufziBjtyDHTny0/AaA7Qnb4AM4aAAAAAElFTkSuQmCC")
|
||||
// { context => context.loginAccount.isDefined }
|
||||
//
|
||||
// addRepositoryMenu("Board", "board", "/board", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEvwAABL8BkeKJvAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIgSURBVEiJtdZNiI1hFAfw36ORhSFFPgYLszOKJAsWRLGzks1gYyFZKFs7C7K2Y2XDRiwmq9kIJWQjJR9Tk48xRtTIRwjH4p473nm99yLNqdNTz/mf//+555x7ektEmEmbNaPs6OkUKKX0YBmWp6/IE8bwIs8xjEfEt0aiiJBl6sEuXMRLfEf8pX/PnIvJ0TPFWxE4+w+Ef/Kzbd5qDx5l8H8tkku7LG17gH7sxWatevdhEUoXsjda5RnDTZzH6jagtMe0lHIa23AJw3iOiSRZlmJ9mfcyfTzFl2AldmI3rkbEkbrAYKrX7S1eVRyWVnxhQ87eiLjQ+o2/mtyve+PuYy3W4+EfsP2/TVGKTHRI+Iz9Fdx8XOmAnZjGWRMYqoF/4ESW4hpOYk1iZ2WsLjDUTeBYBfgeuyux2XiNT5hXud+DD5W8Y90EtifoSfultfjx7MVtrKzcr8No5m7vJtCLx1hQJ8/4IZzClpyoy5ibsYUYQW81Z9o2jYgPeKr15+poEXE9+1XF9WIkOaasaV2P4k4pZUdDbEm+VEQcjIgtEfGxlLIVd/Gs6TX1MhzQquU3HK1t23f4IsuS94fxNXMO/MbXIDBg+tidw5yMbcCmylSdqWEH/kagYLKWeAt9Fcxi3KhhJuXq6SqQBMO15NDalvswmLWux4cbuToIbMS9BpJOfg8bm7imtmmTlVJWaa3hpnU9nufziBjtyDHTny0/AaA7Qnb4AM4aAAAAAElFTkSuQmCC")
|
||||
// { context => true}
|
||||
//
|
||||
// addGlobalAction("/hello"){ (request, response) =>
|
||||
// "Hello World!"
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
package plugin
|
||||
|
||||
import app.Context
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import plugin.PluginSystem._
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import app.Context
|
||||
import plugin.PluginSystem._
|
||||
import plugin.PluginSystem.RepositoryMenu
|
||||
import plugin.Security._
|
||||
import service.RepositoryService.RepositoryInfo
|
||||
import scala.reflect.runtime.currentMirror
|
||||
import scala.tools.reflect.ToolBox
|
||||
import play.twirl.compiler.TwirlCompiler
|
||||
import scala.io.Codec
|
||||
|
||||
// TODO This is a sample implementation for Scala based plug-ins.
|
||||
class ScalaPlugin(val id: String, val version: String,
|
||||
@@ -14,11 +20,13 @@ class ScalaPlugin(val id: String, val version: String,
|
||||
private val globalMenuList = ListBuffer[GlobalMenu]()
|
||||
private val repositoryActionList = ListBuffer[RepositoryAction]()
|
||||
private val globalActionList = ListBuffer[Action]()
|
||||
private val javaScriptList = ListBuffer[JavaScript]()
|
||||
|
||||
def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList
|
||||
def globalMenus : List[GlobalMenu] = globalMenuList.toList
|
||||
def repositoryActions : List[RepositoryAction] = repositoryActionList.toList
|
||||
def globalActions : List[Action] = globalActionList.toList
|
||||
def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList
|
||||
def globalMenus : List[GlobalMenu] = globalMenuList.toList
|
||||
def repositoryActions : List[RepositoryAction] = repositoryActionList.toList
|
||||
def globalActions : List[Action] = globalActionList.toList
|
||||
def javaScripts : List[JavaScript] = javaScriptList.toList
|
||||
|
||||
def addRepositoryMenu(label: String, name: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = {
|
||||
repositoryMenuList += RepositoryMenu(label, name, url, icon, condition)
|
||||
@@ -28,12 +36,42 @@ class ScalaPlugin(val id: String, val version: String,
|
||||
globalMenuList += GlobalMenu(label, url, icon, condition)
|
||||
}
|
||||
|
||||
def addGlobalAction(path: String)(function: (HttpServletRequest, HttpServletResponse) => Any): Unit = {
|
||||
globalActionList += Action(path, function)
|
||||
def addGlobalAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context) => Any): Unit = {
|
||||
globalActionList += Action(method, path, security, function)
|
||||
}
|
||||
|
||||
def addRepositoryAction(path: String)(function: (HttpServletRequest, HttpServletResponse, RepositoryInfo) => Any): Unit = {
|
||||
repositoryActionList += RepositoryAction(path, function)
|
||||
def addRepositoryAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any): Unit = {
|
||||
repositoryActionList += RepositoryAction(method, path, security, function)
|
||||
}
|
||||
|
||||
def addJavaScript(filter: String => Boolean, script: String): Unit = {
|
||||
javaScriptList += JavaScript(filter, script)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ScalaPlugin {
|
||||
|
||||
def define(id: String, version: String, author: String, url: String, description: String)
|
||||
= new ScalaPlugin(id, version, author, url, description)
|
||||
|
||||
def eval(source: String): Any = {
|
||||
val toolbox = currentMirror.mkToolBox()
|
||||
val tree = toolbox.parse(source)
|
||||
toolbox.eval(tree)
|
||||
}
|
||||
|
||||
def compileTemplate(packageName: String, name: String, source: String): String = {
|
||||
val result = TwirlCompiler.parseAndGenerateCodeNewParser(
|
||||
Array(packageName, name),
|
||||
source.getBytes("UTF-8"),
|
||||
Codec(scala.util.Properties.sourceEncoding),
|
||||
"",
|
||||
"play.twirl.api.HtmlFormat.Appendable",
|
||||
"play.twirl.api.HtmlFormat",
|
||||
"",
|
||||
false)
|
||||
|
||||
result.replaceFirst("package .*", "")
|
||||
}
|
||||
}
|
||||
|
||||
36
src/main/scala/plugin/Security.scala
Normal file
36
src/main/scala/plugin/Security.scala
Normal file
@@ -0,0 +1,36 @@
|
||||
package plugin
|
||||
|
||||
/**
|
||||
* Defines enum case classes to specify permission for actions which is provided by plugin.
|
||||
*/
|
||||
object Security {
|
||||
|
||||
sealed trait Security
|
||||
|
||||
/**
|
||||
* All users and guests
|
||||
*/
|
||||
case class All() extends Security
|
||||
|
||||
/**
|
||||
* Only signed-in users
|
||||
*/
|
||||
case class Login() extends Security
|
||||
|
||||
/**
|
||||
* Only repository owner and collaborators
|
||||
*/
|
||||
case class Member() extends Security
|
||||
|
||||
/**
|
||||
* Only repository owner and managers of group repository
|
||||
*/
|
||||
case class Owner() extends Security
|
||||
|
||||
/**
|
||||
* Only administrators
|
||||
*/
|
||||
case class Admin() extends Security
|
||||
|
||||
}
|
||||
|
||||
55
src/main/scala/plugin/package.scala
Normal file
55
src/main/scala/plugin/package.scala
Normal file
@@ -0,0 +1,55 @@
|
||||
import java.sql.PreparedStatement
|
||||
import play.twirl.api.Html
|
||||
import util.ControlUtil._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
package object plugin {
|
||||
|
||||
case class Redirect(path: String)
|
||||
case class Fragment(html: Html)
|
||||
|
||||
object db {
|
||||
// TODO labelled place holder support
|
||||
def select(sql: String, params: Any*): Seq[Map[String, String]] = {
|
||||
defining(PluginConnectionHolder.threadLocal.get){ conn =>
|
||||
using(conn.prepareStatement(sql)){ stmt =>
|
||||
setParams(stmt, params: _*)
|
||||
using(stmt.executeQuery()){ rs =>
|
||||
val list = new ListBuffer[Map[String, String]]()
|
||||
while(rs.next){
|
||||
defining(rs.getMetaData){ meta =>
|
||||
val map = Range(1, meta.getColumnCount + 1).map { i =>
|
||||
val name = meta.getColumnName(i)
|
||||
(name, rs.getString(name))
|
||||
}.toMap
|
||||
list += map
|
||||
}
|
||||
}
|
||||
list
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO labelled place holder support
|
||||
def update(sql: String, params: Any*): Int = {
|
||||
defining(PluginConnectionHolder.threadLocal.get){ conn =>
|
||||
using(conn.prepareStatement(sql)){ stmt =>
|
||||
setParams(stmt, params: _*)
|
||||
stmt.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def setParams(stmt: PreparedStatement, params: Any*): Unit = {
|
||||
params.zipWithIndex.foreach { case (p, i) =>
|
||||
p match {
|
||||
case x: String => stmt.setString(i + 1, x)
|
||||
case x: Int => stmt.setInt(i + 1, x)
|
||||
case x: Boolean => stmt.setBoolean(i + 1, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
24
src/main/scala/service/PluginService.scala
Normal file
24
src/main/scala/service/PluginService.scala
Normal file
@@ -0,0 +1,24 @@
|
||||
package service
|
||||
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.Plugin
|
||||
|
||||
trait PluginService {
|
||||
|
||||
def getPlugins()(implicit s: Session): List[Plugin] =
|
||||
Plugins.sortBy(_.pluginId).list
|
||||
|
||||
def registerPlugin(plugin: Plugin)(implicit s: Session): Unit =
|
||||
Plugins.insert(plugin)
|
||||
|
||||
def updatePlugin(plugin: Plugin)(implicit s: Session): Unit =
|
||||
Plugins.filter(_.pluginId === plugin.pluginId.bind).map(_.version).update(plugin.version)
|
||||
|
||||
def deletePlugin(pluginId: String)(implicit s: Session): Unit =
|
||||
Plugins.filter(_.pluginId === pluginId.bind).delete
|
||||
|
||||
def getPlugin(pluginId: String)(implicit s: Session): Option[Plugin] =
|
||||
Plugins.filter(_.pluginId === pluginId.bind).firstOption
|
||||
|
||||
}
|
||||
@@ -182,7 +182,8 @@ trait WikiService {
|
||||
}
|
||||
builder.finish()
|
||||
|
||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress,
|
||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||
Constants.HEAD, committer.fullName, committer.mailAddress,
|
||||
pageName match {
|
||||
case Some(x) => s"Revert ${from} ... ${to} on ${x}"
|
||||
case None => s"Revert ${from} ... ${to}"
|
||||
@@ -229,7 +230,8 @@ trait WikiService {
|
||||
if(created || updated || removed){
|
||||
builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
|
||||
builder.finish()
|
||||
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer.fullName, committer.mailAddress,
|
||||
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||
Constants.HEAD, committer.fullName, committer.mailAddress,
|
||||
if(message.trim.length == 0) {
|
||||
if(removed){
|
||||
s"Rename ${currentPageName} to ${newPageName}"
|
||||
@@ -269,7 +271,8 @@ trait WikiService {
|
||||
}
|
||||
if(removed){
|
||||
builder.finish()
|
||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message)
|
||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||
Constants.HEAD, committer, mailAddress, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,27 @@ object AutoUpdate {
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(2, 3) {
|
||||
override def update(conn: Connection): Unit = {
|
||||
super.update(conn)
|
||||
using(conn.createStatement.executeQuery("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'")){ rs =>
|
||||
while(rs.next) {
|
||||
val info = rs.getString("ADDITIONAL_INFO")
|
||||
val newInfo = info.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n")
|
||||
if (info != newInfo) {
|
||||
val id = rs.getString("ACTIVITY_ID")
|
||||
using(conn.prepareStatement("UPDATE ACTIVITY SET ADDITIONAL_INFO=? WHERE ACTIVITY_ID=?")) { sql =>
|
||||
sql.setString(1, newInfo)
|
||||
sql.setLong(2, id.toLong)
|
||||
sql.executeUpdate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FileUtils.deleteDirectory(Directory.getPluginCacheDir())
|
||||
FileUtils.deleteDirectory(new File(Directory.PluginHome))
|
||||
}
|
||||
},
|
||||
new Version(2, 2),
|
||||
new Version(2, 1),
|
||||
new Version(2, 0){
|
||||
@@ -161,10 +182,12 @@ class AutoUpdateListener extends ServletContextListener {
|
||||
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")
|
||||
val context = event.getServletContext
|
||||
context.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome};MVCC=true")
|
||||
|
||||
defining(getConnection(event.getServletContext)){ conn =>
|
||||
logger.debug("Start schema update")
|
||||
try {
|
||||
defining(getCurrentVersion()){ currentVersion =>
|
||||
if(currentVersion == headVersion){
|
||||
@@ -174,7 +197,6 @@ class AutoUpdateListener extends ServletContextListener {
|
||||
} 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}")
|
||||
}
|
||||
}
|
||||
@@ -185,17 +207,27 @@ class AutoUpdateListener extends ServletContextListener {
|
||||
conn.rollback()
|
||||
}
|
||||
}
|
||||
logger.debug("End schema update")
|
||||
}
|
||||
logger.debug("End schema update")
|
||||
|
||||
logger.debug("Starting plugin system...")
|
||||
plugin.PluginSystem.init()
|
||||
getDatabase(context).withSession { implicit session =>
|
||||
logger.debug("Starting plugin system...")
|
||||
try {
|
||||
plugin.PluginSystem.init()
|
||||
|
||||
scheduler.start()
|
||||
PluginUpdateJob.schedule(scheduler)
|
||||
logger.debug("PluginUpdateJob is started.")
|
||||
scheduler.start()
|
||||
PluginUpdateJob.schedule(scheduler)
|
||||
logger.debug("PluginUpdateJob is started.")
|
||||
|
||||
logger.debug("Plugin system is initialized.")
|
||||
logger.debug("Plugin system is initialized.")
|
||||
} catch {
|
||||
case ex: Throwable => {
|
||||
logger.error("Failed to initialize plugin system", ex)
|
||||
ex.printStackTrace()
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def contextDestroyed(sce: ServletContextEvent): Unit = {
|
||||
@@ -208,4 +240,10 @@ class AutoUpdateListener extends ServletContextListener {
|
||||
servletContext.getInitParameter("db.user"),
|
||||
servletContext.getInitParameter("db.password"))
|
||||
|
||||
private def getDatabase(servletContext: ServletContext): scala.slick.jdbc.JdbcBackend.Database =
|
||||
slick.jdbc.JdbcBackend.Database.forURL(
|
||||
servletContext.getInitParameter("db.url"),
|
||||
servletContext.getInitParameter("db.user"),
|
||||
servletContext.getInitParameter("db.password"))
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ package servlet
|
||||
import javax.servlet._
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import twirl.api.Html
|
||||
import play.twirl.api.Html
|
||||
import service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
import model.{Account, Session}
|
||||
import util.{JGitUtil, Keys}
|
||||
import plugin.PluginConnectionHolder
|
||||
import plugin.{Fragment, PluginConnectionHolder, Redirect}
|
||||
import service.RepositoryService.RepositoryInfo
|
||||
import service.SystemSettingsService.SystemSettings
|
||||
import plugin.Security._
|
||||
|
||||
class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService {
|
||||
|
||||
@@ -30,18 +30,31 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
|
||||
}
|
||||
}
|
||||
|
||||
private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse): Boolean = {
|
||||
plugin.PluginSystem.globalActions.find(_.path == path).map { action =>
|
||||
val result = action.function(request, response)
|
||||
private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
|
||||
(implicit session: Session): Boolean = {
|
||||
plugin.PluginSystem.globalActions.find(x =>
|
||||
x.method.toLowerCase == request.getMethod.toLowerCase && path.matches(x.path)
|
||||
).map { action =>
|
||||
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||
val systemSettings = loadSystemSettings()
|
||||
result match {
|
||||
case x: String => renderGlobalHtml(request, response, systemSettings, x)
|
||||
case x: org.mozilla.javascript.NativeObject => {
|
||||
x.get("format") match {
|
||||
case "html" => renderGlobalHtml(request, response, systemSettings, x.get("body").toString)
|
||||
case "json" => renderJson(request, response, x.get("body").toString)
|
||||
}
|
||||
implicit val context = app.Context(systemSettings, Option(loginAccount), request)
|
||||
|
||||
if(authenticate(action.security, context)){
|
||||
val result = try {
|
||||
PluginConnectionHolder.threadLocal.set(session.conn)
|
||||
action.function(request, response, context)
|
||||
} finally {
|
||||
PluginConnectionHolder.threadLocal.remove()
|
||||
}
|
||||
result match {
|
||||
case x: String => renderGlobalHtml(request, response, context, x)
|
||||
case x: Html => renderGlobalHtml(request, response, context, x.toString)
|
||||
case x: Fragment => renderFragmentHtml(request, response, context, x.html.toString)
|
||||
case x: Redirect => response.sendRedirect(x.path)
|
||||
case x: AnyRef => renderJson(request, response, x)
|
||||
}
|
||||
} else {
|
||||
// TODO NotFound or Error?
|
||||
}
|
||||
true
|
||||
} getOrElse false
|
||||
@@ -54,23 +67,29 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
|
||||
val owner = elements(1)
|
||||
val name = elements(2)
|
||||
val remain = elements.drop(3).mkString("/", "/", "")
|
||||
|
||||
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||
val systemSettings = loadSystemSettings()
|
||||
implicit val context = app.Context(systemSettings, Option(loginAccount), request)
|
||||
|
||||
getRepository(owner, name, systemSettings.baseUrl(request)).flatMap { repository =>
|
||||
plugin.PluginSystem.repositoryActions.find(_.path == remain).map { action =>
|
||||
val result = try {
|
||||
PluginConnectionHolder.threadLocal.set(session.conn)
|
||||
action.function(request, response, repository)
|
||||
} finally {
|
||||
PluginConnectionHolder.threadLocal.remove()
|
||||
}
|
||||
result match {
|
||||
case x: String => renderRepositoryHtml(request, response, systemSettings, repository, x)
|
||||
case x: org.mozilla.javascript.NativeObject => {
|
||||
x.get("format") match {
|
||||
case "html" => renderRepositoryHtml(request, response, systemSettings, repository, x.get("body").toString)
|
||||
case "json" => renderJson(request, response, x.get("body").toString)
|
||||
}
|
||||
plugin.PluginSystem.repositoryActions.find(x => remain.matches(x.path)).map { action =>
|
||||
if(authenticate(action.security, context, repository)){
|
||||
val result = try {
|
||||
PluginConnectionHolder.threadLocal.set(session.conn)
|
||||
action.function(request, response, context, repository)
|
||||
} finally {
|
||||
PluginConnectionHolder.threadLocal.remove()
|
||||
}
|
||||
result match {
|
||||
case x: String => renderRepositoryHtml(request, response, context, repository, x)
|
||||
case x: Html => renderGlobalHtml(request, response, context, x.toString)
|
||||
case x: Fragment => renderFragmentHtml(request, response, context, x.html.toString)
|
||||
case x: Redirect => response.sendRedirect(x.path)
|
||||
case x: AnyRef => renderJson(request, response, x)
|
||||
}
|
||||
} else {
|
||||
// TODO NotFound or Error?
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -78,27 +97,81 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
|
||||
} else false
|
||||
}
|
||||
|
||||
private def renderGlobalHtml(request: HttpServletRequest, response: HttpServletResponse,
|
||||
systemSettings: SystemSettings, body: String): Unit = {
|
||||
/**
|
||||
* Authentication for global action
|
||||
*/
|
||||
private def authenticate(security: Security, context: app.Context)(implicit session: Session): Boolean = {
|
||||
// Global Action
|
||||
security match {
|
||||
case All() => true
|
||||
case Login() => context.loginAccount.isDefined
|
||||
case Admin() => context.loginAccount.exists(_.isAdmin)
|
||||
case _ => false // TODO throw Exception?
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate for repository action
|
||||
*/
|
||||
private def authenticate(security: Security, context: app.Context, repository: RepositoryInfo)(implicit session: Session): Boolean = {
|
||||
if(repository.repository.isPrivate){
|
||||
// Private Repository
|
||||
security match {
|
||||
case Admin() => context.loginAccount.exists(_.isAdmin)
|
||||
case Owner() => context.loginAccount.exists { account =>
|
||||
account.userName == repository.owner ||
|
||||
getGroupMembers(repository.owner).exists(m => m.userName == account.userName && m.isManager)
|
||||
}
|
||||
case _ => context.loginAccount.exists { account =>
|
||||
account.isAdmin || account.userName == repository.owner ||
|
||||
getCollaborators(repository.owner, repository.name).contains(account.userName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Public Repository
|
||||
security match {
|
||||
case All() => true
|
||||
case Login() => context.loginAccount.isDefined
|
||||
case Owner() => context.loginAccount.exists { account =>
|
||||
account.userName == repository.owner ||
|
||||
getGroupMembers(repository.owner).exists(m => m.userName == account.userName && m.isManager)
|
||||
}
|
||||
case Member() => context.loginAccount.exists { account =>
|
||||
account.userName == repository.owner ||
|
||||
getCollaborators(repository.owner, repository.name).contains(account.userName)
|
||||
}
|
||||
case Admin() => context.loginAccount.exists(_.isAdmin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def renderGlobalHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, body: String): Unit = {
|
||||
response.setContentType("text/html; charset=UTF-8")
|
||||
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||
implicit val context = app.Context(systemSettings, Option(loginAccount), request)
|
||||
val html = _root_.html.main("GitBucket", None)(Html(body))
|
||||
val html = _root_.html.main("GitBucket", None)(Html(body))(context)
|
||||
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
|
||||
}
|
||||
|
||||
private def renderRepositoryHtml(request: HttpServletRequest, response: HttpServletResponse,
|
||||
systemSettings: SystemSettings, repository: RepositoryInfo, body: String): Unit = {
|
||||
private def renderRepositoryHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, repository: RepositoryInfo, body: String): Unit = {
|
||||
response.setContentType("text/html; charset=UTF-8")
|
||||
val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||
implicit val context = app.Context(systemSettings, Option(loginAccount), request)
|
||||
val html = _root_.html.main("GitBucket", None)(_root_.html.menu("", repository)(Html(body))) // TODO specify active side menu
|
||||
val html = _root_.html.main("GitBucket", None)(_root_.html.menu("", repository)(Html(body))(context))(context) // TODO specify active side menu
|
||||
IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
|
||||
}
|
||||
|
||||
private def renderJson(request: HttpServletRequest, response: HttpServletResponse, body: String): Unit = {
|
||||
response.setContentType("application/json; charset=UTF-8")
|
||||
private def renderFragmentHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, body: String): Unit = {
|
||||
response.setContentType("text/html; charset=UTF-8")
|
||||
IOUtils.write(body.getBytes("UTF-8"), response.getOutputStream)
|
||||
}
|
||||
|
||||
private def renderJson(request: HttpServletRequest, response: HttpServletResponse, obj: AnyRef): Unit = {
|
||||
import org.json4s._
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.json4s.jackson.Serialization.write
|
||||
implicit val formats = Serialization.formats(NoTypeHints)
|
||||
|
||||
val json = write(obj)
|
||||
|
||||
response.setContentType("application/json; charset=UTF-8")
|
||||
IOUtils.write(json.getBytes("UTF-8"), response.getOutputStream)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -504,7 +504,7 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def createNewCommit(git: Git, inserter: ObjectInserter, headId: AnyObjectId, treeId: AnyObjectId,
|
||||
fullName: String, mailAddress: String, message: String): ObjectId = {
|
||||
ref: String, fullName: String, mailAddress: String, message: String): ObjectId = {
|
||||
val newCommit = new CommitBuilder()
|
||||
newCommit.setCommitter(new PersonIdent(fullName, mailAddress))
|
||||
newCommit.setAuthor(new PersonIdent(fullName, mailAddress))
|
||||
@@ -518,7 +518,7 @@ object JGitUtil {
|
||||
inserter.flush()
|
||||
inserter.release()
|
||||
|
||||
val refUpdate = git.getRepository.updateRef(Constants.HEAD)
|
||||
val refUpdate = git.getRepository.updateRef(ref)
|
||||
refUpdate.setNewObjectId(newHeadId)
|
||||
refUpdate.update()
|
||||
|
||||
@@ -652,4 +652,15 @@ object JGitUtil {
|
||||
}.head.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modified commit of specified path
|
||||
* @param git the Git object
|
||||
* @param startCommit the search base commit id
|
||||
* @param path the path of target file or directory
|
||||
* @return the last modified commit of specified path
|
||||
*/
|
||||
def getLastModifiedCommit(git: Git, startCommit: RevCommit, path: String): RevCommit = {
|
||||
return git.log.add(startCommit).addPath(path).setMaxCount(1).call.iterator.next
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package view
|
||||
|
||||
import service.RequestCache
|
||||
import twirl.api.Html
|
||||
import play.twirl.api.Html
|
||||
import util.StringUtil
|
||||
|
||||
trait AvatarImageProvider { self: RequestCache =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package view
|
||||
import java.util.Date
|
||||
import java.util.{Date, TimeZone}
|
||||
import java.text.SimpleDateFormat
|
||||
import twirl.api.Html
|
||||
import play.twirl.api.Html
|
||||
import util.StringUtil
|
||||
import service.RequestCache
|
||||
|
||||
@@ -18,7 +18,11 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
/**
|
||||
* Format java.util.Date to "yyyy-MM-dd'T'hh:mm:ss'Z'".
|
||||
*/
|
||||
def datetimeRFC3339(date: Date): String = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'").format(date).replaceAll("(\\d\\d)(\\d\\d)$","$1:$2")
|
||||
def datetimeRFC3339(date: Date): String = {
|
||||
val sf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
sf.setTimeZone(TimeZone.getTimeZone("UTC"))
|
||||
sf.format(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* Format java.util.Date to "yyyy-MM-dd".
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(listparts: twirl.api.Html,
|
||||
@(listparts: play.twirl.api.Html,
|
||||
allCount: Int,
|
||||
assignedCount: Int,
|
||||
createdByCount: Int,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@(listparts: twirl.api.Html,
|
||||
@(listparts: play.twirl.api.Html,
|
||||
counts: List[service.PullRequestService.PullRequestCount],
|
||||
repositories: List[(String, String, Int)],
|
||||
condition: service.IssuesService.IssueSearchCondition,
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
case "fork" => simpleActivity(activity, "activity-fork.png")
|
||||
case "push" => customActivity(activity, "activity-commit.png"){
|
||||
<div class="small activity-message">
|
||||
{activity.additionalInfo.get.split("\n").reverse.filter(_ matches "[0-9a-z]{40}:.*").take(4).zipWithIndex.map{ case (commit, i) =>
|
||||
{activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) =>
|
||||
if(i == 3){
|
||||
<div>...</div>
|
||||
} else {
|
||||
|
||||
@@ -6,6 +6,24 @@
|
||||
<script>
|
||||
// copy to clipboard
|
||||
(function() {
|
||||
// Check flash availablibity
|
||||
var flashAvailable = false;
|
||||
try {
|
||||
var flashObject = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
|
||||
if(flashObject) flashAvailable = true;
|
||||
} catch (e) {
|
||||
if (navigator.mimeTypes
|
||||
&& navigator.mimeTypes['application/x-shockwave-flash'] != undefined
|
||||
&& navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) {
|
||||
flashAvailable = true;
|
||||
}
|
||||
}
|
||||
// if flash is not available, remove the copy button.
|
||||
if(!flashAvailable) {
|
||||
$('#@id').remove();
|
||||
return
|
||||
}
|
||||
|
||||
// Find ZeroClipboard.swf file URI from ZeroClipboard JavaScript file path.
|
||||
// NOTE(tanacasino) I think this way is wrong... but i don't know correct way.
|
||||
var moviePath = (function() {
|
||||
|
||||
@@ -88,6 +88,9 @@
|
||||
$('#search').submit(function(){
|
||||
return $.trim($(this).find('input[name=query]').val()) != '';
|
||||
});
|
||||
@plugin.PluginSystem.javaScripts.filter(_.filter(context.currentPath)).map { js =>
|
||||
@Html(js.script)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<table class="table table-bordered blobview">
|
||||
<tr>
|
||||
<th style="font-weight: normal;">
|
||||
<div class="pull-left">
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<th width="20%">Commit</th>
|
||||
<th width="20%">Download</th>
|
||||
</tr>
|
||||
@repository.tags.map { tag =>
|
||||
@repository.tags.reverse.map { tag =>
|
||||
<tr>
|
||||
<td><a href="@url(repository)/tree/@encodeRefName(tag.name)">@tag.name</a></td>
|
||||
<td>@datetime(tag.time)</td>
|
||||
|
||||
@@ -36,9 +36,6 @@ h6 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h7 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ======================================================================== */
|
||||
/* Global Header */
|
||||
@@ -510,6 +507,10 @@ a.header-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table.blobview {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
table.table-file-list {
|
||||
margin-bottom: 0px;
|
||||
border: 1px solid #ddd;
|
||||
@@ -592,6 +593,7 @@ pre.blob {
|
||||
border: none;
|
||||
background-color: white;
|
||||
padding-left: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#readme {
|
||||
@@ -837,6 +839,8 @@ div.author-info div.committer {
|
||||
/* Diff */
|
||||
/****************************************************************************/
|
||||
table.inlinediff {
|
||||
font-size: 12px;
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.specs2.mock.Mockito
|
||||
import service.RequestCache
|
||||
import model.Account
|
||||
import service.SystemSettingsService.SystemSettings
|
||||
import twirl.api.Html
|
||||
import play.twirl.api.Html
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
class AvatarImageProviderSpec extends Specification with Mockito {
|
||||
|
||||
Reference in New Issue
Block a user