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:
bati11
2014-09-19 12:45:09 +09:00
377 changed files with 101076 additions and 63111 deletions

View File

@@ -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

View File

@@ -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
View 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
View 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

View File

@@ -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
View 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

View 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

View File

@@ -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=

View File

@@ -1 +1 @@
sbt.version=0.13.1
sbt.version=0.13.5

View File

@@ -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)
}

View File

@@ -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")

View File

@@ -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
View File

@@ -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 "$@"

View File

@@ -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();
}
}

View 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);

View File

@@ -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/*")

View File

@@ -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}")

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
})
}

View File

@@ -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

View File

@@ -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.")

View File

@@ -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),

View File

@@ -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}")
})

View File

@@ -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
}
}
}

View File

@@ -2,6 +2,7 @@ package app
import util._
import ControlUtil._
import Implicits._
import service._
import jp.sf.amateras.scalatra.forms._

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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(

View File

@@ -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
)

View File

@@ -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)
}
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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
)

View File

@@ -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,

View File

@@ -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
)

View File

@@ -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"
}
}
}

View File

@@ -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]
)

View 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
)

View 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()
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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
)

View File

@@ -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(

View File

@@ -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
}

View 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]
}

View 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
}
}
}

View 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)
}
}

View 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 .*", "")
}
}

View 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
}

View 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)
}
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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),

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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
}

View 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
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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,

View File

@@ -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)
}
}
}

View File

@@ -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"))
}

View File

@@ -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

View File

@@ -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")
}
}

View 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)
}
}

View File

@@ -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]
}

View File

@@ -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 =>

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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.
*/

View File

@@ -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

View File

@@ -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})" ->

View File

@@ -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.
*

View File

@@ -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.")

View File

@@ -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 =>

View File

@@ -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
}
}

View File

@@ -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"
}

View File

@@ -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>

View File

@@ -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.');
}
});
});

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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">

View File

@@ -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.');
}
});
});

View File

@@ -1,4 +1,4 @@
@(listparts: twirl.api.Html,
@(listparts: play.twirl.api.Html,
allCount: Int,
assignedCount: Int,
createdByCount: Int,

View File

@@ -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,

View File

@@ -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