mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-03 12:05:59 +01:00
Merge branch 'master' into feature-tasklist
Conflicts: src/main/scala/app/IssuesController.scala src/main/twirl/issues/create.scala.html
This commit is contained in:
26
README.md
26
README.md
@@ -80,6 +80,32 @@ 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
|
||||
- tar.gz export for repository contents
|
||||
- LDAP authentication improvement (mail address became optional)
|
||||
- Show news feed of a private repository to members
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.1 - 6 Jul 2014
|
||||
- Upgrade to Slick 2.0 from 1.9
|
||||
- Base part of the plug-in system is merged
|
||||
- Many bug fix and improvements
|
||||
|
||||
### 2.0 - 31 May 2014
|
||||
- Modern Github UI
|
||||
- Preview in AceEditor
|
||||
- Select lines by clicking line number in blob view
|
||||
|
||||
### 1.13 - 29 Apr 2014
|
||||
- Direct file editing in the repository viewer using AceEditor
|
||||
- File attachment for issues
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<property name="target.dir" value="target"/>
|
||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
||||
<property name="jetty.dir" value="embed-jetty"/>
|
||||
<property name="scala.version" value="2.10"/>
|
||||
<property name="scala.version" value="2.11"/>
|
||||
<property name="gitbucket.version" value="0.0.1"/>
|
||||
<property name="jetty.version" value="8.1.8.v20121106"/>
|
||||
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
||||
@@ -50,8 +50,8 @@
|
||||
</target>
|
||||
|
||||
<target name="rename" depends="embed">
|
||||
<rename src="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
||||
dest="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
||||
<move file="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
||||
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
||||
</target>
|
||||
|
||||
<target name="all" depends="rename">
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
## MacOS proxies for System V service hooks:
|
||||
StartService() {
|
||||
start
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
StopService() {
|
||||
stop
|
||||
}
|
||||
|
||||
RestartService() {
|
||||
restart
|
||||
}
|
||||
|
||||
|
||||
if [ `isMac` ]; then
|
||||
RunService "$1"
|
||||
else
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
status)
|
||||
status)
|
||||
status -p $PID_FILE java
|
||||
RETVAL=$?
|
||||
;;
|
||||
*)
|
||||
*)
|
||||
echo $"Usage: $0 [start|stop|restart|status]"
|
||||
RETVAL=2
|
||||
esac
|
||||
esac
|
||||
exit $RETVAL
|
||||
fi
|
||||
|
||||
|
||||
exit $RETVAL
|
||||
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,20 +1,23 @@
|
||||
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"
|
||||
val Name = "gitbucket"
|
||||
val Version = "0.0.1"
|
||||
val ScalaVersion = "2.10.3"
|
||||
val ScalatraVersion = "2.2.1"
|
||||
val ScalaVersion = "2.11.2"
|
||||
val ScalatraVersion = "2.3.0"
|
||||
|
||||
lazy val project = Project (
|
||||
"gitbucket",
|
||||
file("."),
|
||||
settings = Defaults.defaultSettings ++ ScalatraPlugin.scalatraWithJRebel ++ Seq(
|
||||
file(".")
|
||||
)
|
||||
.settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
||||
.settings(
|
||||
sourcesInBase := false,
|
||||
organization := Organization,
|
||||
name := Name,
|
||||
@@ -26,30 +29,32 @@ object MyBuild extends Build {
|
||||
),
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
||||
libraryDependencies ++= Seq(
|
||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.0.0.201306101825-r",
|
||||
"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.5",
|
||||
"jp.sf.amateras" %% "scalatra-forms" % "0.0.14",
|
||||
"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" % "1.0.1",
|
||||
"com.typesafe.slick" %% "slick" % "2.1.0-RC3",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.3.173",
|
||||
"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"
|
||||
"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")
|
||||
) ++ seq(Twirl.settings: _*)
|
||||
)
|
||||
).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" %*
|
||||
|
||||
3
sbt.sh
3
sbt.sh
@@ -1 +1,2 @@
|
||||
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 "$@"
|
||||
#!/bin/sh
|
||||
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);
|
||||
@@ -1,4 +1,4 @@
|
||||
import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter}
|
||||
import _root_.servlet.{PluginActionInvokeFilter, BasicAuthenticationFilter, TransactionFilter}
|
||||
import app._
|
||||
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
|
||||
import org.scalatra._
|
||||
@@ -10,6 +10,8 @@ class ScalatraBootstrap extends LifeCycle {
|
||||
// Register TransactionFilter and BasicAuthenticationFilter at first
|
||||
context.addFilter("transactionFilter", new TransactionFilter)
|
||||
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context.addFilter("pluginActionInvokeFilter", new PluginActionInvokeFilter)
|
||||
context.getFilterRegistration("pluginActionInvokeFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import util._
|
||||
import util.StringUtil._
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import util.Implicits._
|
||||
import ssh.SshUtil
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
@@ -291,7 +292,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
* Create new repository.
|
||||
*/
|
||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||
LockUtil.lock(s"${form.owner}/${form.name}/create"){
|
||||
LockUtil.lock(s"${form.owner}/${form.name}"){
|
||||
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){
|
||||
val ownerAccount = getAccountByUserName(form.owner).get
|
||||
val loginAccount = context.loginAccount.get
|
||||
@@ -334,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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +355,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
|
||||
LockUtil.lock(s"${loginUserName}/${repository.name}/create"){
|
||||
LockUtil.lock(s"${loginUserName}/${repository.name}"){
|
||||
if(repository.owner == loginUserName || getRepository(loginAccount.userName, repository.name, baseUrl).isDefined){
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${loginUserName}/${repository.name}")
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.scalatra.json._
|
||||
import org.json4s._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import model.Account
|
||||
import model._
|
||||
import service.{SystemSettingsService, AccountService}
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
||||
@@ -24,8 +24,9 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
|
||||
implicit val jsonFormats = DefaultFormats
|
||||
|
||||
// Don't set content type via Accept header.
|
||||
override def format(implicit request: HttpServletRequest) = ""
|
||||
// TODO Scala 2.11
|
||||
// // Don't set content type via Accept header.
|
||||
// override def format(implicit request: HttpServletRequest) = ""
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
|
||||
val httpRequest = request.asInstanceOf[HttpServletRequest]
|
||||
@@ -125,11 +126,13 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
}
|
||||
}
|
||||
|
||||
override def fullUrl(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
||||
includeContextPath: Boolean = true, includeServletPath: Boolean = true)
|
||||
(implicit request: HttpServletRequest, response: HttpServletResponse) =
|
||||
// TODO Scala 2.11
|
||||
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
||||
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
||||
absolutize: Boolean = true, withSessionId: Boolean = true)
|
||||
(implicit request: HttpServletRequest, response: HttpServletResponse): String =
|
||||
if (path.startsWith("http")) path
|
||||
else baseUrl + url(path, params, false, false, false)
|
||||
else baseUrl + super.url(path, params, false, false, false)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -49,21 +49,21 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name)
|
||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||
val filterUser = Map(filter -> userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
//
|
||||
|
||||
dashboard.html.issues(
|
||||
issues.html.listparts(
|
||||
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, repositories: _*),
|
||||
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*),
|
||||
countIssue(condition.copy(state = "open" ), filterUser, false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, false, userRepos: _*),
|
||||
condition),
|
||||
countIssue(condition, Map.empty, false, repositories: _*),
|
||||
countIssue(condition, Map("assigned" -> userName), false, repositories: _*),
|
||||
countIssue(condition, Map("created_by" -> userName), false, repositories: _*),
|
||||
countIssueGroupByRepository(condition, filterUser, false, repositories: _*),
|
||||
countIssue(condition, Map.empty, false, userRepos: _*),
|
||||
countIssue(condition, Map("assigned" -> userName), false, userRepos: _*),
|
||||
countIssue(condition, Map("created_by" -> userName), false, userRepos: _*),
|
||||
countIssueGroupByRepository(condition, filterUser, false, userRepos: _*),
|
||||
condition,
|
||||
filter)
|
||||
|
||||
@@ -80,25 +80,26 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
}.copy(repo = repository))
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val repositories = getUserRepositories(userName, context.baseUrl).map(repo => repo.owner -> repo.name)
|
||||
val allRepos = getAllRepositories()
|
||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||
val filterUser = Map(filter -> userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
val counts = countIssueGroupByRepository(
|
||||
IssueSearchCondition().copy(state = condition.state), Map.empty, true, repositories: _*)
|
||||
IssueSearchCondition().copy(state = condition.state), Map.empty, true, userRepos: _*)
|
||||
|
||||
dashboard.html.pulls(
|
||||
pulls.html.listparts(
|
||||
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, repositories: _*),
|
||||
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open"), filterUser, true, repositories: _*),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, true, repositories: _*),
|
||||
countIssue(condition.copy(state = "open" ), filterUser, true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, true, allRepos: _*),
|
||||
condition,
|
||||
None,
|
||||
false),
|
||||
getPullRequestCountGroupByUser(condition.state == "closed", userName, None),
|
||||
getRepositoryNamesOfUser(userName).map { RepoName =>
|
||||
(userName, RepoName, counts.collectFirst { case (_, RepoName, count) => count }.getOrElse(0))
|
||||
getPullRequestCountGroupByUser(condition.state == "closed", None, None),
|
||||
userRepos.map { case (userName, repoName) =>
|
||||
(userName, repoName, counts.find { x => x._1 == userName && x._2 == repoName }.map(_._3).getOrElse(0))
|
||||
}.sortBy(_._3).reverse,
|
||||
condition,
|
||||
filter)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import util._
|
||||
import util.Implicits._
|
||||
import service._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
@@ -19,11 +20,23 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
get("/"){
|
||||
val loginAccount = context.loginAccount
|
||||
|
||||
if(loginAccount.isEmpty) {
|
||||
html.index(getRecentActivities(),
|
||||
getVisibleRepositories(loginAccount, context.baseUrl),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl) }.getOrElse(Nil)
|
||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
||||
)
|
||||
} else {
|
||||
val loginUserName = loginAccount.get.userName
|
||||
val loginUserGroups = getGroupsByUserName(loginUserName)
|
||||
var visibleOwnerSet : Set[String] = Set(loginUserName)
|
||||
|
||||
visibleOwnerSet ++= loginUserGroups
|
||||
|
||||
html.index(getRecentActivitiesByOwners(visibleOwnerSet),
|
||||
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
|
||||
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
get("/signin"){
|
||||
@@ -58,8 +71,12 @@ trait IndexControllerBase extends ControllerBase {
|
||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||
updateLastLoginDate(account.userName)
|
||||
|
||||
if(LDAPUtil.isDummyMailAddress(account)) {
|
||||
redirect("/" + account.userName + "/_edit")
|
||||
}
|
||||
|
||||
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
|
||||
if(redirectUrl.replaceFirst("/$", "") == request.getContextPath){
|
||||
if(redirectUrl.stripSuffix("/") == request.getContextPath){
|
||||
redirect("/")
|
||||
} else {
|
||||
redirect(redirectUrl)
|
||||
@@ -71,8 +88,6 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* JSON API for collaborator completion.
|
||||
*
|
||||
* TODO Move to other controller?
|
||||
*/
|
||||
get("/_user/proposals")(usersOnly {
|
||||
contentType = formats("json")
|
||||
@@ -81,5 +96,11 @@ trait IndexControllerBase extends ControllerBase {
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* JSON APU for checking user existence.
|
||||
*/
|
||||
post("/_user/existence")(usersOnly {
|
||||
getAccountByUserName(params("userName")).isDefined
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@ package app
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import service._
|
||||
import util.CollaboratorsAuthenticator
|
||||
import util.Implicits._
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
class LabelsController extends LabelsControllerBase
|
||||
@@ -53,7 +54,7 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
*/
|
||||
private def labelName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(!value.matches("^[^,]+$")){
|
||||
if(value.contains(',')){
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
||||
Some(s"${name} starts with invalid character.")
|
||||
|
||||
@@ -13,7 +13,6 @@ import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
|
||||
import service.IssuesService._
|
||||
import service.PullRequestService._
|
||||
import util.JGitUtil.DiffInfo
|
||||
import service.RepositoryService.RepositoryTreeNode
|
||||
import util.JGitUtil.CommitInfo
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.eclipse.jgit.merge.MergeStrategy
|
||||
@@ -124,7 +123,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
params("id").toIntOpt.flatMap { issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
LockUtil.lock(s"${owner}/${name}/merge"){
|
||||
LockUtil.lock(s"${owner}/${name}"){
|
||||
getPullRequest(owner, name, issueId).map { case (issue, pullreq) =>
|
||||
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
||||
// mark issue as merged and close.
|
||||
@@ -157,7 +156,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
mergeCommit.setAuthor(personIdent)
|
||||
mergeCommit.setCommitter(personIdent)
|
||||
mergeCommit.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestRepositoryName}\n\n" +
|
||||
mergeCommit.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" +
|
||||
form.message)
|
||||
|
||||
// insertObject and got mergeCommit Object Id
|
||||
@@ -367,7 +366,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
*/
|
||||
private def checkConflict(userName: String, repositoryName: String, branch: String,
|
||||
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
|
||||
LockUtil.lock(s"${userName}/${repositoryName}/merge-check"){
|
||||
LockUtil.lock(s"${userName}/${repositoryName}"){
|
||||
using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git =>
|
||||
val remoteRefName = s"refs/heads/${branch}"
|
||||
val tmpRefName = s"refs/merge-check/${userName}/${branch}"
|
||||
@@ -403,7 +402,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
private def checkConflictInPullRequest(userName: String, repositoryName: String, branch: String,
|
||||
requestUserName: String, requestRepositoryName: String, requestBranch: String,
|
||||
issueId: Int): Boolean = {
|
||||
LockUtil.lock(s"${userName}/${repositoryName}/merge") {
|
||||
LockUtil.lock(s"${userName}/${repositoryName}") {
|
||||
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
// merge
|
||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
||||
@@ -444,7 +443,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
|
||||
new CommitInfo(revCommit)
|
||||
}.toList.splitWith { (commit1, commit2) =>
|
||||
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}
|
||||
|
||||
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
|
||||
@@ -466,7 +465,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
|
||||
pulls.html.list(
|
||||
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||
getPullRequestCountGroupByUser(condition.state == "closed", owner, Some(repoName)),
|
||||
getPullRequestCountGroupByUser(condition.state == "closed", Some(owner), Some(repoName)),
|
||||
userName,
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), filterUser, true, owner -> repoName),
|
||||
|
||||
@@ -2,7 +2,8 @@ package app
|
||||
|
||||
import service._
|
||||
import util.Directory._
|
||||
import util.{UsersAuthenticator, OwnerAuthenticator}
|
||||
import util.Implicits._
|
||||
import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator}
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.i18n.Messages
|
||||
@@ -10,6 +11,7 @@ import service.WebHookService.WebHookPayload
|
||||
import util.JGitUtil.CommitInfo
|
||||
import util.ControlUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
|
||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||
with RepositoryService with AccountService with WebHookService
|
||||
@@ -68,11 +70,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Save the repository options.
|
||||
*/
|
||||
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
|
||||
val defaultBranch = if(repository.branchList.isEmpty) "master" else form.defaultBranch
|
||||
saveRepositoryOptions(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.description,
|
||||
if(repository.branchList.isEmpty) "master" else form.defaultBranch,
|
||||
defaultBranch,
|
||||
repository.repository.parentUserName.map { _ =>
|
||||
repository.repository.isPrivate
|
||||
} getOrElse form.isPrivate
|
||||
@@ -90,6 +93,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
|
||||
}
|
||||
}
|
||||
// Change repository HEAD
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + defaultBranch)
|
||||
}
|
||||
flash += "info" -> "Repository settings has been updated."
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
||||
})
|
||||
@@ -128,7 +135,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Display the web hook page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
||||
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info"))
|
||||
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -150,7 +157,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Send the test request to registered web hook URLs.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
|
||||
post("/:owner/:repository/settings/hooks/test", webHookForm)(ownerOnly { (form, repository) =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
import scala.collection.JavaConverters._
|
||||
val commits = git.log
|
||||
@@ -158,15 +165,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
.setMaxCount(3)
|
||||
.call.iterator.asScala.map(new CommitInfo(_))
|
||||
|
||||
getWebHookURLs(repository.owner, repository.name) match {
|
||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
||||
for(ownerAccount <- getAccountByUserName(repository.owner)){
|
||||
callWebHook(repository.owner, repository.name, webHookURLs,
|
||||
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount))
|
||||
getAccountByUserName(repository.owner).foreach { ownerAccount =>
|
||||
callWebHook(repository.owner, repository.name,
|
||||
List(model.WebHook(repository.owner, repository.name, form.url)),
|
||||
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
|
||||
flash += "url" -> form.url
|
||||
flash += "info" -> "Test payload deployed!"
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
@@ -185,6 +190,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
||||
// Change repository owner
|
||||
if(repository.owner != form.newOwner){
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
||||
// Update database
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
// Move git repository
|
||||
@@ -196,6 +202,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
redirect(s"/${form.newOwner}/${repository.name}")
|
||||
})
|
||||
|
||||
@@ -203,12 +210,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Delete the repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}"){
|
||||
deleteRepository(repository.owner, repository.name)
|
||||
|
||||
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
|
||||
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
|
||||
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
|
||||
|
||||
}
|
||||
redirect(s"/${repository.owner}")
|
||||
})
|
||||
|
||||
|
||||
@@ -8,23 +8,31 @@ import _root_.util._
|
||||
import service._
|
||||
import org.scalatra._
|
||||
import java.io.File
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.treewalk._
|
||||
import java.util.zip.{ZipEntry, ZipOutputStream}
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.eclipse.jgit.dircache.DirCache
|
||||
import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import service.WebHookService.WebHookPayload
|
||||
|
||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
with RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
|
||||
|
||||
/**
|
||||
* The repository viewer.
|
||||
*/
|
||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ActivityService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
|
||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
||||
|
||||
case class EditorForm(
|
||||
branch: String,
|
||||
@@ -32,6 +40,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
content: String,
|
||||
message: Option[String],
|
||||
charset: String,
|
||||
lineSeparator: String,
|
||||
newFileName: String,
|
||||
oldFileName: Option[String]
|
||||
)
|
||||
@@ -49,6 +58,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"content" -> trim(label("Content", text(required))),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
"charset" -> trim(label("Charset", text(required))),
|
||||
"lineSeparator" -> trim(label("Line Separator", text(required))),
|
||||
"newFileName" -> trim(label("Filename", text(required))),
|
||||
"oldFileName" -> trim(label("Old filename", optional(text())))
|
||||
)(EditorForm.apply)
|
||||
@@ -102,7 +112,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
case Right((logs, hasNext)) =>
|
||||
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||
logs.splitWith{ (commit1, commit2) =>
|
||||
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}, page, hasNext)
|
||||
case Left(_) => NotFound
|
||||
}
|
||||
@@ -143,7 +153,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
|
||||
commitFile(repository, form.branch, form.path, Some(form.newFileName), None, form.content, form.charset,
|
||||
commitFile(repository, form.branch, form.path, Some(form.newFileName), None,
|
||||
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
|
||||
form.message.getOrElse(s"Create ${form.newFileName}"))
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
|
||||
@@ -152,7 +163,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
|
||||
commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName, form.content, form.charset,
|
||||
commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName,
|
||||
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
|
||||
if(form.oldFileName.exists(_ == form.newFileName)){
|
||||
form.message.getOrElse(s"Update ${form.newFileName}")
|
||||
} else {
|
||||
@@ -180,6 +192,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
|
||||
@@ -189,7 +202,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
|
||||
}
|
||||
@@ -253,50 +266,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* Download repository contents as an archive.
|
||||
*/
|
||||
get("/:owner/:repository/archive/*")(referrersOnly { repository =>
|
||||
val name = multiParams("splat").head
|
||||
|
||||
if(name.endsWith(".zip")){
|
||||
val revision = name.replaceFirst("\\.zip$", "")
|
||||
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
|
||||
if(workDir.exists){
|
||||
FileUtils.deleteDirectory(workDir)
|
||||
}
|
||||
workDir.mkdirs
|
||||
|
||||
val zipFile = new File(workDir, repository.name + "-" +
|
||||
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + ".zip")
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||
using(new TreeWalk(git.getRepository)){ walk =>
|
||||
val reader = walk.getObjectReader
|
||||
val objectId = new MutableObjectId
|
||||
|
||||
using(new ZipOutputStream(new java.io.FileOutputStream(zipFile))){ out =>
|
||||
walk.addTree(revCommit.getTree)
|
||||
walk.setRecursive(true)
|
||||
|
||||
while(walk.next){
|
||||
val name = walk.getPathString
|
||||
val mode = walk.getFileMode(0)
|
||||
if(mode == FileMode.REGULAR_FILE || mode == FileMode.EXECUTABLE_FILE){
|
||||
walk.getObjectId(objectId, 0)
|
||||
val entry = new ZipEntry(name)
|
||||
val loader = reader.open(objectId)
|
||||
entry.setSize(loader.getSize)
|
||||
out.putNextEntry(entry)
|
||||
loader.copyTo(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentType = "application/octet-stream"
|
||||
response.setHeader("Content-Disposition", s"attachment; filename=${zipFile.getName}")
|
||||
zipFile
|
||||
} else {
|
||||
BadRequest
|
||||
multiParams("splat").head match {
|
||||
case name if name.endsWith(".zip") =>
|
||||
archiveRepository(name, ".zip", repository)
|
||||
case name if name.endsWith(".tar.gz") =>
|
||||
archiveRepository(name, ".tar.gz", repository)
|
||||
case _ => BadRequest
|
||||
}
|
||||
})
|
||||
|
||||
@@ -317,9 +292,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
case branch if(path == branch || path.startsWith(branch + "/")) => branch
|
||||
} orElse repository.tags.collectFirst {
|
||||
case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name
|
||||
} orElse Some(path.split("/")(0)) get
|
||||
} getOrElse path.split("/")(0)
|
||||
|
||||
(id, path.substring(id.length).replaceFirst("^/", ""))
|
||||
(id, path.substring(id.length).stripPrefix("/"))
|
||||
}
|
||||
|
||||
|
||||
@@ -338,10 +313,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
|
||||
@@ -356,7 +331,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
|
||||
@@ -377,7 +352,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)){
|
||||
@@ -392,7 +367,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()
|
||||
@@ -409,8 +384,19 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
|
||||
List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
|
||||
|
||||
// TODO invoke hook
|
||||
// close issue by commit message
|
||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
|
||||
|
||||
// call web hook
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
getWebHookURLs(repository.owner, repository.name) match {
|
||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
||||
for(ownerAccount <- getAccountByUserName(repository.owner)){
|
||||
callWebHook(repository.owner, repository.name, webHookURLs,
|
||||
WebHookPayload(git, loginAccount, headName, repository, List(commit), ownerAccount))
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,4 +416,29 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): File = {
|
||||
val revision = name.stripSuffix(suffix)
|
||||
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
|
||||
if(workDir.exists) {
|
||||
FileUtils.deleteDirectory(workDir)
|
||||
}
|
||||
workDir.mkdirs
|
||||
|
||||
val file = new File(workDir, repository.name + "-" +
|
||||
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + suffix)
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||
using(new java.io.FileOutputStream(file)) { out =>
|
||||
git.archive
|
||||
.setFormat(suffix.tail)
|
||||
.setTree(revCommit.getTree)
|
||||
.setOutputStream(out)
|
||||
.call()
|
||||
}
|
||||
contentType = "application/octet-stream"
|
||||
response.setHeader("Content-Disposition", s"attachment; filename=${file.getName}")
|
||||
file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package app
|
||||
|
||||
import util._
|
||||
import ControlUtil._
|
||||
import Implicits._
|
||||
import service._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
|
||||
@@ -3,8 +3,15 @@ package app
|
||||
import service.{AccountService, SystemSettingsService}
|
||||
import SystemSettingsService._
|
||||
import util.AdminAuthenticator
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import ssh.SshServer
|
||||
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
|
||||
@@ -36,8 +43,9 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
||||
"bindPassword" -> trim(label("Bind Password", optional(text()))),
|
||||
"baseDN" -> trim(label("Base DN", text(required))),
|
||||
"userNameAttribute" -> trim(label("User name attribute", text(required))),
|
||||
"additionalFilterCondition"-> trim(label("Additional filter condition", optional(text()))),
|
||||
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
|
||||
"mailAttribute" -> trim(label("Mail address attribute", text(required))),
|
||||
"mailAttribute" -> trim(label("Mail address attribute", optional(text()))),
|
||||
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
||||
"keystore" -> trim(label("Keystore", optional(text())))
|
||||
)(Ldap.apply))
|
||||
@@ -47,6 +55,11 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
||||
} else Nil
|
||||
}
|
||||
|
||||
private val pluginForm = mapping(
|
||||
"pluginId" -> list(trim(label("", text())))
|
||||
)(PluginForm.apply)
|
||||
|
||||
case class PluginForm(pluginIds: List[String])
|
||||
|
||||
get("/admin/system")(adminOnly {
|
||||
admin.html.system(flash.get("info"))
|
||||
@@ -71,4 +84,104 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
||||
redirect("/admin/system")
|
||||
})
|
||||
|
||||
get("/admin/plugins")(adminOnly {
|
||||
val installedPlugins = plugin.PluginSystem.plugins
|
||||
val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable")
|
||||
admin.plugins.html.installed(installedPlugins, updatablePlugins)
|
||||
})
|
||||
|
||||
post("/admin/plugins/_update", pluginForm)(adminOnly { form =>
|
||||
deletePlugins(form.pluginIds)
|
||||
installPlugins(form.pluginIds)
|
||||
redirect("/admin/plugins")
|
||||
})
|
||||
|
||||
post("/admin/plugins/_delete", pluginForm)(adminOnly { form =>
|
||||
deletePlugins(form.pluginIds)
|
||||
redirect("/admin/plugins")
|
||||
})
|
||||
|
||||
get("/admin/plugins/available")(adminOnly {
|
||||
val installedPlugins = plugin.PluginSystem.plugins
|
||||
val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available")
|
||||
admin.plugins.html.available(availablePlugins)
|
||||
})
|
||||
|
||||
post("/admin/plugins/_install", pluginForm)(adminOnly { form =>
|
||||
installPlugins(form.pluginIds)
|
||||
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.ScalaPlugin.eval(script)
|
||||
Ok()
|
||||
})
|
||||
|
||||
// TODO Move these methods to PluginSystem or Service?
|
||||
private def deletePlugins(pluginIds: List[String]): Unit = {
|
||||
pluginIds.foreach { pluginId =>
|
||||
plugin.PluginSystem.uninstall(pluginId)
|
||||
val dir = new java.io.File(PluginHome, pluginId)
|
||||
if(dir.exists && dir.isDirectory){
|
||||
FileUtils.deleteQuietly(dir)
|
||||
PluginSystem.uninstall(pluginId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def installPlugins(pluginIds: List[String]): Unit = {
|
||||
val dir = getPluginCacheDir()
|
||||
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.deleteDirectory(pluginDir)
|
||||
}
|
||||
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
|
||||
PluginSystem.installPlugin(plugin.id)
|
||||
}
|
||||
}
|
||||
|
||||
private def getAvailablePlugins(installedPlugins: List[Plugin]): List[SystemSettingsControllerBase.AvailablePlugin] = {
|
||||
val repositoryRoot = getPluginCacheDir()
|
||||
|
||||
if(repositoryRoot.exists && repositoryRoot.isDirectory){
|
||||
PluginSystem.repositories.flatMap { repo =>
|
||||
val repoDir = new java.io.File(repositoryRoot, repo.id)
|
||||
if(repoDir.exists && repoDir.isDirectory){
|
||||
repoDir.listFiles.filter(d => d.isDirectory && !d.getName.startsWith(".")).map { plugin =>
|
||||
val propertyFile = new java.io.File(plugin, "plugin.properties")
|
||||
val properties = new java.util.Properties()
|
||||
if(propertyFile.exists && propertyFile.isFile){
|
||||
using(new FileInputStream(propertyFile)){ in =>
|
||||
properties.load(in)
|
||||
}
|
||||
}
|
||||
SystemSettingsControllerBase.AvailablePlugin(
|
||||
repository = repo.id,
|
||||
id = properties.getProperty("id"),
|
||||
version = properties.getProperty("version"),
|
||||
author = properties.getProperty("author"),
|
||||
url = properties.getProperty("url"),
|
||||
description = properties.getProperty("description"),
|
||||
status = installedPlugins.find(_.id == properties.getProperty("id")) match {
|
||||
case Some(x) if(PluginSystem.isUpdatable(x.version, properties.getProperty("version")))=> "updatable"
|
||||
case Some(x) => "installed"
|
||||
case None => "available"
|
||||
})
|
||||
}
|
||||
} else Nil
|
||||
}
|
||||
} else Nil
|
||||
}
|
||||
}
|
||||
|
||||
object SystemSettingsControllerBase {
|
||||
case class AvailablePlugin(repository: String, id: String, version: String,
|
||||
author: String, url: String, description: String, status: String)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import service._
|
||||
import util.AdminAuthenticator
|
||||
import util.StringUtil._
|
||||
import util.ControlUtil._
|
||||
import util.Directory._
|
||||
import util.Implicits._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.apache.commons.io.FileUtils
|
||||
import util.Directory._
|
||||
|
||||
class UserManagementController extends UserManagementControllerBase
|
||||
with AccountService with RepositoryService with AdminAuthenticator
|
||||
@@ -181,11 +182,6 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
// TODO Move to other generic controller?
|
||||
post("/admin/users/_usercheck"){
|
||||
getAccountByUserName(params("userName")).isDefined
|
||||
}
|
||||
|
||||
private def members: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
if(value.split(",").exists {
|
||||
|
||||
@@ -4,10 +4,10 @@ import service._
|
||||
import util._
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import util.Implicits._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.i18n.Messages
|
||||
import scala.Some
|
||||
import java.util.ResourceBundle
|
||||
|
||||
class WikiController extends WikiControllerBase
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait AccountComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
object Accounts extends Table[Account]("ACCOUNT") {
|
||||
def userName = column[String]("USER_NAME", O PrimaryKey)
|
||||
def fullName = column[String]("FULL_NAME")
|
||||
def mailAddress = column[String]("MAIL_ADDRESS")
|
||||
def password = column[String]("PASSWORD")
|
||||
def isAdmin = column[Boolean]("ADMINISTRATOR")
|
||||
def url = column[String]("URL")
|
||||
def registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
def updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE")
|
||||
def image = column[String]("IMAGE")
|
||||
def groupAccount = column[Boolean]("GROUP_ACCOUNT")
|
||||
def removed = column[Boolean]("REMOVED")
|
||||
def * = userName ~ fullName ~ mailAddress ~ password ~ isAdmin ~ url.? ~ registeredDate ~ updatedDate ~ lastLoginDate.? ~ image.? ~ groupAccount ~ removed <> (Account, Account.unapply _)
|
||||
lazy val Accounts = TableQuery[Accounts]
|
||||
|
||||
class Accounts(tag: Tag) extends Table[Account](tag, "ACCOUNT") {
|
||||
val userName = column[String]("USER_NAME", O PrimaryKey)
|
||||
val fullName = column[String]("FULL_NAME")
|
||||
val mailAddress = column[String]("MAIL_ADDRESS")
|
||||
val password = column[String]("PASSWORD")
|
||||
val isAdmin = column[Boolean]("ADMINISTRATOR")
|
||||
val url = column[String]("URL")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val lastLoginDate = column[java.util.Date]("LAST_LOGIN_DATE")
|
||||
val image = column[String]("IMAGE")
|
||||
val groupAccount = column[Boolean]("GROUP_ACCOUNT")
|
||||
val removed = column[Boolean]("REMOVED")
|
||||
def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
case class Account(
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait ActivityComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
object Activities extends Table[Activity]("ACTIVITY") with BasicTemplate {
|
||||
def activityId = column[Int]("ACTIVITY_ID", O AutoInc)
|
||||
def activityUserName = column[String]("ACTIVITY_USER_NAME")
|
||||
def activityType = column[String]("ACTIVITY_TYPE")
|
||||
def message = column[String]("MESSAGE")
|
||||
def additionalInfo = column[String]("ADDITIONAL_INFO")
|
||||
def activityDate = column[java.util.Date]("ACTIVITY_DATE")
|
||||
def * = activityId ~ userName ~ repositoryName ~ activityUserName ~ activityType ~ message ~ additionalInfo.? ~ activityDate <> (Activity, Activity.unapply _)
|
||||
def autoInc = userName ~ repositoryName ~ activityUserName ~ activityType ~ message ~ additionalInfo.? ~ activityDate returning activityId
|
||||
lazy val Activities = TableQuery[Activities]
|
||||
|
||||
class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate {
|
||||
val activityId = column[Int]("ACTIVITY_ID", O AutoInc)
|
||||
val activityUserName = column[String]("ACTIVITY_USER_NAME")
|
||||
val activityType = column[String]("ACTIVITY_TYPE")
|
||||
val message = column[String]("MESSAGE")
|
||||
val additionalInfo = column[String]("ADDITIONAL_INFO")
|
||||
val activityDate = column[java.util.Date]("ACTIVITY_DATE")
|
||||
def * = (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
case class Activity(
|
||||
activityId: Int,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
activityType: String,
|
||||
message: String,
|
||||
additionalInfo: Option[String],
|
||||
activityDate: java.util.Date
|
||||
activityDate: java.util.Date,
|
||||
activityId: Int = 0
|
||||
)
|
||||
|
||||
@@ -1,44 +1,47 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
protected[model] trait TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
protected[model] trait BasicTemplate { self: Table[_] =>
|
||||
def userName = column[String]("USER_NAME")
|
||||
def repositoryName = column[String]("REPOSITORY_NAME")
|
||||
trait BasicTemplate { self: Table[_] =>
|
||||
val userName = column[String]("USER_NAME")
|
||||
val repositoryName = column[String]("REPOSITORY_NAME")
|
||||
|
||||
def byRepository(owner: String, repository: String) =
|
||||
(userName is owner.bind) && (repositoryName is repository.bind)
|
||||
(userName === owner.bind) && (repositoryName === repository.bind)
|
||||
|
||||
def byRepository(userName: Column[String], repositoryName: Column[String]) =
|
||||
(this.userName is userName) && (this.repositoryName is repositoryName)
|
||||
}
|
||||
(this.userName === userName) && (this.repositoryName === repositoryName)
|
||||
}
|
||||
|
||||
protected[model] trait IssueTemplate extends BasicTemplate { self: Table[_] =>
|
||||
def issueId = column[Int]("ISSUE_ID")
|
||||
trait IssueTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val issueId = column[Int]("ISSUE_ID")
|
||||
|
||||
def byIssue(owner: String, repository: String, issueId: Int) =
|
||||
byRepository(owner, repository) && (this.issueId is issueId.bind)
|
||||
byRepository(owner, repository) && (this.issueId === issueId.bind)
|
||||
|
||||
def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.issueId is issueId)
|
||||
}
|
||||
byRepository(userName, repositoryName) && (this.issueId === issueId)
|
||||
}
|
||||
|
||||
protected[model] trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
||||
def labelId = column[Int]("LABEL_ID")
|
||||
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val labelId = column[Int]("LABEL_ID")
|
||||
|
||||
def byLabel(owner: String, repository: String, labelId: Int) =
|
||||
byRepository(owner, repository) && (this.labelId is labelId.bind)
|
||||
byRepository(owner, repository) && (this.labelId === labelId.bind)
|
||||
|
||||
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.labelId is labelId)
|
||||
}
|
||||
byRepository(userName, repositoryName) && (this.labelId === labelId)
|
||||
}
|
||||
|
||||
protected[model] trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
||||
def milestoneId = column[Int]("MILESTONE_ID")
|
||||
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val milestoneId = column[Int]("MILESTONE_ID")
|
||||
|
||||
def byMilestone(owner: String, repository: String, milestoneId: Int) =
|
||||
byRepository(owner, repository) && (this.milestoneId is milestoneId.bind)
|
||||
byRepository(owner, repository) && (this.milestoneId === milestoneId.bind)
|
||||
|
||||
def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) =
|
||||
byRepository(userName, repositoryName) && (this.milestoneId is milestoneId)
|
||||
byRepository(userName, repositoryName) && (this.milestoneId === milestoneId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait CollaboratorComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
object Collaborators extends Table[Collaborator]("COLLABORATOR") with BasicTemplate {
|
||||
def collaboratorName = column[String]("COLLABORATOR_NAME")
|
||||
def * = userName ~ repositoryName ~ collaboratorName <> (Collaborator, Collaborator.unapply _)
|
||||
lazy val Collaborators = TableQuery[Collaborators]
|
||||
|
||||
class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate {
|
||||
val collaboratorName = column[String]("COLLABORATOR_NAME")
|
||||
def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, collaborator: String) =
|
||||
byRepository(owner, repository) && (collaboratorName is collaborator.bind)
|
||||
byRepository(owner, repository) && (collaboratorName === collaborator.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class Collaborator(
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait GroupMemberComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
object GroupMembers extends Table[GroupMember]("GROUP_MEMBER") {
|
||||
def groupName = column[String]("GROUP_NAME", O PrimaryKey)
|
||||
def userName = column[String]("USER_NAME", O PrimaryKey)
|
||||
def isManager = column[Boolean]("MANAGER")
|
||||
def * = groupName ~ userName ~ isManager <> (GroupMember, GroupMember.unapply _)
|
||||
lazy val GroupMembers = TableQuery[GroupMembers]
|
||||
|
||||
class GroupMembers(tag: Tag) extends Table[GroupMember](tag, "GROUP_MEMBER") {
|
||||
val groupName = column[String]("GROUP_NAME", O PrimaryKey)
|
||||
val userName = column[String]("USER_NAME", O PrimaryKey)
|
||||
val isManager = column[Boolean]("MANAGER")
|
||||
def * = (groupName, userName, isManager) <> (GroupMember.tupled, GroupMember.unapply)
|
||||
}
|
||||
}
|
||||
|
||||
case class GroupMember(
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait IssueComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
object IssueId extends Table[(String, String, Int)]("ISSUE_ID") with IssueTemplate {
|
||||
def * = userName ~ repositoryName ~ issueId
|
||||
lazy val IssueId = TableQuery[IssueId]
|
||||
lazy val IssueOutline = TableQuery[IssueOutline]
|
||||
lazy val Issues = TableQuery[Issues]
|
||||
|
||||
class IssueId(tag: Tag) extends Table[(String, String, Int)](tag, "ISSUE_ID") with IssueTemplate {
|
||||
def * = (userName, repositoryName, issueId)
|
||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||
}
|
||||
}
|
||||
|
||||
object IssueOutline extends Table[(String, String, Int, Int)]("ISSUE_OUTLINE_VIEW") with IssueTemplate {
|
||||
def commentCount = column[Int]("COMMENT_COUNT")
|
||||
def * = userName ~ repositoryName ~ issueId ~ commentCount
|
||||
}
|
||||
class IssueOutline(tag: Tag) extends Table[(String, String, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") with IssueTemplate {
|
||||
val commentCount = column[Int]("COMMENT_COUNT")
|
||||
def * = (userName, repositoryName, issueId, commentCount)
|
||||
}
|
||||
|
||||
object Issues extends Table[Issue]("ISSUE") with IssueTemplate with MilestoneTemplate {
|
||||
def openedUserName = column[String]("OPENED_USER_NAME")
|
||||
def assignedUserName = column[String]("ASSIGNED_USER_NAME")
|
||||
def title = column[String]("TITLE")
|
||||
def content = column[String]("CONTENT")
|
||||
def closed = column[Boolean]("CLOSED")
|
||||
def registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
def updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def pullRequest = column[Boolean]("PULL_REQUEST")
|
||||
def * = userName ~ repositoryName ~ issueId ~ openedUserName ~ milestoneId.? ~ assignedUserName.? ~ title ~ content.? ~ closed ~ registeredDate ~ updatedDate ~ pullRequest <> (Issue, Issue.unapply _)
|
||||
class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate {
|
||||
val openedUserName = column[String]("OPENED_USER_NAME")
|
||||
val assignedUserName = column[String]("ASSIGNED_USER_NAME")
|
||||
val title = column[String]("TITLE")
|
||||
val content = column[String]("CONTENT")
|
||||
val closed = column[Boolean]("CLOSED")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val pullRequest = column[Boolean]("PULL_REQUEST")
|
||||
def * = (userName, repositoryName, issueId, openedUserName, milestoneId.?, assignedUserName.?, title, content.?, closed, registeredDate, updatedDate, pullRequest) <> (Issue.tupled, Issue.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
|
||||
}
|
||||
}
|
||||
|
||||
case class Issue(
|
||||
@@ -38,4 +45,5 @@ case class Issue(
|
||||
closed: Boolean,
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date,
|
||||
isPullRequest: Boolean)
|
||||
isPullRequest: Boolean
|
||||
)
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait IssueCommentComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
object IssueComments extends Table[IssueComment]("ISSUE_COMMENT") with IssueTemplate {
|
||||
def commentId = column[Int]("COMMENT_ID", O AutoInc)
|
||||
def action = column[String]("ACTION")
|
||||
def commentedUserName = column[String]("COMMENTED_USER_NAME")
|
||||
def content = column[String]("CONTENT")
|
||||
def registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
def updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def * = userName ~ repositoryName ~ issueId ~ commentId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate <> (IssueComment, IssueComment.unapply _)
|
||||
lazy val IssueComments = new TableQuery(tag => new IssueComments(tag)){
|
||||
def autoInc = this returning this.map(_.commentId)
|
||||
}
|
||||
|
||||
def autoInc = userName ~ repositoryName ~ issueId ~ action ~ commentedUserName ~ content ~ registeredDate ~ updatedDate returning commentId
|
||||
def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind
|
||||
class IssueComments(tag: Tag) extends Table[IssueComment](tag, "ISSUE_COMMENT") with IssueTemplate {
|
||||
val commentId = column[Int]("COMMENT_ID", O AutoInc)
|
||||
val action = column[String]("ACTION")
|
||||
val commentedUserName = column[String]("COMMENTED_USER_NAME")
|
||||
val content = column[String]("CONTENT")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class IssueComment(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
commentId: Int,
|
||||
commentId: Int = 0,
|
||||
action: String,
|
||||
commentedUserName: String,
|
||||
content: String,
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait IssueLabelComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
object IssueLabels extends Table[IssueLabel]("ISSUE_LABEL") with IssueTemplate with LabelTemplate {
|
||||
def * = userName ~ repositoryName ~ issueId ~ labelId <> (IssueLabel, IssueLabel.unapply _)
|
||||
lazy val IssueLabels = TableQuery[IssueLabels]
|
||||
|
||||
class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate {
|
||||
def * = (userName, repositoryName, issueId, labelId) <> (IssueLabel.tupled, IssueLabel.unapply)
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) =
|
||||
byIssue(owner, repository, issueId) && (this.labelId is labelId.bind)
|
||||
byIssue(owner, repository, issueId) && (this.labelId === labelId.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class IssueLabel(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
labelId: Int)
|
||||
labelId: Int
|
||||
)
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait LabelComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
object Labels extends Table[Label]("LABEL") with LabelTemplate {
|
||||
def labelName = column[String]("LABEL_NAME")
|
||||
def color = column[String]("COLOR")
|
||||
def * = userName ~ repositoryName ~ labelId ~ labelName ~ color <> (Label, Label.unapply _)
|
||||
lazy val Labels = TableQuery[Labels]
|
||||
|
||||
class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate {
|
||||
override val labelId = column[Int]("LABEL_ID", O AutoInc)
|
||||
val labelName = column[String]("LABEL_NAME")
|
||||
val color = column[String]("COLOR")
|
||||
def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply)
|
||||
|
||||
def ins = userName ~ repositoryName ~ labelName ~ color
|
||||
def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId)
|
||||
def byPrimaryKey(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = byLabel(userName, repositoryName, labelId)
|
||||
}
|
||||
}
|
||||
|
||||
case class Label(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
labelId: Int,
|
||||
labelId: Int = 0,
|
||||
labelName: String,
|
||||
color: String){
|
||||
|
||||
@@ -30,5 +34,4 @@ case class Label(
|
||||
"FFFFFF"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +1,30 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait MilestoneComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
object Milestones extends Table[Milestone]("MILESTONE") with MilestoneTemplate {
|
||||
def title = column[String]("TITLE")
|
||||
def description = column[String]("DESCRIPTION")
|
||||
def dueDate = column[java.util.Date]("DUE_DATE")
|
||||
def closedDate = column[java.util.Date]("CLOSED_DATE")
|
||||
def * = userName ~ repositoryName ~ milestoneId ~ title ~ description.? ~ dueDate.? ~ closedDate.? <> (Milestone, Milestone.unapply _)
|
||||
lazy val Milestones = TableQuery[Milestones]
|
||||
|
||||
class Milestones(tag: Tag) extends Table[Milestone](tag, "MILESTONE") with MilestoneTemplate {
|
||||
override val milestoneId = column[Int]("MILESTONE_ID", O AutoInc)
|
||||
val title = column[String]("TITLE")
|
||||
val description = column[String]("DESCRIPTION")
|
||||
val dueDate = column[java.util.Date]("DUE_DATE")
|
||||
val closedDate = column[java.util.Date]("CLOSED_DATE")
|
||||
def * = (userName, repositoryName, milestoneId, title, description.?, dueDate.?, closedDate.?) <> (Milestone.tupled, Milestone.unapply)
|
||||
|
||||
def ins = userName ~ repositoryName ~ title ~ description.? ~ dueDate.? ~ closedDate.?
|
||||
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
|
||||
def byPrimaryKey(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) = byMilestone(userName, repositoryName, milestoneId)
|
||||
}
|
||||
}
|
||||
|
||||
case class Milestone(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
milestoneId: Int,
|
||||
milestoneId: Int = 0,
|
||||
title: String,
|
||||
description: Option[String],
|
||||
dueDate: Option[java.util.Date],
|
||||
closedDate: Option[java.util.Date])
|
||||
closedDate: Option[java.util.Date]
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
42
src/main/scala/model/Profile.scala
Normal file
42
src/main/scala/model/Profile.scala
Normal file
@@ -0,0 +1,42 @@
|
||||
package model
|
||||
|
||||
trait Profile {
|
||||
val profile: slick.driver.JdbcProfile
|
||||
import profile.simple._
|
||||
|
||||
// java.util.Date Mapped Column Types
|
||||
implicit val dateColumnType = MappedColumnType.base[java.util.Date, java.sql.Timestamp](
|
||||
d => new java.sql.Timestamp(d.getTime),
|
||||
t => new java.util.Date(t.getTime)
|
||||
)
|
||||
|
||||
implicit class RichColumn(c1: Column[Boolean]){
|
||||
def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Profile extends {
|
||||
val profile = slick.driver.H2Driver
|
||||
|
||||
} with AccountComponent
|
||||
with ActivityComponent
|
||||
with CollaboratorComponent
|
||||
with GroupMemberComponent
|
||||
with IssueComponent
|
||||
with IssueCommentComponent
|
||||
with IssueLabelComponent
|
||||
with LabelComponent
|
||||
with MilestoneComponent
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with WebHookComponent
|
||||
with PluginComponent with Profile {
|
||||
|
||||
/**
|
||||
* Returns system date.
|
||||
*/
|
||||
def currentDate = new java.util.Date()
|
||||
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
object PullRequests extends Table[PullRequest]("PULL_REQUEST") with IssueTemplate {
|
||||
def branch = column[String]("BRANCH")
|
||||
def requestUserName = column[String]("REQUEST_USER_NAME")
|
||||
def requestRepositoryName = column[String]("REQUEST_REPOSITORY_NAME")
|
||||
def requestBranch = column[String]("REQUEST_BRANCH")
|
||||
def commitIdFrom = column[String]("COMMIT_ID_FROM")
|
||||
def commitIdTo = column[String]("COMMIT_ID_TO")
|
||||
def * = userName ~ repositoryName ~ issueId ~ branch ~ requestUserName ~ requestRepositoryName ~ requestBranch ~ commitIdFrom ~ commitIdTo <> (PullRequest, PullRequest.unapply _)
|
||||
lazy val PullRequests = TableQuery[PullRequests]
|
||||
|
||||
class PullRequests(tag: Tag) extends Table[PullRequest](tag, "PULL_REQUEST") with IssueTemplate {
|
||||
val branch = column[String]("BRANCH")
|
||||
val requestUserName = column[String]("REQUEST_USER_NAME")
|
||||
val requestRepositoryName = column[String]("REQUEST_REPOSITORY_NAME")
|
||||
val requestBranch = column[String]("REQUEST_BRANCH")
|
||||
val commitIdFrom = column[String]("COMMIT_ID_FROM")
|
||||
val commitIdTo = column[String]("COMMIT_ID_TO")
|
||||
def * = (userName, repositoryName, issueId, branch, requestUserName, requestRepositoryName, requestBranch, commitIdFrom, commitIdTo) <> (PullRequest.tupled, PullRequest.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = byIssue(userName, repositoryName, issueId)
|
||||
def byPrimaryKey(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) = byIssue(userName, repositoryName, issueId)
|
||||
}
|
||||
}
|
||||
|
||||
case class PullRequest(
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
object Repositories extends Table[Repository]("REPOSITORY") with BasicTemplate {
|
||||
def isPrivate = column[Boolean]("PRIVATE")
|
||||
def description = column[String]("DESCRIPTION")
|
||||
def defaultBranch = column[String]("DEFAULT_BRANCH")
|
||||
def registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
def updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE")
|
||||
def originUserName = column[String]("ORIGIN_USER_NAME")
|
||||
def originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
|
||||
def parentUserName = column[String]("PARENT_USER_NAME")
|
||||
def parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
|
||||
def * = userName ~ repositoryName ~ isPrivate ~ description.? ~ defaultBranch ~ registeredDate ~ updatedDate ~ lastActivityDate ~ originUserName.? ~ originRepositoryName.? ~ parentUserName.? ~ parentRepositoryName.? <> (Repository, Repository.unapply _)
|
||||
lazy val Repositories = TableQuery[Repositories]
|
||||
|
||||
class Repositories(tag: Tag) extends Table[Repository](tag, "REPOSITORY") with BasicTemplate {
|
||||
val isPrivate = column[Boolean]("PRIVATE")
|
||||
val description = column[String]("DESCRIPTION")
|
||||
val defaultBranch = column[String]("DEFAULT_BRANCH")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE")
|
||||
val originUserName = column[String]("ORIGIN_USER_NAME")
|
||||
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
|
||||
val parentUserName = column[String]("PARENT_USER_NAME")
|
||||
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
|
||||
def * = (userName, repositoryName, isPrivate, description.?, defaultBranch, registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?) <> (Repository.tupled, Repository.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||
}
|
||||
}
|
||||
|
||||
case class Repository(
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait SshKeyComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
object SshKeys extends Table[SshKey]("SSH_KEY") {
|
||||
def userName = column[String]("USER_NAME")
|
||||
def sshKeyId = column[Int]("SSH_KEY_ID", O AutoInc)
|
||||
def title = column[String]("TITLE")
|
||||
def publicKey = column[String]("PUBLIC_KEY")
|
||||
lazy val SshKeys = TableQuery[SshKeys]
|
||||
|
||||
def ins = userName ~ title ~ publicKey returning sshKeyId
|
||||
def * = userName ~ sshKeyId ~ title ~ publicKey <> (SshKey, SshKey.unapply _)
|
||||
class SshKeys(tag: Tag) extends Table[SshKey](tag, "SSH_KEY") {
|
||||
val userName = column[String]("USER_NAME")
|
||||
val sshKeyId = column[Int]("SSH_KEY_ID", O AutoInc)
|
||||
val title = column[String]("TITLE")
|
||||
val publicKey = column[String]("PUBLIC_KEY")
|
||||
def * = (userName, sshKeyId, title, publicKey) <> (SshKey.tupled, SshKey.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, sshKeyId: Int) = (this.userName is userName.bind) && (this.sshKeyId is sshKeyId.bind)
|
||||
def byPrimaryKey(userName: String, sshKeyId: Int) = (this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class SshKey(
|
||||
userName: String,
|
||||
sshKeyId: Int,
|
||||
sshKeyId: Int = 0,
|
||||
title: String,
|
||||
publicKey: String
|
||||
)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package model
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
trait WebHookComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
|
||||
object WebHooks extends Table[WebHook]("WEB_HOOK") with BasicTemplate {
|
||||
def url = column[String]("URL")
|
||||
def * = userName ~ repositoryName ~ url <> (WebHook, WebHook.unapply _)
|
||||
lazy val WebHooks = TableQuery[WebHooks]
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url is url.bind)
|
||||
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
|
||||
val url = column[String]("URL")
|
||||
def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class WebHook(
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
package object model {
|
||||
import scala.slick.driver.BasicDriver.Implicit._
|
||||
import scala.slick.lifted.{Column, MappedTypeMapper}
|
||||
|
||||
// java.util.Date TypeMapper
|
||||
implicit val dateTypeMapper = MappedTypeMapper.base[java.util.Date, java.sql.Timestamp](
|
||||
d => new java.sql.Timestamp(d.getTime),
|
||||
t => new java.util.Date(t.getTime)
|
||||
)
|
||||
|
||||
implicit class RichColumn(c1: Column[Boolean]){
|
||||
def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns system date.
|
||||
*/
|
||||
def currentDate = new java.util.Date()
|
||||
|
||||
type Session = slick.jdbc.JdbcBackend#Session
|
||||
}
|
||||
22
src/main/scala/plugin/Plugin.scala
Normal file
22
src/main/scala/plugin/Plugin.scala
Normal file
@@ -0,0 +1,22 @@
|
||||
package plugin
|
||||
|
||||
import plugin.PluginSystem._
|
||||
import java.sql.Connection
|
||||
|
||||
trait Plugin {
|
||||
val id: String
|
||||
val version: String
|
||||
val author: String
|
||||
val url: String
|
||||
val description: String
|
||||
|
||||
def repositoryMenus : List[RepositoryMenu]
|
||||
def globalMenus : List[GlobalMenu]
|
||||
def repositoryActions : List[RepositoryAction]
|
||||
def globalActions : List[Action]
|
||||
def javaScripts : List[JavaScript]
|
||||
}
|
||||
|
||||
object PluginConnectionHolder {
|
||||
val threadLocal = new ThreadLocal[Connection]
|
||||
}
|
||||
194
src/main/scala/plugin/PluginSystem.scala
Normal file
194
src/main/scala/plugin/PluginSystem.scala
Normal file
@@ -0,0 +1,194 @@
|
||||
package plugin
|
||||
|
||||
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.{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 extends PluginService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(PluginSystem.getClass)
|
||||
|
||||
private val initialized = new AtomicBoolean(false)
|
||||
private val pluginsMap = scala.collection.mutable.Map[String, Plugin]()
|
||||
private val repositoriesList = scala.collection.mutable.ListBuffer[PluginRepository]()
|
||||
|
||||
def install(plugin: Plugin): Unit = {
|
||||
pluginsMap.put(plugin.id, plugin)
|
||||
}
|
||||
|
||||
def plugins: List[Plugin] = pluginsMap.values.toList
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* Initializes the plugin system. Load scripts from GITBUCKET_HOME/plugins.
|
||||
*/
|
||||
def init()(implicit session: Session): Unit = {
|
||||
if(initialized.compareAndSet(false, true)){
|
||||
// Load installed plugins
|
||||
val pluginDir = new java.io.File(PluginHome)
|
||||
if(pluginDir.exists && pluginDir.isDirectory){
|
||||
pluginDir.listFiles.filter(f => f.isDirectory && !f.getName.startsWith(".")).foreach { dir =>
|
||||
installPlugin(dir.getName)
|
||||
}
|
||||
}
|
||||
// Add default plugin repositories
|
||||
repositoriesList += PluginRepository("central", "https://github.com/takezoe/gitbucket_plugins.git")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Method name seems to not so good.
|
||||
def installPlugin(id: String)(implicit session: Session): Unit = {
|
||||
val pluginHome = new java.io.File(PluginHome)
|
||||
val pluginDir = new java.io.File(pluginHome, id)
|
||||
|
||||
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, "plugin.properties"))){ in =>
|
||||
properties.load(in)
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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: Throwable => logger.warn(s"Error in plugin loading for ${scalaFile.getAbsolutePath}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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(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.
|
||||
*/
|
||||
def isUpdatable(oldVersion: String, newVersion: String): Boolean = {
|
||||
if(oldVersion == newVersion){
|
||||
false
|
||||
} else {
|
||||
val dim1 = oldVersion.split("\\.").map(_.toInt)
|
||||
val dim2 = newVersion.split("\\.").map(_.toInt)
|
||||
dim1.zip(dim2).foreach { case (a, b) =>
|
||||
if(a < b){
|
||||
return true
|
||||
} else if(a > b){
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
66
src/main/scala/plugin/PluginUpdateJob.scala
Normal file
66
src/main/scala/plugin/PluginUpdateJob.scala
Normal file
@@ -0,0 +1,66 @@
|
||||
package plugin
|
||||
|
||||
import util.Directory._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.quartz.{Scheduler, JobExecutionContext, Job}
|
||||
import org.quartz.JobBuilder._
|
||||
import org.quartz.TriggerBuilder._
|
||||
import org.quartz.SimpleScheduleBuilder._
|
||||
|
||||
class PluginUpdateJob extends Job {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[PluginUpdateJob])
|
||||
private var failedCount = 0
|
||||
|
||||
/**
|
||||
* Clone or pull all plugin repositories
|
||||
*
|
||||
* TODO Support plugin repository access through the proxy server
|
||||
*/
|
||||
override def execute(context: JobExecutionContext): Unit = {
|
||||
try {
|
||||
if(failedCount > 3){
|
||||
logger.error("Skip plugin information updating because failed count is over limit")
|
||||
} else {
|
||||
logger.info("Start plugin information updating")
|
||||
PluginSystem.repositories.foreach { repository =>
|
||||
logger.info(s"Updating ${repository.id}: ${repository.url}...")
|
||||
val dir = getPluginCacheDir()
|
||||
val repo = new java.io.File(dir, repository.id)
|
||||
if(repo.exists){
|
||||
// pull if the repository is already cloned
|
||||
Git.open(repo).pull().call()
|
||||
} else {
|
||||
// clone if the repository is not exist
|
||||
Git.cloneRepository().setURI(repository.url).setDirectory(repo).call()
|
||||
}
|
||||
}
|
||||
logger.info("End plugin information updating")
|
||||
}
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
failedCount = failedCount + 1
|
||||
logger.error("Failed to update plugin information", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object PluginUpdateJob {
|
||||
|
||||
def schedule(scheduler: Scheduler): Unit = {
|
||||
val job = newJob(classOf[PluginUpdateJob])
|
||||
.withIdentity("pluginUpdateJob")
|
||||
.build()
|
||||
|
||||
val trigger = newTrigger()
|
||||
.withIdentity("pluginUpdateTrigger")
|
||||
.startNow()
|
||||
.withSchedule(simpleSchedule().withIntervalInHours(24).repeatForever())
|
||||
.build()
|
||||
|
||||
scheduler.scheduleJob(job, trigger)
|
||||
}
|
||||
|
||||
}
|
||||
77
src/main/scala/plugin/ScalaPlugin.scala
Normal file
77
src/main/scala/plugin/ScalaPlugin.scala
Normal file
@@ -0,0 +1,77 @@
|
||||
package plugin
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
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,
|
||||
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]()
|
||||
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 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)
|
||||
}
|
||||
|
||||
def addGlobalMenu(label: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = {
|
||||
globalMenuList += GlobalMenu(label, url, icon, condition)
|
||||
}
|
||||
|
||||
def addGlobalAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context) => Any): Unit = {
|
||||
globalActionList += Action(method, path, security, 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
|
||||
|
||||
}
|
||||
|
||||
56
src/main/scala/plugin/package.scala
Normal file
56
src/main/scala/plugin/package.scala
Normal file
@@ -0,0 +1,56 @@
|
||||
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)
|
||||
case class RawData(contentType: String, content: Array[Byte])
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
package service
|
||||
|
||||
import model._
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.{Account, GroupMember}
|
||||
// TODO [Slick 2.0]NOT import directly?
|
||||
import model.Profile.dateColumnType
|
||||
import service.SystemSettingsService.SystemSettings
|
||||
import util.StringUtil._
|
||||
import model.GroupMember
|
||||
import scala.Some
|
||||
import model.Account
|
||||
import util.LDAPUtil
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@@ -15,7 +14,7 @@ trait AccountService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[AccountService])
|
||||
|
||||
def authenticate(settings: SystemSettings, userName: String, password: String): Option[Account] =
|
||||
def authenticate(settings: SystemSettings, userName: String, password: String)(implicit s: Session): Option[Account] =
|
||||
if(settings.ldapAuthentication){
|
||||
ldapAuthentication(settings, userName, password)
|
||||
} else {
|
||||
@@ -25,7 +24,7 @@ trait AccountService {
|
||||
/**
|
||||
* Authenticate by internal database.
|
||||
*/
|
||||
private def defaultAuthentication(userName: String, password: String) = {
|
||||
private def defaultAuthentication(userName: String, password: String)(implicit s: Session) = {
|
||||
getAccountByUserName(userName).collect {
|
||||
case account if(!account.isGroupAccount && account.password == sha1(password)) => Some(account)
|
||||
} getOrElse None
|
||||
@@ -34,17 +33,22 @@ trait AccountService {
|
||||
/**
|
||||
* Authenticate by LDAP.
|
||||
*/
|
||||
private def ldapAuthentication(settings: SystemSettings, userName: String, password: String): Option[Account] = {
|
||||
private def ldapAuthentication(settings: SystemSettings, userName: String, password: String)
|
||||
(implicit s: Session): Option[Account] = {
|
||||
LDAPUtil.authenticate(settings.ldap.get, userName, password) match {
|
||||
case Right(ldapUserInfo) => {
|
||||
// Create or update account by LDAP information
|
||||
getAccountByUserName(ldapUserInfo.userName, true) match {
|
||||
case Some(x) if(!x.isRemoved) => {
|
||||
if(settings.ldap.get.mailAttribute.getOrElse("").isEmpty) {
|
||||
updateAccount(x.copy(fullName = ldapUserInfo.fullName))
|
||||
} else {
|
||||
updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName))
|
||||
}
|
||||
getAccountByUserName(ldapUserInfo.userName)
|
||||
}
|
||||
case Some(x) if(x.isRemoved) => {
|
||||
logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..")
|
||||
logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
|
||||
defaultAuthentication(userName, password)
|
||||
}
|
||||
case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match {
|
||||
@@ -53,7 +57,7 @@ trait AccountService {
|
||||
getAccountByUserName(ldapUserInfo.userName)
|
||||
}
|
||||
case Some(x) if(x.isRemoved) => {
|
||||
logger.info(s"LDAP Authentication Failed: Account is already registered but disabled..")
|
||||
logger.info("LDAP Authentication Failed: Account is already registered but disabled.")
|
||||
defaultAuthentication(userName, password)
|
||||
}
|
||||
case None => {
|
||||
@@ -70,20 +74,21 @@ trait AccountService {
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByUserName(userName: String, includeRemoved: Boolean = false): Option[Account] =
|
||||
Query(Accounts) filter(t => (t.userName is userName.bind) && (t.removed is false.bind, !includeRemoved)) firstOption
|
||||
def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
|
||||
Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
|
||||
|
||||
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false): Option[Account] =
|
||||
Query(Accounts) filter(t => (t.mailAddress.toLowerCase is mailAddress.toLowerCase.bind) && (t.removed is false.bind, !includeRemoved)) firstOption
|
||||
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
|
||||
Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
|
||||
|
||||
def getAllUsers(includeRemoved: Boolean = true): List[Account] =
|
||||
def getAllUsers(includeRemoved: Boolean = true)(implicit s: Session): List[Account] =
|
||||
if(includeRemoved){
|
||||
Query(Accounts) sortBy(_.userName) list
|
||||
Accounts sortBy(_.userName) list
|
||||
} else {
|
||||
Query(Accounts) filter (_.removed is false.bind) sortBy(_.userName) list
|
||||
Accounts filter (_.removed === false.bind) sortBy(_.userName) list
|
||||
}
|
||||
|
||||
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String]): Unit =
|
||||
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
|
||||
(implicit s: Session): Unit =
|
||||
Accounts insert Account(
|
||||
userName = userName,
|
||||
password = password,
|
||||
@@ -98,10 +103,10 @@ trait AccountService {
|
||||
isGroupAccount = false,
|
||||
isRemoved = false)
|
||||
|
||||
def updateAccount(account: Account): Unit =
|
||||
def updateAccount(account: Account)(implicit s: Session): Unit =
|
||||
Accounts
|
||||
.filter { a => a.userName is account.userName.bind }
|
||||
.map { a => a.password ~ a.fullName ~ a.mailAddress ~ a.isAdmin ~ a.url.? ~ a.registeredDate ~ a.updatedDate ~ a.lastLoginDate.? ~ a.removed }
|
||||
.filter { a => a.userName === account.userName.bind }
|
||||
.map { a => (a.password, a.fullName, a.mailAddress, a.isAdmin, a.url.?, a.registeredDate, a.updatedDate, a.lastLoginDate.?, a.removed) }
|
||||
.update (
|
||||
account.password,
|
||||
account.fullName,
|
||||
@@ -113,13 +118,13 @@ trait AccountService {
|
||||
account.lastLoginDate,
|
||||
account.isRemoved)
|
||||
|
||||
def updateAvatarImage(userName: String, image: Option[String]): Unit =
|
||||
Accounts.filter(_.userName is userName.bind).map(_.image.?).update(image)
|
||||
def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit =
|
||||
Accounts.filter(_.userName === userName.bind).map(_.image.?).update(image)
|
||||
|
||||
def updateLastLoginDate(userName: String): Unit =
|
||||
Accounts.filter(_.userName is userName.bind).map(_.lastLoginDate).update(currentDate)
|
||||
def updateLastLoginDate(userName: String)(implicit s: Session): Unit =
|
||||
Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate)
|
||||
|
||||
def createGroup(groupName: String, url: Option[String]): Unit =
|
||||
def createGroup(groupName: String, url: Option[String])(implicit s: Session): Unit =
|
||||
Accounts insert Account(
|
||||
userName = groupName,
|
||||
password = "",
|
||||
@@ -134,33 +139,33 @@ trait AccountService {
|
||||
isGroupAccount = true,
|
||||
isRemoved = false)
|
||||
|
||||
def updateGroup(groupName: String, url: Option[String], removed: Boolean): Unit =
|
||||
Accounts.filter(_.userName is groupName.bind).map(t => t.url.? ~ t.removed).update(url, removed)
|
||||
def updateGroup(groupName: String, url: Option[String], removed: Boolean)(implicit s: Session): Unit =
|
||||
Accounts.filter(_.userName === groupName.bind).map(t => t.url.? -> t.removed).update(url, removed)
|
||||
|
||||
def updateGroupMembers(groupName: String, members: List[(String, Boolean)]): Unit = {
|
||||
Query(GroupMembers).filter(_.groupName is groupName.bind).delete
|
||||
def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = {
|
||||
GroupMembers.filter(_.groupName === groupName.bind).delete
|
||||
members.foreach { case (userName, isManager) =>
|
||||
GroupMembers insert GroupMember (groupName, userName, isManager)
|
||||
}
|
||||
}
|
||||
|
||||
def getGroupMembers(groupName: String): List[GroupMember] =
|
||||
Query(GroupMembers)
|
||||
.filter(_.groupName is groupName.bind)
|
||||
def getGroupMembers(groupName: String)(implicit s: Session): List[GroupMember] =
|
||||
GroupMembers
|
||||
.filter(_.groupName === groupName.bind)
|
||||
.sortBy(_.userName)
|
||||
.list
|
||||
|
||||
def getGroupsByUserName(userName: String): List[String] =
|
||||
Query(GroupMembers)
|
||||
.filter(_.userName is userName.bind)
|
||||
def getGroupsByUserName(userName: String)(implicit s: Session): List[String] =
|
||||
GroupMembers
|
||||
.filter(_.userName === userName.bind)
|
||||
.sortBy(_.groupName)
|
||||
.map(_.groupName)
|
||||
.list
|
||||
|
||||
def removeUserRelatedData(userName: String): Unit = {
|
||||
Query(GroupMembers).filter(_.userName is userName.bind).delete
|
||||
Query(Collaborators).filter(_.collaboratorName is userName.bind).delete
|
||||
Query(Repositories).filter(_.userName is userName.bind).delete
|
||||
def removeUserRelatedData(userName: String)(implicit s: Session): Unit = {
|
||||
GroupMembers.filter(_.userName === userName.bind).delete
|
||||
Collaborators.filter(_.collaboratorName === userName.bind).delete
|
||||
Repositories.filter(_.userName === userName.bind).delete
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package service
|
||||
|
||||
import model._
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.Activity
|
||||
|
||||
trait ActivityService {
|
||||
|
||||
def getActivitiesByUser(activityUserName: String, isPublic: Boolean): List[Activity] =
|
||||
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] =
|
||||
Activities
|
||||
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
|
||||
.filter { case (t1, t2) =>
|
||||
if(isPublic){
|
||||
(t1.activityUserName is activityUserName.bind) && (t2.isPrivate is false.bind)
|
||||
(t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind)
|
||||
} else {
|
||||
(t1.activityUserName is activityUserName.bind)
|
||||
(t1.activityUserName === activityUserName.bind)
|
||||
}
|
||||
}
|
||||
.sortBy { case (t1, t2) => t1.activityId desc }
|
||||
@@ -21,133 +21,154 @@ trait ActivityService {
|
||||
.take(30)
|
||||
.list
|
||||
|
||||
def getRecentActivities(): List[Activity] =
|
||||
def getRecentActivities()(implicit s: Session): List[Activity] =
|
||||
Activities
|
||||
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
|
||||
.filter { case (t1, t2) => t2.isPrivate is false.bind }
|
||||
.filter { case (t1, t2) => t2.isPrivate === false.bind }
|
||||
.sortBy { case (t1, t2) => t1.activityId desc }
|
||||
.map { case (t1, t2) => t1 }
|
||||
.take(30)
|
||||
.list
|
||||
|
||||
def getRecentActivitiesByOwners(owners : Set[String])(implicit s: Session): List[Activity] =
|
||||
Activities
|
||||
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
|
||||
.filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) }
|
||||
.sortBy { case (t1, t2) => t1.activityId desc }
|
||||
.map { case (t1, t2) => t1 }
|
||||
.take(30)
|
||||
.list
|
||||
|
||||
def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"create_repository",
|
||||
s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate)
|
||||
|
||||
def recordCreateIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordCreateIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"open_issue",
|
||||
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate)
|
||||
|
||||
def recordCloseIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordCloseIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"close_issue",
|
||||
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate)
|
||||
|
||||
def recordClosePullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordClosePullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"close_issue",
|
||||
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate)
|
||||
|
||||
def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"reopen_issue",
|
||||
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate)
|
||||
|
||||
def recordCommentIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordCommentIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"comment_issue",
|
||||
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(cut(comment, 200)),
|
||||
currentDate)
|
||||
|
||||
def recordCommentPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordCommentPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"comment_issue",
|
||||
s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(cut(comment, 200)),
|
||||
currentDate)
|
||||
|
||||
def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String) =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"create_wiki",
|
||||
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
|
||||
Some(pageName),
|
||||
currentDate)
|
||||
|
||||
def recordEditWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String, commitId: String) =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordEditWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String, commitId: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"edit_wiki",
|
||||
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
|
||||
Some(pageName + ":" + commitId),
|
||||
currentDate)
|
||||
|
||||
def recordPushActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||
branchName: String, commits: List[util.JGitUtil.CommitInfo]) =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
branchName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"push",
|
||||
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
|
||||
Some(commits.map { commit => commit.id + ":" + commit.shortMessage }.mkString("\n")),
|
||||
currentDate)
|
||||
|
||||
def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||
tagName: String, commits: List[util.JGitUtil.CommitInfo]) =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
tagName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"create_tag",
|
||||
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate)
|
||||
|
||||
def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||
tagName: String, commits: List[util.JGitUtil.CommitInfo]) =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
tagName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"delete_tag",
|
||||
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate)
|
||||
|
||||
def recordCreateBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String) =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordCreateBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"create_branch",
|
||||
s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate)
|
||||
|
||||
def recordDeleteBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String) =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordDeleteBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"delete_branch",
|
||||
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate)
|
||||
|
||||
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String) =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String)(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"fork",
|
||||
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${activityUserName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate)
|
||||
|
||||
def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"open_pullreq",
|
||||
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate)
|
||||
|
||||
def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String): Unit =
|
||||
Activities.autoInc insert(userName, repositoryName, activityUserName,
|
||||
def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"merge_pullreq",
|
||||
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(message),
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
package service
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
import scala.slick.jdbc.{StaticQuery => Q}
|
||||
import Q.interpolation
|
||||
|
||||
import model._
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.{Issue, IssueComment, IssueLabel, Label}
|
||||
import util.Implicits._
|
||||
import util.StringUtil._
|
||||
|
||||
trait IssuesService {
|
||||
import IssuesService._
|
||||
|
||||
def getIssue(owner: String, repository: String, issueId: String) =
|
||||
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
||||
if (issueId forall (_.isDigit))
|
||||
Query(Issues) filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
||||
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
||||
else None
|
||||
|
||||
def getComments(owner: String, repository: String, issueId: Int) =
|
||||
Query(IssueComments) filter (_.byIssue(owner, repository, issueId)) list
|
||||
def getComments(owner: String, repository: String, issueId: Int)(implicit s: Session) =
|
||||
IssueComments filter (_.byIssue(owner, repository, issueId)) list
|
||||
|
||||
def getComment(owner: String, repository: String, commentId: String) =
|
||||
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
|
||||
if (commentId forall (_.isDigit))
|
||||
Query(IssueComments) filter { t =>
|
||||
IssueComments filter { t =>
|
||||
t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
|
||||
} firstOption
|
||||
else None
|
||||
|
||||
def getIssueLabels(owner: String, repository: String, issueId: Int) =
|
||||
def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session) =
|
||||
IssueLabels
|
||||
.innerJoin(Labels).on { (t1, t2) =>
|
||||
t1.byLabel(t2.userName, t2.repositoryName, t2.labelId)
|
||||
@@ -36,8 +36,8 @@ trait IssuesService {
|
||||
.map ( _._2 )
|
||||
.list
|
||||
|
||||
def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
|
||||
Query(IssueLabels) filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
|
||||
def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption
|
||||
|
||||
/**
|
||||
* Returns the count of the search result against issues.
|
||||
@@ -49,8 +49,9 @@ trait IssuesService {
|
||||
* @return the count of the search result
|
||||
*/
|
||||
def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
repos: (String, String)*): Int =
|
||||
repos: (String, String)*)(implicit s: Session): Int =
|
||||
Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
|
||||
|
||||
/**
|
||||
* Returns the Map which contains issue count for each labels.
|
||||
*
|
||||
@@ -61,7 +62,7 @@ trait IssuesService {
|
||||
* @return the Map which contains issue count for each labels (key is label name, value is issue count)
|
||||
*/
|
||||
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String]): Map[String, Int] = {
|
||||
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
|
||||
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), filterUser, false)
|
||||
.innerJoin(IssueLabels).on { (t1, t2) =>
|
||||
@@ -74,7 +75,7 @@ trait IssuesService {
|
||||
t3.labelName
|
||||
}
|
||||
.map { case (labelName, t) =>
|
||||
labelName ~ t.length
|
||||
labelName -> t.length
|
||||
}
|
||||
.toMap
|
||||
}
|
||||
@@ -90,13 +91,13 @@ trait IssuesService {
|
||||
*/
|
||||
def countIssueGroupByRepository(
|
||||
condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
repos: (String, String)*): List[(String, String, Int)] = {
|
||||
repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = {
|
||||
searchIssueQuery(repos, condition.copy(repo = None), filterUser, onlyPullRequest)
|
||||
.groupBy { t =>
|
||||
t.userName ~ t.repositoryName
|
||||
t.userName -> t.repositoryName
|
||||
}
|
||||
.map { case (repo, t) =>
|
||||
repo ~ t.length
|
||||
(repo._1, repo._2, t.length)
|
||||
}
|
||||
.sortBy(_._3 desc)
|
||||
.list
|
||||
@@ -114,7 +115,8 @@ trait IssuesService {
|
||||
* @return the search result (list of tuples which contain issue, labels and comment count)
|
||||
*/
|
||||
def searchIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
|
||||
offset: Int, limit: Int, repos: (String, String)*): List[(Issue, List[Label], Int)] = {
|
||||
offset: Int, limit: Int, repos: (String, String)*)
|
||||
(implicit s: Session): List[(Issue, List[Label], Int)] = {
|
||||
|
||||
// get issues and comment count and labels
|
||||
searchIssueQuery(repos, condition, filterUser, onlyPullRequest)
|
||||
@@ -157,20 +159,20 @@ trait IssuesService {
|
||||
* Assembles query for conditional issue searching.
|
||||
*/
|
||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String], onlyPullRequest: Boolean) =
|
||||
Query(Issues) filter { t1 =>
|
||||
filterUser: Map[String, String], onlyPullRequest: Boolean)(implicit s: Session) =
|
||||
Issues filter { t1 =>
|
||||
condition.repo
|
||||
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
|
||||
.getOrElse (repos)
|
||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
||||
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
||||
(t1.closed is (condition.state == "closed").bind) &&
|
||||
(t1.milestoneId is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
||||
(t1.milestoneId isNull, condition.milestoneId == Some(None)) &&
|
||||
(t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
|
||||
(t1.openedUserName is filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
|
||||
(t1.openedUserName isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
|
||||
(t1.pullRequest is true.bind, onlyPullRequest) &&
|
||||
(t1.closed === (condition.state == "closed").bind) &&
|
||||
(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
||||
(t1.milestoneId.? isEmpty, condition.milestoneId == Some(None)) &&
|
||||
(t1.assignedUserName === filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
|
||||
(t1.openedUserName === filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
|
||||
(t1.openedUserName =!= filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
|
||||
(t1.pullRequest === true.bind, onlyPullRequest) &&
|
||||
(IssueLabels filter { t2 =>
|
||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
||||
(t2.labelId in
|
||||
@@ -182,7 +184,8 @@ trait IssuesService {
|
||||
}
|
||||
|
||||
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
|
||||
assignedUserName: Option[String], milestoneId: Option[Int], isPullRequest: Boolean = false) =
|
||||
assignedUserName: Option[String], milestoneId: Option[Int],
|
||||
isPullRequest: Boolean = false)(implicit s: Session) =
|
||||
// next id number
|
||||
sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int]
|
||||
.firstOption.filter { id =>
|
||||
@@ -207,55 +210,57 @@ trait IssuesService {
|
||||
.update (id) > 0
|
||||
} get
|
||||
|
||||
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
|
||||
IssueLabels insert (IssueLabel(owner, repository, issueId, labelId))
|
||||
def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels insert IssueLabel(owner, repository, issueId, labelId)
|
||||
|
||||
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int) =
|
||||
def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session) =
|
||||
IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete
|
||||
|
||||
def createComment(owner: String, repository: String, loginUser: String,
|
||||
issueId: Int, content: String, action: String) =
|
||||
IssueComments.autoInc insert (
|
||||
owner,
|
||||
repository,
|
||||
issueId,
|
||||
action,
|
||||
loginUser,
|
||||
content,
|
||||
currentDate,
|
||||
currentDate)
|
||||
issueId: Int, content: String, action: String)(implicit s: Session): Int =
|
||||
IssueComments.autoInc insert IssueComment(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
issueId = issueId,
|
||||
action = action,
|
||||
commentedUserName = loginUser,
|
||||
content = content,
|
||||
registeredDate = currentDate,
|
||||
updatedDate = currentDate)
|
||||
|
||||
def updateIssue(owner: String, repository: String, issueId: Int,
|
||||
title: String, content: Option[String]) =
|
||||
title: String, content: Option[String])(implicit s: Session) =
|
||||
Issues
|
||||
.filter (_.byPrimaryKey(owner, repository, issueId))
|
||||
.map { t =>
|
||||
t.title ~ t.content.? ~ t.updatedDate
|
||||
(t.title, t.content.?, t.updatedDate)
|
||||
}
|
||||
.update (title, content, currentDate)
|
||||
|
||||
def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String]) =
|
||||
def updateAssignedUserName(owner: String, repository: String, issueId: Int,
|
||||
assignedUserName: Option[String])(implicit s: Session) =
|
||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName)
|
||||
|
||||
def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int]) =
|
||||
def updateMilestoneId(owner: String, repository: String, issueId: Int,
|
||||
milestoneId: Option[Int])(implicit s: Session) =
|
||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
|
||||
|
||||
def updateComment(commentId: Int, content: String) =
|
||||
def updateComment(commentId: Int, content: String)(implicit s: Session) =
|
||||
IssueComments
|
||||
.filter (_.byPrimaryKey(commentId))
|
||||
.map { t =>
|
||||
t.content ~ t.updatedDate
|
||||
t.content -> t.updatedDate
|
||||
}
|
||||
.update (content, currentDate)
|
||||
|
||||
def deleteComment(commentId: Int) =
|
||||
def deleteComment(commentId: Int)(implicit s: Session) =
|
||||
IssueComments filter (_.byPrimaryKey(commentId)) delete
|
||||
|
||||
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean) =
|
||||
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session) =
|
||||
Issues
|
||||
.filter (_.byPrimaryKey(owner, repository, issueId))
|
||||
.map { t =>
|
||||
t.closed ~ t.updatedDate
|
||||
t.closed -> t.updatedDate
|
||||
}
|
||||
.update (closed, currentDate)
|
||||
|
||||
@@ -267,8 +272,9 @@ trait IssuesService {
|
||||
* @param query the keywords separated by whitespace.
|
||||
* @return issues with comment count and matched content of issue or comment
|
||||
*/
|
||||
def searchIssuesByKeyword(owner: String, repository: String, query: String): List[(Issue, Int, String)] = {
|
||||
import scala.slick.driver.H2Driver.likeEncode
|
||||
def searchIssuesByKeyword(owner: String, repository: String, query: String)
|
||||
(implicit s: Session): List[(Issue, Int, String)] = {
|
||||
import slick.driver.JdbcDriver.likeEncode
|
||||
val keywords = splitWords(query.toLowerCase)
|
||||
|
||||
// Search Issue
|
||||
@@ -304,7 +310,7 @@ trait IssuesService {
|
||||
}
|
||||
|
||||
issues.union(comments).sortBy { case (issue, commentId, _, _) =>
|
||||
issue.issueId ~ commentId
|
||||
issue.issueId -> commentId
|
||||
}.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) =>
|
||||
issue1.issueId == issue2.issueId
|
||||
}.map { _.head match {
|
||||
@@ -313,7 +319,7 @@ trait IssuesService {
|
||||
}.toList
|
||||
}
|
||||
|
||||
def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String) = {
|
||||
def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)(implicit s: Session) = {
|
||||
extractCloseId(message).foreach { issueId =>
|
||||
for(issue <- getIssue(owner, repository, issueId) if !issue.closed){
|
||||
createComment(owner, repository, userName, issue.issueId, "Close", "close")
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
package service
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
|
||||
import model._
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.Label
|
||||
|
||||
trait LabelsService {
|
||||
|
||||
def getLabels(owner: String, repository: String): List[Label] =
|
||||
Query(Labels).filter(_.byRepository(owner, repository)).sortBy(_.labelName asc).list
|
||||
def getLabels(owner: String, repository: String)(implicit s: Session): List[Label] =
|
||||
Labels.filter(_.byRepository(owner, repository)).sortBy(_.labelName asc).list
|
||||
|
||||
def getLabel(owner: String, repository: String, labelId: Int): Option[Label] =
|
||||
Query(Labels).filter(_.byPrimaryKey(owner, repository, labelId)).firstOption
|
||||
def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] =
|
||||
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption
|
||||
|
||||
def createLabel(owner: String, repository: String, labelName: String, color: String): Unit =
|
||||
Labels.ins insert (owner, repository, labelName, color)
|
||||
def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Unit =
|
||||
Labels insert Label(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
labelName = labelName,
|
||||
color = color
|
||||
)
|
||||
|
||||
def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String): Unit =
|
||||
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).map(t => t.labelName ~ t.color)
|
||||
def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String)
|
||||
(implicit s: Session): Unit =
|
||||
Labels.filter(_.byPrimaryKey(owner, repository, labelId))
|
||||
.map(t => t.labelName -> t.color)
|
||||
.update(labelName, color)
|
||||
|
||||
def deleteLabel(owner: String, repository: String, labelId: Int): Unit = {
|
||||
def deleteLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Unit = {
|
||||
IssueLabels.filter(_.byLabel(owner, repository, labelId)).delete
|
||||
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).delete
|
||||
}
|
||||
|
||||
@@ -1,39 +1,49 @@
|
||||
package service
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
|
||||
import model._
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.Milestone
|
||||
// TODO [Slick 2.0]NOT import directly?
|
||||
import model.Profile.dateColumnType
|
||||
|
||||
trait MilestonesService {
|
||||
|
||||
def createMilestone(owner: String, repository: String, title: String, description: Option[String],
|
||||
dueDate: Option[java.util.Date]): Unit =
|
||||
Milestones.ins insert (owner, repository, title, description, dueDate, None)
|
||||
dueDate: Option[java.util.Date])(implicit s: Session): Unit =
|
||||
Milestones insert Milestone(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
title = title,
|
||||
description = description,
|
||||
dueDate = dueDate,
|
||||
closedDate = None
|
||||
)
|
||||
|
||||
def updateMilestone(milestone: Milestone): Unit =
|
||||
def updateMilestone(milestone: Milestone)(implicit s: Session): Unit =
|
||||
Milestones
|
||||
.filter (t => t.byPrimaryKey(milestone.userName, milestone.repositoryName, milestone.milestoneId))
|
||||
.map (t => t.title ~ t.description.? ~ t.dueDate.? ~ t.closedDate.?)
|
||||
.map (t => (t.title, t.description.?, t.dueDate.?, t.closedDate.?))
|
||||
.update (milestone.title, milestone.description, milestone.dueDate, milestone.closedDate)
|
||||
|
||||
def openMilestone(milestone: Milestone): Unit = updateMilestone(milestone.copy(closedDate = None))
|
||||
def openMilestone(milestone: Milestone)(implicit s: Session): Unit =
|
||||
updateMilestone(milestone.copy(closedDate = None))
|
||||
|
||||
def closeMilestone(milestone: Milestone): Unit = updateMilestone(milestone.copy(closedDate = Some(currentDate)))
|
||||
def closeMilestone(milestone: Milestone)(implicit s: Session): Unit =
|
||||
updateMilestone(milestone.copy(closedDate = Some(currentDate)))
|
||||
|
||||
def deleteMilestone(owner: String, repository: String, milestoneId: Int): Unit = {
|
||||
def deleteMilestone(owner: String, repository: String, milestoneId: Int)(implicit s: Session): Unit = {
|
||||
Issues.filter(_.byMilestone(owner, repository, milestoneId)).map(_.milestoneId.?).update(None)
|
||||
Milestones.filter(_.byPrimaryKey(owner, repository, milestoneId)).delete
|
||||
}
|
||||
|
||||
def getMilestone(owner: String, repository: String, milestoneId: Int): Option[Milestone] =
|
||||
Query(Milestones).filter(_.byPrimaryKey(owner, repository, milestoneId)).firstOption
|
||||
def getMilestone(owner: String, repository: String, milestoneId: Int)(implicit s: Session): Option[Milestone] =
|
||||
Milestones.filter(_.byPrimaryKey(owner, repository, milestoneId)).firstOption
|
||||
|
||||
def getMilestonesWithIssueCount(owner: String, repository: String): List[(Milestone, Int, Int)] = {
|
||||
def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = {
|
||||
val counts = Issues
|
||||
.filter { t => (t.byRepository(owner, repository)) && (t.milestoneId isNotNull) }
|
||||
.groupBy { t => t.milestoneId ~ t.closed }
|
||||
.map { case (t1, t2) => (t1._1 ~ t1._2) -> t2.length }
|
||||
.filter { t => (t.byRepository(owner, repository)) && (t.milestoneId.? isDefined) }
|
||||
.groupBy { t => t.milestoneId -> t.closed }
|
||||
.map { case (t1, t2) => t1._1 -> t1._2 -> t2.length }
|
||||
.toMap
|
||||
|
||||
getMilestones(owner, repository).map { milestone =>
|
||||
@@ -41,6 +51,7 @@ trait MilestonesService {
|
||||
}
|
||||
}
|
||||
|
||||
def getMilestones(owner: String, repository: String): List[Milestone] =
|
||||
Query(Milestones).filter(_.byRepository(owner, repository)).sortBy(_.milestoneId asc).list
|
||||
def getMilestones(owner: String, repository: String)(implicit s: Session): List[Milestone] =
|
||||
Milestones.filter(_.byRepository(owner, repository)).sortBy(_.milestoneId asc).list
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
@@ -1,42 +1,45 @@
|
||||
package service
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
import model._
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.{PullRequest, Issue}
|
||||
|
||||
trait PullRequestService { self: IssuesService =>
|
||||
import PullRequestService._
|
||||
|
||||
def getPullRequest(owner: String, repository: String, issueId: Int): Option[(Issue, PullRequest)] =
|
||||
def getPullRequest(owner: String, repository: String, issueId: Int)
|
||||
(implicit s: Session): Option[(Issue, PullRequest)] =
|
||||
getIssue(owner, repository, issueId.toString).flatMap{ issue =>
|
||||
Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId)).firstOption.map{
|
||||
PullRequests.filter(_.byPrimaryKey(owner, repository, issueId)).firstOption.map{
|
||||
pullreq => (issue, pullreq)
|
||||
}
|
||||
}
|
||||
|
||||
def updateCommitId(owner: String, repository: String, issueId: Int, commitIdTo: String, commitIdFrom: String): Unit =
|
||||
Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId))
|
||||
.map(pr => pr.commitIdTo ~ pr.commitIdFrom)
|
||||
def updateCommitId(owner: String, repository: String, issueId: Int, commitIdTo: String, commitIdFrom: String)
|
||||
(implicit s: Session): Unit =
|
||||
PullRequests.filter(_.byPrimaryKey(owner, repository, issueId))
|
||||
.map(pr => pr.commitIdTo -> pr.commitIdFrom)
|
||||
.update((commitIdTo, commitIdFrom))
|
||||
|
||||
def getPullRequestCountGroupByUser(closed: Boolean, owner: String, repository: Option[String]): List[PullRequestCount] =
|
||||
Query(PullRequests)
|
||||
def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String])
|
||||
(implicit s: Session): List[PullRequestCount] =
|
||||
PullRequests
|
||||
.innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
.filter { case (t1, t2) =>
|
||||
(t2.closed is closed.bind) &&
|
||||
(t1.userName is owner.bind) &&
|
||||
(t1.repositoryName is repository.get.bind, repository.isDefined)
|
||||
(t2.closed === closed.bind) &&
|
||||
(t1.userName === owner.get.bind, owner.isDefined) &&
|
||||
(t1.repositoryName === repository.get.bind, repository.isDefined)
|
||||
}
|
||||
.groupBy { case (t1, t2) => t2.openedUserName }
|
||||
.map { case (userName, t) => userName ~ t.length }
|
||||
.map { case (userName, t) => userName -> t.length }
|
||||
.sortBy(_._2 desc)
|
||||
.list
|
||||
.map { x => PullRequestCount(x._1, x._2) }
|
||||
|
||||
def createPullRequest(originUserName: String, originRepositoryName: String, issueId: Int,
|
||||
originBranch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String,
|
||||
commitIdFrom: String, commitIdTo: String): Unit =
|
||||
PullRequests insert (PullRequest(
|
||||
commitIdFrom: String, commitIdTo: String)(implicit s: Session): Unit =
|
||||
PullRequests insert PullRequest(
|
||||
originUserName,
|
||||
originRepositoryName,
|
||||
issueId,
|
||||
@@ -45,16 +48,17 @@ trait PullRequestService { self: IssuesService =>
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo))
|
||||
commitIdTo)
|
||||
|
||||
def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Boolean): List[PullRequest] =
|
||||
Query(PullRequests)
|
||||
def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Boolean)
|
||||
(implicit s: Session): List[PullRequest] =
|
||||
PullRequests
|
||||
.innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
.filter { case (t1, t2) =>
|
||||
(t1.requestUserName is userName.bind) &&
|
||||
(t1.requestRepositoryName is repositoryName.bind) &&
|
||||
(t1.requestBranch is branch.bind) &&
|
||||
(t2.closed is closed.bind)
|
||||
(t1.requestUserName === userName.bind) &&
|
||||
(t1.requestRepositoryName === repositoryName.bind) &&
|
||||
(t1.requestBranch === branch.bind) &&
|
||||
(t2.closed === closed.bind)
|
||||
}
|
||||
.map { case (t1, t2) => t1 }
|
||||
.list
|
||||
|
||||
@@ -3,21 +3,20 @@ package service
|
||||
import util.{FileUtil, StringUtil, JGitUtil}
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import model.Issue
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import org.eclipse.jgit.lib.FileMode
|
||||
import org.eclipse.jgit.api.Git
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
|
||||
trait
|
||||
RepositorySearchService { self: IssuesService =>
|
||||
trait RepositorySearchService { self: IssuesService =>
|
||||
import RepositorySearchService._
|
||||
|
||||
def countIssues(owner: String, repository: String, query: String): Int =
|
||||
def countIssues(owner: String, repository: String, query: String)(implicit session: Session): Int =
|
||||
searchIssuesByKeyword(owner, repository, query).length
|
||||
|
||||
def searchIssues(owner: String, repository: String, query: String): List[IssueSearchResult] =
|
||||
def searchIssues(owner: String, repository: String, query: String)(implicit session: Session): List[IssueSearchResult] =
|
||||
searchIssuesByKeyword(owner, repository, query).map { case (issue, commentCount, content) =>
|
||||
IssueSearchResult(
|
||||
issue.issueId,
|
||||
@@ -39,7 +38,7 @@ RepositorySearchService { self: IssuesService =>
|
||||
Nil
|
||||
} else {
|
||||
val files = searchRepositoryFiles(git, query)
|
||||
val commits = JGitUtil.getLatestCommitFromPaths(git, files.toList.map(_._1), "HEAD")
|
||||
val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD")
|
||||
files.map { case (path, text) =>
|
||||
val (highlightText, lineNumber) = getHighlightText(text, query)
|
||||
FileSearchResult(
|
||||
@@ -60,7 +59,7 @@ RepositorySearchService { self: IssuesService =>
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
|
||||
val keywords = StringUtil.splitWords(query.toLowerCase)
|
||||
val list = new ListBuffer[(String, String)]
|
||||
val list = new scala.collection.mutable.ListBuffer[(String, String)]
|
||||
|
||||
while (treeWalk.next()) {
|
||||
val mode = treeWalk.getFileMode(0)
|
||||
@@ -108,7 +107,7 @@ object RepositorySearchService {
|
||||
|
||||
case class SearchResult(
|
||||
files : List[(String, String)],
|
||||
issues: List[(Issue, Int, String)])
|
||||
issues: List[(model.Issue, Int, String)])
|
||||
|
||||
case class IssueSearchResult(
|
||||
issueId: Int,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package service
|
||||
|
||||
import model._
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.{Repository, Account, Collaborator}
|
||||
import util.JGitUtil
|
||||
|
||||
trait RepositoryService { self: AccountService =>
|
||||
@@ -20,7 +20,8 @@ trait RepositoryService { self: AccountService =>
|
||||
*/
|
||||
def createRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean,
|
||||
originRepositoryName: Option[String] = None, originUserName: Option[String] = None,
|
||||
parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None): Unit = {
|
||||
parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None)
|
||||
(implicit s: Session): Unit = {
|
||||
Repositories insert
|
||||
Repository(
|
||||
userName = userName,
|
||||
@@ -39,40 +40,50 @@ trait RepositoryService { self: AccountService =>
|
||||
IssueId insert (userName, repositoryName, 0)
|
||||
}
|
||||
|
||||
def renameRepository(oldUserName: String, oldRepositoryName: String, newUserName: String, newRepositoryName: String): Unit = {
|
||||
def renameRepository(oldUserName: String, oldRepositoryName: String, newUserName: String, newRepositoryName: String)
|
||||
(implicit s: Session): Unit = {
|
||||
getAccountByUserName(newUserName).foreach { account =>
|
||||
(Query(Repositories) filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository =>
|
||||
(Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository =>
|
||||
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
|
||||
|
||||
val webHooks = Query(WebHooks ).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val milestones = Query(Milestones ).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueId = Query(IssueId ).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issues = Query(Issues ).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val pullRequests = Query(PullRequests ).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val labels = Query(Labels ).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueComments = Query(IssueComments).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueLabels = Query(IssueLabels ).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val activities = Query(Activities ).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val collaborators = Query(Collaborators).filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val activities = Activities .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
|
||||
Repositories.filter { t =>
|
||||
(t.originUserName is oldUserName.bind) && (t.originRepositoryName is oldRepositoryName.bind)
|
||||
}.map { t => t.originUserName ~ t.originRepositoryName }.update(newUserName, newRepositoryName)
|
||||
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
|
||||
}.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
|
||||
|
||||
Repositories.filter { t =>
|
||||
(t.parentUserName is oldUserName.bind) && (t.parentRepositoryName is oldRepositoryName.bind)
|
||||
}.map { t => t.originUserName ~ t.originRepositoryName }.update(newUserName, newRepositoryName)
|
||||
(t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind)
|
||||
}.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
|
||||
|
||||
PullRequests.filter { t =>
|
||||
t.requestRepositoryName is oldRepositoryName.bind
|
||||
}.map { t => t.requestUserName ~ t.requestRepositoryName }.update(newUserName, newRepositoryName)
|
||||
t.requestRepositoryName === oldRepositoryName.bind
|
||||
}.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
|
||||
|
||||
deleteRepository(oldUserName, oldRepositoryName)
|
||||
|
||||
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||
Issues .insertAll(issues .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
|
||||
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||
Issues.insertAll(issues.map { x => x.copy(
|
||||
userName = newUserName,
|
||||
repositoryName = newRepositoryName,
|
||||
milestoneId = x.milestoneId.map { id =>
|
||||
newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId
|
||||
}
|
||||
)} :_*)
|
||||
|
||||
PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
@@ -88,10 +99,10 @@ trait RepositoryService { self: AccountService =>
|
||||
val updateActivities = Activities.filter { t =>
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%")
|
||||
}.map { t => t.activityId ~ t.message }.list
|
||||
}.map { t => t.activityId -> t.message }.list
|
||||
|
||||
updateActivities.foreach { case (activityId, message) =>
|
||||
Activities.filter(_.activityId is activityId.bind).map(_.message).update(
|
||||
Activities.filter(_.activityId === activityId.bind).map(_.message).update(
|
||||
message
|
||||
.replace(s"[repo:${oldUserName}/${oldRepositoryName}]" ,s"[repo:${newUserName}/${newRepositoryName}]")
|
||||
.replace(s"[branch:${oldUserName}/${oldRepositoryName}#" ,s"[branch:${newUserName}/${newRepositoryName}#")
|
||||
@@ -104,7 +115,7 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
}
|
||||
|
||||
def deleteRepository(userName: String, repositoryName: String): Unit = {
|
||||
def deleteRepository(userName: String, repositoryName: String)(implicit s: Session): Unit = {
|
||||
Activities .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Collaborators .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueLabels .filter(_.byRepository(userName, repositoryName)).delete
|
||||
@@ -124,8 +135,8 @@ trait RepositoryService { self: AccountService =>
|
||||
* @param userName the user name of repository owner
|
||||
* @return the list of repository names
|
||||
*/
|
||||
def getRepositoryNamesOfUser(userName: String): List[String] =
|
||||
Query(Repositories) filter(_.userName is userName.bind) map (_.repositoryName) list
|
||||
def getRepositoryNamesOfUser(userName: String)(implicit s: Session): List[String] =
|
||||
Repositories filter(_.userName === userName.bind) map (_.repositoryName) list
|
||||
|
||||
/**
|
||||
* Returns the specified repository information.
|
||||
@@ -135,11 +146,11 @@ trait RepositoryService { self: AccountService =>
|
||||
* @param baseUrl the base url of this application
|
||||
* @return the repository information
|
||||
*/
|
||||
def getRepository(userName: String, repositoryName: String, baseUrl: String): Option[RepositoryInfo] = {
|
||||
(Query(Repositories) filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
|
||||
def getRepository(userName: String, repositoryName: String, baseUrl: String)(implicit s: Session): Option[RepositoryInfo] = {
|
||||
(Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
|
||||
// for getting issue count and pull request count
|
||||
val issues = Query(Issues).filter { t =>
|
||||
t.byRepository(repository.userName, repository.repositoryName) && (t.closed is false.bind)
|
||||
val issues = Issues.filter { t =>
|
||||
t.byRepository(repository.userName, repository.repositoryName) && (t.closed === false.bind)
|
||||
}.map(_.pullRequest).list
|
||||
|
||||
new RepositoryInfo(
|
||||
@@ -155,13 +166,24 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
}
|
||||
|
||||
def getUserRepositories(userName: String, baseUrl: String): List[RepositoryInfo] = {
|
||||
Query(Repositories).filter { t1 =>
|
||||
(t1.userName is userName.bind) ||
|
||||
(Query(Collaborators).filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName is userName.bind)} exists)
|
||||
def getAllRepositories()(implicit s: Session): List[(String, String)] = {
|
||||
Repositories.sortBy(_.lastActivityDate desc).map{ t =>
|
||||
(t.userName, t.repositoryName)
|
||||
}.list
|
||||
}
|
||||
|
||||
def getUserRepositories(userName: String, baseUrl: String, withoutPhysicalInfo: Boolean = false)
|
||||
(implicit s: Session): List[RepositoryInfo] = {
|
||||
Repositories.filter { t1 =>
|
||||
(t1.userName === userName.bind) ||
|
||||
(Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists)
|
||||
}.sortBy(_.lastActivityDate desc).list.map{ repository =>
|
||||
new RepositoryInfo(
|
||||
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
|
||||
if(withoutPhysicalInfo){
|
||||
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName, baseUrl)
|
||||
} else {
|
||||
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl)
|
||||
},
|
||||
repository,
|
||||
getForkedCount(
|
||||
repository.originUserName.getOrElse(repository.userName),
|
||||
@@ -178,24 +200,32 @@ trait RepositoryService { self: AccountService =>
|
||||
* @param loginAccount the logged in account
|
||||
* @param baseUrl the base url of this application
|
||||
* @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user)
|
||||
* @param withoutPhysicalInfo if true then the result does not include physical repository information such as commit count,
|
||||
* branches and tags
|
||||
* @return the repository information which is sorted in descending order of lastActivityDate.
|
||||
*/
|
||||
def getVisibleRepositories(loginAccount: Option[Account], baseUrl: String, repositoryUserName: Option[String] = None): List[RepositoryInfo] = {
|
||||
def getVisibleRepositories(loginAccount: Option[Account], baseUrl: String, repositoryUserName: Option[String] = None,
|
||||
withoutPhysicalInfo: Boolean = false)
|
||||
(implicit s: Session): List[RepositoryInfo] = {
|
||||
(loginAccount match {
|
||||
// for Administrators
|
||||
case Some(x) if(x.isAdmin) => Query(Repositories)
|
||||
case Some(x) if(x.isAdmin) => Repositories
|
||||
// for Normal Users
|
||||
case Some(x) if(!x.isAdmin) =>
|
||||
Query(Repositories) filter { t => (t.isPrivate is false.bind) || (t.userName is x.userName) ||
|
||||
(Query(Collaborators).filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName is x.userName.bind)} exists)
|
||||
Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) ||
|
||||
(Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists)
|
||||
}
|
||||
// for Guests
|
||||
case None => Query(Repositories) filter(_.isPrivate is false.bind)
|
||||
case None => Repositories filter(_.isPrivate === false.bind)
|
||||
}).filter { t =>
|
||||
repositoryUserName.map { userName => t.userName is userName.bind } getOrElse ConstColumn.TRUE
|
||||
repositoryUserName.map { userName => t.userName === userName.bind } getOrElse LiteralColumn(true)
|
||||
}.sortBy(_.lastActivityDate desc).list.map{ repository =>
|
||||
new RepositoryInfo(
|
||||
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
|
||||
if(withoutPhysicalInfo){
|
||||
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName, baseUrl)
|
||||
} else {
|
||||
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl)
|
||||
},
|
||||
repository,
|
||||
getForkedCount(
|
||||
repository.originUserName.getOrElse(repository.userName),
|
||||
@@ -205,7 +235,7 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
}
|
||||
|
||||
private def getRepositoryManagers(userName: String): Seq[String] =
|
||||
private def getRepositoryManagers(userName: String)(implicit s: Session): Seq[String] =
|
||||
if(getAccountByUserName(userName).exists(_.isGroupAccount)){
|
||||
getGroupMembers(userName).collect { case x if(x.isManager) => x.userName }
|
||||
} else {
|
||||
@@ -215,16 +245,16 @@ trait RepositoryService { self: AccountService =>
|
||||
/**
|
||||
* Updates the last activity date of the repository.
|
||||
*/
|
||||
def updateLastActivityDate(userName: String, repositoryName: String): Unit =
|
||||
def updateLastActivityDate(userName: String, repositoryName: String)(implicit s: Session): Unit =
|
||||
Repositories.filter(_.byRepository(userName, repositoryName)).map(_.lastActivityDate).update(currentDate)
|
||||
|
||||
/**
|
||||
* Save repository options.
|
||||
*/
|
||||
def saveRepositoryOptions(userName: String, repositoryName: String,
|
||||
description: Option[String], defaultBranch: String, isPrivate: Boolean): Unit =
|
||||
description: Option[String], defaultBranch: String, isPrivate: Boolean)(implicit s: Session): Unit =
|
||||
Repositories.filter(_.byRepository(userName, repositoryName))
|
||||
.map { r => r.description.? ~ r.defaultBranch ~ r.isPrivate ~ r.updatedDate }
|
||||
.map { r => (r.description.?, r.defaultBranch, r.isPrivate, r.updatedDate) }
|
||||
.update (description, defaultBranch, isPrivate, currentDate)
|
||||
|
||||
/**
|
||||
@@ -234,8 +264,8 @@ trait RepositoryService { self: AccountService =>
|
||||
* @param repositoryName the repository name
|
||||
* @param collaboratorName the collaborator name
|
||||
*/
|
||||
def addCollaborator(userName: String, repositoryName: String, collaboratorName: String): Unit =
|
||||
Collaborators insert(Collaborator(userName, repositoryName, collaboratorName))
|
||||
def addCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit =
|
||||
Collaborators insert Collaborator(userName, repositoryName, collaboratorName)
|
||||
|
||||
/**
|
||||
* Remove collaborator from the repository.
|
||||
@@ -244,7 +274,7 @@ trait RepositoryService { self: AccountService =>
|
||||
* @param repositoryName the repository name
|
||||
* @param collaboratorName the collaborator name
|
||||
*/
|
||||
def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String): Unit =
|
||||
def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit =
|
||||
Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete
|
||||
|
||||
/**
|
||||
@@ -253,7 +283,7 @@ trait RepositoryService { self: AccountService =>
|
||||
* @param userName the user name of the repository owner
|
||||
* @param repositoryName the repository name
|
||||
*/
|
||||
def removeCollaborators(userName: String, repositoryName: String): Unit =
|
||||
def removeCollaborators(userName: String, repositoryName: String)(implicit s: Session): Unit =
|
||||
Collaborators.filter(_.byRepository(userName, repositoryName)).delete
|
||||
|
||||
/**
|
||||
@@ -263,10 +293,10 @@ trait RepositoryService { self: AccountService =>
|
||||
* @param repositoryName the repository name
|
||||
* @return the list of collaborators name
|
||||
*/
|
||||
def getCollaborators(userName: String, repositoryName: String): List[String] =
|
||||
Query(Collaborators).filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list
|
||||
def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[String] =
|
||||
Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list
|
||||
|
||||
def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account]): Boolean = {
|
||||
def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(a) if(a.isAdmin) => true
|
||||
case Some(a) if(a.userName == owner) => true
|
||||
@@ -275,17 +305,17 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
}
|
||||
|
||||
private def getForkedCount(userName: String, repositoryName: String): Int =
|
||||
private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
|
||||
Query(Repositories.filter { t =>
|
||||
(t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind)
|
||||
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
|
||||
}.length).first
|
||||
|
||||
|
||||
def getForkedRepositories(userName: String, repositoryName: String): List[(String, String)] =
|
||||
Query(Repositories).filter { t =>
|
||||
(t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind)
|
||||
def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[(String, String)] =
|
||||
Repositories.filter { t =>
|
||||
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
|
||||
}
|
||||
.sortBy(_.userName asc).map(t => t.userName ~ t.repositoryName).list
|
||||
.sortBy(_.userName asc).map(t => t.userName -> t.repositoryName).list
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import model._
|
||||
import model.{Account, Issue, Session}
|
||||
import util.Implicits.request2Session
|
||||
|
||||
/**
|
||||
* This service is used for a view helper mainly.
|
||||
@@ -10,22 +11,27 @@ import model._
|
||||
*/
|
||||
trait RequestCache extends SystemSettingsService with AccountService with IssuesService {
|
||||
|
||||
def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: app.Context): Option[Issue] = {
|
||||
private implicit def context2Session(implicit context: app.Context): Session =
|
||||
request2Session(context.request)
|
||||
|
||||
def getIssue(userName: String, repositoryName: String, issueId: String)
|
||||
(implicit context: app.Context): Option[Issue] = {
|
||||
context.cache(s"issue.${userName}/${repositoryName}#${issueId}"){
|
||||
super.getIssue(userName, repositoryName, issueId)
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByUserName(userName: String)(implicit context: app.Context): Option[Account] = {
|
||||
def getAccountByUserName(userName: String)
|
||||
(implicit context: app.Context): Option[Account] = {
|
||||
context.cache(s"account.${userName}"){
|
||||
super.getAccountByUserName(userName)
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByMailAddress(mailAddress: String)(implicit context: app.Context): Option[Account] = {
|
||||
def getAccountByMailAddress(mailAddress: String)
|
||||
(implicit context: app.Context): Option[Account] = {
|
||||
context.cache(s"account.${mailAddress}"){
|
||||
super.getAccountByMailAddress(mailAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package service
|
||||
|
||||
import model._
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.SshKey
|
||||
|
||||
trait SshKeyService {
|
||||
|
||||
def addPublicKey(userName: String, title: String, publicKey: String): Unit =
|
||||
SshKeys.ins insert (userName, title, publicKey)
|
||||
def addPublicKey(userName: String, title: String, publicKey: String)(implicit s: Session): Unit =
|
||||
SshKeys insert SshKey(userName = userName, title = title, publicKey = publicKey)
|
||||
|
||||
def getPublicKeys(userName: String): List[SshKey] =
|
||||
Query(SshKeys).filter(_.userName is userName.bind).sortBy(_.sshKeyId).list
|
||||
def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
|
||||
SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list
|
||||
|
||||
def deletePublicKey(userName: String, sshKeyId: Int): Unit =
|
||||
def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
|
||||
SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -37,13 +37,16 @@ trait SystemSettingsService {
|
||||
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
||||
props.setProperty(LdapBaseDN, ldap.baseDN)
|
||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
|
||||
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
|
||||
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
|
||||
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x.toString))
|
||||
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
|
||||
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
||||
}
|
||||
}
|
||||
props.store(new java.io.FileOutputStream(GitBucketConf), null)
|
||||
using(new java.io.FileOutputStream(GitBucketConf)){ out =>
|
||||
props.store(out, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +54,9 @@ trait SystemSettingsService {
|
||||
def loadSystemSettings(): SystemSettings = {
|
||||
defining(new java.util.Properties()){ props =>
|
||||
if(GitBucketConf.exists){
|
||||
props.load(new java.io.FileInputStream(GitBucketConf))
|
||||
using(new java.io.FileInputStream(GitBucketConf)){ in =>
|
||||
props.load(in)
|
||||
}
|
||||
}
|
||||
SystemSettings(
|
||||
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||
@@ -81,8 +86,9 @@ trait SystemSettingsService {
|
||||
getOptionValue(props, LdapBindPassword, None),
|
||||
getValue(props, LdapBaseDN, ""),
|
||||
getValue(props, LdapUserNameAttribute, ""),
|
||||
getOptionValue(props, LdapAdditionalFilterCondition, None),
|
||||
getOptionValue(props, LdapFullNameAttribute, None),
|
||||
getValue(props, LdapMailAddressAttribute, ""),
|
||||
getOptionValue(props, LdapMailAddressAttribute, None),
|
||||
getOptionValue[Boolean](props, LdapTls, None),
|
||||
getOptionValue(props, LdapKeystore, None)))
|
||||
} else {
|
||||
@@ -111,7 +117,7 @@ object SystemSettingsService {
|
||||
defining(request.getRequestURL.toString){ url =>
|
||||
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
|
||||
}
|
||||
}.replaceFirst("/$", "")
|
||||
}.stripSuffix("/")
|
||||
}
|
||||
|
||||
case class Ldap(
|
||||
@@ -121,8 +127,9 @@ object SystemSettingsService {
|
||||
bindPassword: Option[String],
|
||||
baseDN: String,
|
||||
userNameAttribute: String,
|
||||
additionalFilterCondition: Option[String],
|
||||
fullNameAttribute: Option[String],
|
||||
mailAttribute: String,
|
||||
mailAttribute: Option[String],
|
||||
tls: Option[Boolean],
|
||||
keystore: Option[String])
|
||||
|
||||
@@ -159,6 +166,7 @@ object SystemSettingsService {
|
||||
private val LdapBindPassword = "ldap.bind_password"
|
||||
private val LdapBaseDN = "ldap.baseDN"
|
||||
private val LdapUserNameAttribute = "ldap.username_attribute"
|
||||
private val LdapAdditionalFilterCondition = "ldap.additional_filter_condition"
|
||||
private val LdapFullNameAttribute = "ldap.fullname_attribute"
|
||||
private val LdapMailAddressAttribute = "ldap.mail_attribute"
|
||||
private val LdapTls = "ldap.tls"
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package service
|
||||
|
||||
import scala.slick.driver.H2Driver.simple._
|
||||
import Database.threadLocalSession
|
||||
|
||||
import model._
|
||||
import model.Profile._
|
||||
import profile.simple._
|
||||
import model.{WebHook, Account}
|
||||
import org.slf4j.LoggerFactory
|
||||
import service.RepositoryService.RepositoryInfo
|
||||
import util.JGitUtil
|
||||
@@ -12,7 +11,6 @@ import util.JGitUtil.CommitInfo
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.apache.http.message.BasicNameValuePair
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity
|
||||
import org.apache.http.protocol.HTTP
|
||||
import org.apache.http.NameValuePair
|
||||
|
||||
trait WebHookService {
|
||||
@@ -20,14 +18,14 @@ trait WebHookService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[WebHookService])
|
||||
|
||||
def getWebHookURLs(owner: String, repository: String): List[WebHook] =
|
||||
Query(WebHooks).filter(_.byRepository(owner, repository)).sortBy(_.url).list
|
||||
def getWebHookURLs(owner: String, repository: String)(implicit s: Session): List[WebHook] =
|
||||
WebHooks.filter(_.byRepository(owner, repository)).sortBy(_.url).list
|
||||
|
||||
def addWebHookURL(owner: String, repository: String, url :String): Unit =
|
||||
WebHooks.insert(WebHook(owner, repository, url))
|
||||
def addWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit =
|
||||
WebHooks insert WebHook(owner, repository, url)
|
||||
|
||||
def deleteWebHookURL(owner: String, repository: String, url :String): Unit =
|
||||
Query(WebHooks).filter(_.byPrimaryKey(owner, repository, url)).delete
|
||||
def deleteWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit =
|
||||
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete
|
||||
|
||||
def callWebHook(owner: String, repository: String, webHookURLs: List[WebHook], payload: WebHookPayload): Unit = {
|
||||
import org.json4s._
|
||||
@@ -46,7 +44,7 @@ trait WebHookService {
|
||||
val httpClient = HttpClientBuilder.create.build
|
||||
|
||||
webHookURLs.foreach { webHookUrl =>
|
||||
val f = future {
|
||||
val f = Future {
|
||||
logger.debug(s"start web hook invocation for ${webHookUrl}")
|
||||
val httpPost = new HttpPost(webHookUrl.url)
|
||||
|
||||
@@ -87,23 +85,23 @@ object WebHookService {
|
||||
refName,
|
||||
commits.map { commit =>
|
||||
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||
val commitUrl = repositoryInfo.httpUrl.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/commit/" + commit.id
|
||||
val commitUrl = repositoryInfo.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/commit/" + commit.id
|
||||
|
||||
WebHookCommit(
|
||||
id = commit.id,
|
||||
message = commit.fullMessage,
|
||||
timestamp = commit.time.toString,
|
||||
timestamp = commit.commitTime.toString,
|
||||
url = commitUrl,
|
||||
added = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.ADD) => x.newPath },
|
||||
removed = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.DELETE) => x.oldPath },
|
||||
modified = diffs._1.collect { case x if(x.changeType != DiffEntry.ChangeType.ADD &&
|
||||
x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath },
|
||||
author = WebHookUser(
|
||||
name = commit.committer,
|
||||
email = commit.mailAddress
|
||||
name = commit.committerName,
|
||||
email = commit.committerEmailAddress
|
||||
)
|
||||
)
|
||||
}.toList,
|
||||
},
|
||||
WebHookRepository(
|
||||
name = repositoryInfo.name,
|
||||
url = repositoryInfo.httpUrl,
|
||||
|
||||
@@ -2,22 +2,18 @@ package service
|
||||
|
||||
import java.util.Date
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.apache.commons.io.FileUtils
|
||||
import util._
|
||||
import _root_.util.ControlUtil._
|
||||
import org.eclipse.jgit.treewalk.{TreeWalk, CanonicalTreeParser}
|
||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry}
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.eclipse.jgit.dircache.DirCache
|
||||
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter}
|
||||
import java.io.ByteArrayInputStream
|
||||
import org.eclipse.jgit.patch._
|
||||
import org.eclipse.jgit.api.errors.PatchFormatException
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.Some
|
||||
import service.RepositoryService.RepositoryInfo
|
||||
|
||||
|
||||
object WikiService {
|
||||
|
||||
/**
|
||||
@@ -68,7 +64,7 @@ trait WikiService {
|
||||
if(!JGitUtil.isEmpty(git)){
|
||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
||||
WikiPageInfo(file.name, StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
|
||||
file.committer, file.time, file.commitId)
|
||||
file.author, file.time, file.commitId)
|
||||
}
|
||||
} else None
|
||||
}
|
||||
@@ -97,7 +93,7 @@ trait WikiService {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||
JGitUtil.getFileList(git, "master", ".")
|
||||
.filter(_.name.endsWith(".md"))
|
||||
.map(_.name.replaceFirst("\\.md$", ""))
|
||||
.map(_.name.stripSuffix(".md"))
|
||||
.sortBy(x => x)
|
||||
}
|
||||
}
|
||||
@@ -143,7 +139,7 @@ trait WikiService {
|
||||
val revertInfo = (p.getFiles.asScala.map { fh =>
|
||||
fh.getChangeType match {
|
||||
case DiffEntry.ChangeType.MODIFY => {
|
||||
val source = getWikiPage(owner, repository, fh.getNewPath.replaceFirst("\\.md$", "")).map(_.content).getOrElse("")
|
||||
val source = getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("")
|
||||
val applied = PatchUtil.apply(source, patch, fh)
|
||||
if(applied != null){
|
||||
Seq(RevertInfo("ADD", fh.getNewPath, applied))
|
||||
@@ -186,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}"
|
||||
@@ -233,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}"
|
||||
@@ -273,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import util.Directory
|
||||
import plugin.PluginUpdateJob
|
||||
|
||||
object AutoUpdate {
|
||||
|
||||
@@ -51,6 +52,29 @@ 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){
|
||||
override def update(conn: Connection): Unit = {
|
||||
import eu.medsea.mimeutil.{MimeUtil2, MimeType}
|
||||
@@ -143,8 +167,14 @@ object AutoUpdate {
|
||||
* 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")
|
||||
@@ -152,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){
|
||||
@@ -165,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}")
|
||||
}
|
||||
}
|
||||
@@ -176,12 +207,31 @@ class AutoUpdateListener extends ServletContextListener {
|
||||
conn.rollback()
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.debug("End schema update")
|
||||
}
|
||||
|
||||
getDatabase(context).withSession { implicit session =>
|
||||
logger.debug("Starting plugin system...")
|
||||
try {
|
||||
plugin.PluginSystem.init()
|
||||
|
||||
scheduler.start()
|
||||
PluginUpdateJob.schedule(scheduler)
|
||||
logger.debug("PluginUpdateJob is started.")
|
||||
|
||||
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 = {
|
||||
// Nothing to do.
|
||||
scheduler.shutdown()
|
||||
}
|
||||
|
||||
private def getConnection(servletContext: ServletContext): Connection =
|
||||
@@ -190,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,7 +3,7 @@ package servlet
|
||||
import javax.servlet._
|
||||
import javax.servlet.http._
|
||||
import service.{SystemSettingsService, AccountService, RepositoryService}
|
||||
import model.Account
|
||||
import model._
|
||||
import org.slf4j.LoggerFactory
|
||||
import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
@@ -21,7 +21,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
||||
def destroy(): Unit = {}
|
||||
|
||||
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
||||
val request = req.asInstanceOf[HttpServletRequest]
|
||||
implicit val request = req.asInstanceOf[HttpServletRequest]
|
||||
val response = res.asInstanceOf[HttpServletResponse]
|
||||
|
||||
val wrappedResponse = new HttpServletResponseWrapper(response){
|
||||
@@ -65,7 +65,8 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
||||
}
|
||||
}
|
||||
|
||||
private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo): Option[Account] =
|
||||
private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo)
|
||||
(implicit session: Session): Option[Account] =
|
||||
authenticate(loadSystemSettings(), username, password) match {
|
||||
case x @ Some(account) if(hasWritePermission(repository.owner, repository.name, x)) => x
|
||||
case _ => None
|
||||
|
||||
@@ -17,6 +17,7 @@ import WebHookService._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import util.JGitUtil.CommitInfo
|
||||
import service.IssuesService.IssueSearchCondition
|
||||
import model.Session
|
||||
|
||||
/**
|
||||
* Provides Git repository via HTTP.
|
||||
@@ -76,15 +77,17 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
|
||||
defining(request.paths){ paths =>
|
||||
val owner = paths(1)
|
||||
val repository = paths(2).replaceFirst("\\.git$", "")
|
||||
val repository = paths(2).stripSuffix(".git")
|
||||
|
||||
logger.debug("repository:" + owner + "/" + repository)
|
||||
|
||||
if(!repository.endsWith(".wiki")){
|
||||
val hook = new CommitLogHook(owner, repository, pusher, baseUrl(request))
|
||||
defining(request) { implicit r =>
|
||||
val hook = new CommitLogHook(owner, repository, pusher, baseUrl)
|
||||
receivePack.setPreReceiveHook(hook)
|
||||
receivePack.setPostReceiveHook(hook)
|
||||
}
|
||||
}
|
||||
receivePack
|
||||
}
|
||||
}
|
||||
@@ -92,7 +95,8 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String) extends PostReceiveHook with PreReceiveHook
|
||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session)
|
||||
extends PostReceiveHook with PreReceiveHook
|
||||
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
@@ -114,6 +118,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||
try {
|
||||
using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
val pushedIds = scala.collection.mutable.Set[String]()
|
||||
commands.asScala.foreach { command =>
|
||||
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
||||
val refName = command.getRefName.split("/")
|
||||
@@ -133,10 +138,16 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
countIssue(IssueSearchCondition(state = "closed"), Map.empty, false, owner -> repository)
|
||||
|
||||
// Extract new commit and apply issue comment
|
||||
val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch
|
||||
val newCommits = commits.flatMap { commit =>
|
||||
if (!existIds.contains(commit.id)) {
|
||||
if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) {
|
||||
if (issueCount > 0) {
|
||||
pushedIds.add(commit.id)
|
||||
createIssueComment(commit)
|
||||
// close issues
|
||||
if(refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE){
|
||||
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository)
|
||||
}
|
||||
}
|
||||
Some(commit)
|
||||
} else None
|
||||
@@ -168,17 +179,6 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
}
|
||||
|
||||
// close issues
|
||||
if(issueCount > 0) {
|
||||
val defaultBranch = getRepository(owner, repository, baseUrl).get.repository.defaultBranch
|
||||
if (refName(1) == "heads" && branchName == defaultBranch && command.getType == ReceiveCommand.Type.UPDATE) {
|
||||
git.log.addRange(command.getOldId, command.getNewId).call.asScala.foreach {
|
||||
commit =>
|
||||
closeIssuesFromMessage(commit.getFullMessage, pusher, owner, repository)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// call web hook
|
||||
getWebHookURLs(owner, repository) match {
|
||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
||||
@@ -205,7 +205,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
private def createIssueComment(commit: CommitInfo) = {
|
||||
StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
|
||||
if(getIssue(owner, repository, issueId).isDefined){
|
||||
getAccountByMailAddress(commit.mailAddress).foreach { account =>
|
||||
getAccountByMailAddress(commit.committerEmailAddress).foreach { account =>
|
||||
createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
|
||||
}
|
||||
}
|
||||
|
||||
192
src/main/scala/servlet/PluginActionInvokeFilter.scala
Normal file
192
src/main/scala/servlet/PluginActionInvokeFilter.scala
Normal file
@@ -0,0 +1,192 @@
|
||||
package servlet
|
||||
|
||||
import javax.servlet._
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import play.twirl.api.Html
|
||||
import service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
import model.{Account, Session}
|
||||
import util.{JGitUtil, Keys}
|
||||
import plugin.{RawData, Fragment, PluginConnectionHolder, Redirect}
|
||||
import service.RepositoryService.RepositoryInfo
|
||||
import plugin.Security._
|
||||
|
||||
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 = request.getRequestURI.substring(request.getServletContext.getContextPath.length)
|
||||
if(!processGlobalAction(path, request, response) && !processRepositoryAction(path, request, response)){
|
||||
chain.doFilter(req, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
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()
|
||||
}
|
||||
processActionResult(result, request, response, context)
|
||||
} else {
|
||||
// TODO NotFound or Error?
|
||||
}
|
||||
true
|
||||
} getOrElse false
|
||||
}
|
||||
|
||||
private def processRepositoryAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
|
||||
(implicit session: Session): Boolean = {
|
||||
val elements = path.split("/")
|
||||
if(elements.length > 3){
|
||||
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(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()
|
||||
}
|
||||
processActionResult(result, request, response, context)
|
||||
} else {
|
||||
// TODO NotFound or Error?
|
||||
}
|
||||
true
|
||||
}
|
||||
} getOrElse false
|
||||
} else false
|
||||
}
|
||||
|
||||
private def processActionResult(result: Any, request: HttpServletRequest, response: HttpServletResponse,
|
||||
context: app.Context): Unit = {
|
||||
result match {
|
||||
case null|None => renderError(request, response, context, 404)
|
||||
case x: String => renderGlobalHtml(request, response, context, x)
|
||||
case Some(x: String) => renderGlobalHtml(request, response, context, x)
|
||||
case x: Html => renderGlobalHtml(request, response, context, x.toString)
|
||||
case Some(x: Html) => renderGlobalHtml(request, response, context, x.toString)
|
||||
case x: Fragment => renderFragmentHtml(request, response, context, x.html.toString)
|
||||
case Some(x: Fragment) => renderFragmentHtml(request, response, context, x.html.toString)
|
||||
case x: RawData => renderRawData(request, response, context, x)
|
||||
case Some(x: RawData) => renderRawData(request, response, context, x)
|
||||
case x: Redirect => response.sendRedirect(x.path)
|
||||
case Some(x: Redirect) => response.sendRedirect(x.path)
|
||||
case x: AnyRef => renderJson(request, response, x)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 renderError(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, error: Int): Unit = {
|
||||
response.sendError(error)
|
||||
}
|
||||
|
||||
private def renderGlobalHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, body: String): Unit = {
|
||||
response.setContentType("text/html; charset=UTF-8")
|
||||
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, context: app.Context, repository: RepositoryInfo, body: String): Unit = {
|
||||
response.setContentType("text/html; charset=UTF-8")
|
||||
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 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 renderRawData(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, rawData: RawData): Unit = {
|
||||
response.setContentType(rawData.contentType)
|
||||
IOUtils.write(rawData.content, 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package servlet
|
||||
import javax.servlet._
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import util.Keys
|
||||
|
||||
/**
|
||||
* Controls the transaction with the open session in view pattern.
|
||||
@@ -20,8 +21,9 @@ class TransactionFilter extends Filter {
|
||||
// assets don't need transaction
|
||||
chain.doFilter(req, res)
|
||||
} else {
|
||||
Database(req.getServletContext) withTransaction {
|
||||
Database(req.getServletContext) withTransaction { session =>
|
||||
logger.debug("begin transaction")
|
||||
req.setAttribute(Keys.Request.DBSession, session)
|
||||
chain.doFilter(req, res)
|
||||
logger.debug("end transaction")
|
||||
}
|
||||
@@ -31,8 +33,13 @@ class TransactionFilter extends Filter {
|
||||
}
|
||||
|
||||
object Database {
|
||||
def apply(context: ServletContext): scala.slick.session.Database =
|
||||
scala.slick.session.Database.forURL(context.getInitParameter("db.url"),
|
||||
|
||||
def apply(context: ServletContext): slick.jdbc.JdbcBackend.Database =
|
||||
slick.jdbc.JdbcBackend.Database.forURL(context.getInitParameter("db.url"),
|
||||
context.getInitParameter("db.user"),
|
||||
context.getInitParameter("db.password"))
|
||||
|
||||
def getSession(req: ServletRequest): slick.jdbc.JdbcBackend#Session =
|
||||
req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import servlet.{Database, CommitLogHook}
|
||||
import service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||
import javax.servlet.ServletContext
|
||||
|
||||
import model.Session
|
||||
|
||||
object GitCommand {
|
||||
val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
|
||||
@@ -27,11 +27,11 @@ abstract class GitCommand(val context: ServletContext, val owner: String, val re
|
||||
protected var out: OutputStream = null
|
||||
protected var callback: ExitCallback = null
|
||||
|
||||
protected def runTask(user: String): Unit
|
||||
protected def runTask(user: String)(implicit session: Session): Unit
|
||||
|
||||
private def newTask(user: String): Runnable = new Runnable {
|
||||
override def run(): Unit = {
|
||||
Database(context) withTransaction {
|
||||
Database(context) withSession { implicit session =>
|
||||
try {
|
||||
runTask(user)
|
||||
callback.onExit(0)
|
||||
@@ -71,7 +71,8 @@ abstract class GitCommand(val context: ServletContext, val owner: String, val re
|
||||
this.in = in
|
||||
}
|
||||
|
||||
protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo): Boolean =
|
||||
protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo)
|
||||
(implicit session: Session): Boolean =
|
||||
getAccountByUserName(username) match {
|
||||
case Some(account) => hasWritePermission(repositoryInfo.owner, repositoryInfo.name, Some(account))
|
||||
case None => false
|
||||
@@ -82,7 +83,7 @@ abstract class GitCommand(val context: ServletContext, val owner: String, val re
|
||||
class GitUploadPack(context: ServletContext, owner: String, repoName: String, baseUrl: String) extends GitCommand(context, owner, repoName)
|
||||
with RepositoryService with AccountService {
|
||||
|
||||
override protected def runTask(user: String): Unit = {
|
||||
override protected def runTask(user: String)(implicit session: Session): Unit = {
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo =>
|
||||
if(!repositoryInfo.repository.isPrivate || isWritableUser(user, repositoryInfo)){
|
||||
using(Git.open(getRepositoryDir(owner, repoName))) { git =>
|
||||
@@ -99,7 +100,7 @@ class GitUploadPack(context: ServletContext, owner: String, repoName: String, ba
|
||||
class GitReceivePack(context: ServletContext, owner: String, repoName: String, baseUrl: String) extends GitCommand(context, owner, repoName)
|
||||
with SystemSettingsService with RepositoryService with AccountService {
|
||||
|
||||
override protected def runTask(user: String): Unit = {
|
||||
override protected def runTask(user: String)(implicit session: Session): Unit = {
|
||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo =>
|
||||
if(isWritableUser(user, repositoryInfo)){
|
||||
using(Git.open(getRepositoryDir(owner, repoName))) { git =>
|
||||
|
||||
@@ -10,7 +10,7 @@ import javax.servlet.ServletContext
|
||||
class PublicKeyAuthenticator(context: ServletContext) extends PublickeyAuthenticator with SshKeyService {
|
||||
|
||||
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
|
||||
Database(context) withTransaction {
|
||||
Database(context) withSession { implicit session =>
|
||||
getPublicKeys(username).exists { sshKey =>
|
||||
SshUtil.str2PublicKey(sshKey.publicKey) match {
|
||||
case Some(publicKey) => key.equals(publicKey)
|
||||
|
||||
@@ -14,7 +14,7 @@ object SshUtil {
|
||||
// TODO RFC 4716 Public Key is not supported...
|
||||
val parts = key.split(" ")
|
||||
if (parts.size < 2) {
|
||||
logger.debug(s"Invalid PublicKey Format: key")
|
||||
logger.debug(s"Invalid PublicKey Format: ${key}")
|
||||
return None
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -34,6 +34,10 @@ object Directory {
|
||||
|
||||
val DatabaseHome = s"${GitBucketHome}/data"
|
||||
|
||||
val PluginHome = s"${GitBucketHome}/plugins"
|
||||
|
||||
val TemporaryHome = s"${GitBucketHome}/tmp"
|
||||
|
||||
/**
|
||||
* Substance directory of the repository.
|
||||
*/
|
||||
@@ -55,13 +59,18 @@ object Directory {
|
||||
* Root of temporary directories for the upload file.
|
||||
*/
|
||||
def getTemporaryDir(sessionId: String): File =
|
||||
new File(s"${GitBucketHome}/tmp/_upload/${sessionId}")
|
||||
new File(s"${TemporaryHome}/_upload/${sessionId}")
|
||||
|
||||
/**
|
||||
* Root of temporary directories for the specified repository.
|
||||
*/
|
||||
def getTemporaryDir(owner: String, repository: String): File =
|
||||
new File(s"${GitBucketHome}/tmp/${owner}/${repository}")
|
||||
new File(s"${TemporaryHome}/${owner}/${repository}")
|
||||
|
||||
/**
|
||||
* Root of plugin cache directory. Plugin repositories are cloned into this directory.
|
||||
*/
|
||||
def getPluginCacheDir(): File = new File(s"${TemporaryHome}/_plugins")
|
||||
|
||||
/**
|
||||
* Temporary directory which is used to create an archive to download repository contents.
|
||||
|
||||
@@ -2,6 +2,8 @@ package util
|
||||
|
||||
import scala.util.matching.Regex
|
||||
import scala.util.control.Exception._
|
||||
import slick.jdbc.JdbcBackend
|
||||
import servlet.Database
|
||||
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
||||
|
||||
/**
|
||||
@@ -9,6 +11,9 @@ import javax.servlet.http.{HttpSession, HttpServletRequest}
|
||||
*/
|
||||
object Implicits {
|
||||
|
||||
// Convert to slick session.
|
||||
implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request)
|
||||
|
||||
implicit class RichSeq[A](seq: Seq[A]) {
|
||||
|
||||
def splitWith(condition: (A, A) => Boolean): Seq[Seq[A]] = split(seq)(condition)
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.eclipse.jgit.api.Git
|
||||
import util.Directory._
|
||||
import util.StringUtil._
|
||||
import util.ControlUtil._
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConverters._
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.revwalk._
|
||||
@@ -35,7 +36,11 @@ object JGitUtil {
|
||||
* @param branchList the list of branch names
|
||||
* @param tags the list of tags
|
||||
*/
|
||||
case class RepositoryInfo(owner: String, name: String, url: String, commitCount: Int, branchList: List[String], tags: List[TagInfo])
|
||||
case class RepositoryInfo(owner: String, name: String, url: String, commitCount: Int, branchList: List[String], tags: List[TagInfo]){
|
||||
def this(owner: String, name: String, baseUrl: String) = {
|
||||
this(owner, name, s"${baseUrl}/git/${owner}/${name}.git", 0, Nil, Nil)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The file data for the file list of the repository viewer.
|
||||
@@ -43,38 +48,45 @@ object JGitUtil {
|
||||
* @param id the object id
|
||||
* @param isDirectory whether is it directory
|
||||
* @param name the file (or directory) name
|
||||
* @param time the last modified time
|
||||
* @param message the last commit message
|
||||
* @param commitId the last commit id
|
||||
* @param committer the last committer name
|
||||
* @param time the last modified time
|
||||
* @param author the last committer name
|
||||
* @param mailAddress the committer's mail address
|
||||
* @param linkUrl the url of submodule
|
||||
*/
|
||||
case class FileInfo(id: ObjectId, isDirectory: Boolean, name: String, time: Date, message: String, commitId: String,
|
||||
committer: String, mailAddress: String, linkUrl: Option[String])
|
||||
case class FileInfo(id: ObjectId, isDirectory: Boolean, name: String, message: String, commitId: String,
|
||||
time: Date, author: String, mailAddress: String, linkUrl: Option[String])
|
||||
|
||||
/**
|
||||
* The commit data.
|
||||
*
|
||||
* @param id the commit id
|
||||
* @param time the commit time
|
||||
* @param committer the committer name
|
||||
* @param mailAddress the mail address of the committer
|
||||
* @param shortMessage the short message
|
||||
* @param fullMessage the full message
|
||||
* @param parents the list of parent commit id
|
||||
* @param authorTime the author time
|
||||
* @param authorName the author name
|
||||
* @param authorEmailAddress the mail address of the author
|
||||
* @param commitTime the commit time
|
||||
* @param committerName the committer name
|
||||
* @param committerEmailAddress the mail address of the committer
|
||||
*/
|
||||
case class CommitInfo(id: String, time: Date, committer: String, mailAddress: String,
|
||||
shortMessage: String, fullMessage: String, parents: List[String]){
|
||||
case class CommitInfo(id: String, shortMessage: String, fullMessage: String, parents: List[String],
|
||||
authorTime: Date, authorName: String, authorEmailAddress: String,
|
||||
commitTime: Date, committerName: String, committerEmailAddress: String){
|
||||
|
||||
def this(rev: org.eclipse.jgit.revwalk.RevCommit) = this(
|
||||
rev.getName,
|
||||
rev.getCommitterIdent.getWhen,
|
||||
rev.getCommitterIdent.getName,
|
||||
rev.getCommitterIdent.getEmailAddress,
|
||||
rev.getShortMessage,
|
||||
rev.getFullMessage,
|
||||
rev.getParents().map(_.name).toList)
|
||||
rev.getParents().map(_.name).toList,
|
||||
rev.getAuthorIdent.getWhen,
|
||||
rev.getAuthorIdent.getName,
|
||||
rev.getAuthorIdent.getEmailAddress,
|
||||
rev.getCommitterIdent.getWhen,
|
||||
rev.getCommitterIdent.getName,
|
||||
rev.getCommitterIdent.getEmailAddress)
|
||||
|
||||
val summary = getSummaryMessage(fullMessage, shortMessage)
|
||||
|
||||
@@ -83,6 +95,8 @@ object JGitUtil {
|
||||
Some(fullMessage.trim.substring(i).trim)
|
||||
} else None
|
||||
}
|
||||
|
||||
def isDifferentFromAuthor: Boolean = authorName != committerName || authorEmailAddress != committerEmailAddress
|
||||
}
|
||||
|
||||
case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String])
|
||||
@@ -94,7 +108,12 @@ object JGitUtil {
|
||||
* @param content the string content
|
||||
* @param charset the character encoding
|
||||
*/
|
||||
case class ContentInfo(viewType: String, content: Option[String], charset: Option[String])
|
||||
case class ContentInfo(viewType: String, content: Option[String], charset: Option[String]){
|
||||
/**
|
||||
* the line separator of this content ("LF" or "CRLF")
|
||||
*/
|
||||
val lineSeparator: String = if(content.exists(_.indexOf("\r\n") >= 0)) "CRLF" else "LF"
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag data.
|
||||
@@ -146,12 +165,12 @@ object JGitUtil {
|
||||
commitCount,
|
||||
// branches
|
||||
git.branchList.call.asScala.map { ref =>
|
||||
ref.getName.replaceFirst("^refs/heads/", "")
|
||||
ref.getName.stripPrefix("refs/heads/")
|
||||
}.toList,
|
||||
// tags
|
||||
git.tagList.call.asScala.map { ref =>
|
||||
val revCommit = getRevCommitFromId(git, ref.getObjectId)
|
||||
TagInfo(ref.getName.replaceFirst("^refs/tags/", ""), revCommit.getCommitterIdent.getWhen, revCommit.getName)
|
||||
TagInfo(ref.getName.stripPrefix("refs/tags/"), revCommit.getCommitterIdent.getWhen, revCommit.getName)
|
||||
}.toList
|
||||
)
|
||||
} catch {
|
||||
@@ -172,38 +191,23 @@ object JGitUtil {
|
||||
* @return HTML of the file list
|
||||
*/
|
||||
def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = {
|
||||
val list = new scala.collection.mutable.ListBuffer[(ObjectId, FileMode, String, String, Option[String])]
|
||||
var list = new scala.collection.mutable.ListBuffer[(ObjectId, FileMode, String, String, Option[String])]
|
||||
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
val revCommit = revWalk.parseCommit(objectId)
|
||||
|
||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||
val treeWalk = if (path == ".") {
|
||||
val treeWalk = new TreeWalk(git.getRepository)
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
if(path != "."){
|
||||
treeWalk.setRecursive(true)
|
||||
treeWalk.setFilter(new TreeFilter(){
|
||||
|
||||
var stopRecursive = false
|
||||
|
||||
def include(walker: TreeWalk): Boolean = {
|
||||
val targetPath = walker.getPathString
|
||||
if((path + "/").startsWith(targetPath)){
|
||||
true
|
||||
} else if(targetPath.startsWith(path + "/") && targetPath.substring(path.length + 1).indexOf("/") < 0){
|
||||
stopRecursive = true
|
||||
treeWalk.setRecursive(false)
|
||||
true
|
||||
treeWalk
|
||||
} else {
|
||||
false
|
||||
}
|
||||
val treeWalk = TreeWalk.forPath(git.getRepository, path, revCommit.getTree)
|
||||
treeWalk.enterSubtree()
|
||||
treeWalk
|
||||
}
|
||||
|
||||
def shouldBeRecursive(): Boolean = !stopRecursive
|
||||
|
||||
override def clone: TreeFilter = return this
|
||||
})
|
||||
}
|
||||
using(treeWalk) { treeWalk =>
|
||||
while (treeWalk.next()) {
|
||||
// submodule
|
||||
val linkUrl = if(treeWalk.getFileMode(0) == FileMode.GITLINK){
|
||||
@@ -212,6 +216,31 @@ object JGitUtil {
|
||||
|
||||
list.append((treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getPathString, treeWalk.getNameString, linkUrl))
|
||||
}
|
||||
|
||||
list = list.map(tuple =>
|
||||
if (tuple._2 != FileMode.TREE)
|
||||
tuple
|
||||
else
|
||||
simplifyPath(tuple)
|
||||
)
|
||||
|
||||
@tailrec
|
||||
def simplifyPath(tuple: (ObjectId, FileMode, String, String, Option[String])): (ObjectId, FileMode, String, String, Option[String]) = {
|
||||
val list = new scala.collection.mutable.ListBuffer[(ObjectId, FileMode, String, String, Option[String])]
|
||||
using(new TreeWalk(git.getRepository)) { walk =>
|
||||
walk.addTree(tuple._1)
|
||||
while (walk.next() && list.size < 2) {
|
||||
val linkUrl = if (walk.getFileMode(0) == FileMode.GITLINK) {
|
||||
getSubmodules(git, revCommit.getTree).find(_.path == walk.getPathString).map(_.url)
|
||||
} else None
|
||||
list.append((walk.getObjectId(0), walk.getFileMode(0), tuple._3 + "/" + walk.getPathString, tuple._4 + "/" + walk.getNameString, linkUrl))
|
||||
}
|
||||
}
|
||||
if (list.size != 1 || list.exists(_._2 != FileMode.TREE))
|
||||
tuple
|
||||
else
|
||||
simplifyPath(list(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,11 +251,11 @@ object JGitUtil {
|
||||
objectId,
|
||||
fileMode == FileMode.TREE || fileMode == FileMode.GITLINK,
|
||||
name,
|
||||
commit.getCommitterIdent.getWhen,
|
||||
getSummaryMessage(commit.getFullMessage, commit.getShortMessage),
|
||||
commit.getName,
|
||||
commit.getCommitterIdent.getName,
|
||||
commit.getCommitterIdent.getEmailAddress,
|
||||
commit.getAuthorIdent.getWhen,
|
||||
commit.getAuthorIdent.getName,
|
||||
commit.getAuthorIdent.getEmailAddress,
|
||||
linkUrl)
|
||||
}
|
||||
}.sortWith { (file1, file2) =>
|
||||
@@ -486,7 +515,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))
|
||||
@@ -500,7 +529,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()
|
||||
|
||||
@@ -634,4 +663,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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,11 @@ object Keys {
|
||||
*/
|
||||
object Request {
|
||||
|
||||
/**
|
||||
* Request key for the Slick Session.
|
||||
*/
|
||||
val DBSession = "DB_SESSION"
|
||||
|
||||
/**
|
||||
* Request key for the Ajax request flag.
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.security.Security
|
||||
import org.slf4j.LoggerFactory
|
||||
import service.SystemSettingsService.Ldap
|
||||
import scala.annotation.tailrec
|
||||
import model.Account
|
||||
|
||||
/**
|
||||
* Utility for LDAP authentication.
|
||||
@@ -16,6 +17,26 @@ object LDAPUtil {
|
||||
private val LDAP_VERSION: Int = LDAPConnection.LDAP_V3
|
||||
private val logger = LoggerFactory.getLogger(getClass().getName())
|
||||
|
||||
private val LDAP_DUMMY_MAL = "@ldap-devnull"
|
||||
|
||||
/**
|
||||
* Returns true if mail address ends with "@ldap-devnull"
|
||||
*/
|
||||
def isDummyMailAddress(account: Account): Boolean = {
|
||||
account.mailAddress.endsWith(LDAP_DUMMY_MAL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dummy address (userName@ldap-devnull) for LDAP login.
|
||||
*
|
||||
* If mail address is not managed in LDAP server, GitBucket stores this dummy address in first LDAP login.
|
||||
* GitBucket does not send any mails to this dummy address. And these users must input their mail address
|
||||
* at the first step after LDAP authentication.
|
||||
*/
|
||||
def createDummyMailAddress(userName: String): String = {
|
||||
userName + LDAP_DUMMY_MAL
|
||||
}
|
||||
|
||||
/**
|
||||
* Try authentication by LDAP using given configuration.
|
||||
* Returns Right(LDAPUserInfo) if authentication is successful, otherwise Left(errorMessage).
|
||||
@@ -30,7 +51,7 @@ object LDAPUtil {
|
||||
keystore = ldapSettings.keystore.getOrElse(""),
|
||||
error = "System LDAP authentication failed."
|
||||
){ conn =>
|
||||
findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute) match {
|
||||
findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute, ldapSettings.additionalFilterCondition) match {
|
||||
case Some(userDN) => userAuthentication(ldapSettings, userDN, userName, password)
|
||||
case None => Left("User does not exist.")
|
||||
}
|
||||
@@ -47,17 +68,26 @@ object LDAPUtil {
|
||||
keystore = ldapSettings.keystore.getOrElse(""),
|
||||
error = "User LDAP Authentication Failed."
|
||||
){ conn =>
|
||||
findMailAddress(conn, userDN, ldapSettings.mailAttribute) match {
|
||||
if(ldapSettings.mailAttribute.getOrElse("").isEmpty) {
|
||||
Right(LDAPUserInfo(
|
||||
userName = userName,
|
||||
fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute =>
|
||||
findFullName(conn, userDN, ldapSettings.userNameAttribute, userName, fullNameAttribute)
|
||||
}.getOrElse(userName),
|
||||
mailAddress = createDummyMailAddress(userName)))
|
||||
} else {
|
||||
findMailAddress(conn, userDN, ldapSettings.userNameAttribute, userName, ldapSettings.mailAttribute.get) match {
|
||||
case Some(mailAddress) => Right(LDAPUserInfo(
|
||||
userName = getUserNameFromMailAddress(userName),
|
||||
fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute =>
|
||||
findFullName(conn, userDN, fullNameAttribute)
|
||||
findFullName(conn, userDN, ldapSettings.userNameAttribute, userName, fullNameAttribute)
|
||||
}.getOrElse(userName),
|
||||
mailAddress = mailAddress))
|
||||
case None => Left("Can't find mail address.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def getUserNameFromMailAddress(userName: String): String = {
|
||||
(userName.indexOf('@') match {
|
||||
@@ -112,7 +142,7 @@ object LDAPUtil {
|
||||
/**
|
||||
* Search a specified user and returns userDN if exists.
|
||||
*/
|
||||
private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String): Option[String] = {
|
||||
private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String, additionalFilterCondition: Option[String]): Option[String] = {
|
||||
@tailrec
|
||||
def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = {
|
||||
if(results.hasMore){
|
||||
@@ -125,20 +155,26 @@ object LDAPUtil {
|
||||
entries.flatten
|
||||
}
|
||||
}
|
||||
getEntries(conn.search(baseDN, LDAPConnection.SCOPE_SUB, userNameAttribute + "=" + userName, null, false)).collectFirst {
|
||||
|
||||
val filterCond = additionalFilterCondition.getOrElse("") match {
|
||||
case "" => userNameAttribute + "=" + userName
|
||||
case x => "(&(" + x + ")(" + userNameAttribute + "=" + userName + "))"
|
||||
}
|
||||
|
||||
getEntries(conn.search(baseDN, LDAPConnection.SCOPE_SUB, filterCond, null, false)).collectFirst {
|
||||
case x => x.getDN
|
||||
}
|
||||
}
|
||||
|
||||
private def findMailAddress(conn: LDAPConnection, userDN: String, mailAttribute: String): Option[String] =
|
||||
defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, null, Array[String](mailAttribute), false)){ results =>
|
||||
private def findMailAddress(conn: LDAPConnection, userDN: String, userNameAttribute: String, userName: String, mailAttribute: String): Option[String] =
|
||||
defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, userNameAttribute + "=" + userName, Array[String](mailAttribute), false)){ results =>
|
||||
if(results.hasMore) {
|
||||
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
||||
} else None
|
||||
}
|
||||
|
||||
private def findFullName(conn: LDAPConnection, userDN: String, nameAttribute: String): Option[String] =
|
||||
defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, null, Array[String](nameAttribute), false)){ results =>
|
||||
private def findFullName(conn: LDAPConnection, userDN: String, userNameAttribute: String, userName: String, nameAttribute: String): Option[String] =
|
||||
defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, userNameAttribute + "=" + userName, Array[String](nameAttribute), false)){ results =>
|
||||
if(results.hasMore) {
|
||||
Option(results.next.getAttribute(nameAttribute)).map(_.getStringValue)
|
||||
} else None
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import app.Context
|
||||
import model.Session
|
||||
import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
|
||||
import servlet.Database
|
||||
import SystemSettingsService.Smtp
|
||||
@@ -15,7 +16,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context): Unit
|
||||
|
||||
protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit context: Context) =
|
||||
protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit session: Session, context: Context) =
|
||||
(
|
||||
// individual repository's owner
|
||||
issue.userName ::
|
||||
@@ -27,7 +28,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
|
||||
)
|
||||
.distinct
|
||||
.withFilter ( _ != context.loginAccount.get.userName ) // the operation in person is excluded
|
||||
.foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) foreach (x => notify(x.mailAddress)) )
|
||||
.foreach ( getAccountByUserName(_) filterNot (_.isGroupAccount) filterNot (LDAPUtil.isDummyMailAddress(_)) foreach (x => notify(x.mailAddress)) )
|
||||
|
||||
}
|
||||
|
||||
@@ -68,9 +69,8 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
(msg: String => String)(implicit context: Context) = {
|
||||
val database = Database(context.request.getServletContext)
|
||||
|
||||
val f = future {
|
||||
// TODO Can we use the Database Session in other than Transaction Filter?
|
||||
database withSession {
|
||||
val f = Future {
|
||||
database withSession { implicit session =>
|
||||
getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
|
||||
defining(
|
||||
s"[${r.name}] ${issue.title} (#${issueId})" ->
|
||||
|
||||
@@ -46,6 +46,22 @@ object StringUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts line separator in the given content.
|
||||
*
|
||||
* @param content the content
|
||||
* @param lineSeparator "LF" or "CRLF"
|
||||
* @return the converted content
|
||||
*/
|
||||
def convertLineSeparator(content: String, lineSeparator: String): String = {
|
||||
val lf = content.replace("\r\n", "\n").replace("\r", "\n")
|
||||
if(lineSeparator == "CRLF"){
|
||||
lf.replace("\n", "\r\n")
|
||||
} else {
|
||||
lf
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract issue id like ```#issueId``` from the given message.
|
||||
*
|
||||
|
||||
@@ -10,7 +10,7 @@ trait Validations {
|
||||
*/
|
||||
def identifier: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(!value.matches("^[a-zA-Z0-9\\-_.]+$")){
|
||||
if(!value.matches("[a-zA-Z0-9\\-_.]+")){
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else if(value.startsWith("_") || value.startsWith("-")){
|
||||
Some(s"${name} starts with invalid character.")
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -51,7 +51,7 @@ class GitBucketLinkRender(context: app.Context, repository: service.RepositorySe
|
||||
(text, text)
|
||||
}
|
||||
|
||||
val url = repository.httpUrl.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/wiki/" + StringUtil.urlEncode(page)
|
||||
val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page)
|
||||
|
||||
if(getWikiPage(repository.owner, repository.name, page).isDefined){
|
||||
new Rendering(url, label)
|
||||
@@ -109,10 +109,17 @@ class GitBucketHtmlSerializer(
|
||||
}
|
||||
|
||||
private def fixUrl(url: String): String = {
|
||||
if(!enableWikiLink || url.startsWith("http://") || url.startsWith("https://") || url.startsWith("#")){
|
||||
if(!enableWikiLink){
|
||||
if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("#") || url.startsWith("/") ||
|
||||
context.currentPath.contains("/blob/")){
|
||||
url
|
||||
} else {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/wiki/_blob/" + url
|
||||
val paths = context.currentPath.split("/")
|
||||
val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url
|
||||
}
|
||||
} else {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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".
|
||||
@@ -74,7 +78,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
* This method looks up Gravatar if avatar icon has not been configured in user settings.
|
||||
*/
|
||||
def avatar(commit: util.JGitUtil.CommitInfo, size: Int)(implicit context: app.Context): Html =
|
||||
getAvatarImageHtml(commit.committer, size, commit.mailAddress)
|
||||
getAvatarImageHtml(commit.authorName, size, commit.authorEmailAddress)
|
||||
|
||||
/**
|
||||
* Converts commit id, issue id and username to the link.
|
||||
@@ -196,7 +200,6 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
case x if(x.endsWith(".sql")) => "sql"
|
||||
case x if(x.endsWith(".tcl")) => "tcl"
|
||||
case x if(x.endsWith(".vbs")) => "vbscript"
|
||||
case x if(x.endsWith(".tcl")) => "tcl"
|
||||
case x if(x.endsWith(".yml")) => "yaml"
|
||||
case _ => "plain_text"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@(account: model.Account, info: Option[Any])(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@import util.LDAPUtil
|
||||
@html.main("Edit your profile"){
|
||||
<div class="container">
|
||||
<div class="row-fluid">
|
||||
@@ -9,6 +10,7 @@
|
||||
</div>
|
||||
<div class="span9">
|
||||
@helper.html.information(info)
|
||||
@if(LDAPUtil.isDummyMailAddress(account)){<div class="alert alert-danger">Please register your mail address.</div>}
|
||||
<form action="@url(account.userName)/_edit" method="POST" validate="true">
|
||||
<div class="box">
|
||||
<div class="box-header">Profile</div>
|
||||
@@ -31,7 +33,7 @@
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label for="mailAddress" class="strong">Mail Address:</label>
|
||||
<input type="text" name="mailAddress" id="mailAddress" value="@account.mailAddress"/>
|
||||
<input type="text" name="mailAddress" id="mailAddress" value="@if(!LDAPUtil.isDummyMailAddress(account)){@account.mailAddress}"/>
|
||||
<span id="error-mailAddress" class="error"></span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
@@ -52,7 +54,7 @@
|
||||
<a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-success" value="Save"/>
|
||||
<a href="@url(account.userName)" class="btn">Cancel</a>
|
||||
@if(!LDAPUtil.isDummyMailAddress(account)){<a href="@url(account.userName)" class="btn">Cancel</a>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,7 +60,7 @@ $(function(){
|
||||
});
|
||||
|
||||
$('#addMember').click(function(){
|
||||
$('#error-memberName').text('');
|
||||
$('#error-members').text('');
|
||||
var userName = $('#memberName').val();
|
||||
|
||||
// check empty
|
||||
@@ -73,18 +73,18 @@ $(function(){
|
||||
return $(this).data('name') == userName;
|
||||
}).length > 0;
|
||||
if(exists){
|
||||
$('#error-memberName').text('User has been already added.');
|
||||
$('#error-members').text('User has been already added.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// check existence
|
||||
$.post('@path/admin/users/_usercheck', {
|
||||
$.post('@path/_user/existence', {
|
||||
'userName': userName
|
||||
}, function(data, status){
|
||||
if(data == 'true'){
|
||||
addMemberHTML(userName, false);
|
||||
} else {
|
||||
$('#error-memberName').text('User does not exist.');
|
||||
$('#error-members').text('User does not exist.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="row-fluid">
|
||||
<div class="span4">
|
||||
<div class="block">
|
||||
<div class="account-image">@avatar(account.userName, 200)</div>
|
||||
<div class="account-image">@avatar(account.userName, 270)</div>
|
||||
<div class="account-fullname">@account.fullName</div>
|
||||
<div class="account-username">@account.userName</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
<li@if(active=="system"){ class="active"}>
|
||||
<a href="@path/admin/system">System Settings</a>
|
||||
</li>
|
||||
<li@if(active=="plugins"){ class="active"}>
|
||||
<a href="@path/admin/plugins">Plugins</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@path/console/login.jsp">H2 Console</a>
|
||||
</li>
|
||||
|
||||
37
src/main/twirl/admin/plugins/available.scala.html
Normal file
37
src/main/twirl/admin/plugins/available.scala.html
Normal file
@@ -0,0 +1,37 @@
|
||||
@(plugins: List[app.SystemSettingsControllerBase.AvailablePlugin])(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@html.main("Plugins"){
|
||||
@admin.html.menu("plugins"){
|
||||
@tab("available")
|
||||
<form action="@path/admin/plugins/_install" method="POST" validate="true">
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Version</th>
|
||||
<th>Provider</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
@plugins.zipWithIndex.map { case (plugin, i) =>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="pluginId[@i]" value="@plugin.id"/>
|
||||
@plugin.id
|
||||
</td>
|
||||
<td>@plugin.version</td>
|
||||
<td><a href="@plugin.url">@plugin.author</a></td>
|
||||
<td>@plugin.description</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
<input type="submit" id="install-plugins" class="btn btn-success" value="Install selected plugins"/>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#install-plugins').click(function(){
|
||||
return confirm('Selected plugin will be installed. Are you sure?');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
37
src/main/twirl/admin/plugins/console.scala.html
Normal file
37
src/main/twirl/admin/plugins/console.scala.html
Normal file
@@ -0,0 +1,37 @@
|
||||
@()(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@html.main("JavaScript Console"){
|
||||
@admin.html.menu("plugins"){
|
||||
@tab("console")
|
||||
<form method="POST">
|
||||
<div class="box">
|
||||
<div class="box-header">JavaScript Console</div>
|
||||
<div class="box-content">
|
||||
<div id="editor" style="width: 100%; height: 400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset>
|
||||
<input type="button" id="evaluate" class="btn btn-success" value="Evaluate"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
<script src="@assets/vendors/ace/ace.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
var editor = ace.edit("editor");
|
||||
editor.setTheme("ace/theme/monokai");
|
||||
editor.getSession().setMode("ace/mode/javascript");
|
||||
|
||||
$('#evaluate').click(function(){
|
||||
$.post('@path/admin/plugins/console', {
|
||||
script: editor.getValue()
|
||||
}, function(data){
|
||||
alert('Success: ' + data);
|
||||
}).fail(function(error){
|
||||
alert(error.statusText);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
47
src/main/twirl/admin/plugins/installed.scala.html
Normal file
47
src/main/twirl/admin/plugins/installed.scala.html
Normal file
@@ -0,0 +1,47 @@
|
||||
@(plugins: List[plugin.Plugin],
|
||||
updatablePlugins: List[app.SystemSettingsControllerBase.AvailablePlugin])(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@html.main("Plugins"){
|
||||
@admin.html.menu("plugins"){
|
||||
@tab("installed")
|
||||
<form method="POST" validate="true">
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Version</th>
|
||||
<th>Provider</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
@plugins.zipWithIndex.map { case (plugin, i) =>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="pluginId[@i]" value="@plugin.id"/>
|
||||
@plugin.id
|
||||
</td>
|
||||
<td>
|
||||
@plugin.version
|
||||
@updatablePlugins.find(_.id == plugin.id).map { x =>
|
||||
(@x.version is available)
|
||||
}
|
||||
</td>
|
||||
<td><a href="@plugin.url">@plugin.author</a></td>
|
||||
<td>@plugin.description</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
<input type="submit" id="update-plugins" class="btn btn-success" value="Update selected plugins" formaction="@path/admin/plugins/_update"/>
|
||||
<input type="submit" id="delete-plugins" class="btn btn-danger" value="Uninstall selected plugins" formaction="@path/admin/plugins/_delete"/>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#update-plugins').click(function(){
|
||||
return confirm('Selected plugin will be updated. Are you sure?');
|
||||
});
|
||||
$('#delete-plugins').click(function(){
|
||||
return confirm('Selected plugin will be removed permanently. Are you sure?');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
9
src/main/twirl/admin/plugins/tab.scala.html
Normal file
9
src/main/twirl/admin/plugins/tab.scala.html
Normal file
@@ -0,0 +1,9 @@
|
||||
@(active: String)(implicit context: app.Context)
|
||||
@import context._
|
||||
<ul class="nav nav-tabs">
|
||||
<li@if(active == "installed"){ class="active"}><a href="@path/admin/plugins">Installed plugins</a></li>
|
||||
<li@if(active == "available"){ class="active"}><a href="@path/admin/plugins/available">Available plugins</a></li>
|
||||
@*
|
||||
<li@if(active == "console" ){ class="active"}><a href="@path/admin/plugins/console">JavaScript console</a></li>
|
||||
*@
|
||||
</ul>
|
||||
@@ -133,6 +133,13 @@
|
||||
<span id="error-ldap_userNameAttribute" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapAdditionalFilterCondition">Additional filter condition</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapAdditionalFilterCondition" name="ldap.additionalFilterCondition" value="@settings.ldap.map(_.additionalFilterCondition)"/>
|
||||
<span id="error-ldap_additionalFilterCondition" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
|
||||
<div class="controls">
|
||||
|
||||
@@ -59,7 +59,7 @@ $(function(){
|
||||
});
|
||||
|
||||
$('#addMember').click(function(){
|
||||
$('#error-memberName').text('');
|
||||
$('#error-members').text('');
|
||||
var userName = $('#memberName').val();
|
||||
|
||||
// check empty
|
||||
@@ -72,18 +72,18 @@ $(function(){
|
||||
return $(this).data('name') == userName;
|
||||
}).length > 0;
|
||||
if(exists){
|
||||
$('#error-memberName').text('User has been already added.');
|
||||
$('#error-members').text('User has been already added.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// check existence
|
||||
$.post('@path/admin/users/_usercheck', {
|
||||
$.post('@path/_user/existence', {
|
||||
'userName': userName
|
||||
}, function(data, status){
|
||||
if(data == 'true'){
|
||||
addMemberHTML(userName, false);
|
||||
} else {
|
||||
$('#error-memberName').text('User does not exist.');
|
||||
$('#error-members').text('User does not exist.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
@@ -24,6 +42,7 @@
|
||||
});
|
||||
var title = $('#@id').attr('title');
|
||||
$('#@id').removeAttr('title')
|
||||
clip.htmlBridge = "#global-zeroclipboard-html-bridge";
|
||||
clip.on('complete', function(client, args) {
|
||||
$(clip.htmlBridge).attr('title', 'copied!').tooltip('fixTitle').tooltip('show');
|
||||
$(clip.htmlBridge).attr('title', title).tooltip('fixTitle');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user