mirror of
				https://github.com/gitbucket/gitbucket.git
				synced 2025-11-03 20:15:59 +01:00 
			
		
		
		
	Compare commits
	
		
			115 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					5674f0e980 | ||
| 
						 | 
					b9ade60eb2 | ||
| 
						 | 
					96303723fa | ||
| 
						 | 
					0f5dbc5788 | ||
| 
						 | 
					8df0c3a439 | ||
| 
						 | 
					ca6a86816a | ||
| 
						 | 
					3ea939798f | ||
| 
						 | 
					d947410e3c | ||
| 
						 | 
					db59bc08ac | ||
| 
						 | 
					95a8649f79 | ||
| 
						 | 
					ffd10122ed | ||
| 
						 | 
					c4c39f36e9 | ||
| 
						 | 
					96900c3cbf | ||
| 
						 | 
					69fa370d12 | ||
| 
						 | 
					7496437d11 | ||
| 
						 | 
					33b7d09af7 | ||
| 
						 | 
					53d0974760 | ||
| 
						 | 
					a87399f223 | ||
| 
						 | 
					975dfb17e1 | ||
| 
						 | 
					8b8bd0289b | ||
| 
						 | 
					3bb69c623b | ||
| 
						 | 
					dd427bdbef | ||
| 
						 | 
					b40657a14a | ||
| 
						 | 
					21ca5b2eec | ||
| 
						 | 
					b78d584d8a | ||
| 
						 | 
					e6b666a66a | ||
| 
						 | 
					bab93ea4f5 | ||
| 
						 | 
					7fe98253ae | ||
| 
						 | 
					13385cbced | ||
| 
						 | 
					3f20cec7b2 | ||
| 
						 | 
					a0e4b020ca | ||
| 
						 | 
					ea5d898b27 | ||
| 
						 | 
					4e652b5ccd | ||
| 
						 | 
					dd809896c8 | ||
| 
						 | 
					93536d3365 | ||
| 
						 | 
					098b18fe6d | ||
| 
						 | 
					66efdac757 | ||
| 
						 | 
					45545d3815 | ||
| 
						 | 
					b65d41731b | ||
| 
						 | 
					be19e97518 | ||
| 
						 | 
					2ebf2b99bd | ||
| 
						 | 
					be79ac2eb2 | ||
| 
						 | 
					05afec3236 | ||
| 
						 | 
					57879eb72e | ||
| 
						 | 
					2bc915f51b | ||
| 
						 | 
					1ca55805b5 | ||
| 
						 | 
					93cc1be166 | ||
| 
						 | 
					f88ce3f671 | ||
| 
						 | 
					20aabfc273 | ||
| 
						 | 
					601f8c4249 | ||
| 
						 | 
					d0ccfc52b8 | ||
| 
						 | 
					c22aef8ee2 | ||
| 
						 | 
					3807e61a48 | ||
| 
						 | 
					55722f87af | ||
| 
						 | 
					212f3725ed | ||
| 
						 | 
					193a312b22 | ||
| 
						 | 
					6a2d2ebfd1 | ||
| 
						 | 
					82beed1f44 | ||
| 
						 | 
					0ede7e9921 | ||
| 
						 | 
					6d200aa340 | ||
| 
						 | 
					a0fbb90048 | ||
| 
						 | 
					08e29e7077 | ||
| 
						 | 
					d2317d0a97 | ||
| 
						 | 
					972628eb65 | ||
| 
						 | 
					51a56356cb | ||
| 
						 | 
					3bef71f5f2 | ||
| 
						 | 
					2bb1f6168a | ||
| 
						 | 
					b13820fc0e | ||
| 
						 | 
					723de9e81e | ||
| 
						 | 
					3e161353ed | ||
| 
						 | 
					2a8706630a | ||
| 
						 | 
					121b6ee641 | ||
| 
						 | 
					34e299bf52 | ||
| 
						 | 
					0822b7b5f3 | ||
| 
						 | 
					618110327a | ||
| 
						 | 
					f58f476060 | ||
| 
						 | 
					f5a544603a | ||
| 
						 | 
					89515cd087 | ||
| 
						 | 
					37731c4163 | ||
| 
						 | 
					1d4720d784 | ||
| 
						 | 
					a10b053489 | ||
| 
						 | 
					6122c8a1e1 | ||
| 
						 | 
					fa9254c240 | ||
| 
						 | 
					10616bca7d | ||
| 
						 | 
					307f7e15e9 | ||
| 
						 | 
					86cf97d76b | ||
| 
						 | 
					01f6590c04 | ||
| 
						 | 
					8f0c22bae9 | ||
| 
						 | 
					652a68c5b1 | ||
| 
						 | 
					1f56e1360d | ||
| 
						 | 
					38475ffefe | ||
| 
						 | 
					7a44a4d726 | ||
| 
						 | 
					9dbc0c3fd6 | ||
| 
						 | 
					56bb43ea6b | ||
| 
						 | 
					b287c1f60d | ||
| 
						 | 
					258d53b7a6 | ||
| 
						 | 
					2e11d6dd78 | ||
| 
						 | 
					a2a2e22485 | ||
| 
						 | 
					c182cde14b | ||
| 
						 | 
					3683a5fb7d | ||
| 
						 | 
					1223bf2fd8 | ||
| 
						 | 
					e2c99a46be | ||
| 
						 | 
					8c35310cd6 | ||
| 
						 | 
					00af52815d | ||
| 
						 | 
					9175cf5c71 | ||
| 
						 | 
					a74bbd3eeb | ||
| 
						 | 
					4e2a3fdbd0 | ||
| 
						 | 
					8d200c72d3 | ||
| 
						 | 
					18cd967a9c | ||
| 
						 | 
					328d6c1d17 | ||
| 
						 | 
					a335c31385 | ||
| 
						 | 
					97349a9bb2 | ||
| 
						 | 
					ce3b6ed7c2 | ||
| 
						 | 
					5e0619b500 | ||
| 
						 | 
					639e7e0b3f | 
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
									
									
									
									
								
							@@ -80,6 +80,22 @@ Run the following commands in `Terminal` to
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Release Notes
 | 
					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
 | 
					### 2.1 - 6 Jul 2014
 | 
				
			||||||
- Upgrade to Slick 2.0 from 1.9
 | 
					- Upgrade to Slick 2.0 from 1.9
 | 
				
			||||||
- Base part of the plug-in system is merged
 | 
					- Base part of the plug-in system is merged
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
  <property name="target.dir" value="target"/>
 | 
					  <property name="target.dir" value="target"/>
 | 
				
			||||||
  <property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
 | 
					  <property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
 | 
				
			||||||
  <property name="jetty.dir" value="embed-jetty"/>
 | 
					  <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="gitbucket.version" value="0.0.1"/>
 | 
				
			||||||
  <property name="jetty.version" value="8.1.8.v20121106"/>
 | 
					  <property name="jetty.version" value="8.1.8.v20121106"/>
 | 
				
			||||||
  <property name="servlet.version" value="3.0.0.v201112011016"/>
 | 
					  <property name="servlet.version" value="3.0.0.v201112011016"/>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								contrib/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								contrib/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					# Contrib Notes #
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The configuration script adapts according to the OS.
 | 
				
			||||||
 | 
					The `linux` directory contains scripts for Ubuntu and RedHat.
 | 
				
			||||||
 | 
					The Mac scripts have been folded in as well.
 | 
				
			||||||
 | 
					Common scripts are in this directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This version of scripts has so far only been tested on Ubuntu and Mac. Someone else will have to test on RedHat.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To run:
 | 
				
			||||||
 | 
					1. Edit `gitbucket.conf` to suit.
 | 
				
			||||||
 | 
					2. Type: `install`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										62
									
								
								contrib/gitbucket.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								contrib/gitbucket.conf
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					# Configuration section is below. Ignore this part
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isUbuntu {
 | 
				
			||||||
 | 
					  if [ -f /etc/lsb-release ]; then
 | 
				
			||||||
 | 
					    grep -i ubuntu /etc/lsb-release | head -n 1 | cut -d \  -f 1 | cut -d = -f 2
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isRedHat {
 | 
				
			||||||
 | 
					  if [ -d "/etc/rc.d/init.d" ]; then echo yes; fi
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isMac {
 | 
				
			||||||
 | 
					  if [[ "$(uname -a | cut -d \  -f 1 )" == "Darwin" ]]; then echo yes; fi
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 
 | 
				
			||||||
 | 
					# Configuration section start
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Bind host
 | 
				
			||||||
 | 
					GITBUCKET_HOST=0.0.0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Other Java option
 | 
				
			||||||
 | 
					GITBUCKET_JVM_OPTS=-Dmail.smtp.starttls.enable=true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Data directory, holds repositories
 | 
				
			||||||
 | 
					GITBUCKET_HOME=/var/lib/gitbucket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GITBUCKET_LOG_DIR=/var/log/gitbucket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Server port
 | 
				
			||||||
 | 
					GITBUCKET_PORT=8080
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# URL prefix for the GitBucket page (http://<host>:<port>/<prefix>/)
 | 
				
			||||||
 | 
					GITBUCKET_PREFIX=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Directory where GitBucket is installed
 | 
				
			||||||
 | 
					# Configuration is stored here:
 | 
				
			||||||
 | 
					GITBUCKET_DIR=/usr/share/gitbucket
 | 
				
			||||||
 | 
					GITBUCKET_WAR_DIR=$GITBUCKET_DIR/lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Path to the WAR file
 | 
				
			||||||
 | 
					GITBUCKET_WAR_FILE=$GITBUCKET_WAR_DIR/gitbucket.war
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# GitBucket version to fetch when installing
 | 
				
			||||||
 | 
					GITBUCKET_VERSION=2.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# End of configuration section. Ignore this part
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					if [ `isUbuntu` ]; then
 | 
				
			||||||
 | 
					  GITBUCKET_SERVICE=/etc/init.d/gitbucket
 | 
				
			||||||
 | 
					elif [ `isRedHat` ]; then
 | 
				
			||||||
 | 
					  GITBUCKET_SERVICE=/etc/rc.d/init.d
 | 
				
			||||||
 | 
					elif [ `isMac` ]; then
 | 
				
			||||||
 | 
					  GITBUCKET_SERVICE=/Library/StartupItems/GitBucket/GitBucket
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					  echo "Don't know how to install onto this OS"
 | 
				
			||||||
 | 
					  exit -2
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
#!/bin/bash
 | 
					#!/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
 | 
					# Starts the GitBucket server
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
@@ -8,28 +10,44 @@
 | 
				
			|||||||
# description: Run GitBucket server
 | 
					# description: Run GitBucket server
 | 
				
			||||||
# processname: java
 | 
					# processname: java
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Source function library
 | 
					set -e
 | 
				
			||||||
. /etc/rc.d/init.d/functions
 | 
					
 | 
				
			||||||
 | 
					[ -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
 | 
					# Default values
 | 
				
			||||||
GITBUCKET_HOME=/var/lib/gitbucket
 | 
					GITBUCKET_HOME=/var/lib/gitbucket
 | 
				
			||||||
GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
 | 
					GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Pull in cq settings
 | 
					# 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
 | 
					# 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
 | 
					PID_FILE=/var/run/gitbucket.pid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Default return value
 | 
					RED='\033[1m\E[37;41m'
 | 
				
			||||||
RETVAL=0
 | 
					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() {
 | 
					start() {
 | 
				
			||||||
	echo -n $"Starting GitBucket server: "
 | 
						echo -n $"Starting GitBucket server: "
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# Compile statup parameters
 | 
					  START_OPTS=
 | 
				
			||||||
	if [ $GITBUCKET_PORT ]; then
 | 
						if [ $GITBUCKET_PORT ]; then
 | 
				
			||||||
		START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
 | 
							START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
 | 
				
			||||||
	fi
 | 
						fi
 | 
				
			||||||
@@ -40,17 +58,15 @@ start() {
 | 
				
			|||||||
		START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
 | 
							START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
 | 
				
			||||||
	fi
 | 
						fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# Run the Java process
 | 
					 | 
				
			||||||
	GITBUCKET_HOME="${GITBUCKET_HOME}" java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
 | 
						GITBUCKET_HOME="${GITBUCKET_HOME}" java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
 | 
				
			||||||
	RETVAL=$?
 | 
						RETVAL=$?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	# Store PID of the Java process into a file
 | 
					 | 
				
			||||||
	echo $! > $PID_FILE
 | 
						echo $! > $PID_FILE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if [ $RETVAL -eq 0 ] ; then
 | 
						if [ $RETVAL -eq 0 ] ; then
 | 
				
			||||||
		success "GitBucket startup"
 | 
							success "Success"
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
		failure "GitBucket startup"
 | 
							failure "Exit code $RETVAL"
 | 
				
			||||||
	fi
 | 
						fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	echo
 | 
						echo
 | 
				
			||||||
@@ -82,25 +98,41 @@ restart() {
 | 
				
			|||||||
	start
 | 
						start
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## MacOS proxies for System V service hooks:
 | 
				
			||||||
 | 
					StartService() {
 | 
				
			||||||
 | 
						start
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
case "$1" in
 | 
					StopService() {
 | 
				
			||||||
start)
 | 
						stop
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RestartService() {
 | 
				
			||||||
 | 
						restart
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ `isMac` ]; then
 | 
				
			||||||
 | 
					  RunService "$1"
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
						case "$1" in
 | 
				
			||||||
 | 
							start)
 | 
				
			||||||
			start
 | 
								start
 | 
				
			||||||
			;;
 | 
								;;
 | 
				
			||||||
stop)
 | 
							stop)
 | 
				
			||||||
			stop
 | 
								stop
 | 
				
			||||||
			;;
 | 
								;;
 | 
				
			||||||
restart)
 | 
							restart)
 | 
				
			||||||
			restart
 | 
								restart
 | 
				
			||||||
			;;
 | 
								;;
 | 
				
			||||||
status)
 | 
							status)
 | 
				
			||||||
			status -p $PID_FILE java
 | 
								status -p $PID_FILE java
 | 
				
			||||||
			RETVAL=$?
 | 
								RETVAL=$?
 | 
				
			||||||
			;;
 | 
								;;
 | 
				
			||||||
*)
 | 
							*)
 | 
				
			||||||
			echo $"Usage: $0 [start|stop|restart|status]"
 | 
								echo $"Usage: $0 [start|stop|restart|status]"
 | 
				
			||||||
			RETVAL=2
 | 
								RETVAL=2
 | 
				
			||||||
esac
 | 
						esac
 | 
				
			||||||
 | 
					  exit $RETVAL
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
exit $RETVAL
 | 
					 | 
				
			||||||
							
								
								
									
										69
									
								
								contrib/install
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										69
									
								
								contrib/install
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Only tested on Ubuntu 14.04
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Uses information stored in GitBucket git repo on GitHub as defaults.
 | 
				
			||||||
 | 
					# Edit gitbucket.conf before running this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GITBUCKET_VERSION=2.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ ! -f gitbucket.conf ]; then
 | 
				
			||||||
 | 
					  echo "gitbucket.conf not found, aborting"
 | 
				
			||||||
 | 
					  exit -3
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					source gitbucket.conf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function createDir {
 | 
				
			||||||
 | 
					  if [ ! -d "$1" ]; then
 | 
				
			||||||
 | 
					    echo "Making $1 directory."
 | 
				
			||||||
 | 
					    sudo mkdir -p "$1"
 | 
				
			||||||
 | 
					  fi
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ "$(which iptables)" ]; then
 | 
				
			||||||
 | 
					  echo "Opening port $GITBUCKET_PORT in firewall."
 | 
				
			||||||
 | 
					  sudo iptables -A INPUT -p tcp --dport $GITBUCKET_PORT -j ACCEPT
 | 
				
			||||||
 | 
					  echo "Please use iptables-persistent:"
 | 
				
			||||||
 | 
					  echo "  sudo apt-get install iptables-persistent"
 | 
				
			||||||
 | 
					  echo "After installed, you can save/reload iptables rules anytime:"
 | 
				
			||||||
 | 
					  echo "  sudo /etc/init.d/iptables-persistent save"
 | 
				
			||||||
 | 
					  echo "  sudo /etc/init.d/iptables-persistent reload"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					createDir "$GITBUCKET_HOME"
 | 
				
			||||||
 | 
					createDir "$GITBUCKET_WAR_DIR"
 | 
				
			||||||
 | 
					createDir "$GITBUCKET_DIR"
 | 
				
			||||||
 | 
					createDir "$GITBUCKET_LOG_DIR"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "Fetching GitBucket v$GITBUCKET_VERSION and saving as $GITBUCKET_WAR_FILE"
 | 
				
			||||||
 | 
					sudo wget -qO "$GITBUCKET_WAR_FILE" https://github.com/takezoe/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sudo rm -f "$GITBUCKET_LOG_DIR/run.log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "Copying gitbucket.conf to $GITBUCKET_DIR"
 | 
				
			||||||
 | 
					sudo cp gitbucket.conf $GITBUCKET_DIR
 | 
				
			||||||
 | 
					if [ `isUbuntu` ] || [ `isRedHat` ]; then
 | 
				
			||||||
 | 
					  sudo cp gitbucket.init "$GITBUCKET_SERVICE"
 | 
				
			||||||
 | 
					  # Install gitbucket as a service that starts when system boots
 | 
				
			||||||
 | 
					  sudo chown root:root $GITBUCKET_SERVICE
 | 
				
			||||||
 | 
					  sudo chmod 755 $GITBUCKET_SERVICE
 | 
				
			||||||
 | 
					  sudo update-rc.d "$(basename $GITBUCKET_SERVICE)" defaults 98 02
 | 
				
			||||||
 | 
					  echo "Starting GitBucket service"
 | 
				
			||||||
 | 
					  sudo $GITBUCKET_SERVICE start
 | 
				
			||||||
 | 
					elif [ `isMac` ]; then
 | 
				
			||||||
 | 
					  sudo macosx/makePlist
 | 
				
			||||||
 | 
					  echo "Starting GitBucket service"
 | 
				
			||||||
 | 
					  sudo cp gitbucket.conf "$GITBUCKET_SERVICE"
 | 
				
			||||||
 | 
					  sudo cp gitbucket.init "$GITBUCKET_SERVICE"
 | 
				
			||||||
 | 
					  sudo chmod a+x "$GITBUCKET_SERVICE"
 | 
				
			||||||
 | 
					  sudo "$GITBUCKET_SERVICE" start
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					  echo "Don't know how to install this OS"
 | 
				
			||||||
 | 
					  exit -2
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ $? != 0 ]; then
 | 
				
			||||||
 | 
					  less "$GITBUCKET_LOG_DIR/run.log"
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
							
								
								
									
										14
									
								
								contrib/macosx/gitbucket.plist → contrib/macosx/makePlist
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										14
									
								
								contrib/macosx/gitbucket.plist → contrib/macosx/makePlist
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@@ -1,3 +1,10 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# From http://docstore.mik.ua/orelly/unix3/mac/ch02_02.htm
 | 
				
			||||||
 | 
					source gitbucket.conf
 | 
				
			||||||
 | 
					GITBUCKET_SERVICE_DIR=`dirname "$GITBUCKET_SERVICE"`
 | 
				
			||||||
 | 
					mkdir -p "$GITBUCKET_SERVICE_DIR"
 | 
				
			||||||
 | 
					cat << EOF > "$GITBUCKET_SERVICE_DIR/gitbucket.plist"
 | 
				
			||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					<?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">
 | 
					<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 | 
				
			||||||
<plist version="1.0">
 | 
					<plist version="1.0">
 | 
				
			||||||
@@ -7,14 +14,15 @@
 | 
				
			|||||||
    <key>ProgramArguments</key>
 | 
					    <key>ProgramArguments</key>
 | 
				
			||||||
    <array>
 | 
					    <array>
 | 
				
			||||||
      <string>/usr/bin/java</string>
 | 
					      <string>/usr/bin/java</string>
 | 
				
			||||||
      <string>-Dmail.smtp.starttls.enable=true</string>
 | 
					      <string>$GITBUCKET_JVM_OPTS</string>
 | 
				
			||||||
      <string>-jar</string>
 | 
					      <string>-jar</string>
 | 
				
			||||||
      <string>gitbucket.war</string>
 | 
					      <string>gitbucket.war</string>
 | 
				
			||||||
      <string>--host=127.0.0.1</string>
 | 
					      <string>--host=$GITBUCKET_HOST</string>
 | 
				
			||||||
      <string>--port=8080</string>
 | 
					      <string>--port=$GITBUCKET_PORT</string>
 | 
				
			||||||
      <string>--https=true</string>
 | 
					      <string>--https=true</string>
 | 
				
			||||||
    </array>
 | 
					    </array>
 | 
				
			||||||
    <key>RunAtLoad</key>
 | 
					    <key>RunAtLoad</key>
 | 
				
			||||||
    <true/>
 | 
					    <true/>
 | 
				
			||||||
  </dict>
 | 
					  </dict>
 | 
				
			||||||
</plist>
 | 
					</plist>
 | 
				
			||||||
 | 
					EOF
 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
# Bind host
 | 
					 | 
				
			||||||
#GITBUCKET_HOST=0.0.0.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Server port
 | 
					 | 
				
			||||||
#GITBUCKET_PORT=8080
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Data directory (GITBUCKET_HOME/gitbucket)
 | 
					 | 
				
			||||||
#GITBUCKET_HOME=/var/lib/gitbucket
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Path to the WAR file
 | 
					 | 
				
			||||||
#GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# URL prefix for the GitBucket page (http://<host>:<port>/<prefix>/)
 | 
					 | 
				
			||||||
#GITBUCKET_PREFIX=
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Other Java option
 | 
					 | 
				
			||||||
#GITBUCKET_JVM_OPTS=
 | 
					 | 
				
			||||||
@@ -1 +1 @@
 | 
				
			|||||||
sbt.version=0.13.1
 | 
					sbt.version=0.13.5
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,23 @@
 | 
				
			|||||||
import sbt._
 | 
					import sbt._
 | 
				
			||||||
import Keys._
 | 
					import Keys._
 | 
				
			||||||
import org.scalatra.sbt._
 | 
					import org.scalatra.sbt._
 | 
				
			||||||
import twirl.sbt.TwirlPlugin._
 | 
					 | 
				
			||||||
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
 | 
					import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
 | 
				
			||||||
 | 
					import play.twirl.sbt.SbtTwirl
 | 
				
			||||||
 | 
					import play.twirl.sbt.Import.TwirlKeys._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object MyBuild extends Build {
 | 
					object MyBuild extends Build {
 | 
				
			||||||
  val Organization = "jp.sf.amateras"
 | 
					  val Organization = "jp.sf.amateras"
 | 
				
			||||||
  val Name = "gitbucket"
 | 
					  val Name = "gitbucket"
 | 
				
			||||||
  val Version = "0.0.1"
 | 
					  val Version = "0.0.1"
 | 
				
			||||||
  val ScalaVersion = "2.10.3"
 | 
					  val ScalaVersion = "2.11.2"
 | 
				
			||||||
  val ScalatraVersion = "2.2.1"
 | 
					  val ScalatraVersion = "2.3.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lazy val project = Project (
 | 
					  lazy val project = Project (
 | 
				
			||||||
    "gitbucket",
 | 
					    "gitbucket",
 | 
				
			||||||
    file("."),
 | 
					    file(".")
 | 
				
			||||||
    settings = Defaults.defaultSettings ++ ScalatraPlugin.scalatraWithJRebel ++ Seq(
 | 
					  )
 | 
				
			||||||
 | 
					  .settings(ScalatraPlugin.scalatraWithJRebel: _*)
 | 
				
			||||||
 | 
					  .settings(
 | 
				
			||||||
    sourcesInBase := false,
 | 
					    sourcesInBase := false,
 | 
				
			||||||
    organization := Organization,
 | 
					    organization := Organization,
 | 
				
			||||||
    name := Name,
 | 
					    name := Name,
 | 
				
			||||||
@@ -26,32 +29,32 @@ object MyBuild extends Build {
 | 
				
			|||||||
    ),
 | 
					    ),
 | 
				
			||||||
    scalacOptions := Seq("-deprecation", "-language:postfixOps"),
 | 
					    scalacOptions := Seq("-deprecation", "-language:postfixOps"),
 | 
				
			||||||
    libraryDependencies ++= Seq(
 | 
					    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" % ScalatraVersion,
 | 
				
			||||||
      "org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
 | 
					      "org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
 | 
				
			||||||
      "org.scalatra" %% "scalatra-json" % ScalatraVersion,
 | 
					      "org.scalatra" %% "scalatra-json" % ScalatraVersion,
 | 
				
			||||||
        "org.json4s" %% "json4s-jackson" % "3.2.5",
 | 
					      "org.json4s" %% "json4s-jackson" % "3.2.10",
 | 
				
			||||||
        "jp.sf.amateras" %% "scalatra-forms" % "0.0.14",
 | 
					      "jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
 | 
				
			||||||
      "commons-io" % "commons-io" % "2.4",
 | 
					      "commons-io" % "commons-io" % "2.4",
 | 
				
			||||||
      "org.pegdown" % "pegdown" % "1.4.1",
 | 
					      "org.pegdown" % "pegdown" % "1.4.1",
 | 
				
			||||||
      "org.apache.commons" % "commons-compress" % "1.5",
 | 
					      "org.apache.commons" % "commons-compress" % "1.5",
 | 
				
			||||||
      "org.apache.commons" % "commons-email" % "1.3.1",
 | 
					      "org.apache.commons" % "commons-email" % "1.3.1",
 | 
				
			||||||
      "org.apache.httpcomponents" % "httpclient" % "4.3",
 | 
					      "org.apache.httpcomponents" % "httpclient" % "4.3",
 | 
				
			||||||
      "org.apache.sshd" % "apache-sshd" % "0.11.0",
 | 
					      "org.apache.sshd" % "apache-sshd" % "0.11.0",
 | 
				
			||||||
        "com.typesafe.slick" %% "slick" % "2.0.2",
 | 
					      "com.typesafe.slick" %% "slick" % "2.1.0-RC3",
 | 
				
			||||||
        "org.mozilla" % "rhino" % "1.7R4",
 | 
					 | 
				
			||||||
      "com.novell.ldap" % "jldap" % "2009-10-07",
 | 
					      "com.novell.ldap" % "jldap" % "2009-10-07",
 | 
				
			||||||
      "org.quartz-scheduler" % "quartz" % "2.2.1",
 | 
					      "org.quartz-scheduler" % "quartz" % "2.2.1",
 | 
				
			||||||
        "com.h2database" % "h2" % "1.3.173",
 | 
					      "com.h2database" % "h2" % "1.4.180",
 | 
				
			||||||
      "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
 | 
					      "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
 | 
				
			||||||
      "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
 | 
					      "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"),
 | 
					      "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,
 | 
					    EclipseKeys.withSource := true,
 | 
				
			||||||
    javacOptions in compile ++= Seq("-target", "6", "-source", "6"),
 | 
					    javacOptions in compile ++= Seq("-target", "6", "-source", "6"),
 | 
				
			||||||
    testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
 | 
					    testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
 | 
				
			||||||
    packageOptions += Package.MainClass("JettyLauncher")
 | 
					    packageOptions += Package.MainClass("JettyLauncher")
 | 
				
			||||||
    ) ++ seq(Twirl.settings: _*)
 | 
					  ).enablePlugins(SbtTwirl)
 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,6 @@ addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
 | 
					addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
resolvers += "spray repo" at "http://repo.spray.io"
 | 
					addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
 | 
				
			||||||
 | 
					 | 
				
			||||||
addSbtPlugin("io.spray" % "sbt-twirl" % "0.7.0")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
 | 
					addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
 | 
				
			||||||
 
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								sbt.bat
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								sbt.bat
									
									
									
									
									
								
							@@ -1,2 +1,2 @@
 | 
				
			|||||||
set SCRIPT_DIR=%~dp0
 | 
					set SCRIPT_DIR=%~dp0
 | 
				
			||||||
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.1.jar" %*
 | 
					java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.5.jar" %*
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								sbt.sh
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								sbt.sh
									
									
									
									
									
								
							@@ -1 +1 @@
 | 
				
			|||||||
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.1.jar "$@"
 | 
					java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.5.jar "$@"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,8 @@
 | 
				
			|||||||
import org.eclipse.jetty.io.EndPoint;
 | 
					 | 
				
			||||||
import org.eclipse.jetty.server.Request;
 | 
					 | 
				
			||||||
import org.eclipse.jetty.server.Server;
 | 
					import org.eclipse.jetty.server.Server;
 | 
				
			||||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
 | 
					import org.eclipse.jetty.server.nio.SelectChannelConnector;
 | 
				
			||||||
import org.eclipse.jetty.webapp.WebAppContext;
 | 
					import org.eclipse.jetty.webapp.WebAppContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.File;
 | 
				
			||||||
import java.net.URL;
 | 
					import java.net.URL;
 | 
				
			||||||
import java.security.ProtectionDomain;
 | 
					import java.security.ProtectionDomain;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,6 +42,14 @@ public class JettyLauncher {
 | 
				
			|||||||
        server.addConnector(connector);
 | 
					        server.addConnector(connector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        WebAppContext context = new WebAppContext();
 | 
					        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();
 | 
					        ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
 | 
				
			||||||
        URL location = domain.getCodeSource().getLocation();
 | 
					        URL location = domain.getCodeSource().getLocation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,4 +65,27 @@ public class JettyLauncher {
 | 
				
			|||||||
        server.start();
 | 
					        server.start();
 | 
				
			||||||
        server.join();
 | 
					        server.join();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static File getGitBucketHome(){
 | 
				
			||||||
 | 
					        String home = System.getProperty("gitbucket.home");
 | 
				
			||||||
 | 
					        if(home != null && home.length() > 0){
 | 
				
			||||||
 | 
					            return new File(home);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        home = System.getenv("GITBUCKET_HOME");
 | 
				
			||||||
 | 
					        if(home != null && home.length() > 0){
 | 
				
			||||||
 | 
					            return new File(home);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return new File(System.getProperty("user.home"), ".gitbucket");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static void deleteDirectory(File dir){
 | 
				
			||||||
 | 
					        for(File file: dir.listFiles()){
 | 
				
			||||||
 | 
					            if(file.isFile()){
 | 
				
			||||||
 | 
					                file.delete();
 | 
				
			||||||
 | 
					            } else if(file.isDirectory()){
 | 
				
			||||||
 | 
					                deleteDirectory(file);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        dir.delete();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								src/main/resources/update/2_3.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/main/resources/update/2_3.sql
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					CREATE TABLE PLUGIN (
 | 
				
			||||||
 | 
					  PLUGIN_ID VARCHAR(100) NOT NULL,
 | 
				
			||||||
 | 
					  VERSION   VARCHAR(100) NOT NULL
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALTER TABLE PLUGIN ADD CONSTRAINT IDX_PLUGIN_PK PRIMARY KEY (PLUGIN_ID);
 | 
				
			||||||
@@ -335,7 +335,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
 | 
				
			|||||||
            builder.finish()
 | 
					            builder.finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
 | 
					            JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
 | 
				
			||||||
              loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
 | 
					              Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,9 @@ abstract class ControllerBase extends ScalatraFilter
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  implicit val jsonFormats = DefaultFormats
 | 
					  implicit val jsonFormats = DefaultFormats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Don't set content type via Accept header.
 | 
					// TODO Scala 2.11
 | 
				
			||||||
  override def format(implicit request: HttpServletRequest) = ""
 | 
					//  // 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 {
 | 
					  override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
 | 
				
			||||||
    val httpRequest  = request.asInstanceOf[HttpServletRequest]
 | 
					    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,
 | 
					  // TODO Scala 2.11
 | 
				
			||||||
                       includeContextPath: Boolean = true, includeServletPath: Boolean = true)
 | 
					  override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
 | 
				
			||||||
                      (implicit request: HttpServletRequest, response: HttpServletResponse) =
 | 
					                   includeContextPath: Boolean = true, includeServletPath: Boolean = true,
 | 
				
			||||||
 | 
					                   absolutize: Boolean = true, withSessionId: Boolean = true)
 | 
				
			||||||
 | 
					                  (implicit request: HttpServletRequest, response: HttpServletResponse): String =
 | 
				
			||||||
    if (path.startsWith("http")) path
 | 
					    if (path.startsWith("http")) path
 | 
				
			||||||
    else baseUrl + url(path, params, false, false, false)
 | 
					    else baseUrl + super.url(path, params, false, false, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,21 +49,21 @@ trait DashboardControllerBase extends ControllerBase {
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val userName   = context.loginAccount.get.userName
 | 
					    val 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 filterUser = Map(filter -> userName)
 | 
				
			||||||
    val page = IssueSearchCondition.page(request)
 | 
					    val page = IssueSearchCondition.page(request)
 | 
				
			||||||
    // 
 | 
					
 | 
				
			||||||
    dashboard.html.issues(
 | 
					    dashboard.html.issues(
 | 
				
			||||||
        issues.html.listparts(
 | 
					        issues.html.listparts(
 | 
				
			||||||
            searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, repositories: _*),
 | 
					            searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
 | 
				
			||||||
            page,
 | 
					            page,
 | 
				
			||||||
            countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*),
 | 
					            countIssue(condition.copy(state = "open"  ), filterUser, false, userRepos: _*),
 | 
				
			||||||
            countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*),
 | 
					            countIssue(condition.copy(state = "closed"), filterUser, false, userRepos: _*),
 | 
				
			||||||
            condition),
 | 
					            condition),
 | 
				
			||||||
        countIssue(condition, Map.empty, false, repositories: _*),
 | 
					        countIssue(condition, Map.empty, false, userRepos: _*),
 | 
				
			||||||
        countIssue(condition, Map("assigned" -> userName), false, repositories: _*),
 | 
					        countIssue(condition, Map("assigned"   -> userName), false, userRepos: _*),
 | 
				
			||||||
        countIssue(condition, Map("created_by" -> userName), false, repositories: _*),
 | 
					        countIssue(condition, Map("created_by" -> userName), false, userRepos: _*),
 | 
				
			||||||
        countIssueGroupByRepository(condition, filterUser, false, repositories: _*),
 | 
					        countIssueGroupByRepository(condition, filterUser, false, userRepos: _*),
 | 
				
			||||||
        condition,
 | 
					        condition,
 | 
				
			||||||
        filter)    
 | 
					        filter)    
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@@ -80,25 +80,26 @@ trait DashboardControllerBase extends ControllerBase {
 | 
				
			|||||||
    }.copy(repo = repository))
 | 
					    }.copy(repo = repository))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val userName   = context.loginAccount.get.userName
 | 
					    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 filterUser = Map(filter -> userName)
 | 
				
			||||||
    val page = IssueSearchCondition.page(request)
 | 
					    val page = IssueSearchCondition.page(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val counts = countIssueGroupByRepository(
 | 
					    val counts = countIssueGroupByRepository(
 | 
				
			||||||
      IssueSearchCondition().copy(state = condition.state), Map.empty, true, repositories: _*)
 | 
					      IssueSearchCondition().copy(state = condition.state), Map.empty, true, userRepos: _*)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dashboard.html.pulls(
 | 
					    dashboard.html.pulls(
 | 
				
			||||||
      pulls.html.listparts(
 | 
					      pulls.html.listparts(
 | 
				
			||||||
        searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, repositories: _*),
 | 
					        searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
 | 
				
			||||||
        page,
 | 
					        page,
 | 
				
			||||||
        countIssue(condition.copy(state = "open"), filterUser, true, repositories: _*),
 | 
					        countIssue(condition.copy(state = "open"  ), filterUser, true, allRepos: _*),
 | 
				
			||||||
        countIssue(condition.copy(state = "closed"), filterUser, true, repositories: _*),
 | 
					        countIssue(condition.copy(state = "closed"), filterUser, true, allRepos: _*),
 | 
				
			||||||
        condition,
 | 
					        condition,
 | 
				
			||||||
        None,
 | 
					        None,
 | 
				
			||||||
        false),
 | 
					        false),
 | 
				
			||||||
      getPullRequestCountGroupByUser(condition.state == "closed", None, None),
 | 
					      getPullRequestCountGroupByUser(condition.state == "closed", None, None),
 | 
				
			||||||
      getRepositoryNamesOfUser(userName).map { RepoName =>
 | 
					      userRepos.map { case (userName, repoName) =>
 | 
				
			||||||
        (userName, RepoName, counts.collectFirst { case (_, RepoName, count) => count }.getOrElse(0))
 | 
					        (userName, repoName, counts.find { x => x._1 == userName && x._2 == repoName }.map(_._3).getOrElse(0))
 | 
				
			||||||
      }.sortBy(_._3).reverse,
 | 
					      }.sortBy(_._3).reverse,
 | 
				
			||||||
      condition,
 | 
					      condition,
 | 
				
			||||||
      filter)
 | 
					      filter)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,11 +20,23 @@ trait IndexControllerBase extends ControllerBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  get("/"){
 | 
					  get("/"){
 | 
				
			||||||
    val loginAccount = context.loginAccount
 | 
					    val loginAccount = context.loginAccount
 | 
				
			||||||
 | 
					    if(loginAccount.isEmpty) {
 | 
				
			||||||
        html.index(getRecentActivities(),
 | 
					        html.index(getRecentActivities(),
 | 
				
			||||||
            getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
 | 
					            getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
 | 
				
			||||||
            loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
 | 
					            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"){
 | 
					  get("/signin"){
 | 
				
			||||||
@@ -59,6 +71,10 @@ trait IndexControllerBase extends ControllerBase {
 | 
				
			|||||||
    session.setAttribute(Keys.Session.LoginAccount, account)
 | 
					    session.setAttribute(Keys.Session.LoginAccount, account)
 | 
				
			||||||
    updateLastLoginDate(account.userName)
 | 
					    updateLastLoginDate(account.userName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(LDAPUtil.isDummyMailAddress(account)) {
 | 
				
			||||||
 | 
					      redirect("/" + account.userName + "/_edit")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
 | 
					    flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
 | 
				
			||||||
      if(redirectUrl.stripSuffix("/") == request.getContextPath){
 | 
					      if(redirectUrl.stripSuffix("/") == request.getContextPath){
 | 
				
			||||||
        redirect("/")
 | 
					        redirect("/")
 | 
				
			||||||
@@ -72,8 +88,6 @@ trait IndexControllerBase extends ControllerBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * JSON API for collaborator completion.
 | 
					   * JSON API for collaborator completion.
 | 
				
			||||||
   *
 | 
					 | 
				
			||||||
   * TODO Move to other controller?
 | 
					 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  get("/_user/proposals")(usersOnly {
 | 
					  get("/_user/proposals")(usersOnly {
 | 
				
			||||||
    contentType = formats("json")
 | 
					    contentType = formats("json")
 | 
				
			||||||
@@ -82,5 +96,11 @@ trait IndexControllerBase extends ControllerBase {
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * JSON APU for checking user existence.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  post("/_user/existence")(usersOnly {
 | 
				
			||||||
 | 
					    getAccountByUserName(params("userName")).isDefined
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import util.Implicits._
 | 
				
			|||||||
import util.ControlUtil._
 | 
					import util.ControlUtil._
 | 
				
			||||||
import org.scalatra.Ok
 | 
					import org.scalatra.Ok
 | 
				
			||||||
import model.Issue
 | 
					import model.Issue
 | 
				
			||||||
 | 
					import plugin.PluginSystem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class IssuesController extends IssuesControllerBase
 | 
					class IssuesController extends IssuesControllerBase
 | 
				
			||||||
  with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
 | 
					  with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -156,7 +156,7 @@ trait PullRequestsControllerBase extends ControllerBase {
 | 
				
			|||||||
            val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
 | 
					            val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
 | 
				
			||||||
            mergeCommit.setAuthor(personIdent)
 | 
					            mergeCommit.setAuthor(personIdent)
 | 
				
			||||||
            mergeCommit.setCommitter(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)
 | 
					                                   form.message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // insertObject and got mergeCommit Object Id
 | 
					            // insertObject and got mergeCommit Object Id
 | 
				
			||||||
@@ -443,7 +443,7 @@ trait PullRequestsControllerBase extends ControllerBase {
 | 
				
			|||||||
      val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
 | 
					      val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
 | 
				
			||||||
        new CommitInfo(revCommit)
 | 
					        new CommitInfo(revCommit)
 | 
				
			||||||
      }.toList.splitWith { (commit1, commit2) =>
 | 
					      }.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)
 | 
					      val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,23 +8,31 @@ import _root_.util._
 | 
				
			|||||||
import service._
 | 
					import service._
 | 
				
			||||||
import org.scalatra._
 | 
					import org.scalatra._
 | 
				
			||||||
import java.io.File
 | 
					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.eclipse.jgit.lib._
 | 
				
			||||||
import org.apache.commons.io.FileUtils
 | 
					import org.apache.commons.io.FileUtils
 | 
				
			||||||
import org.eclipse.jgit.treewalk._
 | 
					import org.eclipse.jgit.treewalk._
 | 
				
			||||||
import java.util.zip.{ZipEntry, ZipOutputStream}
 | 
					 | 
				
			||||||
import jp.sf.amateras.scalatra.forms._
 | 
					import jp.sf.amateras.scalatra.forms._
 | 
				
			||||||
import org.eclipse.jgit.dircache.DirCache
 | 
					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
 | 
					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.
 | 
					 * The repository viewer.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
trait RepositoryViewerControllerBase extends ControllerBase {
 | 
					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(
 | 
					  case class EditorForm(
 | 
				
			||||||
    branch: String,
 | 
					    branch: String,
 | 
				
			||||||
@@ -32,6 +40,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
    content: String,
 | 
					    content: String,
 | 
				
			||||||
    message: Option[String],
 | 
					    message: Option[String],
 | 
				
			||||||
    charset: String,
 | 
					    charset: String,
 | 
				
			||||||
 | 
					    lineSeparator: String,
 | 
				
			||||||
    newFileName: String,
 | 
					    newFileName: String,
 | 
				
			||||||
    oldFileName: Option[String]
 | 
					    oldFileName: Option[String]
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
@@ -49,6 +58,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
    "content"       -> trim(label("Content", text(required))),
 | 
					    "content"       -> trim(label("Content", text(required))),
 | 
				
			||||||
    "message"       -> trim(label("Message", optional(text()))),
 | 
					    "message"       -> trim(label("Message", optional(text()))),
 | 
				
			||||||
    "charset"       -> trim(label("Charset", text(required))),
 | 
					    "charset"       -> trim(label("Charset", text(required))),
 | 
				
			||||||
 | 
					    "lineSeparator" -> trim(label("Line Separator", text(required))),
 | 
				
			||||||
    "newFileName"   -> trim(label("Filename", text(required))),
 | 
					    "newFileName"   -> trim(label("Filename", text(required))),
 | 
				
			||||||
    "oldFileName"   -> trim(label("Old filename", optional(text())))
 | 
					    "oldFileName"   -> trim(label("Old filename", optional(text())))
 | 
				
			||||||
  )(EditorForm.apply)
 | 
					  )(EditorForm.apply)
 | 
				
			||||||
@@ -101,7 +111,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
        case Right((logs, hasNext)) =>
 | 
					        case Right((logs, hasNext)) =>
 | 
				
			||||||
          repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
 | 
					          repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
 | 
				
			||||||
            logs.splitWith{ (commit1, commit2) =>
 | 
					            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)
 | 
					            }, page, hasNext)
 | 
				
			||||||
        case Left(_) => NotFound
 | 
					        case Left(_) => NotFound
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -142,7 +152,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
 | 
					  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}"))
 | 
					      form.message.getOrElse(s"Create ${form.newFileName}"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
 | 
					    redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
 | 
				
			||||||
@@ -151,7 +162,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
 | 
					  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)){
 | 
					      if(form.oldFileName.exists(_ == form.newFileName)){
 | 
				
			||||||
        form.message.getOrElse(s"Update ${form.newFileName}")
 | 
					        form.message.getOrElse(s"Update ${form.newFileName}")
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
@@ -179,6 +191,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
 | 
					    using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
 | 
				
			||||||
      val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
 | 
					      val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
 | 
				
			||||||
 | 
					      val lastModifiedCommit = JGitUtil.getLastModifiedCommit(git, revCommit, path)
 | 
				
			||||||
      getPathObjectId(git, path, revCommit).map { objectId =>
 | 
					      getPathObjectId(git, path, revCommit).map { objectId =>
 | 
				
			||||||
        if(raw){
 | 
					        if(raw){
 | 
				
			||||||
          // Download
 | 
					          // Download
 | 
				
			||||||
@@ -188,7 +201,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
 | 
					          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
 | 
					      } getOrElse NotFound
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -252,50 +265,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
   * Download repository contents as an archive.
 | 
					   * Download repository contents as an archive.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  get("/:owner/:repository/archive/*")(referrersOnly { repository =>
 | 
					  get("/:owner/:repository/archive/*")(referrersOnly { repository =>
 | 
				
			||||||
    val name = multiParams("splat").head
 | 
					    multiParams("splat").head match {
 | 
				
			||||||
 | 
					      case name if name.endsWith(".zip") =>
 | 
				
			||||||
    if(name.endsWith(".zip")){
 | 
					        archiveRepository(name, ".zip", repository)
 | 
				
			||||||
      val revision = name.stripSuffix(".zip")
 | 
					      case name if name.endsWith(".tar.gz") =>
 | 
				
			||||||
      val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
 | 
					        archiveRepository(name, ".tar.gz", repository)
 | 
				
			||||||
      if(workDir.exists){
 | 
					      case _ => BadRequest
 | 
				
			||||||
        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
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -337,10 +312,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
      repo.html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
 | 
					      repo.html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
 | 
					      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
 | 
					        // get specified commit
 | 
				
			||||||
        JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
 | 
					        JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
 | 
				
			||||||
          defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
 | 
					          defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
 | 
				
			||||||
 | 
					            val lastModifiedCommit = if(path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path)
 | 
				
			||||||
            // get files
 | 
					            // get files
 | 
				
			||||||
            val files = JGitUtil.getFileList(git, revision, path)
 | 
					            val files = JGitUtil.getFileList(git, revision, path)
 | 
				
			||||||
            val parentPath = if (path == ".") Nil else path.split("/").toList
 | 
					            val parentPath = if (path == ".") Nil else path.split("/").toList
 | 
				
			||||||
@@ -355,7 +330,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            repo.html.files(revision, repository,
 | 
					            repo.html.files(revision, repository,
 | 
				
			||||||
              if(path == ".") Nil else path.split("/").toList, // current path
 | 
					              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))
 | 
					              files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount))
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } getOrElse NotFound
 | 
					        } getOrElse NotFound
 | 
				
			||||||
@@ -376,7 +351,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
        val builder  = DirCache.newInCore.builder()
 | 
					        val builder  = DirCache.newInCore.builder()
 | 
				
			||||||
        val inserter = git.getRepository.newObjectInserter()
 | 
					        val inserter = git.getRepository.newObjectInserter()
 | 
				
			||||||
        val headName = s"refs/heads/${branch}"
 | 
					        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) =>
 | 
					        JGitUtil.processTree(git, headTip){ (path, tree) =>
 | 
				
			||||||
          if(!newPath.exists(_ == path) && !oldPath.exists(_ == path)){
 | 
					          if(!newPath.exists(_ == path) && !oldPath.exists(_ == path)){
 | 
				
			||||||
@@ -391,7 +366,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
        builder.finish()
 | 
					        builder.finish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter),
 | 
					        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.flush()
 | 
				
			||||||
        inserter.release()
 | 
					        inserter.release()
 | 
				
			||||||
@@ -408,8 +383,19 @@ trait RepositoryViewerControllerBase extends ControllerBase {
 | 
				
			|||||||
        recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
 | 
					        recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
 | 
				
			||||||
          List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
 | 
					          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 _ =>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -429,4 +415,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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,8 @@ import ssh.SshServer
 | 
				
			|||||||
import org.apache.commons.io.FileUtils
 | 
					import org.apache.commons.io.FileUtils
 | 
				
			||||||
import java.io.FileInputStream
 | 
					import java.io.FileInputStream
 | 
				
			||||||
import plugin.{Plugin, PluginSystem}
 | 
					import plugin.{Plugin, PluginSystem}
 | 
				
			||||||
 | 
					import org.scalatra.Ok
 | 
				
			||||||
 | 
					import util.Implicits._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SystemSettingsController extends SystemSettingsControllerBase
 | 
					class SystemSettingsController extends SystemSettingsControllerBase
 | 
				
			||||||
  with AccountService with AdminAuthenticator
 | 
					  with AccountService with AdminAuthenticator
 | 
				
			||||||
@@ -41,8 +43,9 @@ trait SystemSettingsControllerBase extends ControllerBase {
 | 
				
			|||||||
        "bindPassword"             -> trim(label("Bind Password", optional(text()))),
 | 
					        "bindPassword"             -> trim(label("Bind Password", optional(text()))),
 | 
				
			||||||
        "baseDN"                   -> trim(label("Base DN", text(required))),
 | 
					        "baseDN"                   -> trim(label("Base DN", text(required))),
 | 
				
			||||||
        "userNameAttribute"        -> trim(label("User name attribute", 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()))),
 | 
					        "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()))),
 | 
					        "tls"                      -> trim(label("Enable TLS", optional(boolean()))),
 | 
				
			||||||
        "keystore"                 -> trim(label("Keystore", optional(text())))
 | 
					        "keystore"                 -> trim(label("Keystore", optional(text())))
 | 
				
			||||||
    )(Ldap.apply))
 | 
					    )(Ldap.apply))
 | 
				
			||||||
@@ -81,44 +84,43 @@ trait SystemSettingsControllerBase extends ControllerBase {
 | 
				
			|||||||
    redirect("/admin/system")
 | 
					    redirect("/admin/system")
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO Enable commented code to enable plug-in system
 | 
					  get("/admin/plugins")(adminOnly {
 | 
				
			||||||
//  get("/admin/plugins")(adminOnly {
 | 
					    val installedPlugins = plugin.PluginSystem.plugins
 | 
				
			||||||
//    val installedPlugins = plugin.PluginSystem.plugins
 | 
					    val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable")
 | 
				
			||||||
//    val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable")
 | 
					    admin.plugins.html.installed(installedPlugins, updatablePlugins)
 | 
				
			||||||
//    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 {
 | 
					  post("/admin/plugins/_update", pluginForm)(adminOnly { form =>
 | 
				
			||||||
//    admin.plugins.html.console()
 | 
					    deletePlugins(form.pluginIds)
 | 
				
			||||||
//  })
 | 
					    installPlugins(form.pluginIds)
 | 
				
			||||||
//
 | 
					    redirect("/admin/plugins")
 | 
				
			||||||
//  post("/admin/plugins/console")(adminOnly {
 | 
					  })
 | 
				
			||||||
//    val script = request.getParameter("script")
 | 
					
 | 
				
			||||||
//    val result = plugin.JavaScriptPlugin.evaluateJavaScript(script)
 | 
					  post("/admin/plugins/_delete", pluginForm)(adminOnly { form =>
 | 
				
			||||||
//    Ok(result)
 | 
					    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?
 | 
					  // TODO Move these methods to PluginSystem or Service?
 | 
				
			||||||
  private def deletePlugins(pluginIds: List[String]): Unit = {
 | 
					  private def deletePlugins(pluginIds: List[String]): Unit = {
 | 
				
			||||||
@@ -137,9 +139,10 @@ trait SystemSettingsControllerBase extends ControllerBase {
 | 
				
			|||||||
    val installedPlugins = plugin.PluginSystem.plugins
 | 
					    val installedPlugins = plugin.PluginSystem.plugins
 | 
				
			||||||
    getAvailablePlugins(installedPlugins).filter(x => pluginIds.contains(x.id)).foreach { plugin =>
 | 
					    getAvailablePlugins(installedPlugins).filter(x => pluginIds.contains(x.id)).foreach { plugin =>
 | 
				
			||||||
      val pluginDir = new java.io.File(PluginHome, plugin.id)
 | 
					      val pluginDir = new java.io.File(PluginHome, plugin.id)
 | 
				
			||||||
      if(!pluginDir.exists){
 | 
					      if(pluginDir.exists){
 | 
				
			||||||
        FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
 | 
					        FileUtils.deleteDirectory(pluginDir)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
 | 
				
			||||||
      PluginSystem.installPlugin(plugin.id)
 | 
					      PluginSystem.installPlugin(plugin.id)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -182,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(){
 | 
					  private def members: Constraint = new Constraint(){
 | 
				
			||||||
    override def validate(name: String, value: String, messages: Messages): Option[String] = {
 | 
					    override def validate(name: String, value: String, messages: Messages): Option[String] = {
 | 
				
			||||||
      if(value.split(",").exists {
 | 
					      if(value.split(",").exists {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,9 +21,9 @@ trait AccountComponent { self: Profile =>
 | 
				
			|||||||
    val removed = column[Boolean]("REMOVED")
 | 
					    val removed = column[Boolean]("REMOVED")
 | 
				
			||||||
    def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply)
 | 
					    def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed) <> (Account.tupled, Account.unapply)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class Account(
 | 
					case class Account(
 | 
				
			||||||
 | 
					 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  fullName: String,
 | 
					  fullName: String,
 | 
				
			||||||
  mailAddress: String,
 | 
					  mailAddress: String,
 | 
				
			||||||
@@ -36,5 +36,4 @@ trait AccountComponent { self: Profile =>
 | 
				
			|||||||
  image: Option[String],
 | 
					  image: Option[String],
 | 
				
			||||||
  isGroupAccount: Boolean,
 | 
					  isGroupAccount: Boolean,
 | 
				
			||||||
  isRemoved: Boolean
 | 
					  isRemoved: Boolean
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,9 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
    val activityDate = column[java.util.Date]("ACTIVITY_DATE")
 | 
					    val activityDate = column[java.util.Date]("ACTIVITY_DATE")
 | 
				
			||||||
    def * = (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
 | 
					    def * = (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class Activity(
 | 
					case class Activity(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  activityUserName: String,
 | 
					  activityUserName: String,
 | 
				
			||||||
@@ -25,5 +26,4 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
  additionalInfo: Option[String],
 | 
					  additionalInfo: Option[String],
 | 
				
			||||||
  activityDate: java.util.Date,
 | 
					  activityDate: java.util.Date,
 | 
				
			||||||
  activityId: Int = 0
 | 
					  activityId: Int = 0
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,40 +8,40 @@ protected[model] trait TemplateComponent { self: Profile =>
 | 
				
			|||||||
    val repositoryName = column[String]("REPOSITORY_NAME")
 | 
					    val repositoryName = column[String]("REPOSITORY_NAME")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def byRepository(owner: String, repository: String) =
 | 
					    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]) =
 | 
					    def byRepository(userName: Column[String], repositoryName: Column[String]) =
 | 
				
			||||||
      (this.userName is userName) && (this.repositoryName is repositoryName)
 | 
					      (this.userName === userName) && (this.repositoryName === repositoryName)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  trait IssueTemplate extends BasicTemplate { self: Table[_] =>
 | 
					  trait IssueTemplate extends BasicTemplate { self: Table[_] =>
 | 
				
			||||||
    val issueId = column[Int]("ISSUE_ID")
 | 
					    val issueId = column[Int]("ISSUE_ID")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def byIssue(owner: String, repository: String, issueId: Int) =
 | 
					    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]) =
 | 
					    def byIssue(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) =
 | 
				
			||||||
      byRepository(userName, repositoryName) && (this.issueId is issueId)
 | 
					      byRepository(userName, repositoryName) && (this.issueId === issueId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  trait LabelTemplate extends BasicTemplate { self: Table[_] =>
 | 
					  trait LabelTemplate extends BasicTemplate { self: Table[_] =>
 | 
				
			||||||
    val labelId = column[Int]("LABEL_ID")
 | 
					    val labelId = column[Int]("LABEL_ID")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def byLabel(owner: String, repository: String, labelId: Int) =
 | 
					    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]) =
 | 
					    def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
 | 
				
			||||||
      byRepository(userName, repositoryName) && (this.labelId is labelId)
 | 
					      byRepository(userName, repositoryName) && (this.labelId === labelId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
 | 
					  trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
 | 
				
			||||||
    val milestoneId = column[Int]("MILESTONE_ID")
 | 
					    val milestoneId = column[Int]("MILESTONE_ID")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def byMilestone(owner: String, repository: String, milestoneId: Int) =
 | 
					    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]) =
 | 
					    def byMilestone(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) =
 | 
				
			||||||
      byRepository(userName, repositoryName) && (this.milestoneId is milestoneId)
 | 
					      byRepository(userName, repositoryName) && (this.milestoneId === milestoneId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,12 +10,12 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
    def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply)
 | 
					    def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def byPrimaryKey(owner: String, repository: String, collaborator: String) =
 | 
					    def byPrimaryKey(owner: String, repository: String, collaborator: String) =
 | 
				
			||||||
      byRepository(owner, repository) && (collaboratorName is collaborator.bind)
 | 
					      byRepository(owner, repository) && (collaboratorName === collaborator.bind)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class Collaborator(
 | 
					case class Collaborator(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  collaboratorName: String
 | 
					  collaboratorName: String
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,10 +11,10 @@ trait GroupMemberComponent { self: Profile =>
 | 
				
			|||||||
    val isManager = column[Boolean]("MANAGER")
 | 
					    val isManager = column[Boolean]("MANAGER")
 | 
				
			||||||
    def * = (groupName, userName, isManager) <> (GroupMember.tupled, GroupMember.unapply)
 | 
					    def * = (groupName, userName, isManager) <> (GroupMember.tupled, GroupMember.unapply)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class GroupMember(
 | 
					case class GroupMember(
 | 
				
			||||||
  groupName: String,
 | 
					  groupName: String,
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  isManager: Boolean
 | 
					  isManager: Boolean
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,8 +31,9 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
 | 
					    def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class Issue(
 | 
					case class Issue(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  issueId: Int,
 | 
					  issueId: Int,
 | 
				
			||||||
@@ -44,5 +45,5 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
  closed: Boolean,
 | 
					  closed: Boolean,
 | 
				
			||||||
  registeredDate: java.util.Date,
 | 
					  registeredDate: java.util.Date,
 | 
				
			||||||
  updatedDate: java.util.Date,
 | 
					  updatedDate: java.util.Date,
 | 
				
			||||||
    isPullRequest: Boolean)
 | 
					  isPullRequest: Boolean
 | 
				
			||||||
}
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,10 +17,11 @@ trait IssueCommentComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
    val updatedDate = column[java.util.Date]("UPDATED_DATE")
 | 
					    val updatedDate = column[java.util.Date]("UPDATED_DATE")
 | 
				
			||||||
    def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
 | 
					    def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def byPrimaryKey(commentId: Int) = this.commentId is commentId.bind
 | 
					    def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class IssueComment(
 | 
					case class IssueComment(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  issueId: Int,
 | 
					  issueId: Int,
 | 
				
			||||||
@@ -30,5 +31,4 @@ trait IssueCommentComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
  content: String,
 | 
					  content: String,
 | 
				
			||||||
  registeredDate: java.util.Date,
 | 
					  registeredDate: java.util.Date,
 | 
				
			||||||
  updatedDate: java.util.Date
 | 
					  updatedDate: java.util.Date
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,12 +8,13 @@ trait IssueLabelComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
  class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate {
 | 
					  class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate {
 | 
				
			||||||
    def * = (userName, repositoryName, issueId, labelId) <> (IssueLabel.tupled, IssueLabel.unapply)
 | 
					    def * = (userName, repositoryName, issueId, labelId) <> (IssueLabel.tupled, IssueLabel.unapply)
 | 
				
			||||||
    def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) =
 | 
					    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(
 | 
					case class IssueLabel(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  issueId: Int,
 | 
					  issueId: Int,
 | 
				
			||||||
    labelId: Int)
 | 
					  labelId: Int
 | 
				
			||||||
}
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,8 +14,9 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
    def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId)
 | 
					    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)
 | 
					    def byPrimaryKey(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) = byLabel(userName, repositoryName, labelId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class Label(
 | 
					case class Label(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  labelId: Int = 0,
 | 
					  labelId: Int = 0,
 | 
				
			||||||
@@ -33,5 +34,4 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
      "FFFFFF"
 | 
					      "FFFFFF"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,13 +17,14 @@ trait MilestoneComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
    def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
 | 
					    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)
 | 
					    def byPrimaryKey(userName: Column[String], repositoryName: Column[String], milestoneId: Column[Int]) = byMilestone(userName, repositoryName, milestoneId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class Milestone(
 | 
					case class Milestone(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  milestoneId: Int = 0,
 | 
					  milestoneId: Int = 0,
 | 
				
			||||||
  title: String,
 | 
					  title: String,
 | 
				
			||||||
  description: Option[String],
 | 
					  description: Option[String],
 | 
				
			||||||
  dueDate: Option[java.util.Date],
 | 
					  dueDate: Option[java.util.Date],
 | 
				
			||||||
    closedDate: Option[java.util.Date])
 | 
					  closedDate: Option[java.util.Date]
 | 
				
			||||||
}
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								src/main/scala/model/Plugin.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/main/scala/model/Plugin.scala
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					trait PluginComponent extends TemplateComponent { self: Profile =>
 | 
				
			||||||
 | 
					  import profile.simple._
 | 
				
			||||||
 | 
					  import self._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lazy val Plugins = TableQuery[Plugins]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  class Plugins(tag: Tag) extends Table[Plugin](tag, "PLUGIN"){
 | 
				
			||||||
 | 
					    val pluginId = column[String]("PLUGIN_ID", O PrimaryKey)
 | 
				
			||||||
 | 
					    val version = column[String]("VERSION")
 | 
				
			||||||
 | 
					    def * = (pluginId, version) <> (Plugin.tupled, Plugin.unapply)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					case class Plugin(
 | 
				
			||||||
 | 
					  pluginId: String,
 | 
				
			||||||
 | 
					  version: String
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
package model
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import slick.driver.JdbcProfile
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
trait Profile {
 | 
					trait Profile {
 | 
				
			||||||
  val profile: JdbcProfile
 | 
					  val profile: slick.driver.JdbcProfile
 | 
				
			||||||
  import profile.simple._
 | 
					  import profile.simple._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // java.util.Date Mapped Column Types
 | 
					  // java.util.Date Mapped Column Types
 | 
				
			||||||
@@ -17,3 +15,28 @@ trait Profile {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,8 +17,9 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
    def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = byIssue(userName, repositoryName, issueId)
 | 
					    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)
 | 
					    def byPrimaryKey(userName: Column[String], repositoryName: Column[String], issueId: Column[Int]) = byIssue(userName, repositoryName, issueId)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class PullRequest(
 | 
					case class PullRequest(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  issueId: Int,
 | 
					  issueId: Int,
 | 
				
			||||||
@@ -28,5 +29,4 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
  requestBranch: String,
 | 
					  requestBranch: String,
 | 
				
			||||||
  commitIdFrom: String,
 | 
					  commitIdFrom: String,
 | 
				
			||||||
  commitIdTo: String
 | 
					  commitIdTo: String
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,8 +21,9 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
 | 
					    def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class Repository(
 | 
					case class Repository(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  isPrivate: Boolean,
 | 
					  isPrivate: Boolean,
 | 
				
			||||||
@@ -35,5 +36,4 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
  originRepositoryName: Option[String],
 | 
					  originRepositoryName: Option[String],
 | 
				
			||||||
  parentUserName: Option[String],
 | 
					  parentUserName: Option[String],
 | 
				
			||||||
  parentRepositoryName: Option[String]
 | 
					  parentRepositoryName: Option[String]
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,13 +12,13 @@ trait SshKeyComponent { self: Profile =>
 | 
				
			|||||||
    val publicKey = column[String]("PUBLIC_KEY")
 | 
					    val publicKey = column[String]("PUBLIC_KEY")
 | 
				
			||||||
    def * = (userName, sshKeyId, title, publicKey) <> (SshKey.tupled, SshKey.unapply)
 | 
					    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(
 | 
					case class SshKey(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  sshKeyId: Int = 0,
 | 
					  sshKeyId: Int = 0,
 | 
				
			||||||
  title: String,
 | 
					  title: String,
 | 
				
			||||||
  publicKey: String
 | 
					  publicKey: String
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,12 +9,12 @@ trait WebHookComponent extends TemplateComponent { self: Profile =>
 | 
				
			|||||||
    val url = column[String]("URL")
 | 
					    val url = column[String]("URL")
 | 
				
			||||||
    def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply)
 | 
					    def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url is url.bind)
 | 
					    def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class WebHook(
 | 
					case class WebHook(
 | 
				
			||||||
  userName: String,
 | 
					  userName: String,
 | 
				
			||||||
  repositoryName: String,
 | 
					  repositoryName: String,
 | 
				
			||||||
  url: String
 | 
					  url: String
 | 
				
			||||||
  )
 | 
					)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,3 @@
 | 
				
			|||||||
package object model extends {
 | 
					package object model {
 | 
				
			||||||
  // TODO [Slick 2.0]Should be configurable?
 | 
					  type Session = slick.jdbc.JdbcBackend#Session
 | 
				
			||||||
  val profile = slick.driver.H2Driver
 | 
					 | 
				
			||||||
  // TODO [Slick 2.0]To avoid compilation error about delete invocation. Why can't this error be resolved by import profile.simple._?
 | 
					 | 
				
			||||||
  val simple = profile.simple
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
} 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 Profile {
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Returns system date.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  def currentDate = new java.util.Date()
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,88 +0,0 @@
 | 
				
			|||||||
package plugin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.mozilla.javascript.{Context => JsContext}
 | 
					 | 
				
			||||||
import org.mozilla.javascript.{Function => JsFunction}
 | 
					 | 
				
			||||||
import scala.collection.mutable.ListBuffer
 | 
					 | 
				
			||||||
import plugin.PluginSystem.{Action, GlobalMenu, RepositoryMenu}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class JavaScriptPlugin(val id: String, val version: String,
 | 
					 | 
				
			||||||
                       val author: String, val url: String, val description: String) extends Plugin {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private val repositoryMenuList   = ListBuffer[RepositoryMenu]()
 | 
					 | 
				
			||||||
  private val globalMenuList       = ListBuffer[GlobalMenu]()
 | 
					 | 
				
			||||||
  private val repositoryActionList = ListBuffer[Action]()
 | 
					 | 
				
			||||||
  private val globalActionList     = ListBuffer[Action]()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def repositoryMenus   : List[RepositoryMenu] = repositoryMenuList.toList
 | 
					 | 
				
			||||||
  def globalMenus       : List[GlobalMenu]     = globalMenuList.toList
 | 
					 | 
				
			||||||
  def repositoryActions : List[Action]         = repositoryActionList.toList
 | 
					 | 
				
			||||||
  def globalActions     : List[Action]         = globalActionList.toList
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def addRepositoryMenu(label: String, name: String, url: String, icon: String, condition: JsFunction): Unit = {
 | 
					 | 
				
			||||||
    repositoryMenuList += RepositoryMenu(label, name, url, icon, (context) => {
 | 
					 | 
				
			||||||
      val context = JsContext.enter()
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        condition.call(context, condition, condition, Array(context)).asInstanceOf[Boolean]
 | 
					 | 
				
			||||||
      } finally {
 | 
					 | 
				
			||||||
        JsContext.exit()
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def addGlobalMenu(label: String, url: String, icon: String, condition: JsFunction): Unit = {
 | 
					 | 
				
			||||||
    globalMenuList += GlobalMenu(label, url, icon, (context) => {
 | 
					 | 
				
			||||||
      val context = JsContext.enter()
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        condition.call(context, condition, condition, Array(context)).asInstanceOf[Boolean]
 | 
					 | 
				
			||||||
      } finally {
 | 
					 | 
				
			||||||
        JsContext.exit()
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def addGlobalAction(path: String, function: JsFunction): Unit = {
 | 
					 | 
				
			||||||
    globalActionList += Action(path, (request, response) => {
 | 
					 | 
				
			||||||
      val context = JsContext.enter()
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        function.call(context, function, function, Array(request, response))
 | 
					 | 
				
			||||||
      } finally {
 | 
					 | 
				
			||||||
        JsContext.exit()
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def addRepositoryAction(path: String, function: JsFunction): Unit = {
 | 
					 | 
				
			||||||
    repositoryActionList += Action(path, (request, response) => {
 | 
					 | 
				
			||||||
      val context = JsContext.enter()
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        function.call(context, function, function, Array(request, response))
 | 
					 | 
				
			||||||
      } finally {
 | 
					 | 
				
			||||||
        JsContext.exit()
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
object JavaScriptPlugin {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def define(id: String, version: String, author: String, url: String, description: String)
 | 
					 | 
				
			||||||
    = new JavaScriptPlugin(id, version, author, url, description)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def evaluateJavaScript(script: String, vars: Map[String, Any] = Map.empty): Any = {
 | 
					 | 
				
			||||||
    val context = JsContext.enter()
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      val scope = context.initStandardObjects()
 | 
					 | 
				
			||||||
      scope.put("PluginSystem", scope, PluginSystem)
 | 
					 | 
				
			||||||
      scope.put("JavaScriptPlugin", scope, this)
 | 
					 | 
				
			||||||
      vars.foreach { case (key, value) =>
 | 
					 | 
				
			||||||
        scope.put(key, scope, value)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      val result = context.evaluateString(scope, script, "<cmd>", 1, null)
 | 
					 | 
				
			||||||
      result
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
      JsContext.exit
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
package plugin
 | 
					package plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import plugin.PluginSystem.{Action, GlobalMenu, RepositoryMenu}
 | 
					import plugin.PluginSystem._
 | 
				
			||||||
 | 
					import java.sql.Connection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait Plugin {
 | 
					trait Plugin {
 | 
				
			||||||
  val id: String
 | 
					  val id: String
 | 
				
			||||||
@@ -11,6 +12,11 @@ trait Plugin {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  def repositoryMenus       : List[RepositoryMenu]
 | 
					  def repositoryMenus       : List[RepositoryMenu]
 | 
				
			||||||
  def globalMenus           : List[GlobalMenu]
 | 
					  def globalMenus           : List[GlobalMenu]
 | 
				
			||||||
  def repositoryActions : List[Action]
 | 
					  def repositoryActions     : List[RepositoryAction]
 | 
				
			||||||
  def globalActions         : List[Action]
 | 
					  def globalActions         : List[Action]
 | 
				
			||||||
 | 
					  def javaScripts           : List[JavaScript]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object PluginConnectionHolder {
 | 
				
			||||||
 | 
					  val threadLocal = new ThreadLocal[Connection]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,19 +1,24 @@
 | 
				
			|||||||
package plugin
 | 
					package plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import app.Context
 | 
					 | 
				
			||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
 | 
					import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
 | 
				
			||||||
import org.slf4j.LoggerFactory
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
import java.util.concurrent.atomic.AtomicBoolean
 | 
					import java.util.concurrent.atomic.AtomicBoolean
 | 
				
			||||||
import util.Directory._
 | 
					import util.Directory._
 | 
				
			||||||
import util.ControlUtil._
 | 
					import util.ControlUtil._
 | 
				
			||||||
import org.apache.commons.io.FileUtils
 | 
					import org.apache.commons.io.{IOUtils, FileUtils}
 | 
				
			||||||
import util.JGitUtil
 | 
					import Security._
 | 
				
			||||||
import org.eclipse.jgit.api.Git
 | 
					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.
 | 
					 * Provides extension points to plug-ins.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
object PluginSystem {
 | 
					object PluginSystem extends PluginService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private val logger = LoggerFactory.getLogger(PluginSystem.getClass)
 | 
					  private val logger = LoggerFactory.getLogger(PluginSystem.getClass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,8 +32,21 @@ object PluginSystem {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  def plugins: List[Plugin] = pluginsMap.values.toList
 | 
					  def plugins: List[Plugin] = pluginsMap.values.toList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def uninstall(id: String): Unit = {
 | 
					  def uninstall(id: String)(implicit session: Session): Unit = {
 | 
				
			||||||
    pluginsMap.remove(id)
 | 
					    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
 | 
					  def repositories: List[PluginRepository] = repositoriesList.toList
 | 
				
			||||||
@@ -36,7 +54,7 @@ object PluginSystem {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Initializes the plugin system. Load scripts from GITBUCKET_HOME/plugins.
 | 
					   * Initializes the plugin system. Load scripts from GITBUCKET_HOME/plugins.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  def init(): Unit = {
 | 
					  def init()(implicit session: Session): Unit = {
 | 
				
			||||||
    if(initialized.compareAndSet(false, true)){
 | 
					    if(initialized.compareAndSet(false, true)){
 | 
				
			||||||
      // Load installed plugins
 | 
					      // Load installed plugins
 | 
				
			||||||
      val pluginDir = new java.io.File(PluginHome)
 | 
					      val pluginDir = new java.io.File(PluginHome)
 | 
				
			||||||
@@ -51,41 +69,107 @@ object PluginSystem {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO Method name seems to not so good.
 | 
					  // TODO Method name seems to not so good.
 | 
				
			||||||
  def installPlugin(id: String): Unit = {
 | 
					  def installPlugin(id: String)(implicit session: Session): Unit = {
 | 
				
			||||||
    val pluginDir = new java.io.File(PluginHome)
 | 
					    val pluginHome = new java.io.File(PluginHome)
 | 
				
			||||||
    val javaScriptFile = new java.io.File(pluginDir, id + "/plugin.js")
 | 
					    val pluginDir  = new java.io.File(pluginHome, id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(javaScriptFile.exists && javaScriptFile.isFile){
 | 
					    val scalaFile = new java.io.File(pluginDir, "plugin.scala")
 | 
				
			||||||
 | 
					    if(scalaFile.exists && scalaFile.isFile){
 | 
				
			||||||
      val properties = new java.util.Properties()
 | 
					      val properties = new java.util.Properties()
 | 
				
			||||||
      using(new java.io.FileInputStream(new java.io.File(pluginDir, id + "/plugin.properties"))){ in =>
 | 
					      using(new java.io.FileInputStream(new java.io.File(pluginDir, "plugin.properties"))){ in =>
 | 
				
			||||||
        properties.load(in)
 | 
					        properties.load(in)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      val script = FileUtils.readFileToString(javaScriptFile, "UTF-8")
 | 
					      val pluginId     = properties.getProperty("id")
 | 
				
			||||||
 | 
					      val version      = properties.getProperty("version")
 | 
				
			||||||
 | 
					      val author       = properties.getProperty("author")
 | 
				
			||||||
 | 
					      val url          = properties.getProperty("url")
 | 
				
			||||||
 | 
					      val description  = properties.getProperty("description")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      val source = s"""
 | 
				
			||||||
 | 
					        |val id          = "${pluginId}"
 | 
				
			||||||
 | 
					        |val version     = "${version}"
 | 
				
			||||||
 | 
					        |val author      = "${author}"
 | 
				
			||||||
 | 
					        |val url         = "${url}"
 | 
				
			||||||
 | 
					        |val description = "${description}"
 | 
				
			||||||
 | 
					      """.stripMargin + FileUtils.readFileToString(scalaFile, "UTF-8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        JavaScriptPlugin.evaluateJavaScript(script, Map(
 | 
					        // Compile and eval Scala source code
 | 
				
			||||||
          "id"          -> properties.getProperty("id"),
 | 
					        ScalaPlugin.eval(pluginDir.listFiles.filter(_.getName.endsWith(".scala.html")).map { file =>
 | 
				
			||||||
          "version"     -> properties.getProperty("version"),
 | 
					          ScalaPlugin.compileTemplate(
 | 
				
			||||||
          "author"      -> properties.getProperty("author"),
 | 
					            id.replaceAll("-", ""),
 | 
				
			||||||
          "url"         -> properties.getProperty("url"),
 | 
					            file.getName.replaceAll("\\.scala\\.html$", ""),
 | 
				
			||||||
          "description" -> properties.getProperty("description")
 | 
					            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 {
 | 
					      } catch {
 | 
				
			||||||
        case e: Exception => logger.warn(s"Error in plugin loading for ${javaScriptFile.getAbsolutePath}", e)
 | 
					        case e: Throwable => logger.warn(s"Error in plugin loading for ${scalaFile.getAbsolutePath}", e)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // 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 repositoryMenus       : List[RepositoryMenu]   = pluginsMap.values.flatMap(_.repositoryMenus).toList
 | 
				
			||||||
  def globalMenus           : List[GlobalMenu]       = pluginsMap.values.flatMap(_.globalMenus).toList
 | 
					  def globalMenus           : List[GlobalMenu]       = pluginsMap.values.flatMap(_.globalMenus).toList
 | 
				
			||||||
  def repositoryActions : List[Action]         = pluginsMap.values.flatMap(_.repositoryActions).toList
 | 
					  def repositoryActions     : List[RepositoryAction] = pluginsMap.values.flatMap(_.repositoryActions).toList
 | 
				
			||||||
  def globalActions         : List[Action]           = pluginsMap.values.flatMap(_.globalActions).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 classes to hold plug-ins information internally in GitBucket
 | 
				
			||||||
  case class PluginRepository(id: String, url: String)
 | 
					  case class PluginRepository(id: String, url: String)
 | 
				
			||||||
  case class GlobalMenu(label: String, url: String, icon: String, condition: Context => Boolean)
 | 
					  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 RepositoryMenu(label: String, name: String, url: String, icon: String, condition: Context => Boolean)
 | 
				
			||||||
  case class Action(path: String, function: (HttpServletRequest, HttpServletResponse) => Any)
 | 
					  case class Action(method: String, path: String, security: Security, function: (HttpServletRequest, HttpServletResponse, Context) => Any)
 | 
				
			||||||
 | 
					  case class RepositoryAction(method: String, path: String, security: Security, function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any)
 | 
				
			||||||
 | 
					  case class Button(label: String, href: String)
 | 
				
			||||||
 | 
					  case class JavaScript(filter: String => Boolean, script: String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Checks whether the plugin is updatable.
 | 
					   * Checks whether the plugin is updatable.
 | 
				
			||||||
@@ -107,17 +191,4 @@ object PluginSystem {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO This is a test
 | 
					 | 
				
			||||||
//  addGlobalMenu("Google", "http://www.google.co.jp/", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEvwAABL8BkeKJvAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIgSURBVEiJtdZNiI1hFAfw36ORhSFFPgYLszOKJAsWRLGzks1gYyFZKFs7C7K2Y2XDRiwmq9kIJWQjJR9Tk48xRtTIRwjH4p473nm99yLNqdNTz/mf//+555x7ektEmEmbNaPs6OkUKKX0YBmWp6/IE8bwIs8xjEfEt0aiiJBl6sEuXMRLfEf8pX/PnIvJ0TPFWxE4+w+Ef/Kzbd5qDx5l8H8tkku7LG17gH7sxWatevdhEUoXsjda5RnDTZzH6jagtMe0lHIa23AJw3iOiSRZlmJ9mfcyfTzFl2AldmI3rkbEkbrAYKrX7S1eVRyWVnxhQ87eiLjQ+o2/mtyve+PuYy3W4+EfsP2/TVGKTHRI+Iz9Fdx8XOmAnZjGWRMYqoF/4ESW4hpOYk1iZ2WsLjDUTeBYBfgeuyux2XiNT5hXud+DD5W8Y90EtifoSfultfjx7MVtrKzcr8No5m7vJtCLx1hQJ8/4IZzClpyoy5ibsYUYQW81Z9o2jYgPeKr15+poEXE9+1XF9WIkOaasaV2P4k4pZUdDbEm+VEQcjIgtEfGxlLIVd/Gs6TX1MhzQquU3HK1t23f4IsuS94fxNXMO/MbXIDBg+tidw5yMbcCmylSdqWEH/kagYLKWeAt9Fcxi3KhhJuXq6SqQBMO15NDalvswmLWux4cbuToIbMS9BpJOfg8bm7imtmmTlVJWaa3hpnU9nufziBjtyDHTny0/AaA7Qnb4AM4aAAAAAElFTkSuQmCC")
 | 
					 | 
				
			||||||
//    { context => context.loginAccount.isDefined }
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//  addRepositoryMenu("Board", "board", "/board", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAEvwAABL8BkeKJvAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAIgSURBVEiJtdZNiI1hFAfw36ORhSFFPgYLszOKJAsWRLGzks1gYyFZKFs7C7K2Y2XDRiwmq9kIJWQjJR9Tk48xRtTIRwjH4p473nm99yLNqdNTz/mf//+555x7ektEmEmbNaPs6OkUKKX0YBmWp6/IE8bwIs8xjEfEt0aiiJBl6sEuXMRLfEf8pX/PnIvJ0TPFWxE4+w+Ef/Kzbd5qDx5l8H8tkku7LG17gH7sxWatevdhEUoXsjda5RnDTZzH6jagtMe0lHIa23AJw3iOiSRZlmJ9mfcyfTzFl2AldmI3rkbEkbrAYKrX7S1eVRyWVnxhQ87eiLjQ+o2/mtyve+PuYy3W4+EfsP2/TVGKTHRI+Iz9Fdx8XOmAnZjGWRMYqoF/4ESW4hpOYk1iZ2WsLjDUTeBYBfgeuyux2XiNT5hXud+DD5W8Y90EtifoSfultfjx7MVtrKzcr8No5m7vJtCLx1hQJ8/4IZzClpyoy5ibsYUYQW81Z9o2jYgPeKr15+poEXE9+1XF9WIkOaasaV2P4k4pZUdDbEm+VEQcjIgtEfGxlLIVd/Gs6TX1MhzQquU3HK1t23f4IsuS94fxNXMO/MbXIDBg+tidw5yMbcCmylSdqWEH/kagYLKWeAt9Fcxi3KhhJuXq6SqQBMO15NDalvswmLWux4cbuToIbMS9BpJOfg8bm7imtmmTlVJWaa3hpnU9nufziBjtyDHTny0/AaA7Qnb4AM4aAAAAAElFTkSuQmCC")
 | 
					 | 
				
			||||||
//    { context => true}
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//  addGlobalAction("/hello"){ (request, response) =>
 | 
					 | 
				
			||||||
//    "Hello World!"
 | 
					 | 
				
			||||||
//  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,18 +50,17 @@ class PluginUpdateJob extends Job {
 | 
				
			|||||||
object PluginUpdateJob {
 | 
					object PluginUpdateJob {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def schedule(scheduler: Scheduler): Unit = {
 | 
					  def schedule(scheduler: Scheduler): Unit = {
 | 
				
			||||||
// TODO Enable commented code to enable plug-in system
 | 
					    val job = newJob(classOf[PluginUpdateJob])
 | 
				
			||||||
//    val job = newJob(classOf[PluginUpdateJob])
 | 
					      .withIdentity("pluginUpdateJob")
 | 
				
			||||||
//      .withIdentity("pluginUpdateJob")
 | 
					      .build()
 | 
				
			||||||
//      .build()
 | 
					
 | 
				
			||||||
//
 | 
					    val trigger = newTrigger()
 | 
				
			||||||
//    val trigger = newTrigger()
 | 
					      .withIdentity("pluginUpdateTrigger")
 | 
				
			||||||
//      .withIdentity("pluginUpdateTrigger")
 | 
					      .startNow()
 | 
				
			||||||
//      .startNow()
 | 
					      .withSchedule(simpleSchedule().withIntervalInHours(24).repeatForever())
 | 
				
			||||||
//      .withSchedule(simpleSchedule().withIntervalInHours(24).repeatForever())
 | 
					      .build()
 | 
				
			||||||
//      .build()
 | 
					
 | 
				
			||||||
//
 | 
					    scheduler.scheduleJob(job, trigger)
 | 
				
			||||||
//    scheduler.scheduleJob(job, trigger)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,9 +1,16 @@
 | 
				
			|||||||
package plugin
 | 
					package plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import app.Context
 | 
					 | 
				
			||||||
import scala.collection.mutable.ListBuffer
 | 
					import scala.collection.mutable.ListBuffer
 | 
				
			||||||
import plugin.PluginSystem.{Action, GlobalMenu, RepositoryMenu}
 | 
					 | 
				
			||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
 | 
					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.
 | 
					// TODO This is a sample implementation for Scala based plug-ins.
 | 
				
			||||||
class ScalaPlugin(val id: String, val version: String,
 | 
					class ScalaPlugin(val id: String, val version: String,
 | 
				
			||||||
@@ -11,13 +18,15 @@ class ScalaPlugin(val id: String, val version: String,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private val repositoryMenuList   = ListBuffer[RepositoryMenu]()
 | 
					  private val repositoryMenuList   = ListBuffer[RepositoryMenu]()
 | 
				
			||||||
  private val globalMenuList       = ListBuffer[GlobalMenu]()
 | 
					  private val globalMenuList       = ListBuffer[GlobalMenu]()
 | 
				
			||||||
  private val repositoryActionList = ListBuffer[Action]()
 | 
					  private val repositoryActionList = ListBuffer[RepositoryAction]()
 | 
				
			||||||
  private val globalActionList     = ListBuffer[Action]()
 | 
					  private val globalActionList     = ListBuffer[Action]()
 | 
				
			||||||
 | 
					  private val javaScriptList       = ListBuffer[JavaScript]()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def repositoryMenus       : List[RepositoryMenu]   = repositoryMenuList.toList
 | 
					  def repositoryMenus       : List[RepositoryMenu]   = repositoryMenuList.toList
 | 
				
			||||||
  def globalMenus           : List[GlobalMenu]       = globalMenuList.toList
 | 
					  def globalMenus           : List[GlobalMenu]       = globalMenuList.toList
 | 
				
			||||||
  def repositoryActions : List[Action]         = repositoryActionList.toList
 | 
					  def repositoryActions     : List[RepositoryAction] = repositoryActionList.toList
 | 
				
			||||||
  def globalActions         : List[Action]           = globalActionList.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 = {
 | 
					  def addRepositoryMenu(label: String, name: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = {
 | 
				
			||||||
    repositoryMenuList += RepositoryMenu(label, name, url, icon, condition)
 | 
					    repositoryMenuList += RepositoryMenu(label, name, url, icon, condition)
 | 
				
			||||||
@@ -27,12 +36,42 @@ class ScalaPlugin(val id: String, val version: String,
 | 
				
			|||||||
    globalMenuList += GlobalMenu(label, url, icon, condition)
 | 
					    globalMenuList += GlobalMenu(label, url, icon, condition)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def addGlobalAction(path: String)(function: (HttpServletRequest, HttpServletResponse) => Any): Unit = {
 | 
					  def addGlobalAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context) => Any): Unit = {
 | 
				
			||||||
    globalActionList += Action(path, function)
 | 
					    globalActionList += Action(method, path, security, function)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def addRepositoryAction(path: String)(function: (HttpServletRequest, HttpServletResponse) => Any): Unit = {
 | 
					  def addRepositoryAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any): Unit = {
 | 
				
			||||||
    repositoryActionList += Action(path, function)
 | 
					    repositoryActionList += RepositoryAction(method, path, security, function)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def addJavaScript(filter: String => Boolean, script: String): Unit = {
 | 
				
			||||||
 | 
					    javaScriptList += JavaScript(filter, script)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object ScalaPlugin {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def define(id: String, version: String, author: String, url: String, description: String)
 | 
				
			||||||
 | 
					    = new ScalaPlugin(id, version, author, url, description)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def eval(source: String): Any = {
 | 
				
			||||||
 | 
					    val toolbox = currentMirror.mkToolBox()
 | 
				
			||||||
 | 
					    val tree = toolbox.parse(source)
 | 
				
			||||||
 | 
					    toolbox.eval(tree)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def compileTemplate(packageName: String, name: String, source: String): String = {
 | 
				
			||||||
 | 
					    val result = TwirlCompiler.parseAndGenerateCodeNewParser(
 | 
				
			||||||
 | 
					      Array(packageName, name),
 | 
				
			||||||
 | 
					      source.getBytes("UTF-8"),
 | 
				
			||||||
 | 
					      Codec(scala.util.Properties.sourceEncoding),
 | 
				
			||||||
 | 
					      "",
 | 
				
			||||||
 | 
					      "play.twirl.api.HtmlFormat.Appendable",
 | 
				
			||||||
 | 
					      "play.twirl.api.HtmlFormat",
 | 
				
			||||||
 | 
					      "",
 | 
				
			||||||
 | 
					      false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    result.replaceFirst("package .*", "")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								src/main/scala/plugin/Security.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/main/scala/plugin/Security.scala
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Defines enum case classes to specify permission for actions which is provided by plugin.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					object Security {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  sealed trait Security
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * All users and guests
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  case class All() extends Security
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Only signed-in users
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  case class Login() extends Security
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Only repository owner and collaborators
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  case class Member() extends Security
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Only repository owner and managers of group repository
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  case class Owner() extends Security
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Only administrators
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  case class Admin() extends Security
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/main/scala/plugin/package.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/main/scala/plugin/package.scala
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					import java.sql.PreparedStatement
 | 
				
			||||||
 | 
					import play.twirl.api.Html
 | 
				
			||||||
 | 
					import util.ControlUtil._
 | 
				
			||||||
 | 
					import scala.collection.mutable.ListBuffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package object plugin {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  case class Redirect(path: String)
 | 
				
			||||||
 | 
					  case class Fragment(html: Html)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  object db {
 | 
				
			||||||
 | 
					    // TODO labelled place holder support
 | 
				
			||||||
 | 
					    def select(sql: String, params: Any*): Seq[Map[String, String]] = {
 | 
				
			||||||
 | 
					      defining(PluginConnectionHolder.threadLocal.get){ conn =>
 | 
				
			||||||
 | 
					        using(conn.prepareStatement(sql)){ stmt =>
 | 
				
			||||||
 | 
					          setParams(stmt, params: _*)
 | 
				
			||||||
 | 
					          using(stmt.executeQuery()){ rs =>
 | 
				
			||||||
 | 
					            val list = new ListBuffer[Map[String, String]]()
 | 
				
			||||||
 | 
					            while(rs.next){
 | 
				
			||||||
 | 
					              defining(rs.getMetaData){ meta =>
 | 
				
			||||||
 | 
					                val map = Range(1, meta.getColumnCount + 1).map { i =>
 | 
				
			||||||
 | 
					                  val name = meta.getColumnName(i)
 | 
				
			||||||
 | 
					                  (name, rs.getString(name))
 | 
				
			||||||
 | 
					                }.toMap
 | 
				
			||||||
 | 
					                list += map
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            list
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO labelled place holder support
 | 
				
			||||||
 | 
					    def update(sql: String, params: Any*): Int = {
 | 
				
			||||||
 | 
					      defining(PluginConnectionHolder.threadLocal.get){ conn =>
 | 
				
			||||||
 | 
					        using(conn.prepareStatement(sql)){ stmt =>
 | 
				
			||||||
 | 
					          setParams(stmt, params: _*)
 | 
				
			||||||
 | 
					          stmt.executeUpdate()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private def setParams(stmt: PreparedStatement, params: Any*): Unit = {
 | 
				
			||||||
 | 
					      params.zipWithIndex.foreach { case (p, i) =>
 | 
				
			||||||
 | 
					        p match {
 | 
				
			||||||
 | 
					          case x: String  => stmt.setString(i + 1, x)
 | 
				
			||||||
 | 
					          case x: Int     => stmt.setInt(i + 1, x)
 | 
				
			||||||
 | 
					          case x: Boolean => stmt.setBoolean(i + 1, x)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.{Account, GroupMember}
 | 
				
			||||||
// TODO [Slick 2.0]NOT import directly?
 | 
					// TODO [Slick 2.0]NOT import directly?
 | 
				
			||||||
import model.dateColumnType
 | 
					import model.Profile.dateColumnType
 | 
				
			||||||
import service.SystemSettingsService.SystemSettings
 | 
					import service.SystemSettingsService.SystemSettings
 | 
				
			||||||
import util.StringUtil._
 | 
					import util.StringUtil._
 | 
				
			||||||
import util.LDAPUtil
 | 
					import util.LDAPUtil
 | 
				
			||||||
@@ -39,7 +40,11 @@ trait AccountService {
 | 
				
			|||||||
        // Create or update account by LDAP information
 | 
					        // Create or update account by LDAP information
 | 
				
			||||||
        getAccountByUserName(ldapUserInfo.userName, true) match {
 | 
					        getAccountByUserName(ldapUserInfo.userName, true) match {
 | 
				
			||||||
          case Some(x) if(!x.isRemoved) => {
 | 
					          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))
 | 
					              updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            getAccountByUserName(ldapUserInfo.userName)
 | 
					            getAccountByUserName(ldapUserInfo.userName)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          case Some(x) if(x.isRemoved)  => {
 | 
					          case Some(x) if(x.isRemoved)  => {
 | 
				
			||||||
@@ -70,16 +75,16 @@ trait AccountService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
 | 
					  def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
 | 
				
			||||||
    Accounts filter(t => (t.userName is userName.bind) && (t.removed is false.bind, !includeRemoved)) firstOption
 | 
					    Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
 | 
					  def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
 | 
				
			||||||
    Accounts filter(t => (t.mailAddress.toLowerCase is mailAddress.toLowerCase.bind) && (t.removed is false.bind, !includeRemoved)) firstOption
 | 
					    Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def getAllUsers(includeRemoved: Boolean = true)(implicit s: Session): List[Account] =
 | 
					  def getAllUsers(includeRemoved: Boolean = true)(implicit s: Session): List[Account] =
 | 
				
			||||||
    if(includeRemoved){
 | 
					    if(includeRemoved){
 | 
				
			||||||
      Accounts sortBy(_.userName) list
 | 
					      Accounts sortBy(_.userName) list
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      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])
 | 
					  def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
 | 
				
			||||||
@@ -100,7 +105,7 @@ trait AccountService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  def updateAccount(account: Account)(implicit s: Session): Unit =
 | 
					  def updateAccount(account: Account)(implicit s: Session): Unit =
 | 
				
			||||||
    Accounts
 | 
					    Accounts
 | 
				
			||||||
      .filter { a =>  a.userName is account.userName.bind }
 | 
					      .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) }
 | 
					      .map    { a => (a.password, a.fullName, a.mailAddress, a.isAdmin, a.url.?, a.registeredDate, a.updatedDate, a.lastLoginDate.?, a.removed) }
 | 
				
			||||||
      .update (
 | 
					      .update (
 | 
				
			||||||
        account.password,
 | 
					        account.password,
 | 
				
			||||||
@@ -114,10 +119,10 @@ trait AccountService {
 | 
				
			|||||||
        account.isRemoved)
 | 
					        account.isRemoved)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit =
 | 
					  def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit =
 | 
				
			||||||
    Accounts.filter(_.userName is userName.bind).map(_.image.?).update(image)
 | 
					    Accounts.filter(_.userName === userName.bind).map(_.image.?).update(image)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateLastLoginDate(userName: String)(implicit s: Session): Unit =
 | 
					  def updateLastLoginDate(userName: String)(implicit s: Session): Unit =
 | 
				
			||||||
    Accounts.filter(_.userName is userName.bind).map(_.lastLoginDate).update(currentDate)
 | 
					    Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def createGroup(groupName: String, url: Option[String])(implicit s: Session): Unit =
 | 
					  def createGroup(groupName: String, url: Option[String])(implicit s: Session): Unit =
 | 
				
			||||||
    Accounts insert Account(
 | 
					    Accounts insert Account(
 | 
				
			||||||
@@ -135,10 +140,10 @@ trait AccountService {
 | 
				
			|||||||
      isRemoved      = false)
 | 
					      isRemoved      = false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateGroup(groupName: String, url: Option[String], removed: Boolean)(implicit s: Session): Unit =
 | 
					  def updateGroup(groupName: String, url: Option[String], removed: Boolean)(implicit s: Session): Unit =
 | 
				
			||||||
    Accounts.filter(_.userName is groupName.bind).map(t => t.url.? -> t.removed).update(url, removed)
 | 
					    Accounts.filter(_.userName === groupName.bind).map(t => t.url.? -> t.removed).update(url, removed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = {
 | 
					  def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = {
 | 
				
			||||||
    GroupMembers.filter(_.groupName is groupName.bind).delete
 | 
					    GroupMembers.filter(_.groupName === groupName.bind).delete
 | 
				
			||||||
    members.foreach { case (userName, isManager) =>
 | 
					    members.foreach { case (userName, isManager) =>
 | 
				
			||||||
      GroupMembers insert GroupMember (groupName, userName, isManager)
 | 
					      GroupMembers insert GroupMember (groupName, userName, isManager)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -146,21 +151,21 @@ trait AccountService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  def getGroupMembers(groupName: String)(implicit s: Session): List[GroupMember] =
 | 
					  def getGroupMembers(groupName: String)(implicit s: Session): List[GroupMember] =
 | 
				
			||||||
    GroupMembers
 | 
					    GroupMembers
 | 
				
			||||||
      .filter(_.groupName is groupName.bind)
 | 
					      .filter(_.groupName === groupName.bind)
 | 
				
			||||||
      .sortBy(_.userName)
 | 
					      .sortBy(_.userName)
 | 
				
			||||||
      .list
 | 
					      .list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def getGroupsByUserName(userName: String)(implicit s: Session): List[String] =
 | 
					  def getGroupsByUserName(userName: String)(implicit s: Session): List[String] =
 | 
				
			||||||
    GroupMembers
 | 
					    GroupMembers
 | 
				
			||||||
      .filter(_.userName is userName.bind)
 | 
					      .filter(_.userName === userName.bind)
 | 
				
			||||||
      .sortBy(_.groupName)
 | 
					      .sortBy(_.groupName)
 | 
				
			||||||
      .map(_.groupName)
 | 
					      .map(_.groupName)
 | 
				
			||||||
      .list
 | 
					      .list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def removeUserRelatedData(userName: String)(implicit s: Session): Unit = {
 | 
					  def removeUserRelatedData(userName: String)(implicit s: Session): Unit = {
 | 
				
			||||||
    GroupMembers.filter(_.userName is userName.bind).delete
 | 
					    GroupMembers.filter(_.userName === userName.bind).delete
 | 
				
			||||||
    Collaborators.filter(_.collaboratorName is userName.bind).delete
 | 
					    Collaborators.filter(_.collaboratorName === userName.bind).delete
 | 
				
			||||||
    Repositories.filter(_.userName is userName.bind).delete
 | 
					    Repositories.filter(_.userName === userName.bind).delete
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.Activity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait ActivityService {
 | 
					trait ActivityService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,9 +11,9 @@ trait ActivityService {
 | 
				
			|||||||
      .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
 | 
					      .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
 | 
				
			||||||
      .filter { case (t1, t2) =>
 | 
					      .filter { case (t1, t2) =>
 | 
				
			||||||
        if(isPublic){
 | 
					        if(isPublic){
 | 
				
			||||||
          (t1.activityUserName is activityUserName.bind) && (t2.isPrivate is false.bind)
 | 
					          (t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          (t1.activityUserName is activityUserName.bind)
 | 
					          (t1.activityUserName === activityUserName.bind)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .sortBy { case (t1, t2) => t1.activityId desc }
 | 
					      .sortBy { case (t1, t2) => t1.activityId desc }
 | 
				
			||||||
@@ -23,7 +24,16 @@ trait ActivityService {
 | 
				
			|||||||
  def getRecentActivities()(implicit s: Session): List[Activity] =
 | 
					  def getRecentActivities()(implicit s: Session): List[Activity] =
 | 
				
			||||||
    Activities
 | 
					    Activities
 | 
				
			||||||
      .innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
 | 
					      .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 }
 | 
					      .sortBy { case (t1, t2) => t1.activityId desc }
 | 
				
			||||||
      .map    { case (t1, t2) => t1 }
 | 
					      .map    { case (t1, t2) => t1 }
 | 
				
			||||||
      .take(30)
 | 
					      .take(30)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,9 @@ package service
 | 
				
			|||||||
import scala.slick.jdbc.{StaticQuery => Q}
 | 
					import scala.slick.jdbc.{StaticQuery => Q}
 | 
				
			||||||
import Q.interpolation
 | 
					import Q.interpolation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.{Issue, IssueComment, IssueLabel, Label}
 | 
				
			||||||
import util.Implicits._
 | 
					import util.Implicits._
 | 
				
			||||||
import util.StringUtil._
 | 
					import util.StringUtil._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,7 +50,6 @@ trait IssuesService {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
 | 
					  def countIssue(condition: IssueSearchCondition, filterUser: Map[String, String], onlyPullRequest: Boolean,
 | 
				
			||||||
                 repos: (String, String)*)(implicit s: Session): Int =
 | 
					                 repos: (String, String)*)(implicit s: Session): Int =
 | 
				
			||||||
    // TODO check SQL
 | 
					 | 
				
			||||||
    Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
 | 
					    Query(searchIssueQuery(repos, condition, filterUser, onlyPullRequest).length).first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -166,13 +166,13 @@ trait IssuesService {
 | 
				
			|||||||
          .getOrElse (repos)
 | 
					          .getOrElse (repos)
 | 
				
			||||||
          .map { case (owner, repository) => t1.byRepository(owner, repository) }
 | 
					          .map { case (owner, repository) => t1.byRepository(owner, repository) }
 | 
				
			||||||
          .foldLeft[Column[Boolean]](false) ( _ || _ ) &&
 | 
					          .foldLeft[Column[Boolean]](false) ( _ || _ ) &&
 | 
				
			||||||
      (t1.closed           is (condition.state == "closed").bind) &&
 | 
					      (t1.closed           === (condition.state == "closed").bind) &&
 | 
				
			||||||
      (t1.milestoneId      is condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
 | 
					      (t1.milestoneId      === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
 | 
				
			||||||
      (t1.milestoneId      isNull, condition.milestoneId == Some(None)) &&
 | 
					      (t1.milestoneId.?    isEmpty, condition.milestoneId == Some(None)) &&
 | 
				
			||||||
      (t1.assignedUserName is filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
 | 
					      (t1.assignedUserName === filterUser("assigned").bind, filterUser.get("assigned").isDefined) &&
 | 
				
			||||||
      (t1.openedUserName   is filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
 | 
					      (t1.openedUserName   === filterUser("created_by").bind, filterUser.get("created_by").isDefined) &&
 | 
				
			||||||
      (t1.openedUserName   isNot filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
 | 
					      (t1.openedUserName   =!= filterUser("not_created_by").bind, filterUser.get("not_created_by").isDefined) &&
 | 
				
			||||||
      (t1.pullRequest      is true.bind, onlyPullRequest) &&
 | 
					      (t1.pullRequest      === true.bind, onlyPullRequest) &&
 | 
				
			||||||
      (IssueLabels filter { t2 =>
 | 
					      (IssueLabels filter { t2 =>
 | 
				
			||||||
        (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
 | 
					        (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
 | 
				
			||||||
        (t2.labelId in
 | 
					        (t2.labelId in
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.Label
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait LabelsService {
 | 
					trait LabelsService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.Milestone
 | 
				
			||||||
// TODO [Slick 2.0]NOT import directly?
 | 
					// TODO [Slick 2.0]NOT import directly?
 | 
				
			||||||
import model.dateColumnType
 | 
					import model.Profile.dateColumnType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait MilestonesService {
 | 
					trait MilestonesService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,7 +41,7 @@ trait MilestonesService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = {
 | 
					  def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = {
 | 
				
			||||||
    val counts = Issues
 | 
					    val counts = Issues
 | 
				
			||||||
      .filter  { t => (t.byRepository(owner, repository)) && (t.milestoneId isNotNull) }
 | 
					      .filter  { t => (t.byRepository(owner, repository)) && (t.milestoneId.? isDefined) }
 | 
				
			||||||
      .groupBy { t => t.milestoneId -> t.closed }
 | 
					      .groupBy { t => t.milestoneId -> t.closed }
 | 
				
			||||||
      .map     { case (t1, t2) => t1._1 -> t1._2 -> t2.length }
 | 
					      .map     { case (t1, t2) => t1._1 -> t1._2 -> t2.length }
 | 
				
			||||||
      .toMap
 | 
					      .toMap
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								src/main/scala/service/PluginService.scala
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/main/scala/service/PluginService.scala
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import model.Profile._
 | 
				
			||||||
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.Plugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					trait PluginService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def getPlugins()(implicit s: Session): List[Plugin] =
 | 
				
			||||||
 | 
					    Plugins.sortBy(_.pluginId).list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def registerPlugin(plugin: Plugin)(implicit s: Session): Unit =
 | 
				
			||||||
 | 
					    Plugins.insert(plugin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def updatePlugin(plugin: Plugin)(implicit s: Session): Unit =
 | 
				
			||||||
 | 
					    Plugins.filter(_.pluginId === plugin.pluginId.bind).map(_.version).update(plugin.version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def deletePlugin(pluginId: String)(implicit s: Session): Unit =
 | 
				
			||||||
 | 
					    Plugins.filter(_.pluginId === pluginId.bind).delete
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def getPlugin(pluginId: String)(implicit s: Session): Option[Plugin] =
 | 
				
			||||||
 | 
					    Plugins.filter(_.pluginId === pluginId.bind).firstOption
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.{PullRequest, Issue}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait PullRequestService { self: IssuesService =>
 | 
					trait PullRequestService { self: IssuesService =>
 | 
				
			||||||
  import PullRequestService._
 | 
					  import PullRequestService._
 | 
				
			||||||
@@ -25,9 +26,9 @@ trait PullRequestService { self: IssuesService =>
 | 
				
			|||||||
    PullRequests
 | 
					    PullRequests
 | 
				
			||||||
      .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
 | 
					      .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
 | 
				
			||||||
      .filter { case (t1, t2) =>
 | 
					      .filter { case (t1, t2) =>
 | 
				
			||||||
        (t2.closed         is closed.bind) &&
 | 
					        (t2.closed         === closed.bind) &&
 | 
				
			||||||
        (t1.userName       is owner.get.bind, owner.isDefined) &&
 | 
					        (t1.userName       === owner.get.bind, owner.isDefined) &&
 | 
				
			||||||
        (t1.repositoryName is repository.get.bind, repository.isDefined)
 | 
					        (t1.repositoryName === repository.get.bind, repository.isDefined)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .groupBy { case (t1, t2) => t2.openedUserName }
 | 
					      .groupBy { case (t1, t2) => t2.openedUserName }
 | 
				
			||||||
      .map { case (userName, t) => userName -> t.length }
 | 
					      .map { case (userName, t) => userName -> t.length }
 | 
				
			||||||
@@ -54,10 +55,10 @@ trait PullRequestService { self: IssuesService =>
 | 
				
			|||||||
    PullRequests
 | 
					    PullRequests
 | 
				
			||||||
      .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
 | 
					      .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
 | 
				
			||||||
      .filter { case (t1, t2) =>
 | 
					      .filter { case (t1, t2) =>
 | 
				
			||||||
        (t1.requestUserName       is userName.bind) &&
 | 
					        (t1.requestUserName       === userName.bind) &&
 | 
				
			||||||
        (t1.requestRepositoryName is repositoryName.bind) &&
 | 
					        (t1.requestRepositoryName === repositoryName.bind) &&
 | 
				
			||||||
        (t1.requestBranch         is branch.bind) &&
 | 
					        (t1.requestBranch         === branch.bind) &&
 | 
				
			||||||
        (t2.closed                is closed.bind)
 | 
					        (t2.closed                === closed.bind)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      .map { case (t1, t2) => t1 }
 | 
					      .map { case (t1, t2) => t1 }
 | 
				
			||||||
      .list
 | 
					      .list
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ import org.eclipse.jgit.revwalk.RevWalk
 | 
				
			|||||||
import org.eclipse.jgit.treewalk.TreeWalk
 | 
					import org.eclipse.jgit.treewalk.TreeWalk
 | 
				
			||||||
import org.eclipse.jgit.lib.FileMode
 | 
					import org.eclipse.jgit.lib.FileMode
 | 
				
			||||||
import org.eclipse.jgit.api.Git
 | 
					import org.eclipse.jgit.api.Git
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait RepositorySearchService { self: IssuesService =>
 | 
					trait RepositorySearchService { self: IssuesService =>
 | 
				
			||||||
  import RepositorySearchService._
 | 
					  import RepositorySearchService._
 | 
				
			||||||
@@ -107,7 +107,7 @@ object RepositorySearchService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  case class SearchResult(
 | 
					  case class SearchResult(
 | 
				
			||||||
    files : List[(String, String)],
 | 
					    files : List[(String, String)],
 | 
				
			||||||
    issues: List[(Issue, Int, String)])
 | 
					    issues: List[(model.Issue, Int, String)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class IssueSearchResult(
 | 
					  case class IssueSearchResult(
 | 
				
			||||||
    issueId: Int,
 | 
					    issueId: Int,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.{Repository, Account, Collaborator}
 | 
				
			||||||
import util.JGitUtil
 | 
					import util.JGitUtil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait RepositoryService { self: AccountService =>
 | 
					trait RepositoryService { self: AccountService =>
 | 
				
			||||||
@@ -57,15 +58,15 @@ trait RepositoryService { self: AccountService =>
 | 
				
			|||||||
        val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
 | 
					        val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Repositories.filter { t =>
 | 
					        Repositories.filter { t =>
 | 
				
			||||||
          (t.originUserName is oldUserName.bind) && (t.originRepositoryName is oldRepositoryName.bind)
 | 
					          (t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
 | 
				
			||||||
        }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
 | 
					        }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Repositories.filter { t =>
 | 
					        Repositories.filter { t =>
 | 
				
			||||||
          (t.parentUserName is oldUserName.bind) && (t.parentRepositoryName is oldRepositoryName.bind)
 | 
					          (t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind)
 | 
				
			||||||
        }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
 | 
					        }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        PullRequests.filter { t =>
 | 
					        PullRequests.filter { t =>
 | 
				
			||||||
          t.requestRepositoryName is oldRepositoryName.bind
 | 
					          t.requestRepositoryName === oldRepositoryName.bind
 | 
				
			||||||
        }.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
 | 
					        }.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        deleteRepository(oldUserName, oldRepositoryName)
 | 
					        deleteRepository(oldUserName, oldRepositoryName)
 | 
				
			||||||
@@ -73,7 +74,16 @@ trait RepositoryService { self: AccountService =>
 | 
				
			|||||||
        WebHooks      .insertAll(webHooks      .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
					        WebHooks      .insertAll(webHooks      .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
				
			||||||
        Milestones    .insertAll(milestones    .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
					        Milestones    .insertAll(milestones    .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
				
			||||||
        IssueId       .insertAll(issueId       .map(_.copy(_1       = newUserName, _2             = 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)) :_*)
 | 
					        PullRequests  .insertAll(pullRequests  .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
				
			||||||
        IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
					        IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
				
			||||||
        Labels        .insertAll(labels        .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
					        Labels        .insertAll(labels        .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
 | 
				
			||||||
@@ -92,7 +102,7 @@ trait RepositoryService { self: AccountService =>
 | 
				
			|||||||
        }.map { t => t.activityId -> t.message }.list
 | 
					        }.map { t => t.activityId -> t.message }.list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        updateActivities.foreach { case (activityId, message) =>
 | 
					        updateActivities.foreach { case (activityId, message) =>
 | 
				
			||||||
          Activities.filter(_.activityId is activityId.bind).map(_.message).update(
 | 
					          Activities.filter(_.activityId === activityId.bind).map(_.message).update(
 | 
				
			||||||
            message
 | 
					            message
 | 
				
			||||||
              .replace(s"[repo:${oldUserName}/${oldRepositoryName}]"   ,s"[repo:${newUserName}/${newRepositoryName}]")
 | 
					              .replace(s"[repo:${oldUserName}/${oldRepositoryName}]"   ,s"[repo:${newUserName}/${newRepositoryName}]")
 | 
				
			||||||
              .replace(s"[branch:${oldUserName}/${oldRepositoryName}#" ,s"[branch:${newUserName}/${newRepositoryName}#")
 | 
					              .replace(s"[branch:${oldUserName}/${oldRepositoryName}#" ,s"[branch:${newUserName}/${newRepositoryName}#")
 | 
				
			||||||
@@ -126,7 +136,7 @@ trait RepositoryService { self: AccountService =>
 | 
				
			|||||||
   * @return the list of repository names
 | 
					   * @return the list of repository names
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  def getRepositoryNamesOfUser(userName: String)(implicit s: Session): List[String] =
 | 
					  def getRepositoryNamesOfUser(userName: String)(implicit s: Session): List[String] =
 | 
				
			||||||
    Repositories filter(_.userName is userName.bind) map (_.repositoryName) list
 | 
					    Repositories filter(_.userName === userName.bind) map (_.repositoryName) list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Returns the specified repository information.
 | 
					   * Returns the specified repository information.
 | 
				
			||||||
@@ -140,7 +150,7 @@ trait RepositoryService { self: AccountService =>
 | 
				
			|||||||
    (Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
 | 
					    (Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
 | 
				
			||||||
      // for getting issue count and pull request count
 | 
					      // for getting issue count and pull request count
 | 
				
			||||||
      val issues = Issues.filter { t =>
 | 
					      val issues = Issues.filter { t =>
 | 
				
			||||||
        t.byRepository(repository.userName, repository.repositoryName) && (t.closed is false.bind)
 | 
					        t.byRepository(repository.userName, repository.repositoryName) && (t.closed === false.bind)
 | 
				
			||||||
      }.map(_.pullRequest).list
 | 
					      }.map(_.pullRequest).list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      new RepositoryInfo(
 | 
					      new RepositoryInfo(
 | 
				
			||||||
@@ -156,11 +166,17 @@ trait RepositoryService { self: AccountService =>
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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)
 | 
					  def getUserRepositories(userName: String, baseUrl: String, withoutPhysicalInfo: Boolean = false)
 | 
				
			||||||
                         (implicit s: Session): List[RepositoryInfo] = {
 | 
					                         (implicit s: Session): List[RepositoryInfo] = {
 | 
				
			||||||
    Repositories.filter { t1 =>
 | 
					    Repositories.filter { t1 =>
 | 
				
			||||||
      (t1.userName is userName.bind) ||
 | 
					      (t1.userName === userName.bind) ||
 | 
				
			||||||
        (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName is userName.bind)} exists)
 | 
					        (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists)
 | 
				
			||||||
    }.sortBy(_.lastActivityDate desc).list.map{ repository =>
 | 
					    }.sortBy(_.lastActivityDate desc).list.map{ repository =>
 | 
				
			||||||
      new RepositoryInfo(
 | 
					      new RepositoryInfo(
 | 
				
			||||||
        if(withoutPhysicalInfo){
 | 
					        if(withoutPhysicalInfo){
 | 
				
			||||||
@@ -196,13 +212,13 @@ trait RepositoryService { self: AccountService =>
 | 
				
			|||||||
      case Some(x) if(x.isAdmin) => Repositories
 | 
					      case Some(x) if(x.isAdmin) => Repositories
 | 
				
			||||||
      // for Normal Users
 | 
					      // for Normal Users
 | 
				
			||||||
      case Some(x) if(!x.isAdmin) =>
 | 
					      case Some(x) if(!x.isAdmin) =>
 | 
				
			||||||
        Repositories filter { t => (t.isPrivate is false.bind) || (t.userName is x.userName) ||
 | 
					        Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) ||
 | 
				
			||||||
          (Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName is x.userName.bind)} exists)
 | 
					          (Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      // for Guests
 | 
					      // for Guests
 | 
				
			||||||
      case None => Repositories filter(_.isPrivate is false.bind)
 | 
					      case None => Repositories filter(_.isPrivate === false.bind)
 | 
				
			||||||
    }).filter { t =>
 | 
					    }).filter { t =>
 | 
				
			||||||
      repositoryUserName.map { userName => t.userName is userName.bind } getOrElse LiteralColumn(true)
 | 
					      repositoryUserName.map { userName => t.userName === userName.bind } getOrElse LiteralColumn(true)
 | 
				
			||||||
    }.sortBy(_.lastActivityDate desc).list.map{ repository =>
 | 
					    }.sortBy(_.lastActivityDate desc).list.map{ repository =>
 | 
				
			||||||
      new RepositoryInfo(
 | 
					      new RepositoryInfo(
 | 
				
			||||||
        if(withoutPhysicalInfo){
 | 
					        if(withoutPhysicalInfo){
 | 
				
			||||||
@@ -290,15 +306,14 @@ trait RepositoryService { self: AccountService =>
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
 | 
					  private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
 | 
				
			||||||
    // TODO check SQL
 | 
					 | 
				
			||||||
    Query(Repositories.filter { t =>
 | 
					    Query(Repositories.filter { t =>
 | 
				
			||||||
      (t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind)
 | 
					      (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
 | 
				
			||||||
    }.length).first
 | 
					    }.length).first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[(String, String)] =
 | 
					  def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[(String, String)] =
 | 
				
			||||||
    Repositories.filter { t =>
 | 
					    Repositories.filter { t =>
 | 
				
			||||||
      (t.originUserName is userName.bind) && (t.originRepositoryName is repositoryName.bind)
 | 
					      (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    .sortBy(_.userName asc).map(t => t.userName -> t.repositoryName).list
 | 
					    .sortBy(_.userName asc).map(t => t.userName -> t.repositoryName).list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.{Account, Issue, Session}
 | 
				
			||||||
import slick.jdbc.JdbcBackend
 | 
					 | 
				
			||||||
import util.Implicits.request2Session
 | 
					import util.Implicits.request2Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -12,7 +11,7 @@ import util.Implicits.request2Session
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
trait RequestCache extends SystemSettingsService with AccountService with IssuesService {
 | 
					trait RequestCache extends SystemSettingsService with AccountService with IssuesService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private implicit def context2Session(implicit context: app.Context): JdbcBackend#Session =
 | 
					  private implicit def context2Session(implicit context: app.Context): Session =
 | 
				
			||||||
    request2Session(context.request)
 | 
					    request2Session(context.request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def getIssue(userName: String, repositoryName: String, issueId: String)
 | 
					  def getIssue(userName: String, repositoryName: String, issueId: String)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.SshKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait SshKeyService {
 | 
					trait SshKeyService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,7 +10,7 @@ trait SshKeyService {
 | 
				
			|||||||
    SshKeys insert SshKey(userName = userName, title = title, publicKey = publicKey)
 | 
					    SshKeys insert SshKey(userName = userName, title = title, publicKey = publicKey)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
 | 
					  def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
 | 
				
			||||||
    SshKeys.filter(_.userName is userName.bind).sortBy(_.sshKeyId).list
 | 
					    SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
 | 
					  def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
 | 
				
			||||||
    SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete
 | 
					    SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,8 +37,9 @@ trait SystemSettingsService {
 | 
				
			|||||||
          ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
 | 
					          ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
 | 
				
			||||||
          props.setProperty(LdapBaseDN, ldap.baseDN)
 | 
					          props.setProperty(LdapBaseDN, ldap.baseDN)
 | 
				
			||||||
          props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
 | 
					          props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
 | 
				
			||||||
 | 
					          ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
 | 
				
			||||||
          ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, 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.tls.foreach(x => props.setProperty(LdapTls, x.toString))
 | 
				
			||||||
          ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
 | 
					          ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -85,8 +86,9 @@ trait SystemSettingsService {
 | 
				
			|||||||
            getOptionValue(props, LdapBindPassword, None),
 | 
					            getOptionValue(props, LdapBindPassword, None),
 | 
				
			||||||
            getValue(props, LdapBaseDN, ""),
 | 
					            getValue(props, LdapBaseDN, ""),
 | 
				
			||||||
            getValue(props, LdapUserNameAttribute, ""),
 | 
					            getValue(props, LdapUserNameAttribute, ""),
 | 
				
			||||||
 | 
					            getOptionValue(props, LdapAdditionalFilterCondition, None),
 | 
				
			||||||
            getOptionValue(props, LdapFullNameAttribute, None),
 | 
					            getOptionValue(props, LdapFullNameAttribute, None),
 | 
				
			||||||
            getValue(props, LdapMailAddressAttribute, ""),
 | 
					            getOptionValue(props, LdapMailAddressAttribute, None),
 | 
				
			||||||
            getOptionValue[Boolean](props, LdapTls, None),
 | 
					            getOptionValue[Boolean](props, LdapTls, None),
 | 
				
			||||||
            getOptionValue(props, LdapKeystore, None)))
 | 
					            getOptionValue(props, LdapKeystore, None)))
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
@@ -125,8 +127,9 @@ object SystemSettingsService {
 | 
				
			|||||||
    bindPassword: Option[String],
 | 
					    bindPassword: Option[String],
 | 
				
			||||||
    baseDN: String,
 | 
					    baseDN: String,
 | 
				
			||||||
    userNameAttribute: String,
 | 
					    userNameAttribute: String,
 | 
				
			||||||
 | 
					    additionalFilterCondition: Option[String],
 | 
				
			||||||
    fullNameAttribute: Option[String],
 | 
					    fullNameAttribute: Option[String],
 | 
				
			||||||
    mailAttribute: String,
 | 
					    mailAttribute: Option[String],
 | 
				
			||||||
    tls: Option[Boolean],
 | 
					    tls: Option[Boolean],
 | 
				
			||||||
    keystore: Option[String])
 | 
					    keystore: Option[String])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -163,6 +166,7 @@ object SystemSettingsService {
 | 
				
			|||||||
  private val LdapBindPassword = "ldap.bind_password"
 | 
					  private val LdapBindPassword = "ldap.bind_password"
 | 
				
			||||||
  private val LdapBaseDN = "ldap.baseDN"
 | 
					  private val LdapBaseDN = "ldap.baseDN"
 | 
				
			||||||
  private val LdapUserNameAttribute = "ldap.username_attribute"
 | 
					  private val LdapUserNameAttribute = "ldap.username_attribute"
 | 
				
			||||||
 | 
					  private val LdapAdditionalFilterCondition = "ldap.additional_filter_condition"
 | 
				
			||||||
  private val LdapFullNameAttribute = "ldap.fullname_attribute"
 | 
					  private val LdapFullNameAttribute = "ldap.fullname_attribute"
 | 
				
			||||||
  private val LdapMailAddressAttribute = "ldap.mail_attribute"
 | 
					  private val LdapMailAddressAttribute = "ldap.mail_attribute"
 | 
				
			||||||
  private val LdapTls = "ldap.tls"
 | 
					  private val LdapTls = "ldap.tls"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import model._
 | 
					import model.Profile._
 | 
				
			||||||
import simple._
 | 
					import profile.simple._
 | 
				
			||||||
 | 
					import model.{WebHook, Account}
 | 
				
			||||||
import org.slf4j.LoggerFactory
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
import service.RepositoryService.RepositoryInfo
 | 
					import service.RepositoryService.RepositoryInfo
 | 
				
			||||||
import util.JGitUtil
 | 
					import util.JGitUtil
 | 
				
			||||||
@@ -43,7 +44,7 @@ trait WebHookService {
 | 
				
			|||||||
      val httpClient = HttpClientBuilder.create.build
 | 
					      val httpClient = HttpClientBuilder.create.build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      webHookURLs.foreach { webHookUrl =>
 | 
					      webHookURLs.foreach { webHookUrl =>
 | 
				
			||||||
        val f = future {
 | 
					        val f = Future {
 | 
				
			||||||
          logger.debug(s"start web hook invocation for ${webHookUrl}")
 | 
					          logger.debug(s"start web hook invocation for ${webHookUrl}")
 | 
				
			||||||
          val httpPost = new HttpPost(webHookUrl.url)
 | 
					          val httpPost = new HttpPost(webHookUrl.url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -89,15 +90,15 @@ object WebHookService {
 | 
				
			|||||||
          WebHookCommit(
 | 
					          WebHookCommit(
 | 
				
			||||||
            id        = commit.id,
 | 
					            id        = commit.id,
 | 
				
			||||||
            message   = commit.fullMessage,
 | 
					            message   = commit.fullMessage,
 | 
				
			||||||
            timestamp = commit.time.toString,
 | 
					            timestamp = commit.commitTime.toString,
 | 
				
			||||||
            url       = commitUrl,
 | 
					            url       = commitUrl,
 | 
				
			||||||
            added     = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.ADD)    => x.newPath },
 | 
					            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 },
 | 
					            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 &&
 | 
					            modified  = diffs._1.collect { case x if(x.changeType != DiffEntry.ChangeType.ADD &&
 | 
				
			||||||
              x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath },
 | 
					              x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath },
 | 
				
			||||||
            author    = WebHookUser(
 | 
					            author    = WebHookUser(
 | 
				
			||||||
              name  = commit.committer,
 | 
					              name  = commit.committerName,
 | 
				
			||||||
              email = commit.mailAddress
 | 
					              email = commit.committerEmailAddress
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,7 +64,7 @@ trait WikiService {
 | 
				
			|||||||
      if(!JGitUtil.isEmpty(git)){
 | 
					      if(!JGitUtil.isEmpty(git)){
 | 
				
			||||||
        JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
 | 
					        JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
 | 
				
			||||||
          WikiPageInfo(file.name, StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
 | 
					          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
 | 
					      } else None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -182,7 +182,8 @@ trait WikiService {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            builder.finish()
 | 
					            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 {
 | 
					              pageName match {
 | 
				
			||||||
                case Some(x) => s"Revert ${from} ... ${to} on ${x}"
 | 
					                case Some(x) => s"Revert ${from} ... ${to} on ${x}"
 | 
				
			||||||
                case None    => s"Revert ${from} ... ${to}"
 | 
					                case None    => s"Revert ${from} ... ${to}"
 | 
				
			||||||
@@ -229,7 +230,8 @@ trait WikiService {
 | 
				
			|||||||
        if(created || updated || removed){
 | 
					        if(created || updated || removed){
 | 
				
			||||||
          builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
 | 
					          builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
 | 
				
			||||||
          builder.finish()
 | 
					          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(message.trim.length == 0) {
 | 
				
			||||||
              if(removed){
 | 
					              if(removed){
 | 
				
			||||||
                s"Rename ${currentPageName} to ${newPageName}"
 | 
					                s"Rename ${currentPageName} to ${newPageName}"
 | 
				
			||||||
@@ -269,7 +271,8 @@ trait WikiService {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        if(removed){
 | 
					        if(removed){
 | 
				
			||||||
          builder.finish()
 | 
					          builder.finish()
 | 
				
			||||||
          JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), committer, mailAddress, message)
 | 
					          JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
 | 
				
			||||||
 | 
					            Constants.HEAD, committer, mailAddress, message)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,6 +52,28 @@ object AutoUpdate {
 | 
				
			|||||||
   * The history of versions. A head of this sequence is the current BitBucket version.
 | 
					   * The history of versions. A head of this sequence is the current BitBucket version.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  val versions = Seq(
 | 
					  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, 1),
 | 
				
			||||||
    new Version(2, 0){
 | 
					    new Version(2, 0){
 | 
				
			||||||
      override def update(conn: Connection): Unit = {
 | 
					      override def update(conn: Connection): Unit = {
 | 
				
			||||||
@@ -160,10 +182,12 @@ class AutoUpdateListener extends ServletContextListener {
 | 
				
			|||||||
      System.setProperty("gitbucket.home", datadir)
 | 
					      System.setProperty("gitbucket.home", datadir)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    org.h2.Driver.load()
 | 
					    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 =>
 | 
					    defining(getConnection(event.getServletContext)){ conn =>
 | 
				
			||||||
 | 
					      logger.debug("Start schema update")
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        defining(getCurrentVersion()){ currentVersion =>
 | 
					        defining(getCurrentVersion()){ currentVersion =>
 | 
				
			||||||
          if(currentVersion == headVersion){
 | 
					          if(currentVersion == headVersion){
 | 
				
			||||||
@@ -173,7 +197,6 @@ class AutoUpdateListener extends ServletContextListener {
 | 
				
			|||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
 | 
					            versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
 | 
				
			||||||
            FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
 | 
					            FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
 | 
				
			||||||
            conn.commit()
 | 
					 | 
				
			||||||
            logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
 | 
					            logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -184,10 +207,12 @@ class AutoUpdateListener extends ServletContextListener {
 | 
				
			|||||||
          conn.rollback()
 | 
					          conn.rollback()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
      logger.debug("End schema update")
 | 
					      logger.debug("End schema update")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getDatabase(context).withSession { implicit session =>
 | 
				
			||||||
      logger.debug("Starting plugin system...")
 | 
					      logger.debug("Starting plugin system...")
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
        plugin.PluginSystem.init()
 | 
					        plugin.PluginSystem.init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        scheduler.start()
 | 
					        scheduler.start()
 | 
				
			||||||
@@ -195,6 +220,14 @@ class AutoUpdateListener extends ServletContextListener {
 | 
				
			|||||||
        logger.debug("PluginUpdateJob is started.")
 | 
					        logger.debug("PluginUpdateJob is started.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logger.debug("Plugin system is initialized.")
 | 
					        logger.debug("Plugin system is initialized.")
 | 
				
			||||||
 | 
					      } catch {
 | 
				
			||||||
 | 
					        case ex: Throwable => {
 | 
				
			||||||
 | 
					          logger.error("Failed to initialize plugin system", ex)
 | 
				
			||||||
 | 
					          ex.printStackTrace()
 | 
				
			||||||
 | 
					          throw ex
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def contextDestroyed(sce: ServletContextEvent): Unit = {
 | 
					  def contextDestroyed(sce: ServletContextEvent): Unit = {
 | 
				
			||||||
@@ -207,4 +240,10 @@ class AutoUpdateListener extends ServletContextListener {
 | 
				
			|||||||
      servletContext.getInitParameter("db.user"),
 | 
					      servletContext.getInitParameter("db.user"),
 | 
				
			||||||
      servletContext.getInitParameter("db.password"))
 | 
					      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"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ import javax.servlet.http._
 | 
				
			|||||||
import service.{SystemSettingsService, AccountService, RepositoryService}
 | 
					import service.{SystemSettingsService, AccountService, RepositoryService}
 | 
				
			||||||
import model._
 | 
					import model._
 | 
				
			||||||
import org.slf4j.LoggerFactory
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
import slick.jdbc.JdbcBackend
 | 
					 | 
				
			||||||
import util.Implicits._
 | 
					import util.Implicits._
 | 
				
			||||||
import util.ControlUtil._
 | 
					import util.ControlUtil._
 | 
				
			||||||
import util.Keys
 | 
					import util.Keys
 | 
				
			||||||
@@ -67,7 +66,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo)
 | 
					  private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo)
 | 
				
			||||||
                             (implicit session: JdbcBackend#Session): Option[Account] =
 | 
					                             (implicit session: Session): Option[Account] =
 | 
				
			||||||
    authenticate(loadSystemSettings(), username, password) match {
 | 
					    authenticate(loadSystemSettings(), username, password) match {
 | 
				
			||||||
      case x @ Some(account) if(hasWritePermission(repository.owner, repository.name, x)) => x
 | 
					      case x @ Some(account) if(hasWritePermission(repository.owner, repository.name, x)) => x
 | 
				
			||||||
      case _ => None
 | 
					      case _ => None
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ import WebHookService._
 | 
				
			|||||||
import org.eclipse.jgit.api.Git
 | 
					import org.eclipse.jgit.api.Git
 | 
				
			||||||
import util.JGitUtil.CommitInfo
 | 
					import util.JGitUtil.CommitInfo
 | 
				
			||||||
import service.IssuesService.IssueSearchCondition
 | 
					import service.IssuesService.IssueSearchCondition
 | 
				
			||||||
import slick.jdbc.JdbcBackend
 | 
					import model.Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides Git repository via HTTP.
 | 
					 * Provides Git repository via HTTP.
 | 
				
			||||||
@@ -95,7 +95,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import scala.collection.JavaConverters._
 | 
					import scala.collection.JavaConverters._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: JdbcBackend#Session)
 | 
					class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session)
 | 
				
			||||||
  extends PostReceiveHook with PreReceiveHook
 | 
					  extends PostReceiveHook with PreReceiveHook
 | 
				
			||||||
  with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService {
 | 
					  with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService {
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
@@ -205,7 +205,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
 | 
				
			|||||||
  private def createIssueComment(commit: CommitInfo) = {
 | 
					  private def createIssueComment(commit: CommitInfo) = {
 | 
				
			||||||
    StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
 | 
					    StringUtil.extractIssueId(commit.fullMessage).foreach { issueId =>
 | 
				
			||||||
      if(getIssue(owner, repository, issueId).isDefined){
 | 
					      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")
 | 
					          createComment(owner, repository, account.userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,13 @@ package servlet
 | 
				
			|||||||
import javax.servlet._
 | 
					import javax.servlet._
 | 
				
			||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
 | 
					import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
 | 
				
			||||||
import org.apache.commons.io.IOUtils
 | 
					import org.apache.commons.io.IOUtils
 | 
				
			||||||
import twirl.api.Html
 | 
					import play.twirl.api.Html
 | 
				
			||||||
import service.{AccountService, RepositoryService, SystemSettingsService}
 | 
					import service.{AccountService, RepositoryService, SystemSettingsService}
 | 
				
			||||||
import model.Account
 | 
					import model.{Account, Session}
 | 
				
			||||||
import util.{JGitUtil, Keys}
 | 
					import util.{JGitUtil, Keys}
 | 
				
			||||||
 | 
					import plugin.{Fragment, PluginConnectionHolder, Redirect}
 | 
				
			||||||
 | 
					import service.RepositoryService.RepositoryInfo
 | 
				
			||||||
 | 
					import plugin.Security._
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService {
 | 
					class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,50 +30,66 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse): Boolean = {
 | 
					  private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
 | 
				
			||||||
    plugin.PluginSystem.globalActions.find(_.path == path).map { action =>
 | 
					                                 (implicit session: Session): Boolean = {
 | 
				
			||||||
      val result = action.function(request, response)
 | 
					    plugin.PluginSystem.globalActions.find(x =>
 | 
				
			||||||
      result match {
 | 
					      x.method.toLowerCase == request.getMethod.toLowerCase && path.matches(x.path)
 | 
				
			||||||
        case x: String => {
 | 
					    ).map { action =>
 | 
				
			||||||
          response.setContentType("text/html; charset=UTF-8")
 | 
					 | 
				
			||||||
      val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
 | 
					      val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
 | 
				
			||||||
          implicit val context = app.Context(loadSystemSettings(), Option(loginAccount), request)
 | 
					      val systemSettings = loadSystemSettings()
 | 
				
			||||||
          val html = _root_.html.main("GitBucket", None)(Html(x))
 | 
					      implicit val context = app.Context(systemSettings, Option(loginAccount), request)
 | 
				
			||||||
          IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        case x => {
 | 
					 | 
				
			||||||
          // TODO returns as JSON?
 | 
					 | 
				
			||||||
          response.setContentType("application/json; charset=UTF-8")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if(authenticate(action.security, context)){
 | 
				
			||||||
 | 
					        val result = try {
 | 
				
			||||||
 | 
					          PluginConnectionHolder.threadLocal.set(session.conn)
 | 
				
			||||||
 | 
					          action.function(request, response, context)
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					          PluginConnectionHolder.threadLocal.remove()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        result match {
 | 
				
			||||||
 | 
					          case x: String   => renderGlobalHtml(request, response, context, x)
 | 
				
			||||||
 | 
					          case x: Html     => renderGlobalHtml(request, response, context, x.toString)
 | 
				
			||||||
 | 
					          case x: Fragment => renderFragmentHtml(request, response, context, x.html.toString)
 | 
				
			||||||
 | 
					          case x: Redirect => response.sendRedirect(x.path)
 | 
				
			||||||
 | 
					          case x: AnyRef   => renderJson(request, response, x)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // TODO NotFound or Error?
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      true
 | 
					      true
 | 
				
			||||||
    } getOrElse false
 | 
					    } getOrElse false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private def processRepositoryAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
 | 
					  private def processRepositoryAction(path: String, request: HttpServletRequest, response: HttpServletResponse)
 | 
				
			||||||
                                     (implicit session: model.simple.Session): Boolean = {
 | 
					                                     (implicit session: Session): Boolean = {
 | 
				
			||||||
    val elements = path.split("/")
 | 
					    val elements = path.split("/")
 | 
				
			||||||
    if(elements.length > 3){
 | 
					    if(elements.length > 3){
 | 
				
			||||||
      val owner  = elements(1)
 | 
					      val owner  = elements(1)
 | 
				
			||||||
      val name   = elements(2)
 | 
					      val name   = elements(2)
 | 
				
			||||||
      val remain = elements.drop(3).mkString("/", "/", "")
 | 
					      val remain = elements.drop(3).mkString("/", "/", "")
 | 
				
			||||||
      getRepository(owner, name, "").flatMap { repository => // TODO fill baseUrl
 | 
					 | 
				
			||||||
        plugin.PluginSystem.repositoryActions.find(_.path == remain).map { action =>
 | 
					 | 
				
			||||||
          val result = action.function(request, response)
 | 
					 | 
				
			||||||
          result match {
 | 
					 | 
				
			||||||
            case x: String => {
 | 
					 | 
				
			||||||
              response.setContentType("text/html; charset=UTF-8")
 | 
					 | 
				
			||||||
              val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
 | 
					 | 
				
			||||||
              implicit val context = app.Context(loadSystemSettings(), Option(loginAccount), request)
 | 
					 | 
				
			||||||
              val html = _root_.html.main("GitBucket", None)(_root_.html.menu("", repository)(Html(x))) // TODO specify active side menu
 | 
					 | 
				
			||||||
              IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            case x => {
 | 
					 | 
				
			||||||
              // TODO returns as JSON?
 | 
					 | 
				
			||||||
              response.setContentType("application/json; charset=UTF-8")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      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()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            result match {
 | 
				
			||||||
 | 
					              case x: String   => renderRepositoryHtml(request, response, context, repository, x)
 | 
				
			||||||
 | 
					              case x: Html     => renderGlobalHtml(request, response, context, x.toString)
 | 
				
			||||||
 | 
					              case x: Fragment => renderFragmentHtml(request, response, context, x.html.toString)
 | 
				
			||||||
 | 
					              case x: Redirect => response.sendRedirect(x.path)
 | 
				
			||||||
 | 
					              case x: AnyRef   => renderJson(request, response, x)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            // TODO NotFound or Error?
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          true
 | 
					          true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -78,4 +97,81 @@ class PluginActionInvokeFilter extends Filter with SystemSettingsService with Re
 | 
				
			|||||||
    } else false
 | 
					    } else false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Authentication for global action
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private def authenticate(security: Security, context: app.Context)(implicit session: Session): Boolean = {
 | 
				
			||||||
 | 
					    // Global Action
 | 
				
			||||||
 | 
					    security match {
 | 
				
			||||||
 | 
					      case All() => true
 | 
				
			||||||
 | 
					      case Login() => context.loginAccount.isDefined
 | 
				
			||||||
 | 
					      case Admin() => context.loginAccount.exists(_.isAdmin)
 | 
				
			||||||
 | 
					      case _ => false // TODO throw Exception?
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Authenticate for repository action
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  private def authenticate(security: Security, context: app.Context, repository: RepositoryInfo)(implicit session: Session): Boolean = {
 | 
				
			||||||
 | 
					    if(repository.repository.isPrivate){
 | 
				
			||||||
 | 
					      // Private Repository
 | 
				
			||||||
 | 
					      security match {
 | 
				
			||||||
 | 
					        case Admin() => context.loginAccount.exists(_.isAdmin)
 | 
				
			||||||
 | 
					        case Owner() => context.loginAccount.exists { account =>
 | 
				
			||||||
 | 
					          account.userName == repository.owner ||
 | 
				
			||||||
 | 
					            getGroupMembers(repository.owner).exists(m => m.userName == account.userName && m.isManager)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case _ => context.loginAccount.exists { account =>
 | 
				
			||||||
 | 
					          account.isAdmin || account.userName == repository.owner ||
 | 
				
			||||||
 | 
					            getCollaborators(repository.owner, repository.name).contains(account.userName)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      // Public Repository
 | 
				
			||||||
 | 
					      security match {
 | 
				
			||||||
 | 
					        case All() => true
 | 
				
			||||||
 | 
					        case Login() => context.loginAccount.isDefined
 | 
				
			||||||
 | 
					        case Owner() => context.loginAccount.exists { account =>
 | 
				
			||||||
 | 
					          account.userName == repository.owner ||
 | 
				
			||||||
 | 
					            getGroupMembers(repository.owner).exists(m => m.userName == account.userName && m.isManager)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case Member() => context.loginAccount.exists { account =>
 | 
				
			||||||
 | 
					          account.userName == repository.owner ||
 | 
				
			||||||
 | 
					            getCollaborators(repository.owner, repository.name).contains(account.userName)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case Admin() => context.loginAccount.exists(_.isAdmin)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private def renderGlobalHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, body: String): Unit = {
 | 
				
			||||||
 | 
					    response.setContentType("text/html; charset=UTF-8")
 | 
				
			||||||
 | 
					    val 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 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)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ import servlet.{Database, CommitLogHook}
 | 
				
			|||||||
import service.{AccountService, RepositoryService, SystemSettingsService}
 | 
					import service.{AccountService, RepositoryService, SystemSettingsService}
 | 
				
			||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
 | 
					import org.eclipse.jgit.errors.RepositoryNotFoundException
 | 
				
			||||||
import javax.servlet.ServletContext
 | 
					import javax.servlet.ServletContext
 | 
				
			||||||
import model.profile.simple.Session
 | 
					import model.Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object GitCommand {
 | 
					object GitCommand {
 | 
				
			||||||
  val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
 | 
					  val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
 | 
				
			||||||
@@ -31,7 +31,7 @@ abstract class GitCommand(val context: ServletContext, val owner: String, val re
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private def newTask(user: String): Runnable = new Runnable {
 | 
					  private def newTask(user: String): Runnable = new Runnable {
 | 
				
			||||||
    override def run(): Unit = {
 | 
					    override def run(): Unit = {
 | 
				
			||||||
      Database(context) withTransaction { implicit session =>
 | 
					      Database(context) withSession { implicit session =>
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          runTask(user)
 | 
					          runTask(user)
 | 
				
			||||||
          callback.onExit(0)
 | 
					          callback.onExit(0)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ import javax.servlet.ServletContext
 | 
				
			|||||||
class PublicKeyAuthenticator(context: ServletContext) extends PublickeyAuthenticator with SshKeyService {
 | 
					class PublicKeyAuthenticator(context: ServletContext) extends PublickeyAuthenticator with SshKeyService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
 | 
					  override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
 | 
				
			||||||
    Database(context) withTransaction { implicit session =>
 | 
					    Database(context) withSession { implicit session =>
 | 
				
			||||||
      getPublicKeys(username).exists { sshKey =>
 | 
					      getPublicKeys(username).exists { sshKey =>
 | 
				
			||||||
        SshUtil.str2PublicKey(sshKey.publicKey) match {
 | 
					        SshUtil.str2PublicKey(sshKey.publicKey) match {
 | 
				
			||||||
          case Some(publicKey) => key.equals(publicKey)
 | 
					          case Some(publicKey) => key.equals(publicKey)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,38 +47,45 @@ object JGitUtil {
 | 
				
			|||||||
   * @param id the object id
 | 
					   * @param id the object id
 | 
				
			||||||
   * @param isDirectory whether is it directory
 | 
					   * @param isDirectory whether is it directory
 | 
				
			||||||
   * @param name the file (or directory) name
 | 
					   * @param name the file (or directory) name
 | 
				
			||||||
   * @param time the last modified time
 | 
					 | 
				
			||||||
   * @param message the last commit message
 | 
					   * @param message the last commit message
 | 
				
			||||||
   * @param commitId the last commit id
 | 
					   * @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 mailAddress the committer's mail address
 | 
				
			||||||
   * @param linkUrl the url of submodule
 | 
					   * @param linkUrl the url of submodule
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  case class FileInfo(id: ObjectId, isDirectory: Boolean, name: String, time: Date, message: String, commitId: String,
 | 
					  case class FileInfo(id: ObjectId, isDirectory: Boolean, name: String, message: String, commitId: String,
 | 
				
			||||||
                      committer: String, mailAddress: String, linkUrl: Option[String])
 | 
					                      time: Date, author: String, mailAddress: String, linkUrl: Option[String])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * The commit data.
 | 
					   * The commit data.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
   * @param id the commit id
 | 
					   * @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 shortMessage the short message
 | 
				
			||||||
   * @param fullMessage the full message
 | 
					   * @param fullMessage the full message
 | 
				
			||||||
   * @param parents the list of parent commit id
 | 
					   * @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,
 | 
					  case class CommitInfo(id: String, shortMessage: String, fullMessage: String, parents: List[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(
 | 
					    def this(rev: org.eclipse.jgit.revwalk.RevCommit) = this(
 | 
				
			||||||
        rev.getName,
 | 
					        rev.getName,
 | 
				
			||||||
        rev.getCommitterIdent.getWhen,
 | 
					 | 
				
			||||||
        rev.getCommitterIdent.getName,
 | 
					 | 
				
			||||||
        rev.getCommitterIdent.getEmailAddress,
 | 
					 | 
				
			||||||
        rev.getShortMessage,
 | 
					        rev.getShortMessage,
 | 
				
			||||||
        rev.getFullMessage,
 | 
					        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)
 | 
					    val summary = getSummaryMessage(fullMessage, shortMessage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,6 +94,8 @@ object JGitUtil {
 | 
				
			|||||||
        Some(fullMessage.trim.substring(i).trim)
 | 
					        Some(fullMessage.trim.substring(i).trim)
 | 
				
			||||||
      } else None
 | 
					      } else None
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def isDifferentFromAuthor: Boolean = authorName != committerName || authorEmailAddress != committerEmailAddress
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String])
 | 
					  case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String])
 | 
				
			||||||
@@ -98,7 +107,12 @@ object JGitUtil {
 | 
				
			|||||||
   * @param content the string content
 | 
					   * @param content the string content
 | 
				
			||||||
   * @param charset the character encoding
 | 
					   * @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.
 | 
					   * The tag data.
 | 
				
			||||||
@@ -226,11 +240,11 @@ object JGitUtil {
 | 
				
			|||||||
          objectId,
 | 
					          objectId,
 | 
				
			||||||
          fileMode == FileMode.TREE || fileMode == FileMode.GITLINK,
 | 
					          fileMode == FileMode.TREE || fileMode == FileMode.GITLINK,
 | 
				
			||||||
          name,
 | 
					          name,
 | 
				
			||||||
          commit.getCommitterIdent.getWhen,
 | 
					 | 
				
			||||||
          getSummaryMessage(commit.getFullMessage, commit.getShortMessage),
 | 
					          getSummaryMessage(commit.getFullMessage, commit.getShortMessage),
 | 
				
			||||||
          commit.getName,
 | 
					          commit.getName,
 | 
				
			||||||
          commit.getCommitterIdent.getName,
 | 
					          commit.getAuthorIdent.getWhen,
 | 
				
			||||||
          commit.getCommitterIdent.getEmailAddress,
 | 
					          commit.getAuthorIdent.getName,
 | 
				
			||||||
 | 
					          commit.getAuthorIdent.getEmailAddress,
 | 
				
			||||||
          linkUrl)
 | 
					          linkUrl)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }.sortWith { (file1, file2) =>
 | 
					    }.sortWith { (file1, file2) =>
 | 
				
			||||||
@@ -490,7 +504,7 @@ object JGitUtil {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def createNewCommit(git: Git, inserter: ObjectInserter, headId: AnyObjectId, treeId: AnyObjectId,
 | 
					  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()
 | 
					    val newCommit = new CommitBuilder()
 | 
				
			||||||
    newCommit.setCommitter(new PersonIdent(fullName, mailAddress))
 | 
					    newCommit.setCommitter(new PersonIdent(fullName, mailAddress))
 | 
				
			||||||
    newCommit.setAuthor(new PersonIdent(fullName, mailAddress))
 | 
					    newCommit.setAuthor(new PersonIdent(fullName, mailAddress))
 | 
				
			||||||
@@ -504,7 +518,7 @@ object JGitUtil {
 | 
				
			|||||||
    inserter.flush()
 | 
					    inserter.flush()
 | 
				
			||||||
    inserter.release()
 | 
					    inserter.release()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val refUpdate = git.getRepository.updateRef(Constants.HEAD)
 | 
					    val refUpdate = git.getRepository.updateRef(ref)
 | 
				
			||||||
    refUpdate.setNewObjectId(newHeadId)
 | 
					    refUpdate.setNewObjectId(newHeadId)
 | 
				
			||||||
    refUpdate.update()
 | 
					    refUpdate.update()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -638,4 +652,15 @@ object JGitUtil {
 | 
				
			|||||||
      }.head.id
 | 
					      }.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
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import java.security.Security
 | 
				
			|||||||
import org.slf4j.LoggerFactory
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
import service.SystemSettingsService.Ldap
 | 
					import service.SystemSettingsService.Ldap
 | 
				
			||||||
import scala.annotation.tailrec
 | 
					import scala.annotation.tailrec
 | 
				
			||||||
 | 
					import model.Account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Utility for LDAP authentication.
 | 
					 * Utility for LDAP authentication.
 | 
				
			||||||
@@ -16,6 +17,26 @@ object LDAPUtil {
 | 
				
			|||||||
  private val LDAP_VERSION: Int = LDAPConnection.LDAP_V3
 | 
					  private val LDAP_VERSION: Int = LDAPConnection.LDAP_V3
 | 
				
			||||||
  private val logger = LoggerFactory.getLogger(getClass().getName())
 | 
					  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.
 | 
					   * Try authentication by LDAP using given configuration.
 | 
				
			||||||
   * Returns Right(LDAPUserInfo) if authentication is successful, otherwise  Left(errorMessage).
 | 
					   * Returns Right(LDAPUserInfo) if authentication is successful, otherwise  Left(errorMessage).
 | 
				
			||||||
@@ -30,7 +51,7 @@ object LDAPUtil {
 | 
				
			|||||||
      keystore = ldapSettings.keystore.getOrElse(""),
 | 
					      keystore = ldapSettings.keystore.getOrElse(""),
 | 
				
			||||||
      error    = "System LDAP authentication failed."
 | 
					      error    = "System LDAP authentication failed."
 | 
				
			||||||
    ){ conn =>
 | 
					    ){ 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 Some(userDN) => userAuthentication(ldapSettings, userDN, userName, password)
 | 
				
			||||||
        case None         => Left("User does not exist.")
 | 
					        case None         => Left("User does not exist.")
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -47,7 +68,15 @@ object LDAPUtil {
 | 
				
			|||||||
      keystore = ldapSettings.keystore.getOrElse(""),
 | 
					      keystore = ldapSettings.keystore.getOrElse(""),
 | 
				
			||||||
      error    = "User LDAP Authentication Failed."
 | 
					      error    = "User LDAP Authentication Failed."
 | 
				
			||||||
    ){ conn =>
 | 
					    ){ conn =>
 | 
				
			||||||
      findMailAddress(conn, userDN, ldapSettings.userNameAttribute, userName, 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(
 | 
					          case Some(mailAddress) => Right(LDAPUserInfo(
 | 
				
			||||||
            userName    = getUserNameFromMailAddress(userName),
 | 
					            userName    = getUserNameFromMailAddress(userName),
 | 
				
			||||||
            fullName    = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute =>
 | 
					            fullName    = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute =>
 | 
				
			||||||
@@ -58,6 +87,7 @@ object LDAPUtil {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private def getUserNameFromMailAddress(userName: String): String = {
 | 
					  private def getUserNameFromMailAddress(userName: String): String = {
 | 
				
			||||||
    (userName.indexOf('@') match {
 | 
					    (userName.indexOf('@') match {
 | 
				
			||||||
@@ -112,7 +142,7 @@ object LDAPUtil {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Search a specified user and returns userDN if exists.
 | 
					   * 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
 | 
					    @tailrec
 | 
				
			||||||
    def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = {
 | 
					    def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = {
 | 
				
			||||||
      if(results.hasMore){
 | 
					      if(results.hasMore){
 | 
				
			||||||
@@ -125,7 +155,13 @@ object LDAPUtil {
 | 
				
			|||||||
        entries.flatten
 | 
					        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
 | 
					      case x => x.getDN
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,11 +6,11 @@ import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
 | 
				
			|||||||
import org.slf4j.LoggerFactory
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import app.Context
 | 
					import app.Context
 | 
				
			||||||
 | 
					import model.Session
 | 
				
			||||||
import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
 | 
					import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
 | 
				
			||||||
import servlet.Database
 | 
					import servlet.Database
 | 
				
			||||||
import SystemSettingsService.Smtp
 | 
					import SystemSettingsService.Smtp
 | 
				
			||||||
import _root_.util.ControlUtil.defining
 | 
					import _root_.util.ControlUtil.defining
 | 
				
			||||||
import model.profile.simple.Session
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait Notifier extends RepositoryService with AccountService with IssuesService {
 | 
					trait Notifier extends RepositoryService with AccountService with IssuesService {
 | 
				
			||||||
  def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
 | 
					  def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
 | 
				
			||||||
@@ -28,7 +28,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    .distinct
 | 
					    .distinct
 | 
				
			||||||
    .withFilter ( _ != context.loginAccount.get.userName )	// the operation in person is excluded
 | 
					    .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)) )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,8 +69,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
 | 
				
			|||||||
      (msg: String => String)(implicit context: Context) = {
 | 
					      (msg: String => String)(implicit context: Context) = {
 | 
				
			||||||
    val database = Database(context.request.getServletContext)
 | 
					    val database = Database(context.request.getServletContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val f = future {
 | 
					    val f = Future {
 | 
				
			||||||
      // TODO Can we use the Database Session in other than Transaction Filter?
 | 
					 | 
				
			||||||
      database withSession { implicit session =>
 | 
					      database withSession { implicit session =>
 | 
				
			||||||
        getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
 | 
					        getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
 | 
				
			||||||
          defining(
 | 
					          defining(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.
 | 
					   * Extract issue id like ```#issueId``` from the given message.
 | 
				
			||||||
   *
 | 
					   *
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
package view
 | 
					package view
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import service.RequestCache
 | 
					import service.RequestCache
 | 
				
			||||||
import twirl.api.Html
 | 
					import play.twirl.api.Html
 | 
				
			||||||
import util.StringUtil
 | 
					import util.StringUtil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
trait AvatarImageProvider { self: RequestCache =>
 | 
					trait AvatarImageProvider { self: RequestCache =>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
package view
 | 
					package view
 | 
				
			||||||
import java.util.Date
 | 
					import java.util.{Date, TimeZone}
 | 
				
			||||||
import java.text.SimpleDateFormat
 | 
					import java.text.SimpleDateFormat
 | 
				
			||||||
import twirl.api.Html
 | 
					import play.twirl.api.Html
 | 
				
			||||||
import util.StringUtil
 | 
					import util.StringUtil
 | 
				
			||||||
import service.RequestCache
 | 
					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'".
 | 
					   * 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".
 | 
					   * 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.
 | 
					   * 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 =
 | 
					  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.
 | 
					   * Converts commit id, issue id and username to the link.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
@(account: model.Account, info: Option[Any])(implicit context: app.Context)
 | 
					@(account: model.Account, info: Option[Any])(implicit context: app.Context)
 | 
				
			||||||
@import context._
 | 
					@import context._
 | 
				
			||||||
@import view.helpers._
 | 
					@import view.helpers._
 | 
				
			||||||
 | 
					@import util.LDAPUtil
 | 
				
			||||||
@html.main("Edit your profile"){
 | 
					@html.main("Edit your profile"){
 | 
				
			||||||
<div class="container">
 | 
					<div class="container">
 | 
				
			||||||
  <div class="row-fluid">
 | 
					  <div class="row-fluid">
 | 
				
			||||||
@@ -9,6 +10,7 @@
 | 
				
			|||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="span9">
 | 
					    <div class="span9">
 | 
				
			||||||
      @helper.html.information(info)
 | 
					      @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">
 | 
					      <form action="@url(account.userName)/_edit" method="POST" validate="true">
 | 
				
			||||||
      <div class="box">
 | 
					      <div class="box">
 | 
				
			||||||
        <div class="box-header">Profile</div>
 | 
					        <div class="box-header">Profile</div>
 | 
				
			||||||
@@ -31,7 +33,7 @@
 | 
				
			|||||||
              </fieldset>
 | 
					              </fieldset>
 | 
				
			||||||
              <fieldset>
 | 
					              <fieldset>
 | 
				
			||||||
                <label for="mailAddress" class="strong">Mail Address:</label>
 | 
					                <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>
 | 
					                <span id="error-mailAddress" class="error"></span>
 | 
				
			||||||
              </fieldset>
 | 
					              </fieldset>
 | 
				
			||||||
              <fieldset>
 | 
					              <fieldset>
 | 
				
			||||||
@@ -52,7 +54,7 @@
 | 
				
			|||||||
              <a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
 | 
					              <a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <input type="submit" class="btn btn-success" value="Save"/>
 | 
					            <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>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,7 +60,7 @@ $(function(){
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('#addMember').click(function(){
 | 
					  $('#addMember').click(function(){
 | 
				
			||||||
    $('#error-memberName').text('');
 | 
					    $('#error-members').text('');
 | 
				
			||||||
    var userName = $('#memberName').val();
 | 
					    var userName = $('#memberName').val();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // check empty
 | 
					    // check empty
 | 
				
			||||||
@@ -73,18 +73,18 @@ $(function(){
 | 
				
			|||||||
      return $(this).data('name') == userName;
 | 
					      return $(this).data('name') == userName;
 | 
				
			||||||
    }).length > 0;
 | 
					    }).length > 0;
 | 
				
			||||||
    if(exists){
 | 
					    if(exists){
 | 
				
			||||||
      $('#error-memberName').text('User has been already added.');
 | 
					      $('#error-members').text('User has been already added.');
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // check existence
 | 
					    // check existence
 | 
				
			||||||
    $.post('@path/admin/users/_usercheck', {
 | 
					    $.post('@path/_user/existence', {
 | 
				
			||||||
      'userName': userName
 | 
					      'userName': userName
 | 
				
			||||||
    }, function(data, status){
 | 
					    }, function(data, status){
 | 
				
			||||||
      if(data == 'true'){
 | 
					      if(data == 'true'){
 | 
				
			||||||
        addMemberHTML(userName, false);
 | 
					        addMemberHTML(userName, false);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        $('#error-memberName').text('User does not exist.');
 | 
					        $('#error-members').text('User does not exist.');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@
 | 
				
			|||||||
      <div class="row-fluid">
 | 
					      <div class="row-fluid">
 | 
				
			||||||
        <div class="span4">
 | 
					        <div class="span4">
 | 
				
			||||||
          <div class="block">
 | 
					          <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-fullname">@account.fullName</div>
 | 
				
			||||||
            <div class="account-username">@account.userName</div>
 | 
					            <div class="account-username">@account.userName</div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -133,6 +133,13 @@
 | 
				
			|||||||
                <span id="error-ldap_userNameAttribute" class="error"></span>
 | 
					                <span id="error-ldap_userNameAttribute" class="error"></span>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </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">
 | 
					            <div class="control-group">
 | 
				
			||||||
              <label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
 | 
					              <label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
 | 
				
			||||||
              <div class="controls">
 | 
					              <div class="controls">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,7 +59,7 @@ $(function(){
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('#addMember').click(function(){
 | 
					  $('#addMember').click(function(){
 | 
				
			||||||
    $('#error-memberName').text('');
 | 
					    $('#error-members').text('');
 | 
				
			||||||
    var userName = $('#memberName').val();
 | 
					    var userName = $('#memberName').val();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // check empty
 | 
					    // check empty
 | 
				
			||||||
@@ -72,18 +72,18 @@ $(function(){
 | 
				
			|||||||
      return $(this).data('name') == userName;
 | 
					      return $(this).data('name') == userName;
 | 
				
			||||||
    }).length > 0;
 | 
					    }).length > 0;
 | 
				
			||||||
    if(exists){
 | 
					    if(exists){
 | 
				
			||||||
      $('#error-memberName').text('User has been already added.');
 | 
					      $('#error-members').text('User has been already added.');
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // check existence
 | 
					    // check existence
 | 
				
			||||||
    $.post('@path/admin/users/_usercheck', {
 | 
					    $.post('@path/_user/existence', {
 | 
				
			||||||
      'userName': userName
 | 
					      'userName': userName
 | 
				
			||||||
    }, function(data, status){
 | 
					    }, function(data, status){
 | 
				
			||||||
      if(data == 'true'){
 | 
					      if(data == 'true'){
 | 
				
			||||||
        addMemberHTML(userName, false);
 | 
					        addMemberHTML(userName, false);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        $('#error-memberName').text('User does not exist.');
 | 
					        $('#error-members').text('User does not exist.');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
@(listparts: twirl.api.Html,
 | 
					@(listparts: play.twirl.api.Html,
 | 
				
			||||||
  allCount: Int,
 | 
					  allCount: Int,
 | 
				
			||||||
  assignedCount: Int,
 | 
					  assignedCount: Int,
 | 
				
			||||||
  createdByCount: Int,
 | 
					  createdByCount: Int,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
@(listparts: twirl.api.Html,
 | 
					@(listparts: play.twirl.api.Html,
 | 
				
			||||||
  counts: List[service.PullRequestService.PullRequestCount],
 | 
					  counts: List[service.PullRequestService.PullRequestCount],
 | 
				
			||||||
  repositories: List[(String, String, Int)],
 | 
					  repositories: List[(String, String, Int)],
 | 
				
			||||||
  condition: service.IssuesService.IssueSearchCondition,
 | 
					  condition: service.IssuesService.IssueSearchCondition,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,24 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
// copy to clipboard
 | 
					// copy to clipboard
 | 
				
			||||||
(function() {
 | 
					(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.
 | 
					  // 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.
 | 
					  // NOTE(tanacasino) I think this way is wrong... but i don't know correct way.
 | 
				
			||||||
  var moviePath = (function() {
 | 
					  var moviePath = (function() {
 | 
				
			||||||
@@ -24,6 +42,7 @@
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
  var title = $('#@id').attr('title');
 | 
					  var title = $('#@id').attr('title');
 | 
				
			||||||
  $('#@id').removeAttr('title')
 | 
					  $('#@id').removeAttr('title')
 | 
				
			||||||
 | 
					  clip.htmlBridge = "#global-zeroclipboard-html-bridge";
 | 
				
			||||||
  clip.on('complete', function(client, args) {
 | 
					  clip.on('complete', function(client, args) {
 | 
				
			||||||
    $(clip.htmlBridge).attr('title', 'copied!').tooltip('fixTitle').tooltip('show');
 | 
					    $(clip.htmlBridge).attr('title', 'copied!').tooltip('fixTitle').tooltip('show');
 | 
				
			||||||
    $(clip.htmlBridge).attr('title', title).tooltip('fixTitle');
 | 
					    $(clip.htmlBridge).attr('title', title).tooltip('fixTitle');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,11 +11,13 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
$(function(){
 | 
					$(function(){
 | 
				
			||||||
  var callback = function(data){
 | 
					  var callback = function(data){
 | 
				
			||||||
 | 
					    $('#update-comment-@commentId, #cancel-comment-@commentId').removeAttr('disabled');
 | 
				
			||||||
    $('#commentContent-@commentId').empty().html(data.content);
 | 
					    $('#commentContent-@commentId').empty().html(data.content);
 | 
				
			||||||
    prettyPrint();
 | 
					    prettyPrint();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('#update-comment-@commentId').click(function(){
 | 
					  $('#update-comment-@commentId').click(function(){
 | 
				
			||||||
 | 
					    $('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
 | 
				
			||||||
    $.ajax({
 | 
					    $.ajax({
 | 
				
			||||||
      url: '@path/@owner/@repository/issue_comments/edit/@commentId',
 | 
					      url: '@path/@owner/@repository/issue_comments/edit/@commentId',
 | 
				
			||||||
      type: 'POST',
 | 
					      type: 'POST',
 | 
				
			||||||
@@ -26,11 +28,13 @@ $(function(){
 | 
				
			|||||||
    }).done(
 | 
					    }).done(
 | 
				
			||||||
      callback
 | 
					      callback
 | 
				
			||||||
    ).fail(function(req) {
 | 
					    ).fail(function(req) {
 | 
				
			||||||
 | 
					      $('#update-comment-@commentId, #cancel-comment-@commentId').removeAttr('disabled');
 | 
				
			||||||
      $('#error-edit-content-@commentId').text($.parseJSON(req.responseText).content);
 | 
					      $('#error-edit-content-@commentId').text($.parseJSON(req.responseText).content);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('#cancel-comment-@commentId').click(function(){
 | 
					  $('#cancel-comment-@commentId').click(function(){
 | 
				
			||||||
 | 
					    $('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
 | 
				
			||||||
    $.get('@path/@owner/@repository/issue_comments/_data/@commentId', callback);
 | 
					    $.get('@path/@owner/@repository/issue_comments/_data/@commentId', callback);
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,11 +14,13 @@ $(function(){
 | 
				
			|||||||
  $('#edit-content').elastic();
 | 
					  $('#edit-content').elastic();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var callback = function(data){
 | 
					  var callback = function(data){
 | 
				
			||||||
 | 
					    $('#update, #cancel').removeAttr('disabled');
 | 
				
			||||||
    $('#issueTitle').empty().text(data.title);
 | 
					    $('#issueTitle').empty().text(data.title);
 | 
				
			||||||
    $('#issueContent').empty().html(data.content);
 | 
					    $('#issueContent').empty().html(data.content);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('#update').click(function(){
 | 
					  $('#update').click(function(){
 | 
				
			||||||
 | 
					    $('#update, #cancel').attr('disabled', 'disabled');
 | 
				
			||||||
    $.ajax({
 | 
					    $.ajax({
 | 
				
			||||||
      url: '@path/@owner/@repository/issues/edit/@issueId',
 | 
					      url: '@path/@owner/@repository/issues/edit/@issueId',
 | 
				
			||||||
      type: 'POST',
 | 
					      type: 'POST',
 | 
				
			||||||
@@ -29,11 +31,13 @@ $(function(){
 | 
				
			|||||||
    }).done(
 | 
					    }).done(
 | 
				
			||||||
      callback
 | 
					      callback
 | 
				
			||||||
    ).fail(function(req) {
 | 
					    ).fail(function(req) {
 | 
				
			||||||
 | 
					      $('#update, #cancel').removeAttr('disabled');
 | 
				
			||||||
      $('#error-edit-title').text($.parseJSON(req.responseText).title);
 | 
					      $('#error-edit-title').text($.parseJSON(req.responseText).title);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $('#cancel').click(function(){
 | 
					  $('#cancel').click(function(){
 | 
				
			||||||
 | 
					    $('#update, #cancel').attr('disabled', 'disabled');
 | 
				
			||||||
    $.get('@path/@owner/@repository/issues/_data/@issueId', callback);
 | 
					    $.get('@path/@owner/@repository/issues/_data/@issueId', callback);
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,6 +88,9 @@
 | 
				
			|||||||
        $('#search').submit(function(){
 | 
					        $('#search').submit(function(){
 | 
				
			||||||
          return $.trim($(this).find('input[name=query]').val()) != '';
 | 
					          return $.trim($(this).find('input[name=query]').val()) != '';
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        @plugin.PluginSystem.javaScripts.filter(_.filter(context.currentPath)).map { js =>
 | 
				
			||||||
 | 
					          @Html(js.script)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,6 +87,9 @@
 | 
				
			|||||||
        <div style="margin-top: 10px;">
 | 
					        <div style="margin-top: 10px;">
 | 
				
			||||||
          <a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Download ZIP</a>
 | 
					          <a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Download ZIP</a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div style="margin-top: 10px;">
 | 
				
			||||||
 | 
					          <a href="@{url(repository)}/archive/@{encodeRefName(id)}.tar.gz" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Download TAR.GZ</a>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,13 @@
 | 
				
			|||||||
  <table class="table table-file-list" style="border: 1px solid silver;">
 | 
					  <table class="table table-file-list" style="border: 1px solid silver;">
 | 
				
			||||||
    @commits.map { day =>
 | 
					    @commits.map { day =>
 | 
				
			||||||
      <tr>
 | 
					      <tr>
 | 
				
			||||||
        <th colspan="3" class="box-header" style="font-weight: normal;">@date(day.head.time)</th>
 | 
					        <th colspan="3" class="box-header" style="font-weight: normal;">@date(day.head.commitTime)</th>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      @day.map { commit =>
 | 
					      @day.map { commit =>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
          <td style="width: 20%;">
 | 
					          <td style="width: 20%;">
 | 
				
			||||||
            @avatar(commit, 20)
 | 
					            @avatar(commit, 20)
 | 
				
			||||||
            @user(commit.committer, commit.mailAddress, "username")
 | 
					            @user(commit.authorName, commit.authorEmailAddress, "username")
 | 
				
			||||||
          </td>
 | 
					          </td>
 | 
				
			||||||
          <td>@commit.shortMessage</td>
 | 
					          <td>@commit.shortMessage</td>
 | 
				
			||||||
          <td style="width: 10%; text-align: right;">
 | 
					          <td style="width: 10%; text-align: right;">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@
 | 
				
			|||||||
        pullreq.repositoryName == pullreq.requestRepositoryName && repository.branchList.contains(pullreq.requestBranch)){
 | 
					        pullreq.repositoryName == pullreq.requestRepositoryName && repository.branchList.contains(pullreq.requestBranch)){
 | 
				
			||||||
        <div class="box issue-comment-box" style="background-color: #d0eeff;">
 | 
					        <div class="box issue-comment-box" style="background-color: #d0eeff;">
 | 
				
			||||||
          <div class="box-content"class="issue-content" style="border: 1px solid #87a8c9; padding: 10px;">
 | 
					          <div class="box-content"class="issue-content" style="border: 1px solid #87a8c9; padding: 10px;">
 | 
				
			||||||
            <a href="@url(repository)/pull/@issue.issueId/delete/@pullreq.requestBranch" class="btn btn-info pull-right delete-branch" data-name="@pullreq.requestBranch">Delete branch</a>
 | 
					            <a href="@url(repository)/pull/@issue.issueId/delete/@encodeRefName(pullreq.requestBranch)" class="btn btn-info pull-right delete-branch" data-name="@pullreq.requestBranch">Delete branch</a>
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
              <span class="strong">Pull request successfully merged and closed</span>
 | 
					              <span class="strong">Pull request successfully merged and closed</span>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@
 | 
				
			|||||||
    <p>
 | 
					    <p>
 | 
				
			||||||
      <span class="strong">Step 1:</span> Check out a new branch to test the changes — run this from your project directory
 | 
					      <span class="strong">Step 1:</span> Check out a new branch to test the changes — run this from your project directory
 | 
				
			||||||
    </p>
 | 
					    </p>
 | 
				
			||||||
    @defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.requestBranch}"){ command =>
 | 
					    @defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}"){ command =>
 | 
				
			||||||
      @helper.html.copy("merge-command-copy-1", command){
 | 
					      @helper.html.copy("merge-command-copy-1", command){
 | 
				
			||||||
        <pre style="width: 500px; float: left;">@command</pre>
 | 
					        <pre style="width: 500px; float: left;">@command</pre>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -62,7 +62,7 @@
 | 
				
			|||||||
    <p>
 | 
					    <p>
 | 
				
			||||||
      <span class="strong">Step 3:</span> Merge the changes and update the server
 | 
					      <span class="strong">Step 3:</span> Merge the changes and update the server
 | 
				
			||||||
    </p>
 | 
					    </p>
 | 
				
			||||||
    @defining(s"git checkout master\ngit merge ${pullreq.requestUserName}-${pullreq.branch}\ngit push origin ${pullreq.branch}"){ command =>
 | 
					    @defining(s"git checkout master\ngit merge ${pullreq.requestUserName}-${pullreq.requestBranch}\ngit push origin ${pullreq.branch}"){ command =>
 | 
				
			||||||
      @helper.html.copy("merge-command-copy-3", command){
 | 
					      @helper.html.copy("merge-command-copy-3", command){
 | 
				
			||||||
        <pre style="width: 500px; float: left;">@command</pre>
 | 
					        <pre style="width: 500px; float: left;">@command</pre>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,13 +28,13 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <table class="table table-bordered">
 | 
					    <table class="table table-bordered blobview">
 | 
				
			||||||
      <tr>
 | 
					      <tr>
 | 
				
			||||||
        <th style="font-weight: normal;">
 | 
					        <th style="font-weight: normal;">
 | 
				
			||||||
          <div class="pull-left">
 | 
					          <div class="pull-left">
 | 
				
			||||||
            @avatar(latestCommit, 20)
 | 
					            @avatar(latestCommit, 20)
 | 
				
			||||||
            @user(latestCommit.committer, latestCommit.mailAddress, "username strong")
 | 
					            @user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")
 | 
				
			||||||
            <span class="muted">@datetime(latestCommit.time)</span>
 | 
					            <span class="muted">@datetime(latestCommit.commitTime)</span>
 | 
				
			||||||
            <a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
 | 
					            <a href="@url(repository)/commit/@latestCommit.id" class="commit-message">@link(latestCommit.summary, repository)</a>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div class="btn-group pull-right">
 | 
					          <div class="btn-group pull-right">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,10 @@
 | 
				
			|||||||
              <a href="@url(repository)/compare/@{encodeRefName(repository.repository.defaultBranch)}...@{encodeRefName(branchName)}">to @{repository.repository.defaultBranch}</a>
 | 
					              <a href="@url(repository)/compare/@{encodeRefName(repository.repository.defaultBranch)}...@{encodeRefName(branchName)}">to @{repository.repository.defaultBranch}</a>
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          </td>
 | 
					          </td>
 | 
				
			||||||
          <td><a href="@url(repository)/archive/@{encodeRefName(branchName)}.zip">ZIP</a></td>
 | 
					          <td>
 | 
				
			||||||
 | 
					            <a href="@url(repository)/archive/@{encodeRefName(branchName)}.zip">ZIP</a>
 | 
				
			||||||
 | 
					            <a href="@url(repository)/archive/@{encodeRefName(branchName)}.tar.gz">TAR.GZ</a>
 | 
				
			||||||
 | 
					          </td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,9 +42,6 @@
 | 
				
			|||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      <tr>
 | 
					      <tr>
 | 
				
			||||||
        <td>
 | 
					        <td>
 | 
				
			||||||
          @avatar(commit, 20)
 | 
					 | 
				
			||||||
          @user(commit.committer, commit.mailAddress, "username strong")
 | 
					 | 
				
			||||||
          <span class="muted">@datetime(commit.time)</span>
 | 
					 | 
				
			||||||
          <div class="pull-right monospace small" style="text-align: right;">
 | 
					          <div class="pull-right monospace small" style="text-align: right;">
 | 
				
			||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
              @if(commit.parents.size == 0){
 | 
					              @if(commit.parents.size == 0){
 | 
				
			||||||
@@ -66,6 +63,21 @@
 | 
				
			|||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div class="author-info">
 | 
				
			||||||
 | 
					            <div class="author">
 | 
				
			||||||
 | 
					              @avatar(commit, 20)
 | 
				
			||||||
 | 
					              <span>@user(commit.authorName, commit.authorEmailAddress, "username strong")</span>
 | 
				
			||||||
 | 
					              <span class="muted">authored on @datetime(commit.authorTime)</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            @if(commit.isDifferentFromAuthor) {
 | 
				
			||||||
 | 
					            <div class="committer">
 | 
				
			||||||
 | 
					              <span class="icon-arrow-right"></span>
 | 
				
			||||||
 | 
					              <span>@user(commit.committerName, commit.committerEmailAddress, "username strong")</span>
 | 
				
			||||||
 | 
					              <span class="muted"> committed on @datetime(commit.commitTime)</span>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,7 @@
 | 
				
			|||||||
    @commits.map { day =>
 | 
					    @commits.map { day =>
 | 
				
			||||||
      <table class="table table-bordered">
 | 
					      <table class="table table-bordered">
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
          <th>@date(day.head.time)</th>
 | 
					          <th>@date(day.head.commitTime)</th>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
        @day.map { commit =>
 | 
					        @day.map { commit =>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
@@ -57,8 +57,13 @@
 | 
				
			|||||||
                  <pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
 | 
					                  <pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                <div class="small">
 | 
					                <div class="small">
 | 
				
			||||||
                  @user(commit.committer, commit.mailAddress, "username")
 | 
					                  @user(commit.authorName, commit.authorEmailAddress, "username")
 | 
				
			||||||
                  <span class="muted">@datetime(commit.time)</span>
 | 
					                  <span class="muted">authored @datetime(commit.authorTime)</span>
 | 
				
			||||||
 | 
					                  @if(commit.isDifferentFromAuthor) {
 | 
				
			||||||
 | 
					                    <span class="icon-arrow-right" style="margin-top : -2px ;"></span>
 | 
				
			||||||
 | 
					                    @user(commit.committerName, commit.committerEmailAddress, "username")
 | 
				
			||||||
 | 
					                    <span class="muted">committed @datetime(commit.authorTime)</span>
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,6 +60,7 @@
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            <input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
 | 
					            <input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
 | 
				
			||||||
            <input type="hidden" id="charset" name="charset" value="@content.charset"/>
 | 
					            <input type="hidden" id="charset" name="charset" value="@content.charset"/>
 | 
				
			||||||
 | 
					            <input type="hidden" id="lineSeparator" name="lineSeparator" value="@content.lineSeparator"/>
 | 
				
			||||||
            <input type="hidden" id="content" name="content" value=""/>
 | 
					            <input type="hidden" id="content" name="content" value=""/>
 | 
				
			||||||
            <input type="hidden" id="initial" value="@content.content"/>
 | 
					            <input type="hidden" id="initial" value="@content.content"/>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,12 +40,23 @@
 | 
				
			|||||||
      <tr>
 | 
					      <tr>
 | 
				
			||||||
        <td colspan="4" class="latest-commit">
 | 
					        <td colspan="4" class="latest-commit">
 | 
				
			||||||
          <div>
 | 
					          <div>
 | 
				
			||||||
            @avatar(latestCommit, 20)
 | 
					 | 
				
			||||||
            @user(latestCommit.committer, latestCommit.mailAddress, "username strong")
 | 
					 | 
				
			||||||
            <span class="muted">@datetime(latestCommit.time)</span>
 | 
					 | 
				
			||||||
            <div class="pull-right align-right monospace" style="line-height: 18px;">
 | 
					            <div class="pull-right align-right monospace" style="line-height: 18px;">
 | 
				
			||||||
              <a href="@url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> @latestCommit.id.substring(0, 10)</a>
 | 
					              <a href="@url(repository)/commit/@latestCommit.id" class="commit-id"><span class="muted">latest commit</span> @latestCommit.id.substring(0, 10)</a>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="author-info">
 | 
				
			||||||
 | 
					              <div class="author">
 | 
				
			||||||
 | 
					                @avatar(latestCommit, 20)
 | 
				
			||||||
 | 
					                <span>@user(latestCommit.authorName, latestCommit.authorEmailAddress, "username strong")</span>
 | 
				
			||||||
 | 
					                <span class="muted"> authored on @datetime(latestCommit.authorTime)</span>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					              @if(latestCommit.isDifferentFromAuthor) {
 | 
				
			||||||
 | 
					              <div class="committer">
 | 
				
			||||||
 | 
					                <span class="icon-arrow-right"></span>
 | 
				
			||||||
 | 
					                <span>@user(latestCommit.committerName, latestCommit.committerEmailAddress, "username strong")</span>
 | 
				
			||||||
 | 
					                <span class="muted"> committed on @datetime(latestCommit.commitTime)</span>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
@@ -83,7 +94,7 @@
 | 
				
			|||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
        <td class="mute">
 | 
					        <td class="mute">
 | 
				
			||||||
          <a href="@url(repository)/commit/@file.commitId" class="commit-message">@link(file.message, repository)</a>
 | 
					          <a href="@url(repository)/commit/@file.commitId" class="commit-message">@link(file.message, repository)</a>
 | 
				
			||||||
          [@user(file.committer, file.mailAddress)]
 | 
					          [@user(file.author, file.mailAddress)]
 | 
				
			||||||
        </td>
 | 
					        </td>
 | 
				
			||||||
        <td style="text-align: right;">@datetime(file.time)</td>
 | 
					        <td style="text-align: right;">@datetime(file.time)</td>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,12 +11,15 @@
 | 
				
			|||||||
        <th width="20%">Commit</th>
 | 
					        <th width="20%">Commit</th>
 | 
				
			||||||
        <th width="20%">Download</th>
 | 
					        <th width="20%">Download</th>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      @repository.tags.map { tag =>
 | 
					      @repository.tags.reverse.map { tag =>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
          <td><a href="@url(repository)/tree/@encodeRefName(tag.name)">@tag.name</a></td>
 | 
					          <td><a href="@url(repository)/tree/@encodeRefName(tag.name)">@tag.name</a></td>
 | 
				
			||||||
          <td>@datetime(tag.time)</td>
 | 
					          <td>@datetime(tag.time)</td>
 | 
				
			||||||
          <td class="monospace"><a href="@url(repository)/commit/@tag.id">@tag.id.substring(0, 10)</a></td>
 | 
					          <td class="monospace"><a href="@url(repository)/commit/@tag.id">@tag.id.substring(0, 10)</a></td>
 | 
				
			||||||
          <td><a href="@url(repository)/archive/@{encodeRefName(tag.name)}.zip">ZIP</a></td>
 | 
					          <td>
 | 
				
			||||||
 | 
					              <a href="@url(repository)/archive/@{encodeRefName(tag.name)}.zip">ZIP</a>
 | 
				
			||||||
 | 
					              <a href="@url(repository)/archive/@{encodeRefName(tag.name)}.tar.gz">TAR.GZ</a>
 | 
				
			||||||
 | 
					          </td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,9 +34,9 @@
 | 
				
			|||||||
      @commits.map { commit =>
 | 
					      @commits.map { commit =>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
          <td width="0%"><input type="checkbox" name="commitId" value="@commit.id"></td>
 | 
					          <td width="0%"><input type="checkbox" name="commitId" value="@commit.id"></td>
 | 
				
			||||||
          <td>@avatar(commit, 20) @user(commit.committer, commit.mailAddress)</td>
 | 
					          <td>@avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress)</td>
 | 
				
			||||||
          <td width="80%">
 | 
					          <td width="80%">
 | 
				
			||||||
            <span class="muted">@datetime(commit.time):</span> @commit.shortMessage
 | 
					            <span class="muted">@datetime(commit.authorTime):</span> @commit.shortMessage
 | 
				
			||||||
          </td>
 | 
					          </td>
 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,7 +52,7 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div style="margin-right: 220px;">
 | 
					    <div style="width: 650px;" class="pull-left">
 | 
				
			||||||
      <div class="markdown-body">
 | 
					      <div class="markdown-body">
 | 
				
			||||||
        @markdown(page.content, repository, true, false)
 | 
					        @markdown(page.content, repository, true, false)
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user