mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 13:16:22 +02:00
Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b37967162 | ||
|
|
a03b9584ee | ||
|
|
34649dfeda | ||
|
|
bc84cfc2c8 | ||
|
|
bcba1f068b | ||
|
|
e6f30ef86b | ||
|
|
9e67999ef0 | ||
|
|
be75cef752 | ||
|
|
19ead71b48 | ||
|
|
7ebe1d6c62 | ||
|
|
2331b58b87 | ||
|
|
d495b04d85 | ||
|
|
751a8703ef | ||
|
|
9019d93449 | ||
|
|
32006e02c0 | ||
|
|
5ba0f6d51e | ||
|
|
c004d501f6 | ||
|
|
6067fa0fca | ||
|
|
e2c6658e59 | ||
|
|
0a6e50cbbe | ||
|
|
3732963d4b | ||
|
|
20217058fe | ||
|
|
e67365a19f | ||
|
|
c2f1817c6a | ||
|
|
4a948d9b01 | ||
|
|
377bc2703b | ||
|
|
196890b26f | ||
|
|
491fc2c164 | ||
|
|
1bed38f175 | ||
|
|
b124c31f65 | ||
|
|
8c588cbd66 | ||
|
|
334753b1ad | ||
|
|
f6e7401d1b | ||
|
|
ab564cc2d4 | ||
|
|
b10839a5c2 | ||
|
|
bb3323fb0e | ||
|
|
0b15ecbacd | ||
|
|
9f6afaed07 | ||
|
|
925420734e | ||
|
|
fb6bb12c52 | ||
|
|
22d12d0488 | ||
|
|
6888d959e1 | ||
|
|
65e66f52f6 | ||
|
|
225abfa126 | ||
|
|
876757f2d4 | ||
|
|
7e77398645 | ||
|
|
73c76a5a88 | ||
|
|
e5fca0d6cc | ||
|
|
eed7e5177f | ||
|
|
dd54ab31cb | ||
|
|
0085cb24ad | ||
|
|
6a758902ef | ||
|
|
0d81a9a9b6 | ||
|
|
6e4f6da633 | ||
|
|
15118ca5c1 | ||
|
|
8161560757 | ||
|
|
9ba564c864 | ||
|
|
06b5b92673 | ||
|
|
a417c373f1 | ||
|
|
5f5cc8d454 | ||
|
|
b9b6589bd7 | ||
|
|
b79f6a5fa0 | ||
|
|
0acbaeae86 | ||
|
|
bd046da3d0 | ||
|
|
a889ed7c46 | ||
|
|
e24684cb2b | ||
|
|
5f939c18b4 | ||
|
|
140f1eb31b | ||
|
|
d412dd5009 | ||
|
|
8643bfeb37 | ||
|
|
31b6adf0e5 | ||
|
|
f1ac2b3507 | ||
|
|
172af307a6 | ||
|
|
135e1ef73d | ||
|
|
da55bf6af3 | ||
|
|
883a9c8b17 | ||
|
|
7da89940e3 | ||
|
|
3233b0ae3c | ||
|
|
4c2ed09915 | ||
|
|
256b6c480f | ||
|
|
dc311837f9 | ||
|
|
92aec48c99 | ||
|
|
a6ada8c457 | ||
|
|
dcc601502e | ||
|
|
dd58d8c804 | ||
|
|
2ade54b7e3 | ||
|
|
136c5854f3 | ||
|
|
c597238d9c | ||
|
|
2552a58e08 | ||
|
|
74ad5872a3 | ||
|
|
485d502bd3 | ||
|
|
47bc8d030e | ||
|
|
48fe7133f7 | ||
|
|
5d962dc5e4 | ||
|
|
31e8e5a951 | ||
|
|
858373c628 | ||
|
|
7f142d2c0d | ||
|
|
08b86232a8 | ||
|
|
6bf4f42fdb | ||
|
|
f3c7de36d8 | ||
|
|
19f556de57 | ||
|
|
e4467df411 | ||
|
|
8d305a1fb1 | ||
|
|
b47153e645 | ||
|
|
c71766c84b | ||
|
|
23e4d679ae | ||
|
|
182acb2e02 | ||
|
|
b255b15006 | ||
|
|
b458f88161 | ||
|
|
398d8f2f1c | ||
|
|
85c1a56cbf | ||
|
|
da216c6960 | ||
|
|
bc91b153bf | ||
|
|
bc50b47d3a | ||
|
|
aed15a7f25 | ||
|
|
a1f09117b0 | ||
|
|
0a4a4a51ca | ||
|
|
f7fd53bf09 | ||
|
|
cbfb863a54 | ||
|
|
9d56d72611 | ||
|
|
527c91ff9d | ||
|
|
c58c2d6700 | ||
|
|
5518eca952 | ||
|
|
6e2b67ec0b | ||
|
|
837b1e44a7 | ||
|
|
e04c230c6e | ||
|
|
a01b5a4a59 | ||
|
|
427b6ce846 | ||
|
|
b7b5af2b72 | ||
|
|
39fec57f72 | ||
|
|
238dedb6df | ||
|
|
af091117b7 | ||
|
|
ddea4e12f0 | ||
|
|
9767903252 | ||
|
|
bc75f9f8a2 | ||
|
|
63627fc1d0 | ||
|
|
c23985c1a7 | ||
|
|
af58e99dcf | ||
|
|
676670e9e3 | ||
|
|
823c52e941 | ||
|
|
7f42007648 | ||
|
|
7214ef21d2 | ||
|
|
18a4492975 | ||
|
|
99f73b1016 | ||
|
|
0c1ce6a088 | ||
|
|
ae6291ab83 | ||
|
|
617fcf7c99 | ||
|
|
9df4a74837 | ||
|
|
966d4251be | ||
|
|
84b2e9cdcd | ||
|
|
e29d63c91a | ||
|
|
805d2b8e79 | ||
|
|
9983fd1292 | ||
|
|
1de202e927 | ||
|
|
4eb9f4a485 | ||
|
|
a8801e4e41 | ||
|
|
ee1c84dbf2 | ||
|
|
e40e1fa6cd | ||
|
|
055f648ea2 | ||
|
|
37a399c3a2 | ||
|
|
bc0b11b60a | ||
|
|
65a1ca7146 | ||
|
|
2293030d4e | ||
|
|
2848f07b83 | ||
|
|
55224ddcd8 | ||
|
|
054ae75b6b | ||
|
|
c83fab611e | ||
|
|
29baf1223c | ||
|
|
2a60f607ff | ||
|
|
78f4d26aa0 | ||
|
|
f59e86f5ca | ||
|
|
1c2af36c92 | ||
|
|
badbe73f4e | ||
|
|
a9d58698cd | ||
|
|
bb3f086aa6 | ||
|
|
2db674bb03 | ||
|
|
4bc4a16a80 | ||
|
|
d88a105628 | ||
|
|
15d0c5b506 | ||
|
|
dbde79d2f2 | ||
|
|
e6e3786b47 | ||
|
|
4c1b8004fc | ||
|
|
ff4052f097 | ||
|
|
13c206d068 | ||
|
|
5b875d7c73 | ||
|
|
e33dd9008b | ||
|
|
8764910553 | ||
|
|
4c89c40944 | ||
|
|
0f0986afcf | ||
|
|
5d5f1f8bdd | ||
|
|
03e386b3ce | ||
|
|
435eac7ae6 | ||
|
|
bd5df3977d | ||
|
|
ba218053f9 | ||
|
|
1fe448a83b | ||
|
|
26a45d0117 | ||
|
|
320585a530 | ||
|
|
ca0f888a99 | ||
|
|
3b08dc2e41 | ||
|
|
cc128a49c1 | ||
|
|
e0148695f2 | ||
|
|
a10188260c | ||
|
|
26b14ded58 | ||
|
|
e1f310317d | ||
|
|
937814ec5d | ||
|
|
b55fc649a6 | ||
|
|
6175eb7c08 | ||
|
|
ebb9d9329a | ||
|
|
843722f82e | ||
|
|
ce79eaada8 |
3
.travis.yml
Normal file
3
.travis.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
language: scala
|
||||
scala:
|
||||
- 2.11.2
|
||||
40
README.md
40
README.md
@@ -1,7 +1,7 @@
|
||||
GitBucket [](https://gitter.im/takezoe/gitbucket) [](https://buildhive.cloudbees.com/job/takezoe/job/gitbucket/)
|
||||
GitBucket [](https://gitter.im/takezoe/gitbucket) [](https://travis-ci.org/takezoe/gitbucket)
|
||||
=========
|
||||
|
||||
GitBucket is the easily installable Github clone written with Scala.
|
||||
GitBucket is the easily installable GitHub clone powered by Scala.
|
||||
|
||||
|
||||
Features
|
||||
@@ -23,7 +23,6 @@ The current version of GitBucket provides a basic features below:
|
||||
|
||||
Following features are not implemented, but we will make them in the future release!
|
||||
|
||||
- Comment for the changeset
|
||||
- Network graph
|
||||
- Statistics
|
||||
- Watch / Star
|
||||
@@ -80,6 +79,41 @@ Run the following commands in `Terminal` to
|
||||
|
||||
Release Notes
|
||||
--------
|
||||
### 3.0 - 3 Mar 2015
|
||||
- New plug-in system is available
|
||||
- Connection pooling by c3p0
|
||||
- New branch UI
|
||||
- Compare between specified commit ids
|
||||
|
||||
### 2.8 - 1 Feb 2015
|
||||
- New logo and icons
|
||||
- New system setting options to control visibility
|
||||
- Comment on side-by-side diff
|
||||
- Information message on sign-in page
|
||||
- Fork repository by group account
|
||||
|
||||
### 2.7 - 29 Dec 2014
|
||||
- Comment for commit and diff
|
||||
- Fix security issue in markdown rendering
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.6 - 24 Nov 2014
|
||||
- Search box at issues and pull requests
|
||||
- Information from administrator
|
||||
- Pull request UI has been updated
|
||||
- Move to TravisCI from Buildhive
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.5 - 4 Nov 2014
|
||||
- New Dashboard
|
||||
- Change datetime format
|
||||
- Create branch from Web UI
|
||||
- Task list in Markdown
|
||||
- Some bug fix and improvements
|
||||
|
||||
### 2.4.1 - 6 Oct 2014
|
||||
- Bug fix
|
||||
|
||||
### 2.4 - 6 Oct 2014
|
||||
- New UI is applied to Issues and Pull requests
|
||||
- Side-by-side diff is available
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
||||
<property name="jetty.dir" value="embed-jetty"/>
|
||||
<property name="scala.version" value="2.11"/>
|
||||
<property name="gitbucket.version" value="0.0.1"/>
|
||||
<property name="gitbucket.version" value="3.0.0"/>
|
||||
<property name="jetty.version" value="8.1.8.v20121106"/>
|
||||
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ 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`
|
||||
|
||||
1. Edit `gitbucket.conf` to suit.
|
||||
2. Type: `install`
|
||||
|
||||
15
contrib/linux/redhat/README.md
Normal file
15
contrib/linux/redhat/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Contrib Notes #
|
||||
|
||||
RPM spec file and init script for Red Hat Enterprise Linux 6.x.
|
||||
|
||||
To create RPM:
|
||||
1. Edit `../../gitbucket.conf` to suit.
|
||||
2. Edit `gitbucket.init` to suit.
|
||||
3. Edit `gitbucket.spec` to suit.
|
||||
4. Place `gitbucket.spec` to rpm/SPECS/.
|
||||
5. Place `gitbucket.init` and `gitbucket.war` to rpm/SOURCES/.
|
||||
6. Execute `rpmbuild -ba rpm/SPECS/gitbucket.spec`
|
||||
|
||||
This rpm runs gitbucket not as root user but as gitbucket user.
|
||||
This rpm creates user and group named `gitbucket` at installation.
|
||||
This rpm make chkconfig of gitbucket to be on.
|
||||
108
contrib/linux/redhat/gitbucket.init
Normal file
108
contrib/linux/redhat/gitbucket.init
Normal file
@@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# RedHat: /etc/rc.d/init.d/gitbucket
|
||||
#
|
||||
# Starts the GitBucket server
|
||||
#
|
||||
# chkconfig: 345 60 40
|
||||
# description: Run GitBucket server
|
||||
# processname: java
|
||||
|
||||
[ -f /etc/rc.d/init.d/functions ] && source /etc/rc.d/init.d/functions # RedHat
|
||||
|
||||
# Default values
|
||||
GITBUCKET_HOME=/var/lib/gitbucket
|
||||
GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
|
||||
|
||||
# Pull in cq settings
|
||||
[ -f /etc/sysconfig/gitbucket ] && source /etc/sysconfig/gitbucket # RedHat
|
||||
[ -f gitbucket.conf ] && source gitbucket.conf # For all systems
|
||||
|
||||
# Location of the log and PID file
|
||||
LOG_FILE=$GITBUCKET_LOG_DIR/run.log
|
||||
|
||||
RED='\033[1m\E[37;41m'
|
||||
GREEN='\033[1m\E[37;42m'
|
||||
OFF='\E[0m'
|
||||
|
||||
RETVAL=0
|
||||
|
||||
start() {
|
||||
echo -n $"Starting GitBucket server: "
|
||||
|
||||
START_OPTS=
|
||||
if [ $GITBUCKET_PORT ]; then
|
||||
START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
|
||||
fi
|
||||
if [ $GITBUCKET_PREFIX ]; then
|
||||
START_OPTS="${START_OPTS} --prefix=${GITBUCKET_PREFIX}"
|
||||
fi
|
||||
if [ $GITBUCKET_HOST ]; then
|
||||
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
|
||||
fi
|
||||
|
||||
GITBUCKET_HOME="${GITBUCKET_HOME}" daemon --user=gitbucket java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
|
||||
sleep 3
|
||||
pgrep -f $GITBUCKET_WAR_FILE >> $LOG_FILE 2>&1
|
||||
RETVAL=$?
|
||||
|
||||
if [ $RETVAL -eq 0 ] ; then
|
||||
success "Success"
|
||||
else
|
||||
failure "Exit code $RETVAL"
|
||||
fi
|
||||
|
||||
echo
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping GitBucket server: "
|
||||
|
||||
# Run the Java process
|
||||
pkill -f $GITBUCKET_WAR_FILE >>$LOG_FILE 2>&1
|
||||
RETVAL=$?
|
||||
|
||||
if [ $RETVAL -eq 0 ] ; then
|
||||
success "GitBucket stopping"
|
||||
else
|
||||
failure "GitBucket stopping"
|
||||
fi
|
||||
|
||||
echo
|
||||
return $RETVAL
|
||||
}
|
||||
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
status)
|
||||
pgrep -f $GITBUCKET_WAR_FILE >> $LOG_FILE 2>&1
|
||||
RETVAL=$?
|
||||
if [ $RETVAL -eq 0 ]; then
|
||||
echo $"GitBucket is running...."
|
||||
else
|
||||
echo $"GitBucket is stopped"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 [start|stop|restart|status]"
|
||||
RETVAL=2
|
||||
esac
|
||||
|
||||
exit $RETVAL
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Name: gitbucket
|
||||
Summary: GitHub clone written with Scala.
|
||||
Version: 1.7
|
||||
Version: 2.6
|
||||
Release: 1%{?dist}
|
||||
License: Apache
|
||||
URL: https://github.com/takezoe/gitbucket
|
||||
@@ -26,6 +26,25 @@ GitBucket is the easily installable GitHub clone written with Scala.
|
||||
%{__install} -m 0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/sysconfig/%{name}
|
||||
touch %{buildroot}%{_localstatedir}/log/%{name}/run.log
|
||||
|
||||
%pre
|
||||
/usr/sbin/groupadd -r gitbucket &> /dev/null || :
|
||||
/usr/sbin/useradd -g gitbucket -s /bin/false -r -c "GitBucket GitHub clone" -d %{_sharedstatedir}/%{name} gitbucket &> /dev/null || :
|
||||
|
||||
%post
|
||||
/sbin/chkconfig --add gitbucket
|
||||
|
||||
%preun
|
||||
if [ "$1" = 0 ]; then
|
||||
/sbin/service gitbucket stop > /dev/null 2>&1
|
||||
/sbin/chkconfig --del gitbucket
|
||||
fi
|
||||
exit 0
|
||||
|
||||
%postun
|
||||
if [ "$1" -ge 1 ]; then
|
||||
/sbin/service gitbucket restart > /dev/null 2>&1
|
||||
fi
|
||||
exit 0
|
||||
|
||||
%clean
|
||||
[ "%{buildroot}" != / ] && %{__rm} -rf "%{buildroot}"
|
||||
@@ -34,12 +53,28 @@ touch %{buildroot}%{_localstatedir}/log/%{name}/run.log
|
||||
%files
|
||||
%defattr(-,root,root,-)
|
||||
%{_datarootdir}/%{name}/lib/%{name}.war
|
||||
%{_sysconfdir}/init.d/%{name}
|
||||
%config %{_sysconfdir}/sysconfig/%{name}
|
||||
%{_localstatedir}/log/%{name}/run.log
|
||||
%config %{_sysconfdir}/init.d/%{name}
|
||||
%config(noreplace) %{_sysconfdir}/sysconfig/%{name}
|
||||
%attr(0755,gitbucket,gitbucket) %{_sharedstatedir}/%{name}
|
||||
%attr(0750,gitbucket,gitbucket) %{_localstatedir}/log/%{name}
|
||||
|
||||
|
||||
%changelog
|
||||
* Mon Nov 24 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- Version bump to v2.6
|
||||
|
||||
* Sun Nov 09 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- Version bump to v2.5
|
||||
|
||||
* Sun Oct 26 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- Version bump to v2.4.1
|
||||
|
||||
* Mon Jul 21 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- execute as gitbucket user
|
||||
|
||||
* Sun Jul 20 2014 Toru Takahashi <torutk at gmail.com>
|
||||
- Version bump to v2.1.
|
||||
|
||||
* Mon Oct 28 2013 Jiri Tyr <jiri_DOT_tyr at gmail.com>
|
||||
- Version bump to v1.7.
|
||||
|
||||
|
||||
9
etc/deploy-assemby-jar.sh
Executable file
9
etc/deploy-assemby-jar.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
mvn deploy:deploy-file \
|
||||
-DgroupId=gitbucket\
|
||||
-DartifactId=gitbucket-assembly\
|
||||
-Dversion=3.0.0\
|
||||
-Dpackaging=jar\
|
||||
-Dfile=../target/scala-2.11/gitbucket-assembly-3.0.0.jar\
|
||||
-DrepositoryId=sourceforge.jp\
|
||||
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/
|
||||
2592
etc/icons.svg
2592
etc/icons.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 78 KiB |
17
etc/pom.xml
Normal file
17
etc/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>jp.sf.amateras</groupId>
|
||||
<artifactId>gitbucket-assembly</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<build>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>org.apache.maven.wagon</groupId>
|
||||
<artifactId>wagon-ssh</artifactId>
|
||||
<version>1.0-beta-6</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
</build>
|
||||
</project>
|
||||
@@ -4,11 +4,13 @@ import org.scalatra.sbt._
|
||||
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
||||
import play.twirl.sbt.SbtTwirl
|
||||
import play.twirl.sbt.Import.TwirlKeys._
|
||||
import sbtassembly._
|
||||
import sbtassembly.AssemblyKeys._
|
||||
|
||||
object MyBuild extends Build {
|
||||
val Organization = "jp.sf.amateras"
|
||||
val Organization = "gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val Version = "0.0.1"
|
||||
val Version = "3.0.0"
|
||||
val ScalaVersion = "2.11.2"
|
||||
val ScalatraVersion = "2.3.0"
|
||||
|
||||
@@ -17,6 +19,17 @@ object MyBuild extends Build {
|
||||
file(".")
|
||||
)
|
||||
.settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
||||
.settings(
|
||||
test in assembly := {},
|
||||
assemblyMergeStrategy in assembly := {
|
||||
case PathList("META-INF", xs @ _*) =>
|
||||
(xs map {_.toLowerCase}) match {
|
||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||
case _ => MergeStrategy.discard
|
||||
}
|
||||
case x => MergeStrategy.first
|
||||
}
|
||||
)
|
||||
.settings(
|
||||
sourcesInBase := false,
|
||||
organization := Organization,
|
||||
@@ -44,16 +57,18 @@ object MyBuild extends Build {
|
||||
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"org.quartz-scheduler" % "quartz" % "2.2.1",
|
||||
"com.h2database" % "h2" % "1.4.180",
|
||||
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
||||
// "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
|
||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
||||
"junit" % "junit" % "4.11" % "test",
|
||||
"com.mchange" % "c3p0" % "0.9.5",
|
||||
"com.typesafe" % "config" % "1.2.1",
|
||||
"com.typesafe.play" %% "twirl-compiler" % "1.0.2"
|
||||
),
|
||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
|
||||
EclipseKeys.withSource := true,
|
||||
javacOptions in compile ++= Seq("-target", "6", "-source", "6"),
|
||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
||||
packageOptions += Package.MainClass("JettyLauncher")
|
||||
).enablePlugins(SbtTwirl)
|
||||
|
||||
@@ -7,3 +7,5 @@ addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
|
||||
|
||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
|
||||
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
||||
@@ -1,4 +1,4 @@
|
||||
package util;
|
||||
package gitbucket.core.util;
|
||||
|
||||
import org.eclipse.jgit.api.errors.PatchApplyException;
|
||||
import org.eclipse.jgit.diff.RawText;
|
||||
6
src/main/resources/database.conf
Normal file
6
src/main/resources/database.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
db {
|
||||
driver = "org.h2.Driver"
|
||||
url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
||||
user = "sa"
|
||||
password = "sa"
|
||||
}
|
||||
18
src/main/resources/update/2_7.sql
Normal file
18
src/main/resources/update/2_7.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE COMMIT_COMMENT (
|
||||
USER_NAME VARCHAR(100) NOT NULL,
|
||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
||||
COMMIT_ID VARCHAR(100) NOT NULL,
|
||||
COMMENT_ID INT AUTO_INCREMENT,
|
||||
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
|
||||
CONTENT TEXT NOT NULL,
|
||||
FILE_NAME NVARCHAR(100),
|
||||
OLD_LINE_NUMBER INT,
|
||||
NEW_LINE_NUMBER INT,
|
||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
||||
PULL_REQUEST BOOLEAN NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_PK PRIMARY KEY (COMMENT_ID);
|
||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, COMMENT_ID);
|
||||
1
src/main/resources/update/2_8.sql
Normal file
1
src/main/resources/update/2_8.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE COMMIT_COMMENT ALTER COLUMN FILE_NAME NVARCHAR(260);
|
||||
@@ -1,5 +1,9 @@
|
||||
import _root_.servlet.{PluginActionInvokeFilter, BasicAuthenticationFilter, TransactionFilter}
|
||||
import app._
|
||||
|
||||
import gitbucket.core.controller._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.servlet.{TransactionFilter, BasicAuthenticationFilter}
|
||||
import gitbucket.core.util.Directory
|
||||
|
||||
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
|
||||
import org.scalatra._
|
||||
import javax.servlet._
|
||||
@@ -10,12 +14,16 @@ class ScalatraBootstrap extends LifeCycle {
|
||||
// Register TransactionFilter and BasicAuthenticationFilter at first
|
||||
context.addFilter("transactionFilter", new TransactionFilter)
|
||||
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context.addFilter("pluginActionInvokeFilter", new PluginActionInvokeFilter)
|
||||
context.getFilterRegistration("pluginActionInvokeFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||
|
||||
// Register controllers
|
||||
context.mount(new AnonymousAccessController, "/*")
|
||||
|
||||
PluginRegistry().getControllers.foreach { case (controller, path) =>
|
||||
context.mount(controller, path)
|
||||
}
|
||||
|
||||
context.mount(new IndexController, "/")
|
||||
context.mount(new SearchController, "/")
|
||||
context.mount(new FileUploadController, "/upload")
|
||||
@@ -32,7 +40,7 @@ class ScalatraBootstrap extends LifeCycle {
|
||||
context.mount(new RepositorySettingsController, "/*")
|
||||
|
||||
// Create GITBUCKET_HOME directory if it does not exist
|
||||
val dir = new java.io.File(_root_.util.Directory.GitBucketHome)
|
||||
val dir = new java.io.File(Directory.GitBucketHome)
|
||||
if(!dir.exists){
|
||||
dir.mkdirs()
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.{UsersAuthenticator, Keys}
|
||||
import util.Implicits._
|
||||
|
||||
class DashboardController extends DashboardControllerBase
|
||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
||||
with UsersAuthenticator
|
||||
|
||||
trait DashboardControllerBase extends ControllerBase {
|
||||
self: IssuesService with PullRequestService with RepositoryService with UsersAuthenticator =>
|
||||
|
||||
get("/dashboard/issues/repos")(usersOnly {
|
||||
searchIssues("all")
|
||||
})
|
||||
|
||||
get("/dashboard/issues/assigned")(usersOnly {
|
||||
searchIssues("assigned")
|
||||
})
|
||||
|
||||
get("/dashboard/issues/created_by")(usersOnly {
|
||||
searchIssues("created_by")
|
||||
})
|
||||
|
||||
get("/dashboard/pulls")(usersOnly {
|
||||
searchPullRequests("created_by", None)
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/owned")(usersOnly {
|
||||
searchPullRequests("created_by", None)
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/public")(usersOnly {
|
||||
searchPullRequests("not_created_by", None)
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/for/:owner/:repository")(usersOnly {
|
||||
searchPullRequests("all", Some(params("owner") + "/" + params("repository")))
|
||||
})
|
||||
|
||||
private def searchIssues(filter: String) = {
|
||||
import IssuesService._
|
||||
|
||||
// condition
|
||||
val condition = session.putAndGet(Keys.Session.DashboardIssues,
|
||||
if(request.hasQueryString) IssueSearchCondition(request)
|
||||
else session.getAs[IssueSearchCondition](Keys.Session.DashboardIssues).getOrElse(IssueSearchCondition())
|
||||
)
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||
//val filterUser = Map(filter -> userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
dashboard.html.issues(
|
||||
dashboard.html.issueslist(
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||
condition),
|
||||
countIssue(condition.copy(assigned = None, author = None), false, userRepos: _*),
|
||||
countIssue(condition.copy(assigned = Some(userName), author = None), false, userRepos: _*),
|
||||
countIssue(condition.copy(assigned = None, author = Some(userName)), false, userRepos: _*),
|
||||
countIssueGroupByRepository(condition, false, userRepos: _*),
|
||||
condition,
|
||||
filter)
|
||||
|
||||
}
|
||||
|
||||
private def searchPullRequests(filter: String, repository: Option[String]) = {
|
||||
import IssuesService._
|
||||
import PullRequestService._
|
||||
|
||||
// condition
|
||||
val condition = session.putAndGet(Keys.Session.DashboardPulls, {
|
||||
if(request.hasQueryString) IssueSearchCondition(request)
|
||||
else session.getAs[IssueSearchCondition](Keys.Session.DashboardPulls).getOrElse(IssueSearchCondition())
|
||||
}.copy(repo = repository))
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val allRepos = getAllRepositories(userName)
|
||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||
val filterUser = Map(filter -> userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
val counts = countIssueGroupByRepository(
|
||||
IssueSearchCondition().copy(state = condition.state), true, userRepos: _*)
|
||||
|
||||
dashboard.html.pulls(
|
||||
dashboard.html.pullslist(
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||
condition,
|
||||
None,
|
||||
false),
|
||||
getAllPullRequestCountGroupByUser(condition.state == "closed", userName),
|
||||
userRepos.map { case (userName, repoName) =>
|
||||
(userName, repoName, counts.find { x => x._1 == userName && x._2 == repoName }.map(_._3).getOrElse(0))
|
||||
}.sortBy(_._3).reverse,
|
||||
condition,
|
||||
filter)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
package app
|
||||
|
||||
import service.{AccountService, SystemSettingsService}
|
||||
import SystemSettingsService._
|
||||
import util.AdminAuthenticator
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import ssh.SshServer
|
||||
import org.apache.commons.io.FileUtils
|
||||
import java.io.FileInputStream
|
||||
import plugin.{Plugin, PluginSystem}
|
||||
import org.scalatra.Ok
|
||||
import util.Implicits._
|
||||
|
||||
class SystemSettingsController extends SystemSettingsControllerBase
|
||||
with AccountService with AdminAuthenticator
|
||||
|
||||
trait SystemSettingsControllerBase extends ControllerBase {
|
||||
self: AccountService with AdminAuthenticator =>
|
||||
|
||||
private val form = mapping(
|
||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||
"notification" -> trim(label("Notification", boolean())),
|
||||
"ssh" -> trim(label("SSH access", boolean())),
|
||||
"sshPort" -> trim(label("SSH port", optional(number()))),
|
||||
"smtp" -> optionalIfNotChecked("notification", mapping(
|
||||
"host" -> trim(label("SMTP Host", text(required))),
|
||||
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||
"user" -> trim(label("SMTP User", optional(text()))),
|
||||
"password" -> trim(label("SMTP Password", optional(text()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"fromAddress" -> trim(label("FROM Address", optional(text()))),
|
||||
"fromName" -> trim(label("FROM Name", optional(text())))
|
||||
)(Smtp.apply)),
|
||||
"ldapAuthentication" -> trim(label("LDAP", boolean())),
|
||||
"ldap" -> optionalIfNotChecked("ldapAuthentication", mapping(
|
||||
"host" -> trim(label("LDAP host", text(required))),
|
||||
"port" -> trim(label("LDAP port", optional(number()))),
|
||||
"bindDN" -> trim(label("Bind DN", optional(text()))),
|
||||
"bindPassword" -> trim(label("Bind Password", optional(text()))),
|
||||
"baseDN" -> trim(label("Base DN", text(required))),
|
||||
"userNameAttribute" -> trim(label("User name attribute", text(required))),
|
||||
"additionalFilterCondition"-> trim(label("Additional filter condition", optional(text()))),
|
||||
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
|
||||
"mailAttribute" -> trim(label("Mail address attribute", optional(text()))),
|
||||
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
||||
"keystore" -> trim(label("Keystore", optional(text())))
|
||||
)(Ldap.apply))
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
if(settings.ssh && settings.baseUrl.isEmpty){
|
||||
Seq("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
||||
} else Nil
|
||||
}
|
||||
|
||||
private val pluginForm = mapping(
|
||||
"pluginId" -> list(trim(label("", text())))
|
||||
)(PluginForm.apply)
|
||||
|
||||
case class PluginForm(pluginIds: List[String])
|
||||
|
||||
get("/admin/system")(adminOnly {
|
||||
admin.html.system(flash.get("info"))
|
||||
})
|
||||
|
||||
post("/admin/system", form)(adminOnly { form =>
|
||||
saveSystemSettings(form)
|
||||
|
||||
if(form.ssh && SshServer.isActive && context.settings.sshPort != form.sshPort){
|
||||
SshServer.stop()
|
||||
}
|
||||
|
||||
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
|
||||
SshServer.start(request.getServletContext,
|
||||
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
|
||||
form.baseUrl.get)
|
||||
} else if(!form.ssh && SshServer.isActive){
|
||||
SshServer.stop()
|
||||
}
|
||||
|
||||
flash += "info" -> "System settings has been updated."
|
||||
redirect("/admin/system")
|
||||
})
|
||||
|
||||
get("/admin/plugins")(adminOnly {
|
||||
if(enablePluginSystem){
|
||||
val installedPlugins = plugin.PluginSystem.plugins
|
||||
val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable")
|
||||
admin.plugins.html.installed(installedPlugins, updatablePlugins)
|
||||
} else NotFound
|
||||
})
|
||||
|
||||
post("/admin/plugins/_update", pluginForm)(adminOnly { form =>
|
||||
if(enablePluginSystem){
|
||||
deletePlugins(form.pluginIds)
|
||||
installPlugins(form.pluginIds)
|
||||
redirect("/admin/plugins")
|
||||
} else NotFound
|
||||
})
|
||||
|
||||
post("/admin/plugins/_delete", pluginForm)(adminOnly { form =>
|
||||
if(enablePluginSystem){
|
||||
deletePlugins(form.pluginIds)
|
||||
redirect("/admin/plugins")
|
||||
} else NotFound
|
||||
})
|
||||
|
||||
get("/admin/plugins/available")(adminOnly {
|
||||
if(enablePluginSystem){
|
||||
val installedPlugins = plugin.PluginSystem.plugins
|
||||
val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available")
|
||||
admin.plugins.html.available(availablePlugins)
|
||||
} else NotFound
|
||||
})
|
||||
|
||||
post("/admin/plugins/_install", pluginForm)(adminOnly { form =>
|
||||
if(enablePluginSystem){
|
||||
installPlugins(form.pluginIds)
|
||||
redirect("/admin/plugins")
|
||||
} else NotFound
|
||||
})
|
||||
|
||||
get("/admin/plugins/console")(adminOnly {
|
||||
if(enablePluginSystem){
|
||||
admin.plugins.html.console()
|
||||
} else NotFound
|
||||
})
|
||||
|
||||
post("/admin/plugins/console")(adminOnly {
|
||||
if(enablePluginSystem){
|
||||
val script = request.getParameter("script")
|
||||
val result = plugin.ScalaPlugin.eval(script)
|
||||
Ok()
|
||||
} else NotFound
|
||||
})
|
||||
|
||||
// TODO Move these methods to PluginSystem or Service?
|
||||
private def deletePlugins(pluginIds: List[String]): Unit = {
|
||||
pluginIds.foreach { pluginId =>
|
||||
plugin.PluginSystem.uninstall(pluginId)
|
||||
val dir = new java.io.File(PluginHome, pluginId)
|
||||
if(dir.exists && dir.isDirectory){
|
||||
FileUtils.deleteQuietly(dir)
|
||||
PluginSystem.uninstall(pluginId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def installPlugins(pluginIds: List[String]): Unit = {
|
||||
val dir = getPluginCacheDir()
|
||||
val installedPlugins = plugin.PluginSystem.plugins
|
||||
getAvailablePlugins(installedPlugins).filter(x => pluginIds.contains(x.id)).foreach { plugin =>
|
||||
val pluginDir = new java.io.File(PluginHome, plugin.id)
|
||||
if(pluginDir.exists){
|
||||
FileUtils.deleteDirectory(pluginDir)
|
||||
}
|
||||
FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir)
|
||||
PluginSystem.installPlugin(plugin.id)
|
||||
}
|
||||
}
|
||||
|
||||
private def getAvailablePlugins(installedPlugins: List[Plugin]): List[SystemSettingsControllerBase.AvailablePlugin] = {
|
||||
val repositoryRoot = getPluginCacheDir()
|
||||
|
||||
if(repositoryRoot.exists && repositoryRoot.isDirectory){
|
||||
PluginSystem.repositories.flatMap { repo =>
|
||||
val repoDir = new java.io.File(repositoryRoot, repo.id)
|
||||
if(repoDir.exists && repoDir.isDirectory){
|
||||
repoDir.listFiles.filter(d => d.isDirectory && !d.getName.startsWith(".")).map { plugin =>
|
||||
val propertyFile = new java.io.File(plugin, "plugin.properties")
|
||||
val properties = new java.util.Properties()
|
||||
if(propertyFile.exists && propertyFile.isFile){
|
||||
using(new FileInputStream(propertyFile)){ in =>
|
||||
properties.load(in)
|
||||
}
|
||||
}
|
||||
SystemSettingsControllerBase.AvailablePlugin(
|
||||
repository = repo.id,
|
||||
id = properties.getProperty("id"),
|
||||
version = properties.getProperty("version"),
|
||||
author = properties.getProperty("author"),
|
||||
url = properties.getProperty("url"),
|
||||
description = properties.getProperty("description"),
|
||||
status = installedPlugins.find(_.id == properties.getProperty("id")) match {
|
||||
case Some(x) if(PluginSystem.isUpdatable(x.version, properties.getProperty("version")))=> "updatable"
|
||||
case Some(x) => "installed"
|
||||
case None => "available"
|
||||
})
|
||||
}
|
||||
} else Nil
|
||||
}
|
||||
} else Nil
|
||||
}
|
||||
}
|
||||
|
||||
object SystemSettingsControllerBase {
|
||||
case class AvailablePlugin(repository: String, id: String, version: String,
|
||||
author: String, url: String, description: String, status: String)
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import service._
|
||||
import util._
|
||||
import util.StringUtil._
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import util.Implicits._
|
||||
import ssh.SshUtil
|
||||
import gitbucket.core.account.html
|
||||
import gitbucket.core.helper
|
||||
import gitbucket.core.model.GroupMember
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.ssh.SshUtil
|
||||
import gitbucket.core.service._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.{FileMode, Constants}
|
||||
import org.eclipse.jgit.dircache.DirCache
|
||||
import model.GroupMember
|
||||
|
||||
|
||||
class AccountController extends AccountControllerBase
|
||||
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||
@@ -88,6 +91,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"name" -> trim(label("Repository name", text(required)))
|
||||
)(ForkRepositoryForm.apply)
|
||||
|
||||
case class AccountForm(accountName: String)
|
||||
|
||||
val accountForm = mapping(
|
||||
"account" -> trim(label("Group/User name", text(required, validAccountName)))
|
||||
)(AccountForm.apply)
|
||||
|
||||
/**
|
||||
* Displays user information.
|
||||
*/
|
||||
@@ -97,21 +106,21 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
params.getOrElse("tab", "repositories") match {
|
||||
// Public Activity
|
||||
case "activity" =>
|
||||
_root_.account.html.activity(account,
|
||||
gitbucket.core.account.html.activity(account,
|
||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getActivitiesByUser(userName, true))
|
||||
|
||||
// Members
|
||||
case "members" if(account.isGroupAccount) => {
|
||||
val members = getGroupMembers(account.userName)
|
||||
_root_.account.html.members(account, members.map(_.userName),
|
||||
gitbucket.core.account.html.members(account, members.map(_.userName),
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||
}
|
||||
|
||||
// Repositories
|
||||
case _ => {
|
||||
val members = getGroupMembers(account.userName)
|
||||
_root_.account.html.repositories(account,
|
||||
gitbucket.core.account.html.repositories(account,
|
||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||
@@ -129,8 +138,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:userName/_avatar"){
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
||||
contentType = FileUtil.getMimeType(image)
|
||||
new java.io.File(getUserUploadDir(userName), image)
|
||||
RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))
|
||||
} getOrElse {
|
||||
contentType = "image/png"
|
||||
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
|
||||
@@ -140,7 +148,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:userName/_edit")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
account.html.edit(x, flash.get("info"))
|
||||
html.edit(x, flash.get("info"))
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
@@ -184,7 +192,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:userName/_ssh")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
account.html.ssh(x, getPublicKeys(x.userName))
|
||||
html.ssh(x, getPublicKeys(x.userName))
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
@@ -206,7 +214,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
if(context.loginAccount.isDefined){
|
||||
redirect("/")
|
||||
} else {
|
||||
account.html.register()
|
||||
html.register()
|
||||
}
|
||||
} else NotFound
|
||||
}
|
||||
@@ -220,7 +228,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
|
||||
get("/groups/new")(usersOnly {
|
||||
account.html.group(None, List(GroupMember("", context.loginAccount.get.userName, true)))
|
||||
html.group(None, List(GroupMember("", context.loginAccount.get.userName, true)))
|
||||
})
|
||||
|
||||
post("/groups/new", newGroupForm)(usersOnly { form =>
|
||||
@@ -236,7 +244,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
get("/:groupName/_editgroup")(managersOnly {
|
||||
defining(params("groupName")){ groupName =>
|
||||
account.html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -285,7 +293,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
* Show the new repository form.
|
||||
*/
|
||||
get("/new")(usersOnly {
|
||||
account.html.newrepo(getGroupsByUserName(context.loginAccount.get.userName))
|
||||
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -354,11 +362,31 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
val groups = getGroupsByUserName(loginUserName)
|
||||
groups match {
|
||||
case _: List[String] =>
|
||||
val managerPermissions = groups.map { group =>
|
||||
val members = getGroupMembers(group)
|
||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
|
||||
}
|
||||
helper.html.forkrepository(
|
||||
repository,
|
||||
(groups zip managerPermissions).toMap
|
||||
)
|
||||
case _ => redirect(s"/${loginUserName}")
|
||||
}
|
||||
})
|
||||
|
||||
LockUtil.lock(s"${loginUserName}/${repository.name}"){
|
||||
if(repository.owner == loginUserName || getRepository(loginAccount.userName, repository.name, baseUrl).isDefined){
|
||||
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
val accountName = form.accountName
|
||||
|
||||
LockUtil.lock(s"${accountName}/${repository.name}"){
|
||||
if(getRepository(accountName, repository.name, baseUrl).isDefined ||
|
||||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${loginUserName}/${repository.name}")
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
} else {
|
||||
// Insert to the database at first
|
||||
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
||||
@@ -366,7 +394,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
createRepository(
|
||||
repositoryName = repository.name,
|
||||
userName = loginUserName,
|
||||
userName = accountName,
|
||||
description = repository.repository.description,
|
||||
isPrivate = repository.repository.isPrivate,
|
||||
originRepositoryName = Some(originRepositoryName),
|
||||
@@ -376,22 +404,22 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
)
|
||||
|
||||
// Insert default labels
|
||||
insertDefaultLabels(loginUserName, repository.name)
|
||||
insertDefaultLabels(accountName, repository.name)
|
||||
|
||||
// clone repository actually
|
||||
JGitUtil.cloneRepository(
|
||||
getRepositoryDir(repository.owner, repository.name),
|
||||
getRepositoryDir(loginUserName, repository.name))
|
||||
getRepositoryDir(accountName, repository.name))
|
||||
|
||||
// Create Wiki repository
|
||||
JGitUtil.cloneRepository(
|
||||
getWikiRepositoryDir(repository.owner, repository.name),
|
||||
getWikiRepositoryDir(loginUserName, repository.name))
|
||||
getWikiRepositoryDir(accountName, repository.name))
|
||||
|
||||
// Record activity
|
||||
recordForkActivity(repository.owner, repository.name, loginUserName)
|
||||
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
|
||||
// redirect to the repository
|
||||
redirect(s"/${loginUserName}/${repository.name}")
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -431,4 +459,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
case None => Some("Key is invalid.")
|
||||
}
|
||||
}
|
||||
|
||||
private def validAccountName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
getAccountByUserName(value) match {
|
||||
case Some(_) => None
|
||||
case None => Some("Invalid Group/User Account.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
class AnonymousAccessController extends AnonymousAccessControllerBase
|
||||
|
||||
trait AnonymousAccessControllerBase extends ControllerBase {
|
||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register")) {
|
||||
Unauthorized()
|
||||
} else {
|
||||
pass()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import _root_.util.Directory._
|
||||
import _root_.util.Implicits._
|
||||
import _root_.util.ControlUtil._
|
||||
import _root_.util.{StringUtil, FileUtil, Validations, Keys}
|
||||
import gitbucket.core.service.{AccountService, SystemSettingsService}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.model.Account
|
||||
import org.scalatra._
|
||||
import org.scalatra.json._
|
||||
import org.json4s._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import model._
|
||||
import service.{SystemSettingsService, AccountService}
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
||||
import org.scalatra.i18n._
|
||||
@@ -104,10 +104,10 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
org.scalatra.NotFound()
|
||||
} else {
|
||||
org.scalatra.NotFound(html.error("Not Found"))
|
||||
org.scalatra.NotFound(gitbucket.core.html.error("Not Found"))
|
||||
}
|
||||
|
||||
protected def Unauthorized()(implicit context: app.Context) =
|
||||
protected def Unauthorized()(implicit context: Context) =
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
org.scalatra.Unauthorized()
|
||||
} else {
|
||||
@@ -134,6 +134,18 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
if (path.startsWith("http")) path
|
||||
else baseUrl + super.url(path, params, false, false, false)
|
||||
|
||||
/**
|
||||
* Use this method to response the raw data against XSS.
|
||||
*/
|
||||
protected def RawData[T](contentType: String, rawData: T): T = {
|
||||
if(contentType.split(";").head.trim.toLowerCase.startsWith("text/html")){
|
||||
this.contentType = "text/plain"
|
||||
} else {
|
||||
this.contentType = contentType
|
||||
}
|
||||
response.addHeader("X-Content-Type-Options", "nosniff")
|
||||
rawData
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,138 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.dashboard.html
|
||||
import gitbucket.core.service.{RepositoryService, PullRequestService, AccountService, IssuesService}
|
||||
import gitbucket.core.util.{StringUtil, Keys, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.service.IssuesService._
|
||||
|
||||
class DashboardController extends DashboardControllerBase
|
||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
||||
with UsersAuthenticator
|
||||
|
||||
trait DashboardControllerBase extends ControllerBase {
|
||||
self: IssuesService with PullRequestService with RepositoryService with AccountService
|
||||
with UsersAuthenticator =>
|
||||
|
||||
get("/dashboard/issues")(usersOnly {
|
||||
val q = request.getParameter("q")
|
||||
val account = context.loginAccount.get
|
||||
Option(q).map { q =>
|
||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
||||
q match {
|
||||
case q if(q.contains("is:pr")) => redirect(s"/dashboard/pulls?q=${StringUtil.urlEncode(q)}")
|
||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/issues/created_by${condition.toURL}")
|
||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/issues/assigned${condition.toURL}")
|
||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/issues/mentioned${condition.toURL}")
|
||||
case _ => searchIssues("created_by")
|
||||
}
|
||||
} getOrElse {
|
||||
searchIssues("created_by")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/issues/assigned")(usersOnly {
|
||||
searchIssues("assigned")
|
||||
})
|
||||
|
||||
get("/dashboard/issues/created_by")(usersOnly {
|
||||
searchIssues("created_by")
|
||||
})
|
||||
|
||||
get("/dashboard/issues/mentioned")(usersOnly {
|
||||
searchIssues("mentioned")
|
||||
})
|
||||
|
||||
get("/dashboard/pulls")(usersOnly {
|
||||
val q = request.getParameter("q")
|
||||
val account = context.loginAccount.get
|
||||
Option(q).map { q =>
|
||||
val condition = IssueSearchCondition(q, Map[String, Int]())
|
||||
q match {
|
||||
case q if(q.contains("is:issue")) => redirect(s"/dashboard/issues?q=${StringUtil.urlEncode(q)}")
|
||||
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/pulls/created_by${condition.toURL}")
|
||||
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/pulls/assigned${condition.toURL}")
|
||||
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/pulls/mentioned${condition.toURL}")
|
||||
case _ => searchPullRequests("created_by")
|
||||
}
|
||||
} getOrElse {
|
||||
searchPullRequests("created_by")
|
||||
}
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/created_by")(usersOnly {
|
||||
searchPullRequests("created_by")
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/assigned")(usersOnly {
|
||||
searchPullRequests("assigned")
|
||||
})
|
||||
|
||||
get("/dashboard/pulls/mentioned")(usersOnly {
|
||||
searchPullRequests("mentioned")
|
||||
})
|
||||
|
||||
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
||||
val condition = session.putAndGet(key, if(request.hasQueryString){
|
||||
val q = request.getParameter("q")
|
||||
if(q == null){
|
||||
IssueSearchCondition(request)
|
||||
} else {
|
||||
IssueSearchCondition(q, Map[String, Int]())
|
||||
}
|
||||
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
|
||||
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(userName), author = None , mentioned = None)
|
||||
case "mentioned" => condition.copy(assigned = None , author = None , mentioned = Some(userName))
|
||||
case _ => condition.copy(assigned = None , author = Some(userName), mentioned = None)
|
||||
}
|
||||
}
|
||||
|
||||
private def searchIssues(filter: String) = {
|
||||
import IssuesService._
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
html.issues(
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(userName))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
case _ => condition.copy(author = Some(userName))
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName))
|
||||
}
|
||||
|
||||
private def searchPullRequests(filter: String) = {
|
||||
import IssuesService._
|
||||
import PullRequestService._
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
||||
val allRepos = getAllRepositories(userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
html.pulls(
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(userName))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
case _ => condition.copy(author = Some(userName))
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import util.{Keys, FileUtil}
|
||||
import util.ControlUtil._
|
||||
import util.Directory._
|
||||
import gitbucket.core.util.{Keys, FileUtil}
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import org.scalatra._
|
||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
||||
import org.apache.commons.io.FileUtils
|
||||
@@ -1,8 +1,11 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import util._
|
||||
import util.Implicits._
|
||||
import service._
|
||||
import gitbucket.core.html
|
||||
import gitbucket.core.helper.xml
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService}
|
||||
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class IndexController extends IndexControllerBase
|
||||
@@ -61,13 +64,13 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
get("/activities.atom"){
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
helper.xml.feed(getRecentActivities())
|
||||
xml.feed(getRecentActivities())
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account information into HttpSession and redirect.
|
||||
*/
|
||||
private def signin(account: model.Account) = {
|
||||
private def signin(account: Account) = {
|
||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||
updateLastLoginDate(account.userName)
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.html
|
||||
import gitbucket.core.model.Issue
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.Markdown
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
import service._
|
||||
import IssuesService._
|
||||
import util._
|
||||
import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
import org.scalatra.Ok
|
||||
import model.Issue
|
||||
import plugin.PluginSystem
|
||||
|
||||
class IssuesController extends IssuesControllerBase
|
||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
||||
@@ -50,13 +52,18 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
)(IssueStateForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
||||
searchIssues(repository)
|
||||
val q = request.getParameter("q")
|
||||
if(Option(q).exists(_.contains("is:pr"))){
|
||||
redirect(s"/${repository.owner}/${repository.name}/pulls?q=" + StringUtil.urlEncode(q))
|
||||
} else {
|
||||
searchIssues(repository)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
||||
getIssue(owner, name, issueId) map {
|
||||
issues.html.issue(
|
||||
html.issue(
|
||||
_,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
getIssueLabels(owner, name, issueId.toInt),
|
||||
@@ -71,7 +78,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
issues.html.create(
|
||||
html.create(
|
||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
||||
getMilestones(owner, name),
|
||||
getLabels(owner, name),
|
||||
@@ -188,14 +195,14 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => issues.html.editissue(
|
||||
case t if t == "html" => html.editissue(
|
||||
x.content, x.issueId, x.userName, x.repositoryName)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("title" -> x.title,
|
||||
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
||||
repository, false, true)
|
||||
"content" -> Markdown.toHtml(x.content getOrElse "No description given.",
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
@@ -206,13 +213,13 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => issues.html.editcomment(
|
||||
case t if t == "html" => html.editcomment(
|
||||
x.content, x.commentId, x.userName, x.repositoryName)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("content" -> view.Markdown.toHtml(x.content,
|
||||
repository, false, true)
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
@@ -222,14 +229,14 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
||||
defining(params("id").toInt){ issueId =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
||||
defining(params("id").toInt){ issueId =>
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -243,7 +250,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
milestoneId("milestoneId").map { milestoneId =>
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
|
||||
issues.milestones.html.progress(openCount + closeCount, closeCount)
|
||||
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
|
||||
} getOrElse NotFound
|
||||
} getOrElse Ok()
|
||||
})
|
||||
@@ -288,8 +295,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
(Directory.getAttachedDir(repository.owner, repository.name) match {
|
||||
case dir if(dir.exists && dir.isDirectory) =>
|
||||
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
|
||||
contentType = FileUtil.getMimeType(file.getName)
|
||||
file
|
||||
RawData(FileUtil.getMimeType(file.getName), file)
|
||||
}
|
||||
case _ => None
|
||||
}) getOrElse NotFound
|
||||
@@ -298,7 +304,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
|
||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
||||
@@ -322,13 +328,13 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
||||
*/
|
||||
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
|
||||
(getAction: model.Issue => Option[String] =
|
||||
(getAction: Issue => Option[String] =
|
||||
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
|
||||
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
val userName = context.loginAccount.get.userName
|
||||
|
||||
getIssue(owner, name, issueId.toString) map { issue =>
|
||||
getIssue(owner, name, issueId.toString) flatMap { issue =>
|
||||
val (action, recordActivity) =
|
||||
getAction(issue)
|
||||
.collect {
|
||||
@@ -343,11 +349,10 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
.getOrElse(None -> None)
|
||||
|
||||
val commentId = content
|
||||
.map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
|
||||
.getOrElse ( action.get.capitalize -> action.get )
|
||||
match {
|
||||
case (content, action) => createComment(owner, name, userName, issueId, content, action)
|
||||
val commentId = (content, action) match {
|
||||
case (None, None) => None
|
||||
case (None, Some(action)) => Some(createComment(owner, name, userName, issueId, action.capitalize, action))
|
||||
case (Some(content), _) => Some(createComment(owner, name, userName, issueId, content, action.map(_+ "_comment").getOrElse("comment")))
|
||||
}
|
||||
|
||||
// record comment activity if comment is entered
|
||||
@@ -368,7 +373,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
content foreach {
|
||||
f.toNotify(repository, issueId, _){
|
||||
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId.get}")
|
||||
}
|
||||
}
|
||||
action foreach {
|
||||
@@ -378,7 +383,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
issue -> commentId
|
||||
commentId.map( issue -> _ )
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,11 +395,17 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
// retrieve search condition
|
||||
val condition = session.putAndGet(sessionKey,
|
||||
if(request.hasQueryString) IssueSearchCondition(request)
|
||||
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||
if(request.hasQueryString){
|
||||
val q = request.getParameter("q")
|
||||
if(q == null || q.trim.isEmpty){
|
||||
IssueSearchCondition(request)
|
||||
} else {
|
||||
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
|
||||
}
|
||||
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||
)
|
||||
|
||||
issues.html.list(
|
||||
html.list(
|
||||
"issues",
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
page,
|
||||
@@ -1,9 +1,10 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.labels.html
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import service._
|
||||
import util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import util.Implicits._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.Ok
|
||||
|
||||
@@ -23,39 +24,39 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
)(LabelForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
||||
issues.labels.html.list(
|
||||
html.list(
|
||||
getLabels(repository.owner, repository.name),
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
||||
issues.labels.html.edit(None, repository)
|
||||
html.edit(None, repository)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
||||
issues.labels.html.label(
|
||||
html.label(
|
||||
getLabel(repository.owner, repository.name, labelId).get,
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||
issues.labels.html.edit(Some(label), repository)
|
||||
html.edit(Some(label), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
|
||||
issues.labels.html.label(
|
||||
html.label(
|
||||
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
|
||||
// TODO futility
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition()),
|
||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||
repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
@@ -1,11 +1,11 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.milestones.html
|
||||
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
import service._
|
||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator}
|
||||
import util.Implicits._
|
||||
|
||||
class MilestonesController extends MilestonesControllerBase
|
||||
with MilestonesService with RepositoryService with AccountService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
@@ -23,7 +23,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
)(MilestoneForm.apply)
|
||||
|
||||
get("/:owner/:repository/issues/milestones")(referrersOnly { repository =>
|
||||
issues.milestones.html.list(
|
||||
html.list(
|
||||
params.getOrElse("state", "open"),
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||
repository,
|
||||
@@ -31,7 +31,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/new")(collaboratorsOnly {
|
||||
issues.milestones.html.edit(None, _)
|
||||
html.edit(None, _)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/milestones/new", milestoneForm)(collaboratorsOnly { (form, repository) =>
|
||||
@@ -41,7 +41,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
|
||||
params("milestoneId").toIntOpt.map{ milestoneId =>
|
||||
issues.milestones.html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
|
||||
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator, Notifier, Keys}
|
||||
import util.Directory._
|
||||
import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
import service._
|
||||
import gitbucket.core.pulls.html
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import org.eclipse.jgit.api.Git
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
import scala.collection.JavaConverters._
|
||||
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
|
||||
import service.IssuesService._
|
||||
import service.PullRequestService._
|
||||
import util.JGitUtil.DiffInfo
|
||||
import util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service.PullRequestService._
|
||||
import gitbucket.core.service.WebHookService.WebHookPayload
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.eclipse.jgit.merge.MergeStrategy
|
||||
import org.eclipse.jgit.errors.NoMergeBaseException
|
||||
import service.WebHookService.WebHookPayload
|
||||
|
||||
|
||||
class PullRequestsController extends PullRequestsControllerBase
|
||||
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
||||
with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with CommitsService with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
|
||||
trait PullRequestsControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
|
||||
with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
with CommitsService with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
|
||||
|
||||
@@ -59,7 +62,12 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
case class MergeForm(message: String)
|
||||
|
||||
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||
searchPullRequests(None, repository)
|
||||
val q = request.getParameter("q")
|
||||
if(Option(q).exists(_.contains("is:issue"))){
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
|
||||
} else {
|
||||
searchPullRequests(None, repository)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||
@@ -71,9 +79,10 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val (commits, diffs) =
|
||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||
|
||||
pulls.html.pullreq(
|
||||
html.pullreq(
|
||||
issue, pullreq,
|
||||
getComments(owner, name, issueId),
|
||||
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
|
||||
.sortWith((a, b) => a.registeredDate before b.registeredDate),
|
||||
getIssueLabels(owner, name, issueId),
|
||||
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
@@ -92,7 +101,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||
pulls.html.mergeguide(
|
||||
html.mergeguide(
|
||||
checkConflictInPullRequest(owner, name, pullreq.branch, pullreq.requestUserName, name, pullreq.requestBranch, issueId),
|
||||
pullreq,
|
||||
s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
|
||||
@@ -235,8 +244,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
|
||||
val Seq(origin, forked) = multiParams("splat")
|
||||
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
|
||||
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
|
||||
val (originOwner, originId) = parseCompareIdentifie(origin, forkedRepository.owner)
|
||||
val (forkedOwner, forkedId) = parseCompareIdentifie(forked, forkedRepository.owner)
|
||||
|
||||
(for(
|
||||
originRepositoryName <- if(originOwner == forkedOwner){
|
||||
@@ -252,29 +261,33 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||
){ case (oldGit, newGit) =>
|
||||
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
||||
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
||||
val (oldId, newId) =
|
||||
if(originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId)){
|
||||
// Branch name
|
||||
val rootId = JGitUtil.getForkedCommitId(oldGit, newGit,
|
||||
originRepository.owner, originRepository.name, originId,
|
||||
forkedRepository.owner, forkedRepository.name, forkedId)
|
||||
|
||||
val forkedId = JGitUtil.getForkedCommitId(oldGit, newGit,
|
||||
originRepository.owner, originRepository.name, originBranch,
|
||||
forkedRepository.owner, forkedRepository.name, forkedBranch)
|
||||
|
||||
val oldId = oldGit.getRepository.resolve(forkedId)
|
||||
val newId = newGit.getRepository.resolve(forkedBranch)
|
||||
(oldGit.getRepository.resolve(rootId), newGit.getRepository.resolve(forkedId))
|
||||
} else {
|
||||
// Commit id
|
||||
(oldGit.getRepository.resolve(originId), newGit.getRepository.resolve(forkedId))
|
||||
}
|
||||
|
||||
val (commits, diffs) = getRequestCompareInfo(
|
||||
originRepository.owner, originRepository.name, oldId.getName,
|
||||
forkedRepository.owner, forkedRepository.name, newId.getName)
|
||||
|
||||
pulls.html.compare(
|
||||
html.compare(
|
||||
commits,
|
||||
diffs,
|
||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
|
||||
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
|
||||
},
|
||||
originBranch,
|
||||
forkedBranch,
|
||||
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
|
||||
originId,
|
||||
forkedId,
|
||||
oldId.getName,
|
||||
newId.getName,
|
||||
forkedRepository,
|
||||
@@ -307,7 +320,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
||||
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
||||
|
||||
pulls.html.mergecheck(
|
||||
html.mergecheck(
|
||||
checkConflict(originRepository.owner, originRepository.name, originBranch,
|
||||
forkedRepository.owner, forkedRepository.name, forkedBranch))
|
||||
}
|
||||
@@ -439,7 +452,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
|
||||
new CommitInfo(revCommit)
|
||||
}.toList.splitWith { (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}
|
||||
|
||||
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
|
||||
@@ -458,7 +471,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||
)
|
||||
|
||||
issues.html.list(
|
||||
gitbucket.core.issues.html.list(
|
||||
"pulls",
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||
page,
|
||||
@@ -1,15 +1,17 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import service._
|
||||
import util.Directory._
|
||||
import util.Implicits._
|
||||
import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator}
|
||||
import gitbucket.core.settings.html
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, WebHookService}
|
||||
import gitbucket.core.service.WebHookService.WebHookPayload
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.i18n.Messages
|
||||
import service.WebHookService.WebHookPayload
|
||||
import util.JGitUtil.CommitInfo
|
||||
import util.ControlUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
|
||||
@@ -63,7 +65,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Display the Options page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/options")(ownerOnly {
|
||||
settings.html.options(_, flash.get("info"))
|
||||
html.options(_, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -94,7 +96,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
// Change repository HEAD
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, form.repositoryName))) { git =>
|
||||
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + defaultBranch)
|
||||
}
|
||||
flash += "info" -> "Repository settings has been updated."
|
||||
@@ -105,7 +107,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Display the Collaborators page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
||||
settings.html.collaborators(
|
||||
html.collaborators(
|
||||
getCollaborators(repository.owner, repository.name),
|
||||
getAccountByUserName(repository.owner).get.isGroupAccount,
|
||||
repository)
|
||||
@@ -135,7 +137,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Display the web hook page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
||||
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
|
||||
html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -167,7 +169,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
|
||||
getAccountByUserName(repository.owner).foreach { ownerAccount =>
|
||||
callWebHook(repository.owner, repository.name,
|
||||
List(model.WebHook(repository.owner, repository.name, form.url)),
|
||||
List(WebHook(repository.owner, repository.name, form.url)),
|
||||
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
|
||||
)
|
||||
}
|
||||
@@ -181,7 +183,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Display the danger zone.
|
||||
*/
|
||||
get("/:owner/:repository/settings/danger")(ownerOnly {
|
||||
settings.html.danger(_)
|
||||
html.danger(_)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -1,13 +1,18 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import _root_.util.JGitUtil.CommitInfo
|
||||
import util.Directory._
|
||||
import util.Implicits._
|
||||
import _root_.util.ControlUtil._
|
||||
import _root_.util._
|
||||
import service._
|
||||
import gitbucket.core.repo.html
|
||||
import gitbucket.core.helper
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.WebHookService.WebHookPayload
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import org.scalatra._
|
||||
import java.io.File
|
||||
|
||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||
@@ -17,19 +22,18 @@ import org.eclipse.jgit.treewalk._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.eclipse.jgit.dircache.DirCache
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import service.WebHookService.WebHookPayload
|
||||
|
||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService
|
||||
|
||||
|
||||
/**
|
||||
* The repository viewer.
|
||||
*/
|
||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService
|
||||
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService =>
|
||||
|
||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
||||
@@ -52,6 +56,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
fileName: String
|
||||
)
|
||||
|
||||
case class CommentForm(
|
||||
fileName: Option[String],
|
||||
oldLineNumber: Option[Int],
|
||||
newLineNumber: Option[Int],
|
||||
content: String,
|
||||
issueId: Option[Int]
|
||||
)
|
||||
|
||||
val editorForm = mapping(
|
||||
"branch" -> trim(label("Branch", text(required))),
|
||||
"path" -> trim(label("Path", text())),
|
||||
@@ -70,14 +82,24 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"fileName" -> trim(label("Filename", text(required)))
|
||||
)(DeleteForm.apply)
|
||||
|
||||
val commentForm = mapping(
|
||||
"fileName" -> trim(label("Filename", optional(text()))),
|
||||
"oldLineNumber" -> trim(label("Old line number", optional(number()))),
|
||||
"newLineNumber" -> trim(label("New line number", optional(number()))),
|
||||
"content" -> trim(label("Content", text(required))),
|
||||
"issueId" -> trim(label("Issue Id", optional(number())))
|
||||
)(CommentForm.apply)
|
||||
|
||||
/**
|
||||
* Returns converted HTML from Markdown for preview.
|
||||
*/
|
||||
post("/:owner/:repository/_preview")(referrersOnly { repository =>
|
||||
contentType = "text/html"
|
||||
view.helpers.markdown(params("content"), repository,
|
||||
helpers.markdown(params("content"), repository,
|
||||
params("enableWikiLink").toBoolean,
|
||||
params("enableRefsLink").toBoolean)
|
||||
params("enableRefsLink").toBoolean,
|
||||
params("enableTaskList").toBoolean,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -109,10 +131,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
||||
case Right((logs, hasNext)) =>
|
||||
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||
html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||
logs.splitWith{ (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}, page, hasNext)
|
||||
}, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
case Left(_) => NotFound
|
||||
}
|
||||
}
|
||||
@@ -120,7 +142,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
||||
repo.html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
||||
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
||||
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")))
|
||||
})
|
||||
|
||||
@@ -132,7 +154,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
val paths = path.split("/")
|
||||
repo.html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
|
||||
html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
|
||||
JGitUtil.getContentInfo(git, path, objectId))
|
||||
} getOrElse NotFound
|
||||
}
|
||||
@@ -145,7 +167,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
val paths = path.split("/")
|
||||
repo.html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last,
|
||||
html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last,
|
||||
JGitUtil.getContentInfo(git, path, objectId))
|
||||
} getOrElse NotFound
|
||||
}
|
||||
@@ -196,11 +218,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
if(raw){
|
||||
// Download
|
||||
defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes =>
|
||||
contentType = FileUtil.getContentType(path, bytes)
|
||||
bytes
|
||||
RawData(FileUtil.getContentType(path, bytes), bytes)
|
||||
}
|
||||
} else {
|
||||
repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
|
||||
html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
}
|
||||
} getOrElse NotFound
|
||||
@@ -216,26 +237,117 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))){ revCommit =>
|
||||
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
|
||||
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||
html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
repository, diffs, oldCommitId)
|
||||
getCommitComments(repository.owner, repository.name, id, false),
|
||||
repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
val id = params("id")
|
||||
createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content,
|
||||
form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined)
|
||||
form.issueId match {
|
||||
case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
|
||||
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/commit/:id/comment/_form")(readableUsersOnly { repository =>
|
||||
val id = params("id")
|
||||
val fileName = params.get("fileName")
|
||||
val oldLineNumber = params.get("oldLineNumber") map (_.toInt)
|
||||
val newLineNumber = params.get("newLineNumber") map (_.toInt)
|
||||
val issueId = params.get("issueId") map (_.toInt)
|
||||
html.commentform(
|
||||
commitId = id,
|
||||
fileName, oldLineNumber, newLineNumber, issueId,
|
||||
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||
repository = repository
|
||||
)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
val id = params("id")
|
||||
val commentId = createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName,
|
||||
form.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined)
|
||||
form.issueId match {
|
||||
case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
|
||||
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
||||
}
|
||||
helper.html.commitcomment(getCommitComment(repository.owner, repository.name, commentId.toString).get,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository =>
|
||||
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
|
||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||
params.get("dataType") collect {
|
||||
case t if t == "html" => html.editcomment(
|
||||
x.content, x.commentId, x.userName, x.repositoryName)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("content" -> view.Markdown.toHtml(x.content,
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/commit_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getCommitComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
updateCommitComment(comment.commentId, form.content)
|
||||
redirect(s"/${owner}/${name}/commit_comments/_data/${comment.commentId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/commit_comments/delete/:id")(readableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getCommitComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
Ok(deleteCommitComment(comment.commentId))
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays branches.
|
||||
*/
|
||||
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
||||
val branches = JGitUtil.getBranches(repository.owner, repository.name, repository.repository.defaultBranch)
|
||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||
.map(br => br -> getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId))
|
||||
.reverse
|
||||
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||
})
|
||||
|
||||
/**
|
||||
* Creates a branch.
|
||||
*/
|
||||
post("/:owner/:repository/branches")(collaboratorsOnly { repository =>
|
||||
val newBranchName = params.getOrElse("new", halt(400))
|
||||
val fromBranchName = params.getOrElse("from", halt(400))
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
// retrieve latest update date of each branch
|
||||
val branchInfo = repository.branchList.map { branchName =>
|
||||
val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next
|
||||
(branchName, revCommit.getCommitterIdent.getWhen)
|
||||
}
|
||||
repo.html.branches(branchInfo, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||
JGitUtil.createBranch(git, fromBranchName, newBranchName)
|
||||
} match {
|
||||
case Right(message) =>
|
||||
flash += "info" -> message
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}")
|
||||
case Left(message) =>
|
||||
flash += "error" -> message
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -258,7 +370,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* Displays tags.
|
||||
*/
|
||||
get("/:owner/:repository/tags")(referrersOnly {
|
||||
repo.html.tags(_)
|
||||
html.tags(_)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -275,7 +387,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/network/members")(referrersOnly { repository =>
|
||||
repo.html.forked(
|
||||
html.forked(
|
||||
getRepository(
|
||||
repository.repository.originUserName.getOrElse(repository.owner),
|
||||
repository.repository.originRepositoryName.getOrElse(repository.name),
|
||||
@@ -286,7 +398,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repository)
|
||||
})
|
||||
|
||||
private def splitPath(repository: service.RepositoryService.RepositoryInfo, path: String): (String, String) = {
|
||||
private def splitPath(repository: RepositoryService.RepositoryInfo, path: String): (String, String) = {
|
||||
val id = repository.branchList.collectFirst {
|
||||
case branch if(path == branch || path.startsWith(branch + "/")) => branch
|
||||
} orElse repository.tags.collectFirst {
|
||||
@@ -309,7 +421,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
*/
|
||||
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
|
||||
if(repository.commitCount == 0){
|
||||
repo.html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} else {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
// get specified commit
|
||||
@@ -328,17 +440,22 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
||||
}
|
||||
|
||||
repo.html.files(revision, repository,
|
||||
html.files(revision, repository,
|
||||
if(path == ".") Nil else path.split("/").toList, // current path
|
||||
context.loginAccount match {
|
||||
case None => List()
|
||||
case account: Option[Account] => getGroupsByUserName(account.get.userName)
|
||||
}, // groups of current user
|
||||
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),
|
||||
flash.get("info"), flash.get("error"))
|
||||
}
|
||||
} getOrElse NotFound
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def commitFile(repository: service.RepositoryService.RepositoryInfo,
|
||||
private def commitFile(repository: RepositoryService.RepositoryInfo,
|
||||
branch: String, path: String, newFileName: Option[String], oldFileName: Option[String],
|
||||
content: String, charset: String, message: String) = {
|
||||
|
||||
@@ -379,6 +496,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
//refUpdate.setRefLogMessage("merged", true)
|
||||
refUpdate.update()
|
||||
|
||||
// update pull request
|
||||
updatePullRequests(repository.owner, repository.name, branch)
|
||||
|
||||
// record activity
|
||||
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
|
||||
List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
|
||||
@@ -415,7 +535,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): File = {
|
||||
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): Unit = {
|
||||
val revision = name.stripSuffix(suffix)
|
||||
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
|
||||
if(workDir.exists) {
|
||||
@@ -423,21 +543,26 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
workDir.mkdirs
|
||||
|
||||
val file = new File(workDir, repository.name + "-" +
|
||||
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + suffix)
|
||||
val filename = 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
|
||||
response.setHeader("Content-Disposition", s"attachment; filename=${filename}")
|
||||
response.setBufferSize(1024 * 1024);
|
||||
|
||||
git.archive
|
||||
.setFormat(suffix.tail)
|
||||
.setTree(revCommit.getTree)
|
||||
.setOutputStream(response.getOutputStream)
|
||||
.call()
|
||||
|
||||
Unit
|
||||
}
|
||||
}
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import util._
|
||||
import gitbucket.core.search.html
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.{StringUtil, ControlUtil, ReferrerAuthenticator, Implicits}
|
||||
import ControlUtil._
|
||||
import Implicits._
|
||||
import service._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class SearchController extends SearchControllerBase
|
||||
@@ -34,12 +35,12 @@ trait SearchControllerBase extends ControllerBase { self: RepositoryService
|
||||
}
|
||||
|
||||
target.toLowerCase match {
|
||||
case "issue" => search.html.issues(
|
||||
case "issue" => html.issues(
|
||||
searchIssues(repository.owner, repository.name, query),
|
||||
countFiles(repository.owner, repository.name, query),
|
||||
query, page, repository)
|
||||
|
||||
case _ => search.html.code(
|
||||
case _ => html.code(
|
||||
searchFiles(repository.owner, repository.name, query),
|
||||
countIssues(repository.owner, repository.name, query),
|
||||
query, page, repository)
|
||||
@@ -0,0 +1,85 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.admin.html
|
||||
import gitbucket.core.service.{AccountService, SystemSettingsService}
|
||||
import gitbucket.core.util.AdminAuthenticator
|
||||
import gitbucket.core.ssh.SshServer
|
||||
import SystemSettingsService._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class SystemSettingsController extends SystemSettingsControllerBase
|
||||
with AccountService with AdminAuthenticator
|
||||
|
||||
trait SystemSettingsControllerBase extends ControllerBase {
|
||||
self: AccountService with AdminAuthenticator =>
|
||||
|
||||
private val form = mapping(
|
||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||
"information" -> trim(label("Information", optional(text()))),
|
||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
||||
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
|
||||
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
|
||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||
"notification" -> trim(label("Notification", boolean())),
|
||||
"ssh" -> trim(label("SSH access", boolean())),
|
||||
"sshPort" -> trim(label("SSH port", optional(number()))),
|
||||
"smtp" -> optionalIfNotChecked("notification", mapping(
|
||||
"host" -> trim(label("SMTP Host", text(required))),
|
||||
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||
"user" -> trim(label("SMTP User", optional(text()))),
|
||||
"password" -> trim(label("SMTP Password", optional(text()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"fromAddress" -> trim(label("FROM Address", optional(text()))),
|
||||
"fromName" -> trim(label("FROM Name", optional(text())))
|
||||
)(Smtp.apply)),
|
||||
"ldapAuthentication" -> trim(label("LDAP", boolean())),
|
||||
"ldap" -> optionalIfNotChecked("ldapAuthentication", mapping(
|
||||
"host" -> trim(label("LDAP host", text(required))),
|
||||
"port" -> trim(label("LDAP port", optional(number()))),
|
||||
"bindDN" -> trim(label("Bind DN", optional(text()))),
|
||||
"bindPassword" -> trim(label("Bind Password", optional(text()))),
|
||||
"baseDN" -> trim(label("Base DN", text(required))),
|
||||
"userNameAttribute" -> trim(label("User name attribute", text(required))),
|
||||
"additionalFilterCondition"-> trim(label("Additional filter condition", optional(text()))),
|
||||
"fullNameAttribute" -> trim(label("Full name attribute", optional(text()))),
|
||||
"mailAttribute" -> trim(label("Mail address attribute", optional(text()))),
|
||||
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||
"keystore" -> trim(label("Keystore", optional(text())))
|
||||
)(Ldap.apply))
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
if(settings.ssh && settings.baseUrl.isEmpty){
|
||||
Seq("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
||||
} else Nil
|
||||
}
|
||||
|
||||
private val pluginForm = mapping(
|
||||
"pluginId" -> list(trim(label("", text())))
|
||||
)(PluginForm.apply)
|
||||
|
||||
case class PluginForm(pluginIds: List[String])
|
||||
|
||||
get("/admin/system")(adminOnly {
|
||||
html.system(flash.get("info"))
|
||||
})
|
||||
|
||||
post("/admin/system", form)(adminOnly { form =>
|
||||
saveSystemSettings(form)
|
||||
|
||||
if(form.ssh && SshServer.isActive && context.settings.sshPort != form.sshPort){
|
||||
SshServer.stop()
|
||||
}
|
||||
|
||||
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
|
||||
SshServer.start(
|
||||
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
|
||||
form.baseUrl.get)
|
||||
} else if(!form.ssh && SshServer.isActive){
|
||||
SshServer.stop()
|
||||
}
|
||||
|
||||
flash += "info" -> "System settings has been updated."
|
||||
redirect("/admin/system")
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import service._
|
||||
import util.AdminAuthenticator
|
||||
import util.StringUtil._
|
||||
import util.ControlUtil._
|
||||
import util.Directory._
|
||||
import util.Implicits._
|
||||
import gitbucket.core.service.{RepositoryService, AccountService}
|
||||
import gitbucket.core.admin.users.html
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.apache.commons.io.FileUtils
|
||||
@@ -49,7 +50,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
|
||||
"fileId" -> trim(label("File ID" ,optional(text()))),
|
||||
"clearImage" -> trim(label("Clear image" ,boolean())),
|
||||
"removed" -> trim(label("Disable" ,boolean()))
|
||||
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
|
||||
)(EditUserForm.apply)
|
||||
|
||||
val newGroupForm = mapping(
|
||||
@@ -75,11 +76,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
||||
}.toMap
|
||||
|
||||
admin.users.html.list(users, members, includeRemoved)
|
||||
html.list(users, members, includeRemoved)
|
||||
})
|
||||
|
||||
get("/admin/users/_newuser")(adminOnly {
|
||||
admin.users.html.user(None)
|
||||
html.user(None)
|
||||
})
|
||||
|
||||
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
|
||||
@@ -90,7 +91,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
get("/admin/users/:userName/_edituser")(adminOnly {
|
||||
val userName = params("userName")
|
||||
admin.users.html.user(getAccountByUserName(userName, true))
|
||||
html.user(getAccountByUserName(userName, true))
|
||||
})
|
||||
|
||||
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
||||
@@ -124,7 +125,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/admin/users/_newgroup")(adminOnly {
|
||||
admin.users.html.group(None, Nil)
|
||||
html.group(None, Nil)
|
||||
})
|
||||
|
||||
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
||||
@@ -140,7 +141,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||
defining(params("groupName")){ groupName =>
|
||||
admin.users.html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -190,4 +191,14 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
params.get(paramName).flatMap { userName =>
|
||||
if(userName == context.loginAccount.get.userName)
|
||||
Some("You can't disable your account yourself")
|
||||
else
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package app
|
||||
package gitbucket.core.controller
|
||||
|
||||
import service._
|
||||
import util._
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import util.Implicits._
|
||||
import gitbucket.core.wiki.html
|
||||
import gitbucket.core.service.{RepositoryService, WikiService, ActivityService, AccountService}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.i18n.Messages
|
||||
import java.util.ResourceBundle
|
||||
|
||||
class WikiController extends WikiControllerBase
|
||||
with WikiService with RepositoryService with AccountService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator
|
||||
@@ -36,7 +36,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
||||
wiki.html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
||||
html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||
})
|
||||
@@ -45,7 +45,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
|
||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||
wiki.html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
||||
html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||
})
|
||||
@@ -55,7 +55,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
||||
case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository)
|
||||
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository)
|
||||
case Left(_) => NotFound
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
||||
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
||||
}
|
||||
})
|
||||
@@ -75,7 +75,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
wiki.html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
||||
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
||||
}
|
||||
})
|
||||
@@ -105,7 +105,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
wiki.html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
||||
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
||||
@@ -120,7 +120,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
||||
wiki.html.edit("", None, _)
|
||||
html.edit("", None, _)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
||||
@@ -147,14 +147,14 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||
wiki.html.pages(getWikiPageList(repository.owner, repository.name), repository,
|
||||
html.pages(getWikiPageList(repository.owner, repository.name), repository,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
JGitUtil.getCommitLog(git, "master") match {
|
||||
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
|
||||
case Right((logs, hasNext)) => html.history(None, logs, repository)
|
||||
case Left(_) => NotFound
|
||||
}
|
||||
}
|
||||
@@ -164,8 +164,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val path = multiParams("splat").head
|
||||
|
||||
getFileContent(repository.owner, repository.name, path).map { bytes =>
|
||||
contentType = FileUtil.getContentType(path, bytes)
|
||||
bytes
|
||||
RawData(FileUtil.getContentType(path, bytes), bytes)
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait AccountComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait ActivityComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
protected[model] trait TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -44,4 +44,11 @@ protected[model] trait TemplateComponent { self: Profile =>
|
||||
byRepository(userName, repositoryName) && (this.milestoneId === milestoneId)
|
||||
}
|
||||
|
||||
trait CommitTemplate extends BasicTemplate { self: Table[_] =>
|
||||
val commitId = column[String]("COMMIT_ID")
|
||||
|
||||
def byCommit(owner: String, repository: String, commitId: String) =
|
||||
byRepository(owner, repository) && (this.commitId === commitId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait CollaboratorComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
78
src/main/scala/gitbucket/core/model/Comment.scala
Normal file
78
src/main/scala/gitbucket/core/model/Comment.scala
Normal file
@@ -0,0 +1,78 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait Comment {
|
||||
val commentedUserName: String
|
||||
val registeredDate: java.util.Date
|
||||
}
|
||||
|
||||
trait IssueCommentComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val IssueComments = new TableQuery(tag => new IssueComments(tag)){
|
||||
def autoInc = this returning this.map(_.commentId)
|
||||
}
|
||||
|
||||
class IssueComments(tag: Tag) extends Table[IssueComment](tag, "ISSUE_COMMENT") with IssueTemplate {
|
||||
val commentId = column[Int]("COMMENT_ID", O AutoInc)
|
||||
val action = column[String]("ACTION")
|
||||
val commentedUserName = column[String]("COMMENTED_USER_NAME")
|
||||
val content = column[String]("CONTENT")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply)
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class IssueComment (
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
commentId: Int = 0,
|
||||
action: String,
|
||||
commentedUserName: String,
|
||||
content: String,
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date
|
||||
) extends Comment
|
||||
|
||||
trait CommitCommentComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import self._
|
||||
|
||||
lazy val CommitComments = new TableQuery(tag => new CommitComments(tag)){
|
||||
def autoInc = this returning this.map(_.commentId)
|
||||
}
|
||||
|
||||
class CommitComments(tag: Tag) extends Table[CommitComment](tag, "COMMIT_COMMENT") with CommitTemplate {
|
||||
val commentId = column[Int]("COMMENT_ID", O AutoInc)
|
||||
val commentedUserName = column[String]("COMMENTED_USER_NAME")
|
||||
val content = column[String]("CONTENT")
|
||||
val fileName = column[Option[String]]("FILE_NAME")
|
||||
val oldLine = column[Option[Int]]("OLD_LINE_NUMBER")
|
||||
val newLine = column[Option[Int]]("NEW_LINE_NUMBER")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val pullRequest = column[Boolean]("PULL_REQUEST")
|
||||
def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, pullRequest) <> (CommitComment.tupled, CommitComment.unapply)
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class CommitComment(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
commitId: String,
|
||||
commentId: Int = 0,
|
||||
commentedUserName: String,
|
||||
content: String,
|
||||
fileName: Option[String],
|
||||
oldLine: Option[Int],
|
||||
newLine: Option[Int],
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date,
|
||||
pullRequest: Boolean
|
||||
) extends Comment
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait GroupMemberComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait IssueComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait IssueLabelComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait LabelComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait MilestoneComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait PluginComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,27 +1,40 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait Profile {
|
||||
val profile: slick.driver.JdbcProfile
|
||||
import profile.simple._
|
||||
|
||||
// java.util.Date Mapped Column Types
|
||||
/**
|
||||
* java.util.Date Mapped Column Types
|
||||
*/
|
||||
implicit val dateColumnType = MappedColumnType.base[java.util.Date, java.sql.Timestamp](
|
||||
d => new java.sql.Timestamp(d.getTime),
|
||||
t => new java.util.Date(t.getTime)
|
||||
d => new java.sql.Timestamp(d.getTime),
|
||||
t => new java.util.Date(t.getTime)
|
||||
)
|
||||
|
||||
/**
|
||||
* Extends Column to add conditional condition
|
||||
*/
|
||||
implicit class RichColumn(c1: Column[Boolean]){
|
||||
def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns system date.
|
||||
*/
|
||||
def currentDate = new java.util.Date()
|
||||
|
||||
}
|
||||
|
||||
object Profile extends {
|
||||
trait ProfileProvider { self: Profile =>
|
||||
val profile = slick.driver.H2Driver
|
||||
}
|
||||
|
||||
} with AccountComponent
|
||||
trait CoreProfile extends ProfileProvider with Profile
|
||||
with AccountComponent
|
||||
with ActivityComponent
|
||||
with CollaboratorComponent
|
||||
with CommitCommentComponent
|
||||
with GroupMemberComponent
|
||||
with IssueComponent
|
||||
with IssueCommentComponent
|
||||
@@ -32,11 +45,6 @@ object Profile extends {
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with WebHookComponent
|
||||
with PluginComponent with Profile {
|
||||
with PluginComponent
|
||||
|
||||
/**
|
||||
* Returns system date.
|
||||
*/
|
||||
def currentDate = new java.util.Date()
|
||||
|
||||
}
|
||||
object Profile extends CoreProfile
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait SshKeyComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,4 +1,4 @@
|
||||
package model
|
||||
package gitbucket.core.model
|
||||
|
||||
trait WebHookComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
@@ -1,3 +1,5 @@
|
||||
package gitbucket.core
|
||||
|
||||
package object model {
|
||||
type Session = slick.jdbc.JdbcBackend#Session
|
||||
}
|
||||
10
src/main/scala/gitbucket/core/plugin/Images.scala
Normal file
10
src/main/scala/gitbucket/core/plugin/Images.scala
Normal file
@@ -0,0 +1,10 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
/**
|
||||
* Provides a helper method to generate data URI of images registered by plug-in.
|
||||
*/
|
||||
object Images {
|
||||
|
||||
def dataURI(id: String) = s"data:image/png;base64,${PluginRegistry().getImage(id)}"
|
||||
|
||||
}
|
||||
28
src/main/scala/gitbucket/core/plugin/Plugin.scala
Normal file
28
src/main/scala/gitbucket/core/plugin/Plugin.scala
Normal file
@@ -0,0 +1,28 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
import gitbucket.core.util.Version
|
||||
|
||||
/**
|
||||
* Trait for define plugin interface.
|
||||
* To provide plugin, put Plugin class which mixed in this trait into the package root.
|
||||
*/
|
||||
trait Plugin {
|
||||
|
||||
val pluginId: String
|
||||
val pluginName: String
|
||||
val description: String
|
||||
val versions: Seq[Version]
|
||||
|
||||
/**
|
||||
* This method is invoked in initialization of plugin system.
|
||||
* Register plugin functionality to PluginRegistry.
|
||||
*/
|
||||
def initialize(registry: PluginRegistry): Unit
|
||||
|
||||
/**
|
||||
* This method is invoked in shutdown of plugin system.
|
||||
* If the plugin has any resources, release them in this method.
|
||||
*/
|
||||
def shutdown(registry: PluginRegistry): Unit
|
||||
|
||||
}
|
||||
161
src/main/scala/gitbucket/core/plugin/PluginRegistory.scala
Normal file
161
src/main/scala/gitbucket/core/plugin/PluginRegistory.scala
Normal file
@@ -0,0 +1,161 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
import java.io.{File, FilenameFilter, InputStream}
|
||||
import java.net.URLClassLoader
|
||||
import javax.servlet.ServletContext
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import gitbucket.core.controller.{Context, ControllerBase}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.JDBCUtil._
|
||||
import gitbucket.core.util.{Version, Versions}
|
||||
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
class PluginRegistry {
|
||||
|
||||
private val plugins = new ListBuffer[PluginInfo]
|
||||
private val javaScripts = new ListBuffer[(String, String)]
|
||||
private val controllers = new ListBuffer[(ControllerBase, String)]
|
||||
private val images = mutable.Map[String, String]()
|
||||
|
||||
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
||||
plugins += pluginInfo
|
||||
}
|
||||
|
||||
def getPlugins(): List[PluginInfo] = plugins.toList
|
||||
|
||||
def addImage(id: String, in: InputStream): Unit = {
|
||||
val bytes = using(in){ in =>
|
||||
val bytes = new Array[Byte](in.available)
|
||||
in.read(bytes)
|
||||
bytes
|
||||
}
|
||||
val encoded = StringUtils.newStringUtf8(Base64.encodeBase64(bytes, false))
|
||||
images += ((id, encoded))
|
||||
}
|
||||
|
||||
def getImage(id: String): String = images(id)
|
||||
|
||||
def addController(controller: ControllerBase, path: String): Unit = {
|
||||
controllers += ((controller, path))
|
||||
}
|
||||
|
||||
def getControllers(): List[(ControllerBase, String)] = controllers.toList
|
||||
|
||||
def addJavaScript(path: String, script: String): Unit = {
|
||||
javaScripts += Tuple2(path, script)
|
||||
}
|
||||
|
||||
//def getJavaScripts(): List[(String, String)] = javaScripts.toList
|
||||
|
||||
def getJavaScript(currentPath: String): Option[String] = {
|
||||
javaScripts.find(x => currentPath.matches(x._1)).map(_._2)
|
||||
}
|
||||
|
||||
private case class GlobalAction(
|
||||
method: String,
|
||||
path: String,
|
||||
function: (HttpServletRequest, HttpServletResponse, Context) => Any
|
||||
)
|
||||
|
||||
private case class RepositoryAction(
|
||||
method: String,
|
||||
path: String,
|
||||
function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides entry point to PluginRegistry.
|
||||
*/
|
||||
object PluginRegistry {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[PluginRegistry])
|
||||
|
||||
private val instance = new PluginRegistry()
|
||||
|
||||
/**
|
||||
* Returns the PluginRegistry singleton instance.
|
||||
*/
|
||||
def apply(): PluginRegistry = instance
|
||||
|
||||
/**
|
||||
* Initializes all installed plugins.
|
||||
*/
|
||||
def initialize(context: ServletContext, conn: java.sql.Connection): Unit = {
|
||||
val pluginDir = new File(PluginHome)
|
||||
if(pluginDir.exists && pluginDir.isDirectory){
|
||||
pluginDir.listFiles(new FilenameFilter {
|
||||
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
||||
}).foreach { pluginJar =>
|
||||
val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader)
|
||||
try {
|
||||
val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin]
|
||||
|
||||
// Migration
|
||||
val headVersion = plugin.versions.head
|
||||
val currentVersion = conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match {
|
||||
case Some(x) => {
|
||||
val dim = x.split("\\.")
|
||||
Version(dim(0).toInt, dim(1).toInt)
|
||||
}
|
||||
case None => Version(0, 0)
|
||||
}
|
||||
|
||||
Versions.update(conn, headVersion, currentVersion, plugin.versions, new URLClassLoader(Array(pluginJar.toURI.toURL))){ conn =>
|
||||
currentVersion.versionString match {
|
||||
case "0.0" =>
|
||||
conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, headVersion.versionString)
|
||||
case _ =>
|
||||
conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", headVersion.versionString, plugin.pluginId)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
plugin.initialize(instance)
|
||||
instance.addPlugin(PluginInfo(
|
||||
pluginId = plugin.pluginId,
|
||||
pluginName = plugin.pluginName,
|
||||
version = plugin.versions.head.versionString,
|
||||
description = plugin.description,
|
||||
pluginClass = plugin
|
||||
))
|
||||
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
logger.error(s"Error during plugin initialization", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def shutdown(context: ServletContext): Unit = {
|
||||
instance.getPlugins().foreach { pluginInfo =>
|
||||
try {
|
||||
pluginInfo.pluginClass.shutdown(instance)
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
logger.error(s"Error during plugin shutdown", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
case class PluginInfo(
|
||||
pluginId: String,
|
||||
pluginName: String,
|
||||
version: String,
|
||||
description: String,
|
||||
pluginClass: Plugin
|
||||
)
|
||||
11
src/main/scala/gitbucket/core/plugin/Results.scala
Normal file
11
src/main/scala/gitbucket/core/plugin/Results.scala
Normal file
@@ -0,0 +1,11 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
import play.twirl.api.Html
|
||||
|
||||
/**
|
||||
* Defines result case classes returned by plugin controller.
|
||||
*/
|
||||
object Results {
|
||||
case class Redirect(path: String)
|
||||
case class Fragment(html: Html)
|
||||
}
|
||||
11
src/main/scala/gitbucket/core/plugin/Sessions.scala
Normal file
11
src/main/scala/gitbucket/core/plugin/Sessions.scala
Normal file
@@ -0,0 +1,11 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
import scala.slick.jdbc.JdbcBackend.Session
|
||||
|
||||
/**
|
||||
* Provides Slick Session to Plug-ins.
|
||||
*/
|
||||
object Sessions {
|
||||
val sessions = new ThreadLocal[Session]
|
||||
implicit def session: Session = sessions.get()
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.{GroupMember, Account}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.util.{StringUtil, LDAPUtil}
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import profile.simple._
|
||||
import model.{Account, GroupMember}
|
||||
// TODO [Slick 2.0]NOT import directly?
|
||||
import model.Profile.dateColumnType
|
||||
import service.SystemSettingsService.SystemSettings
|
||||
import util.StringUtil._
|
||||
import util.LDAPUtil
|
||||
import StringUtil._
|
||||
import org.slf4j.LoggerFactory
|
||||
// TODO Why is direct import required?
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
|
||||
trait AccountService {
|
||||
|
||||
@@ -168,6 +168,11 @@ trait AccountService {
|
||||
Repositories.filter(_.userName === userName.bind).delete
|
||||
}
|
||||
|
||||
def getGroupNames(userName: String)(implicit s: Session): List[String] = {
|
||||
List(userName) ++
|
||||
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object AccountService extends AccountService
|
||||
@@ -1,8 +1,9 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import profile.simple._
|
||||
import model.Activity
|
||||
|
||||
trait ActivityService {
|
||||
|
||||
@@ -95,6 +96,15 @@ trait ActivityService {
|
||||
Some(cut(comment, 200)),
|
||||
currentDate)
|
||||
|
||||
def recordCommentCommitActivity(userName: String, repositoryName: String, activityUserName: String, commitId: String, comment: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"comment_commit",
|
||||
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
|
||||
Some(cut(comment, 200)),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String)
|
||||
(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
@@ -112,7 +122,7 @@ trait ActivityService {
|
||||
currentDate)
|
||||
|
||||
def recordPushActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||
branchName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
branchName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"push",
|
||||
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
|
||||
@@ -120,7 +130,7 @@ trait ActivityService {
|
||||
currentDate)
|
||||
|
||||
def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||
tagName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"create_tag",
|
||||
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
|
||||
@@ -128,7 +138,7 @@ trait ActivityService {
|
||||
currentDate)
|
||||
|
||||
def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||
tagName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"delete_tag",
|
||||
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
|
||||
@@ -151,10 +161,10 @@ trait ActivityService {
|
||||
None,
|
||||
currentDate)
|
||||
|
||||
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String)(implicit s: Session): Unit =
|
||||
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)(implicit s: Session): Unit =
|
||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||
"fork",
|
||||
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${activityUserName}/${repositoryName}]",
|
||||
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate)
|
||||
|
||||
53
src/main/scala/gitbucket/core/service/CommitsService.scala
Normal file
53
src/main/scala/gitbucket/core/service/CommitsService.scala
Normal file
@@ -0,0 +1,53 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.CommitComment
|
||||
import gitbucket.core.util.{StringUtil, Implicits}
|
||||
|
||||
import scala.slick.jdbc.{StaticQuery => Q}
|
||||
import Q.interpolation
|
||||
import gitbucket.core.model.Profile._
|
||||
import profile.simple._
|
||||
import Implicits._
|
||||
import StringUtil._
|
||||
|
||||
|
||||
trait CommitsService {
|
||||
|
||||
def getCommitComments(owner: String, repository: String, commitId: String, pullRequest: Boolean)(implicit s: Session) =
|
||||
CommitComments filter {
|
||||
t => t.byCommit(owner, repository, commitId) && (t.pullRequest === pullRequest || pullRequest)
|
||||
} list
|
||||
|
||||
def getCommitComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
|
||||
if (commentId forall (_.isDigit))
|
||||
CommitComments filter { t =>
|
||||
t.byPrimaryKey(commentId.toInt) && t.byRepository(owner, repository)
|
||||
} firstOption
|
||||
else
|
||||
None
|
||||
|
||||
def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String,
|
||||
content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int], pullRequest: Boolean)(implicit s: Session): Int =
|
||||
CommitComments.autoInc insert CommitComment(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
commitId = commitId,
|
||||
commentedUserName = loginUser,
|
||||
content = content,
|
||||
fileName = fileName,
|
||||
oldLine = oldLine,
|
||||
newLine = newLine,
|
||||
registeredDate = currentDate,
|
||||
updatedDate = currentDate,
|
||||
pullRequest = pullRequest)
|
||||
|
||||
def updateCommitComment(commentId: Int, content: String)(implicit s: Session) =
|
||||
CommitComments
|
||||
.filter (_.byPrimaryKey(commentId))
|
||||
.map { t =>
|
||||
t.content -> t.updatedDate
|
||||
}.update (content, currentDate)
|
||||
|
||||
def deleteCommitComment(commentId: Int)(implicit s: Session) =
|
||||
CommitComments filter (_.byPrimaryKey(commentId)) delete
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import scala.slick.jdbc.{StaticQuery => Q}
|
||||
import Q.interpolation
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.Profile._
|
||||
import profile.simple._
|
||||
import model.{Issue, IssueComment, IssueLabel, Label}
|
||||
import util.Implicits._
|
||||
import util.StringUtil._
|
||||
|
||||
trait IssuesService {
|
||||
import IssuesService._
|
||||
@@ -47,8 +47,8 @@ trait IssuesService {
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the count of the search result
|
||||
*/
|
||||
def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean, repos: (String, String)*)
|
||||
(implicit s: Session): Int =
|
||||
def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean,
|
||||
repos: (String, String)*)(implicit s: Session): Int =
|
||||
Query(searchIssueQuery(repos, condition, onlyPullRequest).length).first
|
||||
|
||||
/**
|
||||
@@ -59,8 +59,8 @@ trait IssuesService {
|
||||
* @param condition the search condition
|
||||
* @return the Map which contains issue count for each labels (key is label name, value is issue count)
|
||||
*/
|
||||
def countIssueGroupByLabels(owner: String, repository: String,
|
||||
condition: IssueSearchCondition)(implicit s: Session): Map[String, Int] = {
|
||||
def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition,
|
||||
filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = {
|
||||
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
|
||||
.innerJoin(IssueLabels).on { (t1, t2) =>
|
||||
@@ -77,28 +77,6 @@ trait IssuesService {
|
||||
}
|
||||
.toMap
|
||||
}
|
||||
/**
|
||||
* Returns list which contains issue count for each repository.
|
||||
* If the issue does not exist, its repository is not included in the result.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param onlyPullRequest if true then returns only pull request, false then returns both of issue and pull request.
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return list which contains issue count for each repository
|
||||
*/
|
||||
def countIssueGroupByRepository(
|
||||
condition: IssueSearchCondition, onlyPullRequest: Boolean,
|
||||
repos: (String, String)*)(implicit s: Session): List[(String, String, Int)] = {
|
||||
searchIssueQuery(repos, condition.copy(repo = None), onlyPullRequest)
|
||||
.groupBy { t =>
|
||||
t.userName -> t.repositoryName
|
||||
}
|
||||
.map { case (repo, t) =>
|
||||
(repo._1, repo._2, t.length)
|
||||
}
|
||||
.sortBy(_._3 desc)
|
||||
.list
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search result against issues.
|
||||
@@ -110,8 +88,7 @@ trait IssuesService {
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the search result (list of tuples which contain issue, labels and comment count)
|
||||
*/
|
||||
def searchIssue(condition: IssueSearchCondition, pullRequest: Boolean,
|
||||
offset: Int, limit: Int, repos: (String, String)*)
|
||||
def searchIssue(condition: IssueSearchCondition, pullRequest: Boolean, offset: Int, limit: Int, repos: (String, String)*)
|
||||
(implicit s: Session): List[IssueInfo] = {
|
||||
|
||||
// get issues and comment count and labels
|
||||
@@ -156,20 +133,23 @@ trait IssuesService {
|
||||
/**
|
||||
* Assembles query for conditional issue searching.
|
||||
*/
|
||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition,
|
||||
pullRequest: Boolean)(implicit s: Session) =
|
||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, pullRequest: Boolean)(implicit s: Session) =
|
||||
Issues filter { t1 =>
|
||||
condition.repo
|
||||
.map { _.split('/') match { case array => Seq(array(0) -> array(1)) } }
|
||||
.getOrElse (repos)
|
||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
||||
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
||||
repos
|
||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
||||
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
||||
(t1.closed === (condition.state == "closed").bind) &&
|
||||
(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
||||
(t1.milestoneId.? isEmpty, condition.milestoneId == Some(None)) &&
|
||||
//(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
||||
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
|
||||
(t1.assignedUserName === condition.assigned.get.bind, condition.assigned.isDefined) &&
|
||||
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||
(t1.pullRequest === pullRequest.bind) &&
|
||||
// Milestone filter
|
||||
(Milestones filter { t2 =>
|
||||
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
|
||||
(t2.title === condition.milestone.get.get.bind)
|
||||
} exists, condition.milestone.flatten.isDefined) &&
|
||||
// Label filter
|
||||
(IssueLabels filter { t2 =>
|
||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
||||
(t2.labelId in
|
||||
@@ -177,7 +157,19 @@ trait IssuesService {
|
||||
(t3.byRepository(t1.userName, t1.repositoryName)) &&
|
||||
(t3.labelName inSetBind condition.labels)
|
||||
} map(_.labelId)))
|
||||
} exists, condition.labels.nonEmpty)
|
||||
} exists, condition.labels.nonEmpty) &&
|
||||
// Visibility filter
|
||||
(Repositories filter { t2 =>
|
||||
(t2.byRepository(t1.userName, t1.repositoryName)) &&
|
||||
(t2.isPrivate === (condition.visibility == Some("private")).bind)
|
||||
} exists, condition.visibility.nonEmpty) &&
|
||||
// Organization (group) filter
|
||||
(t1.userName inSetBind condition.groups, condition.groups.nonEmpty) &&
|
||||
// Mentioned filter
|
||||
((t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind ||
|
||||
(IssueComments filter { t2 =>
|
||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind)
|
||||
} exists), condition.mentioned.isDefined)
|
||||
}
|
||||
|
||||
def createIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String],
|
||||
@@ -276,6 +268,7 @@ trait IssuesService {
|
||||
|
||||
// Search Issue
|
||||
val issues = Issues
|
||||
.filter(_.byRepository(owner, repository))
|
||||
.innerJoin(IssueOutline).on { case (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
@@ -291,6 +284,7 @@ trait IssuesService {
|
||||
|
||||
// Search IssueComment
|
||||
val comments = IssueComments
|
||||
.filter(_.byRepository(owner, repository))
|
||||
.innerJoin(Issues).on { case (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
}
|
||||
@@ -333,34 +327,65 @@ object IssuesService {
|
||||
|
||||
case class IssueSearchCondition(
|
||||
labels: Set[String] = Set.empty,
|
||||
milestoneId: Option[Option[Int]] = None,
|
||||
milestone: Option[Option[String]] = None,
|
||||
author: Option[String] = None,
|
||||
assigned: Option[String] = None,
|
||||
repo: Option[String] = None,
|
||||
mentioned: Option[String] = None,
|
||||
state: String = "open",
|
||||
sort: String = "created",
|
||||
direction: String = "desc"){
|
||||
direction: String = "desc",
|
||||
visibility: Option[String] = None,
|
||||
groups: Set[String] = Set.empty){
|
||||
|
||||
def isEmpty: Boolean = {
|
||||
labels.isEmpty && milestoneId.isEmpty && author.isEmpty && assigned.isEmpty &&
|
||||
state == "open" && sort == "created" && direction == "desc"
|
||||
labels.isEmpty && milestone.isEmpty && author.isEmpty && assigned.isEmpty &&
|
||||
state == "open" && sort == "created" && direction == "desc" && visibility.isEmpty
|
||||
}
|
||||
|
||||
def nonEmpty: Boolean = !isEmpty
|
||||
|
||||
def toFilterString: String = (
|
||||
List(
|
||||
Some(s"is:${state}"),
|
||||
author.map(author => s"author:${author}"),
|
||||
assigned.map(assignee => s"assignee:${assignee}"),
|
||||
mentioned.map(mentioned => s"mentions:${mentioned}")
|
||||
).flatten ++
|
||||
labels.map(label => s"label:${label}") ++
|
||||
List(
|
||||
milestone.map { _ match {
|
||||
case Some(x) => s"milestone:${x}"
|
||||
case None => "no:milestone"
|
||||
}},
|
||||
(sort, direction) match {
|
||||
case ("created" , "desc") => None
|
||||
case ("created" , "asc" ) => Some("sort:created-asc")
|
||||
case ("comments", "desc") => Some("sort:comments-desc")
|
||||
case ("comments", "asc" ) => Some("sort:comments-asc")
|
||||
case ("updated" , "desc") => Some("sort:updated-desc")
|
||||
case ("updated" , "asc" ) => Some("sort:updated-asc")
|
||||
},
|
||||
visibility.map(visibility => s"visibility:${visibility}")
|
||||
).flatten ++
|
||||
groups.map(group => s"group:${group}")
|
||||
).mkString(" ")
|
||||
|
||||
def toURL: String =
|
||||
"?" + List(
|
||||
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||
milestoneId.map { id => "milestone=" + (id match {
|
||||
case Some(x) => x.toString
|
||||
case None => "none"
|
||||
})},
|
||||
author .map(x => "author=" + urlEncode(x)),
|
||||
assigned.map(x => "assigned=" + urlEncode(x)),
|
||||
repo.map("for=" + urlEncode(_)),
|
||||
milestone.map { _ match {
|
||||
case Some(x) => "milestone=" + urlEncode(x)
|
||||
case None => "milestone=none"
|
||||
}},
|
||||
author .map(x => "author=" + urlEncode(x)),
|
||||
assigned .map(x => "assigned=" + urlEncode(x)),
|
||||
mentioned.map(x => "mentioned=" + urlEncode(x)),
|
||||
Some("state=" + urlEncode(state)),
|
||||
Some("sort=" + urlEncode(sort)),
|
||||
Some("direction=" + urlEncode(direction))).flatten.mkString("&")
|
||||
Some("direction=" + urlEncode(direction)),
|
||||
visibility.map(x => "visibility=" + urlEncode(x)),
|
||||
if(groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(",")))
|
||||
).flatten.mkString("&")
|
||||
|
||||
}
|
||||
|
||||
@@ -371,19 +396,63 @@ object IssuesService {
|
||||
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores IssueSearchCondition instance from filter query.
|
||||
*/
|
||||
def apply(filter: String, milestones: Map[String, Int]): IssueSearchCondition = {
|
||||
val conditions = filter.split("[ \t]+").map { x =>
|
||||
val dim = x.split(":")
|
||||
dim(0) -> dim(1)
|
||||
}.groupBy(_._1).map { case (key, values) =>
|
||||
key -> values.map(_._2).toSeq
|
||||
}
|
||||
|
||||
val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match {
|
||||
case "created-asc" => ("created" , "asc" )
|
||||
case "comments-desc" => ("comments", "desc")
|
||||
case "comments-asc" => ("comments", "asc" )
|
||||
case "updated-desc" => ("comments", "desc")
|
||||
case "updated-asc" => ("comments", "asc" )
|
||||
case _ => ("created" , "desc")
|
||||
}
|
||||
|
||||
IssueSearchCondition(
|
||||
conditions.get("label").map(_.toSet).getOrElse(Set.empty),
|
||||
conditions.get("milestone").flatMap(_.headOption) match {
|
||||
case None => None
|
||||
case Some("none") => Some(None)
|
||||
case Some(x) => Some(Some(x)) //milestones.get(x).map(x => Some(x))
|
||||
},
|
||||
conditions.get("author").flatMap(_.headOption),
|
||||
conditions.get("assignee").flatMap(_.headOption),
|
||||
conditions.get("mentions").flatMap(_.headOption),
|
||||
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
|
||||
sort,
|
||||
direction,
|
||||
conditions.get("visibility").flatMap(_.headOption),
|
||||
conditions.get("group").map(_.toSet).getOrElse(Set.empty)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores IssueSearchCondition instance from request parameters.
|
||||
*/
|
||||
def apply(request: HttpServletRequest): IssueSearchCondition =
|
||||
IssueSearchCondition(
|
||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||
param(request, "milestone").map{
|
||||
param(request, "milestone").map {
|
||||
case "none" => None
|
||||
case x => x.toIntOpt
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "author"),
|
||||
param(request, "assigned"),
|
||||
param(request, "for"),
|
||||
param(request, "mentioned"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"))
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||
param(request, "visibility"),
|
||||
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
|
||||
)
|
||||
|
||||
def page(request: HttpServletRequest) = try {
|
||||
val i = param(request, "page").getOrElse("1").toInt
|
||||
@@ -1,8 +1,8 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.Label
|
||||
import gitbucket.core.model.Profile._
|
||||
import profile.simple._
|
||||
import model.Label
|
||||
|
||||
trait LabelsService {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.Milestone
|
||||
import gitbucket.core.model.Profile._
|
||||
import profile.simple._
|
||||
import model.Milestone
|
||||
// TODO [Slick 2.0]NOT import directly?
|
||||
import model.Profile.dateColumnType
|
||||
// TODO Why is direct import required?
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
|
||||
trait MilestonesService {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.Plugin
|
||||
import gitbucket.core.model.Profile._
|
||||
import profile.simple._
|
||||
import model.Plugin
|
||||
|
||||
trait PluginService {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.{Issue, PullRequest}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import profile.simple._
|
||||
import model.{PullRequest, Issue}
|
||||
|
||||
trait PullRequestService { self: IssuesService =>
|
||||
import PullRequestService._
|
||||
@@ -36,23 +37,23 @@ trait PullRequestService { self: IssuesService =>
|
||||
.list
|
||||
.map { x => PullRequestCount(x._1, x._2) }
|
||||
|
||||
def getAllPullRequestCountGroupByUser(closed: Boolean, userName: String)(implicit s: Session): List[PullRequestCount] =
|
||||
PullRequests
|
||||
.innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
.innerJoin(Repositories).on { case ((t1, t2), t3) => t2.byRepository(t3.userName, t3.repositoryName) }
|
||||
.filter { case ((t1, t2), t3) =>
|
||||
(t2.closed === closed.bind) &&
|
||||
(
|
||||
(t3.isPrivate === false.bind) ||
|
||||
(t3.userName === userName.bind) ||
|
||||
(Collaborators.filter { t4 => t4.byRepository(t3.userName, t3.repositoryName) && (t4.collaboratorName === userName.bind)} exists)
|
||||
)
|
||||
}
|
||||
.groupBy { case ((t1, t2), t3) => t2.openedUserName }
|
||||
.map { case (userName, t) => userName -> t.length }
|
||||
.sortBy(_._2 desc)
|
||||
.list
|
||||
.map { x => PullRequestCount(x._1, x._2) }
|
||||
// def getAllPullRequestCountGroupByUser(closed: Boolean, userName: String)(implicit s: Session): List[PullRequestCount] =
|
||||
// PullRequests
|
||||
// .innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
// .innerJoin(Repositories).on { case ((t1, t2), t3) => t2.byRepository(t3.userName, t3.repositoryName) }
|
||||
// .filter { case ((t1, t2), t3) =>
|
||||
// (t2.closed === closed.bind) &&
|
||||
// (
|
||||
// (t3.isPrivate === false.bind) ||
|
||||
// (t3.userName === userName.bind) ||
|
||||
// (Collaborators.filter { t4 => t4.byRepository(t3.userName, t3.repositoryName) && (t4.collaboratorName === userName.bind)} exists)
|
||||
// )
|
||||
// }
|
||||
// .groupBy { case ((t1, t2), t3) => t2.openedUserName }
|
||||
// .map { case (userName, t) => userName -> t.length }
|
||||
// .sortBy(_._2 desc)
|
||||
// .list
|
||||
// .map { x => PullRequestCount(x._1, x._2) }
|
||||
|
||||
def createPullRequest(originUserName: String, originRepositoryName: String, issueId: Int,
|
||||
originBranch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String,
|
||||
@@ -81,6 +82,38 @@ trait PullRequestService { self: IssuesService =>
|
||||
.map { case (t1, t2) => t1 }
|
||||
.list
|
||||
|
||||
/**
|
||||
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
|
||||
*/
|
||||
def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit =
|
||||
getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq =>
|
||||
if(Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run){
|
||||
val (commitIdTo, commitIdFrom) = JGitUtil.updatePullRequest(
|
||||
pullreq.userName, pullreq.repositoryName, pullreq.branch, pullreq.issueId,
|
||||
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch)
|
||||
updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
|
||||
}
|
||||
}
|
||||
|
||||
def getPullRequestByRequestCommit(userName: String, repositoryName: String, toBranch:String, fromBranch: String, commitId: String)
|
||||
(implicit s: Session): Option[(PullRequest, Issue)] = {
|
||||
if(toBranch == fromBranch){
|
||||
None
|
||||
} else {
|
||||
PullRequests
|
||||
.innerJoin(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) }
|
||||
.filter { case (t1, t2) =>
|
||||
(t1.userName === userName.bind) &&
|
||||
(t1.repositoryName === repositoryName.bind) &&
|
||||
(t1.branch === toBranch.bind) &&
|
||||
(t1.requestUserName === userName.bind) &&
|
||||
(t1.requestRepositoryName === repositoryName.bind) &&
|
||||
(t1.requestBranch === fromBranch.bind) &&
|
||||
(t1.commitIdTo === commitId.bind)
|
||||
}
|
||||
.firstOption
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object PullRequestService {
|
||||
@@ -1,13 +1,15 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import util.{FileUtil, StringUtil, JGitUtil}
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import gitbucket.core.model.Issue
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.StringUtil
|
||||
import Directory._
|
||||
import ControlUtil._
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
import org.eclipse.jgit.lib.FileMode
|
||||
import org.eclipse.jgit.api.Git
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.Profile._
|
||||
import profile.simple._
|
||||
|
||||
trait RepositorySearchService { self: IssuesService =>
|
||||
@@ -20,6 +22,7 @@ trait RepositorySearchService { self: IssuesService =>
|
||||
searchIssuesByKeyword(owner, repository, query).map { case (issue, commentCount, content) =>
|
||||
IssueSearchResult(
|
||||
issue.issueId,
|
||||
issue.isPullRequest,
|
||||
issue.title,
|
||||
issue.openedUserName,
|
||||
issue.registeredDate,
|
||||
@@ -107,10 +110,11 @@ object RepositorySearchService {
|
||||
|
||||
case class SearchResult(
|
||||
files : List[(String, String)],
|
||||
issues: List[(model.Issue, Int, String)])
|
||||
issues: List[(Issue, Int, String)])
|
||||
|
||||
case class IssueSearchResult(
|
||||
issueId: Int,
|
||||
isPullRequest: Boolean,
|
||||
title: String,
|
||||
openedUserName: String,
|
||||
registeredDate: java.util.Date,
|
||||
@@ -1,9 +1,9 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.{Collaborator, Repository, Account}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import profile.simple._
|
||||
import model.{Repository, Account, Collaborator}
|
||||
import util.JGitUtil
|
||||
|
||||
trait RepositoryService { self: AccountService =>
|
||||
import RepositoryService._
|
||||
@@ -46,16 +46,16 @@ trait RepositoryService { self: AccountService =>
|
||||
(Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository =>
|
||||
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
|
||||
|
||||
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val activities = Activities .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
|
||||
Repositories.filter { t =>
|
||||
(t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind)
|
||||
@@ -69,11 +69,18 @@ trait RepositoryService { self: AccountService =>
|
||||
t.requestRepositoryName === oldRepositoryName.bind
|
||||
}.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
|
||||
|
||||
// Updates activity fk before deleting repository because activity is sorted by activityId
|
||||
// and it can't be changed by deleting-and-inserting record.
|
||||
Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity =>
|
||||
Activities.filter(_.activityId === activity.activityId.bind)
|
||||
.map(x => (x.userName, x.repositoryName)).update(newUserName, newRepositoryName)
|
||||
}
|
||||
|
||||
deleteRepository(oldUserName, oldRepositoryName)
|
||||
|
||||
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Milestones.insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||
|
||||
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||
Issues.insertAll(issues.map { x => x.copy(
|
||||
@@ -87,8 +94,17 @@ trait RepositoryService { self: AccountService =>
|
||||
PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueLabels .insertAll(issueLabels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Activities .insertAll(activities .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
CommitComments.insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
|
||||
// Convert labelId
|
||||
val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap
|
||||
val newLabelMap = Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap
|
||||
IssueLabels.insertAll(issueLabels.map(x => x.copy(
|
||||
labelId = newLabelMap(oldLabelMap(x.labelId)),
|
||||
userName = newUserName,
|
||||
repositoryName = newRepositoryName
|
||||
)) :_*)
|
||||
|
||||
if(account.isGroupAccount){
|
||||
Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
|
||||
} else {
|
||||
@@ -96,12 +112,11 @@ trait RepositoryService { self: AccountService =>
|
||||
}
|
||||
|
||||
// Update activity messages
|
||||
val updateActivities = Activities.filter { t =>
|
||||
Activities.filter { t =>
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%")
|
||||
}.map { t => t.activityId -> t.message }.list
|
||||
|
||||
updateActivities.foreach { case (activityId, message) =>
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%") ||
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}@%")
|
||||
}.map { t => t.activityId -> t.message }.list.foreach { case (activityId, message) =>
|
||||
Activities.filter(_.activityId === activityId.bind).map(_.message).update(
|
||||
message
|
||||
.replace(s"[repo:${oldUserName}/${oldRepositoryName}]" ,s"[repo:${newUserName}/${newRepositoryName}]")
|
||||
@@ -109,6 +124,7 @@ trait RepositoryService { self: AccountService =>
|
||||
.replace(s"[tag:${oldUserName}/${oldRepositoryName}#" ,s"[tag:${newUserName}/${newRepositoryName}#")
|
||||
.replace(s"[pullreq:${oldUserName}/${oldRepositoryName}#",s"[pullreq:${newUserName}/${newRepositoryName}#")
|
||||
.replace(s"[issue:${oldUserName}/${oldRepositoryName}#" ,s"[issue:${newUserName}/${newRepositoryName}#")
|
||||
.replace(s"[commit:${oldUserName}/${oldRepositoryName}@" ,s"[commit:${newUserName}/${newRepositoryName}@")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -118,6 +134,7 @@ trait RepositoryService { self: AccountService =>
|
||||
def deleteRepository(userName: String, repositoryName: String)(implicit s: Session): Unit = {
|
||||
Activities .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Collaborators .filter(_.byRepository(userName, repositoryName)).delete
|
||||
CommitComments.filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueLabels .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Labels .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueComments .filter(_.byRepository(userName, repositoryName)).delete
|
||||
@@ -127,6 +144,30 @@ trait RepositoryService { self: AccountService =>
|
||||
Milestones .filter(_.byRepository(userName, repositoryName)).delete
|
||||
WebHooks .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Repositories .filter(_.byRepository(userName, repositoryName)).delete
|
||||
|
||||
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME
|
||||
Repositories
|
||||
.filter { x => (x.originUserName === userName.bind) && (x.originRepositoryName === repositoryName.bind) }
|
||||
.map { x => (x.userName, x.repositoryName) }
|
||||
.list
|
||||
.foreach { case (userName, repositoryName) =>
|
||||
Repositories
|
||||
.filter(_.byRepository(userName, repositoryName))
|
||||
.map(x => (x.originUserName?, x.originRepositoryName?))
|
||||
.update(None, None)
|
||||
}
|
||||
|
||||
// Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME
|
||||
Repositories
|
||||
.filter { x => (x.parentUserName === userName.bind) && (x.parentRepositoryName === repositoryName.bind) }
|
||||
.map { x => (x.userName, x.repositoryName) }
|
||||
.list
|
||||
.foreach { case (userName, repositoryName) =>
|
||||
Repositories
|
||||
.filter(_.byRepository(userName, repositoryName))
|
||||
.map(x => (x.parentUserName?, x.parentRepositoryName?))
|
||||
.update(None, None)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,8 +197,8 @@ trait RepositoryService { self: AccountService =>
|
||||
new RepositoryInfo(
|
||||
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
|
||||
repository,
|
||||
issues.size,
|
||||
issues.filter(_ == true).size,
|
||||
issues.count(_ == false),
|
||||
issues.count(_ == true),
|
||||
getForkedCount(
|
||||
repository.originUserName.getOrElse(repository.userName),
|
||||
repository.originRepositoryName.getOrElse(repository.repositoryName)
|
||||
@@ -334,7 +375,7 @@ object RepositoryService {
|
||||
|
||||
case class RepositoryInfo(owner: String, name: String, httpUrl: String, repository: Repository,
|
||||
issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int,
|
||||
branchList: Seq[String], tags: Seq[util.JGitUtil.TagInfo], managers: Seq[String]){
|
||||
branchList: Seq[String], tags: Seq[JGitUtil.TagInfo], managers: Seq[String]){
|
||||
|
||||
lazy val host = """^https?://(.+?)(:\d+)?/""".r.findFirstMatchIn(httpUrl).get.group(1)
|
||||
|
||||
@@ -355,4 +396,4 @@ object RepositoryService {
|
||||
|
||||
case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode])
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.{Account, Issue, Session}
|
||||
import util.Implicits.request2Session
|
||||
import gitbucket.core.model.{Session, Issue, Account}
|
||||
import gitbucket.core.util.Implicits
|
||||
import gitbucket.core.controller.Context
|
||||
import Implicits.request2Session
|
||||
|
||||
/**
|
||||
* This service is used for a view helper mainly.
|
||||
@@ -11,25 +13,22 @@ import util.Implicits.request2Session
|
||||
*/
|
||||
trait RequestCache extends SystemSettingsService with AccountService with IssuesService {
|
||||
|
||||
private implicit def context2Session(implicit context: app.Context): Session =
|
||||
private implicit def context2Session(implicit context: Context): Session =
|
||||
request2Session(context.request)
|
||||
|
||||
def getIssue(userName: String, repositoryName: String, issueId: String)
|
||||
(implicit context: app.Context): Option[Issue] = {
|
||||
def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = {
|
||||
context.cache(s"issue.${userName}/${repositoryName}#${issueId}"){
|
||||
super.getIssue(userName, repositoryName, issueId)
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByUserName(userName: String)
|
||||
(implicit context: app.Context): Option[Account] = {
|
||||
def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = {
|
||||
context.cache(s"account.${userName}"){
|
||||
super.getAccountByUserName(userName)
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByMailAddress(mailAddress: String)
|
||||
(implicit context: app.Context): Option[Account] = {
|
||||
def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = {
|
||||
context.cache(s"account.${mailAddress}"){
|
||||
super.getAccountByMailAddress(mailAddress)
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.SshKey
|
||||
import gitbucket.core.model.Profile._
|
||||
import profile.simple._
|
||||
import model.SshKey
|
||||
|
||||
trait SshKeyService {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import gitbucket.core.util.{Directory, ControlUtil}
|
||||
import Directory._
|
||||
import ControlUtil._
|
||||
import SystemSettingsService._
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
@@ -12,7 +13,10 @@ trait SystemSettingsService {
|
||||
def saveSystemSettings(settings: SystemSettings): Unit = {
|
||||
defining(new java.util.Properties()){ props =>
|
||||
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
||||
settings.information.foreach(x => props.setProperty(Information, x))
|
||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
|
||||
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
|
||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||
props.setProperty(Notification, settings.notification.toString)
|
||||
props.setProperty(Ssh, settings.ssh.toString)
|
||||
@@ -39,8 +43,9 @@ trait SystemSettingsService {
|
||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
|
||||
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
|
||||
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x.toString))
|
||||
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
|
||||
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
|
||||
ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString))
|
||||
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
||||
}
|
||||
}
|
||||
@@ -60,7 +65,10 @@ trait SystemSettingsService {
|
||||
}
|
||||
SystemSettings(
|
||||
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||
getOptionValue[String](props, Information, None),
|
||||
getValue(props, AllowAccountRegistration, false),
|
||||
getValue(props, AllowAnonymousAccess, true),
|
||||
getValue(props, IsCreateRepoOptionPublic, true),
|
||||
getValue(props, Gravatar, true),
|
||||
getValue(props, Notification, false),
|
||||
getValue(props, Ssh, false),
|
||||
@@ -90,6 +98,7 @@ trait SystemSettingsService {
|
||||
getOptionValue(props, LdapFullNameAttribute, None),
|
||||
getOptionValue(props, LdapMailAddressAttribute, None),
|
||||
getOptionValue[Boolean](props, LdapTls, None),
|
||||
getOptionValue[Boolean](props, LdapSsl, None),
|
||||
getOptionValue(props, LdapKeystore, None)))
|
||||
} else {
|
||||
None
|
||||
@@ -105,7 +114,10 @@ object SystemSettingsService {
|
||||
|
||||
case class SystemSettings(
|
||||
baseUrl: Option[String],
|
||||
information: Option[String],
|
||||
allowAccountRegistration: Boolean,
|
||||
allowAnonymousAccess: Boolean,
|
||||
isCreateRepoOptionPublic: Boolean,
|
||||
gravatar: Boolean,
|
||||
notification: Boolean,
|
||||
ssh: Boolean,
|
||||
@@ -131,6 +143,7 @@ object SystemSettingsService {
|
||||
fullNameAttribute: Option[String],
|
||||
mailAttribute: Option[String],
|
||||
tls: Option[Boolean],
|
||||
ssl: Option[Boolean],
|
||||
keystore: Option[String])
|
||||
|
||||
case class Smtp(
|
||||
@@ -147,7 +160,10 @@ object SystemSettingsService {
|
||||
val DefaultLdapPort = 389
|
||||
|
||||
private val BaseURL = "base_url"
|
||||
private val Information = "information"
|
||||
private val AllowAccountRegistration = "allow_account_registration"
|
||||
private val AllowAnonymousAccess = "allow_anonymous_access"
|
||||
private val IsCreateRepoOptionPublic = "is_create_repository_option_public"
|
||||
private val Gravatar = "gravatar"
|
||||
private val Notification = "notification"
|
||||
private val Ssh = "ssh"
|
||||
@@ -170,6 +186,7 @@ object SystemSettingsService {
|
||||
private val LdapFullNameAttribute = "ldap.fullname_attribute"
|
||||
private val LdapMailAddressAttribute = "ldap.mail_attribute"
|
||||
private val LdapTls = "ldap.tls"
|
||||
private val LdapSsl = "ldap.ssl"
|
||||
private val LdapKeystore = "ldap.keystore"
|
||||
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =
|
||||
@@ -191,7 +208,7 @@ object SystemSettingsService {
|
||||
else value
|
||||
}
|
||||
|
||||
// TODO temporary flag
|
||||
val enablePluginSystem = Option(System.getProperty("enable.plugin")).getOrElse("false").toBoolean
|
||||
// // TODO temporary flag
|
||||
// val enablePluginSystem = Option(System.getProperty("enable.plugin")).getOrElse("false").toBoolean
|
||||
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import model.Profile._
|
||||
import gitbucket.core.model.{WebHook, Account}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import profile.simple._
|
||||
import model.{WebHook, Account}
|
||||
import org.slf4j.LoggerFactory
|
||||
import service.RepositoryService.RepositoryInfo
|
||||
import util.JGitUtil
|
||||
import RepositoryService.RepositoryInfo
|
||||
import org.eclipse.jgit.diff.DiffEntry
|
||||
import util.JGitUtil.CommitInfo
|
||||
import JGitUtil.CommitInfo
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.apache.http.message.BasicNameValuePair
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity
|
||||
@@ -1,9 +1,10 @@
|
||||
package service
|
||||
package gitbucket.core.service
|
||||
|
||||
import java.util.Date
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import util._
|
||||
import _root_.util.ControlUtil._
|
||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.dircache.DirCache
|
||||
@@ -12,7 +13,7 @@ import java.io.ByteArrayInputStream
|
||||
import org.eclipse.jgit.patch._
|
||||
import org.eclipse.jgit.api.errors.PatchFormatException
|
||||
import scala.collection.JavaConverters._
|
||||
import service.RepositoryService.RepositoryInfo
|
||||
import RepositoryService.RepositoryInfo
|
||||
|
||||
object WikiService {
|
||||
|
||||
@@ -46,7 +47,7 @@ object WikiService {
|
||||
trait WikiService {
|
||||
import WikiService._
|
||||
|
||||
def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit =
|
||||
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
||||
defining(Directory.getWikiRepositoryDir(owner, repository)){ dir =>
|
||||
if(!dir.exists){
|
||||
@@ -102,7 +103,7 @@ trait WikiService {
|
||||
* Reverts specified changes.
|
||||
*/
|
||||
def revertWikiPage(owner: String, repository: String, from: String, to: String,
|
||||
committer: model.Account, pageName: Option[String]): Boolean = {
|
||||
committer: Account, pageName: Option[String]): Boolean = {
|
||||
|
||||
case class RevertInfo(operation: String, filePath: String, source: String)
|
||||
|
||||
@@ -204,7 +205,7 @@ trait WikiService {
|
||||
* Save the wiki page.
|
||||
*/
|
||||
def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String,
|
||||
content: String, committer: model.Account, message: String, currentId: Option[String]): Option[String] = {
|
||||
content: String, committer: Account, message: String, currentId: Option[String]): Option[String] = {
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
@@ -0,0 +1,91 @@
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import javax.servlet._
|
||||
import javax.servlet.http._
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
|
||||
import gitbucket.core.util.{ControlUtil, Keys, Implicits}
|
||||
import org.slf4j.LoggerFactory
|
||||
import Implicits._
|
||||
import ControlUtil._
|
||||
|
||||
/**
|
||||
* Provides BASIC Authentication for [[GitRepositoryServlet]].
|
||||
*/
|
||||
class BasicAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[BasicAuthenticationFilter])
|
||||
|
||||
def init(config: FilterConfig) = {}
|
||||
|
||||
def destroy(): Unit = {}
|
||||
|
||||
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
||||
implicit val request = req.asInstanceOf[HttpServletRequest]
|
||||
val response = res.asInstanceOf[HttpServletResponse]
|
||||
|
||||
val wrappedResponse = new HttpServletResponseWrapper(response){
|
||||
override def setCharacterEncoding(encoding: String) = {}
|
||||
}
|
||||
|
||||
val isUpdating = request.getRequestURI.endsWith("/git-receive-pack") || "service=git-receive-pack".equals(request.getQueryString)
|
||||
|
||||
val settings = loadSystemSettings()
|
||||
|
||||
try {
|
||||
defining(request.paths){
|
||||
case Array(_, repositoryOwner, repositoryName, _*) =>
|
||||
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
|
||||
case Some(repository) => {
|
||||
if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){
|
||||
chain.doFilter(req, wrappedResponse)
|
||||
} else {
|
||||
request.getHeader("Authorization") match {
|
||||
case null => requireAuth(response)
|
||||
case auth => decodeAuthHeader(auth).split(":") match {
|
||||
case Array(username, password) => {
|
||||
authenticate(settings, username, password) match {
|
||||
case Some(account) => {
|
||||
if(isUpdating && hasWritePermission(repository.owner, repository.name, Some(account))){
|
||||
request.setAttribute(Keys.Request.UserName, account.userName)
|
||||
}
|
||||
chain.doFilter(req, wrappedResponse)
|
||||
}
|
||||
case None => requireAuth(response)
|
||||
}
|
||||
}
|
||||
case _ => requireAuth(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case None => {
|
||||
logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.")
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
||||
}
|
||||
}
|
||||
case _ => {
|
||||
logger.debug(s"Not enough path arguments: ${request.paths}")
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception => {
|
||||
logger.error("error", ex)
|
||||
requireAuth(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def requireAuth(response: HttpServletResponse): Unit = {
|
||||
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
|
||||
}
|
||||
|
||||
private def decodeAuthHeader(header: String): String = {
|
||||
try {
|
||||
new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6)))
|
||||
} catch {
|
||||
case _: Throwable => ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package servlet
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import gitbucket.core.model.Session
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util._
|
||||
import org.eclipse.jgit.http.server.GitServlet
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.transport._
|
||||
@@ -9,21 +12,19 @@ import org.slf4j.LoggerFactory
|
||||
import javax.servlet.ServletConfig
|
||||
import javax.servlet.ServletContext
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
import util.{StringUtil, Keys, JGitUtil, Directory}
|
||||
import util.ControlUtil._
|
||||
import util.Implicits._
|
||||
import service._
|
||||
import gitbucket.core.util.StringUtil
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import WebHookService._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import util.JGitUtil.CommitInfo
|
||||
import service.IssuesService.IssueSearchCondition
|
||||
import model.Session
|
||||
import JGitUtil.CommitInfo
|
||||
import IssuesService.IssueSearchCondition
|
||||
|
||||
/**
|
||||
* Provides Git repository via HTTP.
|
||||
*
|
||||
* This servlet provides only Git repository functionality.
|
||||
* Authentication is provided by [[servlet.BasicAuthenticationFilter]].
|
||||
* Authentication is provided by [[BasicAuthenticationFilter]].
|
||||
*/
|
||||
class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
|
||||
@@ -174,7 +175,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
case ReceiveCommand.Type.CREATE |
|
||||
ReceiveCommand.Type.UPDATE |
|
||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD =>
|
||||
updatePullRequests(branchName)
|
||||
updatePullRequests(owner, repository, branchName)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
@@ -211,26 +212,4 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
|
||||
*/
|
||||
private def updatePullRequests(branch: String) =
|
||||
getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq =>
|
||||
if(getRepository(pullreq.userName, pullreq.repositoryName, baseUrl).isDefined){
|
||||
using(Git.open(Directory.getRepositoryDir(pullreq.userName, pullreq.repositoryName)),
|
||||
Git.open(Directory.getRepositoryDir(pullreq.requestUserName, pullreq.requestRepositoryName))){ (oldGit, newGit) =>
|
||||
oldGit.fetch
|
||||
.setRemote(Directory.getRepositoryDir(owner, repository).toURI.toString)
|
||||
.setRefSpecs(new RefSpec(s"refs/heads/${branch}:refs/pull/${pullreq.issueId}/head").setForceUpdate(true))
|
||||
.call
|
||||
|
||||
val commitIdTo = oldGit.getRepository.resolve(s"refs/pull/${pullreq.issueId}/head").getName
|
||||
val commitIdFrom = JGitUtil.getForkedCommitId(oldGit, newGit,
|
||||
pullreq.userName, pullreq.repositoryName, pullreq.branch,
|
||||
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch)
|
||||
updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
207
src/main/scala/gitbucket/core/servlet/InitializeListener.scala
Normal file
207
src/main/scala/gitbucket/core/servlet/InitializeListener.scala
Normal file
@@ -0,0 +1,207 @@
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import java.io.File
|
||||
import java.sql.{DriverManager, Connection}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.util._
|
||||
import org.apache.commons.io.FileUtils
|
||||
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
||||
import org.slf4j.LoggerFactory
|
||||
import Directory._
|
||||
import ControlUtil._
|
||||
import JDBCUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import gitbucket.core.util.Versions
|
||||
import gitbucket.core.util.Directory
|
||||
import gitbucket.core.plugin._
|
||||
|
||||
object AutoUpdate {
|
||||
|
||||
/**
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(3, 0),
|
||||
new Version(2, 8),
|
||||
new Version(2, 7) {
|
||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||
super.update(conn, cl)
|
||||
conn.select("SELECT * FROM REPOSITORY"){ rs =>
|
||||
// Rename attached files directory from /issues to /comments
|
||||
val userName = rs.getString("USER_NAME")
|
||||
val repoName = rs.getString("REPOSITORY_NAME")
|
||||
defining(Directory.getAttachedDir(userName, repoName)){ newDir =>
|
||||
val oldDir = new File(newDir.getParentFile, "issues")
|
||||
if(oldDir.exists && oldDir.isDirectory){
|
||||
oldDir.renameTo(newDir)
|
||||
}
|
||||
}
|
||||
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME if it does not exist
|
||||
val originalUserName = rs.getString("ORIGIN_USER_NAME")
|
||||
val originalRepoName = rs.getString("ORIGIN_REPOSITORY_NAME")
|
||||
if(originalUserName != null && originalRepoName != null){
|
||||
if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?",
|
||||
originalUserName, originalRepoName) == 0){
|
||||
conn.update("UPDATE REPOSITORY SET ORIGIN_USER_NAME = NULL, ORIGIN_REPOSITORY_NAME = NULL " +
|
||||
"WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName)
|
||||
}
|
||||
}
|
||||
// Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME if it does not exist
|
||||
val parentUserName = rs.getString("PARENT_USER_NAME")
|
||||
val parentRepoName = rs.getString("PARENT_REPOSITORY_NAME")
|
||||
if(parentUserName != null && parentRepoName != null){
|
||||
if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?",
|
||||
parentUserName, parentRepoName) == 0){
|
||||
conn.update("UPDATE REPOSITORY SET PARENT_USER_NAME = NULL, PARENT_REPOSITORY_NAME = NULL " +
|
||||
"WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Version(2, 6),
|
||||
new Version(2, 5),
|
||||
new Version(2, 4),
|
||||
new Version(2, 3) {
|
||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||
super.update(conn, cl)
|
||||
conn.select("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'"){ rs =>
|
||||
val curInfo = rs.getString("ADDITIONAL_INFO")
|
||||
val newInfo = curInfo.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n")
|
||||
if (curInfo != newInfo) {
|
||||
conn.update("UPDATE ACTIVITY SET ADDITIONAL_INFO = ? WHERE ACTIVITY_ID = ?", newInfo, rs.getInt("ACTIVITY_ID"))
|
||||
}
|
||||
}
|
||||
ignore {
|
||||
FileUtils.deleteDirectory(Directory.getPluginCacheDir())
|
||||
//FileUtils.deleteDirectory(new File(Directory.PluginHome))
|
||||
}
|
||||
}
|
||||
},
|
||||
new Version(2, 2),
|
||||
new Version(2, 1),
|
||||
new Version(2, 0){
|
||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||
import eu.medsea.mimeutil.{MimeUtil2, MimeType}
|
||||
|
||||
val mimeUtil = new MimeUtil2()
|
||||
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
|
||||
|
||||
super.update(conn, cl)
|
||||
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
||||
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
|
||||
if(dir.exists && dir.isDirectory){
|
||||
dir.listFiles.foreach { file =>
|
||||
if(file.getName.indexOf('.') < 0){
|
||||
val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
|
||||
if(mimeType.startsWith("image/")){
|
||||
file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Version(1, 13),
|
||||
Version(1, 12),
|
||||
Version(1, 11),
|
||||
Version(1, 10),
|
||||
Version(1, 9),
|
||||
Version(1, 8),
|
||||
Version(1, 7),
|
||||
Version(1, 6),
|
||||
Version(1, 5),
|
||||
Version(1, 4),
|
||||
new Version(1, 3){
|
||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||
super.update(conn, cl)
|
||||
// Fix wiki repository configuration
|
||||
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
||||
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
||||
defining(git.getRepository.getConfig){ config =>
|
||||
if(!config.getBoolean("http", "receivepack", false)){
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Version(1, 2),
|
||||
Version(1, 1),
|
||||
Version(1, 0),
|
||||
Version(0, 0)
|
||||
)
|
||||
|
||||
/**
|
||||
* The head version of BitBucket.
|
||||
*/
|
||||
val headVersion = versions.head
|
||||
|
||||
/**
|
||||
* The version file (GITBUCKET_HOME/version).
|
||||
*/
|
||||
lazy val versionFile = new File(GitBucketHome, "version")
|
||||
|
||||
/**
|
||||
* Returns the current version from the version file.
|
||||
*/
|
||||
def getCurrentVersion(): Version = {
|
||||
if(versionFile.exists){
|
||||
FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match {
|
||||
case Array(majorVersion, minorVersion) => {
|
||||
versions.find { v =>
|
||||
v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt
|
||||
}.getOrElse(Version(0, 0))
|
||||
}
|
||||
case _ => Version(0, 0)
|
||||
}
|
||||
} else Version(0, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize GitBucket system.
|
||||
* Update database schema and load plug-ins automatically in the context initializing.
|
||||
*/
|
||||
class InitializeListener extends ServletContextListener {
|
||||
import AutoUpdate._
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[InitializeListener])
|
||||
|
||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||
if(dataDir != null){
|
||||
System.setProperty("gitbucket.home", dataDir)
|
||||
}
|
||||
org.h2.Driver.load()
|
||||
|
||||
defining(getConnection()){ conn =>
|
||||
// Migration
|
||||
logger.debug("Start schema update")
|
||||
Versions.update(conn, headVersion, getCurrentVersion(), versions, Thread.currentThread.getContextClassLoader){ conn =>
|
||||
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
||||
}
|
||||
// Load plugins
|
||||
logger.debug("Initialize plugins")
|
||||
PluginRegistry.initialize(event.getServletContext, conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def contextDestroyed(event: ServletContextEvent): Unit = {
|
||||
// Shutdown plugins
|
||||
PluginRegistry.shutdown(event.getServletContext)
|
||||
}
|
||||
|
||||
private def getConnection(): Connection =
|
||||
DriverManager.getConnection(
|
||||
DatabaseConfig.url,
|
||||
DatabaseConfig.user,
|
||||
DatabaseConfig.password)
|
||||
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package servlet
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import javax.servlet.http.{HttpSessionEvent, HttpSessionListener}
|
||||
import gitbucket.core.util.Directory
|
||||
import org.apache.commons.io.FileUtils
|
||||
import util.Directory._
|
||||
import Directory._
|
||||
|
||||
/**
|
||||
* Removes session associated temporary files when session is destroyed.
|
||||
@@ -0,0 +1,60 @@
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import javax.servlet._
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import com.mchange.v2.c3p0.ComboPooledDataSource
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import org.slf4j.LoggerFactory
|
||||
import slick.jdbc.JdbcBackend.{Database => SlickDatabase, Session}
|
||||
import gitbucket.core.util.Keys
|
||||
|
||||
/**
|
||||
* Controls the transaction with the open session in view pattern.
|
||||
*/
|
||||
class TransactionFilter extends Filter {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[TransactionFilter])
|
||||
|
||||
def init(config: FilterConfig) = {}
|
||||
|
||||
def destroy(): Unit = {}
|
||||
|
||||
def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
||||
if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){
|
||||
// assets don't need transaction
|
||||
chain.doFilter(req, res)
|
||||
} else {
|
||||
Database() withTransaction { session =>
|
||||
logger.debug("begin transaction")
|
||||
req.setAttribute(Keys.Request.DBSession, session)
|
||||
chain.doFilter(req, res)
|
||||
logger.debug("end transaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Database {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(Database.getClass)
|
||||
|
||||
private val db: SlickDatabase = {
|
||||
val datasource = new ComboPooledDataSource
|
||||
|
||||
datasource.setDriverClass(DatabaseConfig.driver)
|
||||
datasource.setJdbcUrl(DatabaseConfig.url)
|
||||
datasource.setUser(DatabaseConfig.user)
|
||||
datasource.setPassword(DatabaseConfig.password)
|
||||
|
||||
logger.debug("load database connection pool")
|
||||
|
||||
SlickDatabase.forDataSource(datasource)
|
||||
}
|
||||
|
||||
def apply(): SlickDatabase = db
|
||||
|
||||
def getSession(req: ServletRequest): Session =
|
||||
req.getAttribute(Keys.Request.DBSession).asInstanceOf[Session]
|
||||
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
package ssh
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import gitbucket.core.model.Session
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
|
||||
import gitbucket.core.servlet.{Database, CommitLogHook}
|
||||
import gitbucket.core.util.{Directory, ControlUtil}
|
||||
import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command}
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.{InputStream, OutputStream}
|
||||
import util.ControlUtil._
|
||||
import ControlUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import util.Directory._
|
||||
import Directory._
|
||||
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
||||
import org.apache.sshd.server.command.UnknownCommand
|
||||
import servlet.{Database, CommitLogHook}
|
||||
import service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||
import javax.servlet.ServletContext
|
||||
import model.Session
|
||||
|
||||
object GitCommand {
|
||||
val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
|
||||
}
|
||||
|
||||
abstract class GitCommand(val context: ServletContext, val owner: String, val repoName: String) extends Command {
|
||||
abstract class GitCommand(val owner: String, val repoName: String) extends Command {
|
||||
self: RepositoryService with AccountService =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||
@@ -31,7 +31,7 @@ abstract class GitCommand(val context: ServletContext, val owner: String, val re
|
||||
|
||||
private def newTask(user: String): Runnable = new Runnable {
|
||||
override def run(): Unit = {
|
||||
Database(context) withSession { implicit session =>
|
||||
Database() withSession { implicit session =>
|
||||
try {
|
||||
runTask(user)
|
||||
callback.onExit(0)
|
||||
@@ -80,7 +80,7 @@ abstract class GitCommand(val context: ServletContext, val owner: String, val re
|
||||
|
||||
}
|
||||
|
||||
class GitUploadPack(context: ServletContext, owner: String, repoName: String, baseUrl: String) extends GitCommand(context, owner, repoName)
|
||||
class GitUploadPack(owner: String, repoName: String, baseUrl: String) extends GitCommand(owner, repoName)
|
||||
with RepositoryService with AccountService {
|
||||
|
||||
override protected def runTask(user: String)(implicit session: Session): Unit = {
|
||||
@@ -97,7 +97,7 @@ class GitUploadPack(context: ServletContext, owner: String, repoName: String, ba
|
||||
|
||||
}
|
||||
|
||||
class GitReceivePack(context: ServletContext, owner: String, repoName: String, baseUrl: String) extends GitCommand(context, owner, repoName)
|
||||
class GitReceivePack(owner: String, repoName: String, baseUrl: String) extends GitCommand(owner, repoName)
|
||||
with SystemSettingsService with RepositoryService with AccountService {
|
||||
|
||||
override protected def runTask(user: String)(implicit session: Session): Unit = {
|
||||
@@ -119,15 +119,15 @@ class GitReceivePack(context: ServletContext, owner: String, repoName: String, b
|
||||
|
||||
}
|
||||
|
||||
class GitCommandFactory(context: ServletContext, baseUrl: String) extends CommandFactory {
|
||||
class GitCommandFactory(baseUrl: String) extends CommandFactory {
|
||||
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
||||
|
||||
override def createCommand(command: String): Command = {
|
||||
logger.debug(s"command: $command")
|
||||
command match {
|
||||
case GitCommand.CommandRegex("upload", owner, repoName) => new GitUploadPack(context, owner, repoName, baseUrl)
|
||||
case GitCommand.CommandRegex("receive", owner, repoName) => new GitReceivePack(context, owner, repoName, baseUrl)
|
||||
case GitCommand.CommandRegex("upload", owner, repoName) => new GitUploadPack(owner, repoName, baseUrl)
|
||||
case GitCommand.CommandRegex("receive", owner, repoName) => new GitReceivePack(owner, repoName, baseUrl)
|
||||
case _ => new UnknownCommand(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package ssh
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import org.apache.sshd.common.Factory
|
||||
import org.apache.sshd.server.{Environment, ExitCallback, Command}
|
||||
import java.io.{OutputStream, InputStream}
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
import service.SystemSettingsService
|
||||
|
||||
class NoShell extends Factory[Command] with SystemSettingsService {
|
||||
override def create(): Command = new Command() {
|
||||
@@ -1,16 +1,15 @@
|
||||
package ssh
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import gitbucket.core.service.SshKeyService
|
||||
import gitbucket.core.servlet.Database
|
||||
import org.apache.sshd.server.PublickeyAuthenticator
|
||||
import org.apache.sshd.server.session.ServerSession
|
||||
import java.security.PublicKey
|
||||
import service.SshKeyService
|
||||
import servlet.Database
|
||||
import javax.servlet.ServletContext
|
||||
|
||||
class PublicKeyAuthenticator(context: ServletContext) extends PublickeyAuthenticator with SshKeyService {
|
||||
class PublicKeyAuthenticator extends PublickeyAuthenticator with SshKeyService {
|
||||
|
||||
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
|
||||
Database(context) withSession { implicit session =>
|
||||
Database() withSession { implicit session =>
|
||||
getPublicKeys(username).exists { sshKey =>
|
||||
SshUtil.str2PublicKey(sshKey.publicKey) match {
|
||||
case Some(publicKey) => key.equals(publicKey)
|
||||
@@ -1,10 +1,10 @@
|
||||
package ssh
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import javax.servlet.{ServletContext, ServletContextEvent, ServletContextListener}
|
||||
import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.util.Directory
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
|
||||
import org.slf4j.LoggerFactory
|
||||
import util.Directory
|
||||
import service.SystemSettingsService
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
object SshServer {
|
||||
@@ -12,17 +12,17 @@ object SshServer {
|
||||
private val server = org.apache.sshd.SshServer.setUpDefaultServer()
|
||||
private val active = new AtomicBoolean(false)
|
||||
|
||||
private def configure(context: ServletContext, port: Int, baseUrl: String) = {
|
||||
private def configure(port: Int, baseUrl: String) = {
|
||||
server.setPort(port)
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(s"${Directory.GitBucketHome}/gitbucket.ser"))
|
||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(context))
|
||||
server.setCommandFactory(new GitCommandFactory(context, baseUrl))
|
||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator)
|
||||
server.setCommandFactory(new GitCommandFactory(baseUrl))
|
||||
server.setShellFactory(new NoShell)
|
||||
}
|
||||
|
||||
def start(context: ServletContext, port: Int, baseUrl: String) = {
|
||||
def start(port: Int, baseUrl: String) = {
|
||||
if(active.compareAndSet(false, true)){
|
||||
configure(context, port, baseUrl)
|
||||
configure(port, baseUrl)
|
||||
server.start()
|
||||
logger.info(s"Start SSH Server Listen on ${server.getPort}")
|
||||
}
|
||||
@@ -55,8 +55,7 @@ class SshServerListener extends ServletContextListener with SystemSettingsServic
|
||||
case None =>
|
||||
logger.error("Could not start SshServer because the baseUrl is not configured.")
|
||||
case Some(baseUrl) =>
|
||||
SshServer.start(sce.getServletContext,
|
||||
settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), baseUrl)
|
||||
SshServer.start(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), baseUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package ssh
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import java.security.PublicKey
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -1,10 +1,10 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import app.ControllerBase
|
||||
import service._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{RepositoryService, AccountService}
|
||||
import RepositoryService.RepositoryInfo
|
||||
import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
import Implicits._
|
||||
import ControlUtil._
|
||||
|
||||
/**
|
||||
* Allows only oneself and administrators.
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
@@ -37,4 +37,10 @@ object ControlUtil {
|
||||
def using[T](treeWalk: TreeWalk)(f: TreeWalk => T): T =
|
||||
try f(treeWalk) finally treeWalk.release()
|
||||
|
||||
def ignore[T](f: => Unit): Unit = try {
|
||||
f
|
||||
} catch {
|
||||
case e: Exception => ()
|
||||
}
|
||||
|
||||
}
|
||||
19
src/main/scala/gitbucket/core/util/DatabaseConfig.scala
Normal file
19
src/main/scala/gitbucket/core/util/DatabaseConfig.scala
Normal file
@@ -0,0 +1,19 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import Directory.DatabaseHome
|
||||
|
||||
object DatabaseConfig {
|
||||
|
||||
private val config = ConfigFactory.load("database")
|
||||
private val dbUrl = config.getString("db.url")
|
||||
|
||||
def url(directory: Option[String]): String =
|
||||
dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome))
|
||||
|
||||
val url: String = url(None)
|
||||
val user: String = config.getString("db.user")
|
||||
val password: String = config.getString("db.password")
|
||||
val driver: String = config.getString("db.driver")
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.io.File
|
||||
import util.ControlUtil._
|
||||
import ControlUtil._
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
/**
|
||||
@@ -48,7 +48,7 @@ object Directory {
|
||||
* Directory for files which are attached to issue.
|
||||
*/
|
||||
def getAttachedDir(owner: String, repository: String): File =
|
||||
new File(s"${RepositoryHome}/${owner}/${repository}/issues")
|
||||
new File(s"${RepositoryHome}/${owner}/${repository}/comments")
|
||||
|
||||
/**
|
||||
* Directory for uploaded files by the specified user.
|
||||
@@ -1,9 +1,9 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
import java.net.URLConnection
|
||||
import java.io.File
|
||||
import util.ControlUtil._
|
||||
import ControlUtil._
|
||||
import scala.util.Random
|
||||
|
||||
object FileUtil {
|
||||
@@ -1,9 +1,10 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import gitbucket.core.servlet.Database
|
||||
|
||||
import scala.util.matching.Regex
|
||||
import scala.util.control.Exception._
|
||||
import slick.jdbc.JdbcBackend
|
||||
import servlet.Database
|
||||
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
||||
|
||||
/**
|
||||
63
src/main/scala/gitbucket/core/util/JDBCUtil.scala
Normal file
63
src/main/scala/gitbucket/core/util/JDBCUtil.scala
Normal file
@@ -0,0 +1,63 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.sql._
|
||||
import ControlUtil._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
/**
|
||||
* Provides implicit class which extends java.sql.Connection.
|
||||
* This is used in automatic migration in [[servlet.AutoUpdateListener]].
|
||||
*/
|
||||
object JDBCUtil {
|
||||
|
||||
implicit class RichConnection(conn: Connection){
|
||||
|
||||
def update(sql: String, params: Any*): Int = {
|
||||
execute(sql, params: _*){ stmt =>
|
||||
stmt.executeUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
def find[T](sql: String, params: Any*)(f: ResultSet => T): Option[T] = {
|
||||
execute(sql, params: _*){ stmt =>
|
||||
using(stmt.executeQuery()){ rs =>
|
||||
if(rs.next) Some(f(rs)) else None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def select[T](sql: String, params: Any*)(f: ResultSet => T): Seq[T] = {
|
||||
execute(sql, params: _*){ stmt =>
|
||||
using(stmt.executeQuery()){ rs =>
|
||||
val list = new ListBuffer[T]
|
||||
while(rs.next){
|
||||
list += f(rs)
|
||||
}
|
||||
list.toSeq
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def selectInt(sql: String, params: Any*): Int = {
|
||||
execute(sql, params: _*){ stmt =>
|
||||
using(stmt.executeQuery()){ rs =>
|
||||
if(rs.next) rs.getInt(1) else 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def execute[T](sql: String, params: Any*)(f: (PreparedStatement) => T): T = {
|
||||
using(conn.prepareStatement(sql)){ stmt =>
|
||||
params.zipWithIndex.foreach { case (p, i) =>
|
||||
p match {
|
||||
case x: Int => stmt.setInt(i + 1, x)
|
||||
case x: String => stmt.setString(i + 1, x)
|
||||
}
|
||||
}
|
||||
f(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import gitbucket.core.service.RepositoryService
|
||||
import org.eclipse.jgit.api.Git
|
||||
import util.Directory._
|
||||
import util.StringUtil._
|
||||
import util.ControlUtil._
|
||||
import Directory._
|
||||
import StringUtil._
|
||||
import ControlUtil._
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.JavaConverters._
|
||||
import org.eclipse.jgit.lib._
|
||||
@@ -13,9 +14,9 @@ import org.eclipse.jgit.treewalk._
|
||||
import org.eclipse.jgit.treewalk.filter._
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
import java.util.Date
|
||||
import org.eclipse.jgit.api.errors.NoHeadException
|
||||
import service.RepositoryService
|
||||
import org.eclipse.jgit.api.errors.{JGitInternalException, InvalidRefNameException, RefAlreadyExistsException, NoHeadException}
|
||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@@ -133,6 +134,10 @@ object JGitUtil {
|
||||
*/
|
||||
case class SubmoduleInfo(name: String, path: String, url: String)
|
||||
|
||||
case class BranchMergeInfo(ahead: Int, behind: Int, isMerged: Boolean)
|
||||
|
||||
case class BranchInfo(name: String, committerName: String, commitTime: Date, committerEmailAddress:String, mergeInfo: Option[BranchMergeInfo], commitId: String)
|
||||
|
||||
/**
|
||||
* Returns RevCommit from the commit or tag id.
|
||||
*
|
||||
@@ -217,7 +222,7 @@ object JGitUtil {
|
||||
list.append((treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getPathString, treeWalk.getNameString, linkUrl))
|
||||
}
|
||||
|
||||
list = list.map(tuple =>
|
||||
list.transform(tuple =>
|
||||
if (tuple._2 != FileMode.TREE)
|
||||
tuple
|
||||
else
|
||||
@@ -507,6 +512,17 @@ object JGitUtil {
|
||||
}.find(_._1 != null)
|
||||
}
|
||||
|
||||
def createBranch(git: Git, fromBranch: String, newBranch: String) = {
|
||||
try {
|
||||
git.branchCreate().setStartPoint(fromBranch).setName(newBranch).call()
|
||||
Right("Branch created.")
|
||||
} catch {
|
||||
case e: RefAlreadyExistsException => Left("Sorry, that branch already exists.")
|
||||
// JGitInternalException occurs when new branch name is 'a' and the branch whose name is 'a/*' exists.
|
||||
case _: InvalidRefNameException | _: JGitInternalException => Left("Sorry, that name is invalid.")
|
||||
}
|
||||
}
|
||||
|
||||
def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = {
|
||||
val entry = new DirCacheEntry(path)
|
||||
entry.setFileMode(mode)
|
||||
@@ -663,6 +679,25 @@ object JGitUtil {
|
||||
}.head.id
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch pull request contents into refs/pull/${issueId}/head and return (commitIdTo, commitIdFrom)
|
||||
*/
|
||||
def updatePullRequest(userName: String, repositoryName:String, branch: String, issueId: Int,
|
||||
requestUserName: String, requestRepositoryName: String, requestBranch: String):(String, String) =
|
||||
using(Git.open(Directory.getRepositoryDir(userName, repositoryName)),
|
||||
Git.open(Directory.getRepositoryDir(requestUserName, requestRepositoryName))){ (oldGit, newGit) =>
|
||||
oldGit.fetch
|
||||
.setRemote(Directory.getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString)
|
||||
.setRefSpecs(new RefSpec(s"refs/heads/${requestBranch}:refs/pull/${issueId}/head").setForceUpdate(true))
|
||||
.call
|
||||
|
||||
val commitIdTo = oldGit.getRepository.resolve(s"refs/pull/${issueId}/head").getName
|
||||
val commitIdFrom = getForkedCommitId(oldGit, newGit,
|
||||
userName, repositoryName, branch,
|
||||
requestUserName, requestRepositoryName, requestBranch)
|
||||
(commitIdTo, commitIdFrom)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modified commit of specified path
|
||||
* @param git the Git object
|
||||
@@ -674,4 +709,43 @@ object JGitUtil {
|
||||
return git.log.add(startCommit).addPath(path).setMaxCount(1).call.iterator.next
|
||||
}
|
||||
|
||||
def getBranches(owner: String, name: String, defaultBranch: String): Seq[BranchInfo] = {
|
||||
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
||||
val repo = git.getRepository
|
||||
val defaultObject = repo.resolve(defaultBranch)
|
||||
git.branchList.call.asScala.map { ref =>
|
||||
val walk = new RevWalk(repo)
|
||||
try{
|
||||
val defaultCommit = walk.parseCommit(defaultObject)
|
||||
val branchName = ref.getName.stripPrefix("refs/heads/")
|
||||
val branchCommit = if(branchName == defaultBranch){
|
||||
defaultCommit
|
||||
}else{
|
||||
walk.parseCommit(ref.getObjectId)
|
||||
}
|
||||
val when = branchCommit.getCommitterIdent.getWhen
|
||||
val committer = branchCommit.getCommitterIdent.getName
|
||||
val committerEmail = branchCommit.getCommitterIdent.getEmailAddress
|
||||
val mergeInfo = if(branchName==defaultBranch){
|
||||
None
|
||||
}else{
|
||||
walk.reset()
|
||||
walk.setRevFilter( RevFilter.MERGE_BASE )
|
||||
walk.markStart(branchCommit)
|
||||
walk.markStart(defaultCommit)
|
||||
val mergeBase = walk.next()
|
||||
walk.reset()
|
||||
walk.setRevFilter(RevFilter.ALL)
|
||||
Some(BranchMergeInfo(
|
||||
ahead = RevWalkUtils.count(walk, branchCommit, mergeBase),
|
||||
behind = RevWalkUtils.count(walk, defaultCommit, mergeBase),
|
||||
isMerged = walk.isMergedInto(branchCommit, defaultCommit)))
|
||||
}
|
||||
BranchInfo(branchName, committer, when, committerEmail, mergeInfo, ref.getObjectId.name)
|
||||
} finally {
|
||||
walk.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
/**
|
||||
* Define key strings for request attributes, session attributes or flash attributes.
|
||||
@@ -1,13 +1,13 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import util.ControlUtil._
|
||||
import service.SystemSettingsService
|
||||
import gitbucket.core.model.Account
|
||||
import ControlUtil._
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.service.SystemSettingsService.Ldap
|
||||
import com.novell.ldap._
|
||||
import java.security.Security
|
||||
import org.slf4j.LoggerFactory
|
||||
import service.SystemSettingsService.Ldap
|
||||
import scala.annotation.tailrec
|
||||
import model.Account
|
||||
|
||||
/**
|
||||
* Utility for LDAP authentication.
|
||||
@@ -48,6 +48,7 @@ object LDAPUtil {
|
||||
dn = ldapSettings.bindDN.getOrElse(""),
|
||||
password = ldapSettings.bindPassword.getOrElse(""),
|
||||
tls = ldapSettings.tls.getOrElse(false),
|
||||
ssl = ldapSettings.ssl.getOrElse(false),
|
||||
keystore = ldapSettings.keystore.getOrElse(""),
|
||||
error = "System LDAP authentication failed."
|
||||
){ conn =>
|
||||
@@ -65,6 +66,7 @@ object LDAPUtil {
|
||||
dn = userDN,
|
||||
password = password,
|
||||
tls = ldapSettings.tls.getOrElse(false),
|
||||
ssl = ldapSettings.ssl.getOrElse(false),
|
||||
keystore = ldapSettings.keystore.getOrElse(""),
|
||||
error = "User LDAP Authentication Failed."
|
||||
){ conn =>
|
||||
@@ -96,7 +98,7 @@ object LDAPUtil {
|
||||
}).replaceAll("[^a-zA-Z0-9\\-_.]", "").replaceAll("^[_\\-]", "")
|
||||
}
|
||||
|
||||
private def bind[A](host: String, port: Int, dn: String, password: String, tls: Boolean, keystore: String, error: String)
|
||||
private def bind[A](host: String, port: Int, dn: String, password: String, tls: Boolean, ssl: Boolean, keystore: String, error: String)
|
||||
(f: LDAPConnection => Either[String, A]): Either[String, A] = {
|
||||
if (tls) {
|
||||
// Dynamically set Sun as the security provider
|
||||
@@ -109,7 +111,13 @@ object LDAPUtil {
|
||||
}
|
||||
}
|
||||
|
||||
val conn: LDAPConnection = new LDAPConnection(new LDAPJSSEStartTLSFactory())
|
||||
val conn: LDAPConnection =
|
||||
if(ssl) {
|
||||
new LDAPConnection(new LDAPJSSESecureSocketFactory())
|
||||
}else {
|
||||
new LDAPConnection(new LDAPJSSEStartTLSFactory())
|
||||
}
|
||||
|
||||
try {
|
||||
// Connect to the server
|
||||
conn.connect(host, port)
|
||||
@@ -1,8 +1,8 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.locks.{ReentrantLock, Lock}
|
||||
import util.ControlUtil._
|
||||
import ControlUtil._
|
||||
|
||||
object LockUtil {
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import gitbucket.core.model.{Session, Issue}
|
||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, SystemSettingsService}
|
||||
import gitbucket.core.servlet.Database
|
||||
import gitbucket.core.view.Markdown
|
||||
|
||||
import scala.concurrent._
|
||||
import ExecutionContext.Implicits.global
|
||||
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import app.Context
|
||||
import model.Session
|
||||
import service.{AccountService, RepositoryService, IssuesService, SystemSettingsService}
|
||||
import servlet.Database
|
||||
import gitbucket.core.controller.Context
|
||||
import SystemSettingsService.Smtp
|
||||
import _root_.util.ControlUtil.defining
|
||||
import ControlUtil.defining
|
||||
|
||||
trait Notifier extends RepositoryService with AccountService with IssuesService {
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context): Unit
|
||||
|
||||
protected def recipients(issue: model.Issue)(notify: String => Unit)(implicit session: Session, context: Context) =
|
||||
protected def recipients(issue: Issue)(notify: String => Unit)(implicit session: Session, context: Context) =
|
||||
(
|
||||
// individual repository's owner
|
||||
issue.userName ::
|
||||
@@ -27,7 +29,7 @@ trait Notifier extends RepositoryService with AccountService with IssuesService
|
||||
getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
|
||||
)
|
||||
.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) filterNot (LDAPUtil.isDummyMailAddress(_)) foreach (x => notify(x.mailAddress)) )
|
||||
|
||||
}
|
||||
@@ -67,14 +69,14 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context) = {
|
||||
val database = Database(context.request.getServletContext)
|
||||
val database = Database()
|
||||
|
||||
val f = Future {
|
||||
database withSession { implicit session =>
|
||||
getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
|
||||
defining(
|
||||
s"[${r.name}] ${issue.title} (#${issueId})" ->
|
||||
msg(view.Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
|
||||
msg(Markdown.toHtml(content, r, false, true))) { case (subject, msg) =>
|
||||
recipients(issue) { to =>
|
||||
val email = new HtmlEmail
|
||||
email.setHostName(smtp.host)
|
||||
@@ -113,4 +115,4 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
class MockMailer extends Notifier {
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context): Unit = {}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.net.{URLDecoder, URLEncoder}
|
||||
import org.mozilla.universalchardet.UniversalDetector
|
||||
import util.ControlUtil._
|
||||
import ControlUtil._
|
||||
import org.apache.commons.io.input.BOMInputStream
|
||||
import org.apache.commons.io.IOUtils
|
||||
|
||||
@@ -30,7 +30,7 @@ object StringUtil {
|
||||
value.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """)
|
||||
|
||||
/**
|
||||
* Make string from byte array. Character encoding is detected automatically by [[util.StringUtil.detectEncoding]].
|
||||
* Make string from byte array. Character encoding is detected automatically by [[StringUtil.detectEncoding]].
|
||||
* And if given bytes contains UTF-8 BOM, it's removed from returned string.
|
||||
*/
|
||||
def convertFromByteArray(content: Array[Byte]): String =
|
||||
@@ -1,4 +1,4 @@
|
||||
package util
|
||||
package gitbucket.core.util
|
||||
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
67
src/main/scala/gitbucket/core/util/Version.scala
Normal file
67
src/main/scala/gitbucket/core/util/Version.scala
Normal file
@@ -0,0 +1,67 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.sql.Connection
|
||||
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import ControlUtil._
|
||||
|
||||
case class Version(majorVersion: Int, minorVersion: Int) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[Version])
|
||||
|
||||
/**
|
||||
* Execute update/MAJOR_MINOR.sql to update schema to this version.
|
||||
* If corresponding SQL file does not exist, this method do nothing.
|
||||
*/
|
||||
def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||
val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
|
||||
|
||||
using(cl.getResourceAsStream(sqlPath)){ in =>
|
||||
if(in != null){
|
||||
val sql = IOUtils.toString(in, "UTF-8")
|
||||
using(conn.createStatement()){ stmt =>
|
||||
logger.debug(sqlPath + "=" + sql)
|
||||
stmt.executeUpdate(sql)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MAJOR.MINOR
|
||||
*/
|
||||
val versionString = s"${majorVersion}.${minorVersion}"
|
||||
|
||||
}
|
||||
|
||||
object Versions {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(Versions.getClass)
|
||||
|
||||
def update(conn: Connection, headVersion: Version, currentVersion: Version, versions: Seq[Version], cl: ClassLoader)
|
||||
(save: Connection => Unit): Unit = {
|
||||
logger.debug("Start schema update")
|
||||
try {
|
||||
if(currentVersion == headVersion){
|
||||
logger.debug("No update")
|
||||
} else if(currentVersion.versionString != "0.0" && !versions.contains(currentVersion)){
|
||||
logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.")
|
||||
} else {
|
||||
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn, cl))
|
||||
save(conn)
|
||||
logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
|
||||
}
|
||||
} catch {
|
||||
case ex: Throwable => {
|
||||
logger.error("Failed to schema update", ex)
|
||||
ex.printStackTrace()
|
||||
conn.rollback()
|
||||
}
|
||||
}
|
||||
logger.debug("End schema update")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package view
|
||||
package gitbucket.core.view
|
||||
|
||||
import service.RequestCache
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.service.RequestCache
|
||||
import gitbucket.core.util.StringUtil
|
||||
import play.twirl.api.Html
|
||||
import util.StringUtil
|
||||
|
||||
trait AvatarImageProvider { self: RequestCache =>
|
||||
|
||||
@@ -11,7 +12,7 @@ trait AvatarImageProvider { self: RequestCache =>
|
||||
* Looks up Gravatar if avatar icon has not been configured in user settings.
|
||||
*/
|
||||
protected def getAvatarImageHtml(userName: String, size: Int,
|
||||
mailAddress: String = "", tooltip: Boolean = false)(implicit context: app.Context): Html = {
|
||||
mailAddress: String = "", tooltip: Boolean = false)(implicit context: Context): Html = {
|
||||
|
||||
val src = if(mailAddress.isEmpty){
|
||||
// by user name
|
||||
@@ -1,18 +1,20 @@
|
||||
package view
|
||||
package gitbucket.core.view
|
||||
|
||||
import service.RequestCache
|
||||
import util.Implicits.RichString
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.service.{RepositoryService, RequestCache}
|
||||
import gitbucket.core.util.Implicits
|
||||
import gitbucket.core.util.Implicits.RichString
|
||||
|
||||
trait LinkConverter { self: RequestCache =>
|
||||
|
||||
/**
|
||||
* Converts issue id, username and commit id to link.
|
||||
*/
|
||||
protected def convertRefsLinks(value: String, repository: service.RepositoryService.RepositoryInfo,
|
||||
issueIdPrefix: String = "#")(implicit context: app.Context): String = {
|
||||
protected def convertRefsLinks(value: String, repository: RepositoryService.RepositoryInfo,
|
||||
issueIdPrefix: String = "#")(implicit context: Context): String = {
|
||||
value
|
||||
// escape HTML tags
|
||||
.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """)
|
||||
.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """)
|
||||
// convert issue id to link
|
||||
.replaceBy(("(?<=(^|\\W))" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r){ m =>
|
||||
getIssue(repository.owner, repository.name, m.group(2)) match {
|
||||
@@ -1,39 +1,56 @@
|
||||
package view
|
||||
package gitbucket.core.view
|
||||
|
||||
import util.StringUtil
|
||||
import util.ControlUtil._
|
||||
import util.Directory._
|
||||
import org.parboiled.common.StringUtils
|
||||
import org.pegdown._
|
||||
import org.pegdown.ast._
|
||||
import org.pegdown.LinkRenderer.Rendering
|
||||
import java.text.Normalizer
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.service.{RepositoryService, RequestCache, WikiService}
|
||||
import gitbucket.core.util.StringUtil
|
||||
import org.parboiled.common.StringUtils
|
||||
import org.pegdown.LinkRenderer.Rendering
|
||||
import org.pegdown._
|
||||
import org.pegdown.ast._
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import service.{RequestCache, WikiService}
|
||||
|
||||
object Markdown {
|
||||
|
||||
/**
|
||||
* Converts Markdown of Wiki pages to HTML.
|
||||
*/
|
||||
def toHtml(markdown: String, repository: service.RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): String = {
|
||||
def toHtml(markdown: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableTaskList: Boolean = false,
|
||||
hasWritePermission: Boolean = false,
|
||||
pages: List[String] = Nil)(implicit context: Context): String = {
|
||||
|
||||
// escape issue id
|
||||
val source = if(enableRefsLink){
|
||||
val s = if(enableRefsLink){
|
||||
markdown.replaceAll("(?<=(\\W|^))#(\\d+)(?=(\\W|$))", "issue:$2")
|
||||
} else markdown
|
||||
|
||||
// escape task list
|
||||
val source = if(enableTaskList){
|
||||
GitBucketHtmlSerializer.escapeTaskList(s)
|
||||
} else s
|
||||
|
||||
val rootNode = new PegDownProcessor(
|
||||
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS
|
||||
Extensions.AUTOLINKS | Extensions.WIKILINKS | Extensions.FENCED_CODE_BLOCKS | Extensions.TABLES | Extensions.HARDWRAPS | Extensions.SUPPRESS_ALL_HTML
|
||||
).parseMarkdown(source.toCharArray)
|
||||
|
||||
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink).toHtml(rootNode)
|
||||
new GitBucketHtmlSerializer(markdown, repository, enableWikiLink, enableRefsLink, enableTaskList, hasWritePermission, pages).toHtml(rootNode)
|
||||
}
|
||||
}
|
||||
|
||||
class GitBucketLinkRender(context: app.Context, repository: service.RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean) extends LinkRenderer with WikiService {
|
||||
class GitBucketLinkRender(
|
||||
context: Context,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
pages: List[String]) extends LinkRenderer with WikiService {
|
||||
|
||||
override def render(node: WikiLinkNode): Rendering = {
|
||||
if(enableWikiLink){
|
||||
try {
|
||||
@@ -47,7 +64,7 @@ class GitBucketLinkRender(context: app.Context, repository: service.RepositorySe
|
||||
|
||||
val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page)
|
||||
|
||||
if(getWikiPage(repository.owner, repository.name, page).isDefined){
|
||||
if(pages.contains(page)){
|
||||
new Rendering(url, label)
|
||||
} else {
|
||||
new Rendering(url, label).withAttribute("class", "absent")
|
||||
@@ -80,11 +97,14 @@ class GitBucketVerbatimSerializer extends VerbatimSerializer {
|
||||
|
||||
class GitBucketHtmlSerializer(
|
||||
markdown: String,
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean
|
||||
)(implicit val context: app.Context) extends ToHtmlSerializer(
|
||||
new GitBucketLinkRender(context, repository, enableWikiLink),
|
||||
enableRefsLink: Boolean,
|
||||
enableTaskList: Boolean,
|
||||
hasWritePermission: Boolean,
|
||||
pages: List[String]
|
||||
)(implicit val context: Context) extends ToHtmlSerializer(
|
||||
new GitBucketLinkRender(context, repository, enableWikiLink, pages),
|
||||
Map[String, VerbatimSerializer](VerbatimSerializer.DEFAULT -> new GitBucketVerbatimSerializer).asJava
|
||||
) with LinkConverter with RequestCache {
|
||||
|
||||
@@ -103,10 +123,10 @@ class GitBucketHtmlSerializer(
|
||||
}
|
||||
|
||||
private def fixUrl(url: String, isImage: Boolean = false): String = {
|
||||
if(!enableWikiLink){
|
||||
if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("#") || url.startsWith("/")){
|
||||
url
|
||||
} else if(context.currentPath.contains("/blob/")){
|
||||
if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("#") || url.startsWith("/")){
|
||||
url
|
||||
} else if(!enableWikiLink){
|
||||
if(context.currentPath.contains("/blob/")){
|
||||
url + (if(isImage) "?raw=true" else "")
|
||||
} else if(context.currentPath.contains("/tree/")){
|
||||
val paths = context.currentPath.split("/")
|
||||
@@ -143,7 +163,10 @@ class GitBucketHtmlSerializer(
|
||||
|
||||
override def visit(node: TextNode): Unit = {
|
||||
// convert commit id and username to link.
|
||||
val text = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
||||
val t = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
||||
|
||||
// convert task list to checkbox.
|
||||
val text = if(enableTaskList) GitBucketHtmlSerializer.convertCheckBox(t, hasWritePermission) else t
|
||||
|
||||
if (abbreviations.isEmpty) {
|
||||
printer.print(text)
|
||||
@@ -151,6 +174,54 @@ class GitBucketHtmlSerializer(
|
||||
printWithAbbreviations(text)
|
||||
}
|
||||
}
|
||||
|
||||
override def visit(node: BulletListNode): Unit = {
|
||||
if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) {
|
||||
printer.println().print("""<ul class="task-list">""").indent(+2)
|
||||
visitChildren(node)
|
||||
printer.indent(-2).println().print("</ul>")
|
||||
} else {
|
||||
printIndentedTag(node, "ul")
|
||||
}
|
||||
}
|
||||
|
||||
override def visit(node: ListItemNode): Unit = {
|
||||
if (printChildrenToString(node).contains("""class="task-list-item-checkbox" """)) {
|
||||
printer.println()
|
||||
printer.print("""<li class="task-list-item">""")
|
||||
visitChildren(node)
|
||||
printer.print("</li>")
|
||||
} else {
|
||||
printer.println()
|
||||
printTag(node, "li")
|
||||
}
|
||||
}
|
||||
|
||||
override def visit(node: ExpLinkNode) {
|
||||
printLink(linkRenderer.render(node, printLinkChildrenToString(node)))
|
||||
}
|
||||
|
||||
def printLinkChildrenToString(node: SuperNode) = {
|
||||
val priorPrinter = printer
|
||||
printer = new Printer()
|
||||
visitLinkChildren(node)
|
||||
val result = printer.getString()
|
||||
printer = priorPrinter
|
||||
result
|
||||
}
|
||||
|
||||
def visitLinkChildren(node: SuperNode) {
|
||||
import scala.collection.JavaConversions._
|
||||
node.getChildren.foreach(child => child match {
|
||||
case node: ExpImageNode => visitLinkChild(node)
|
||||
case node: SuperNode => visitLinkChildren(node)
|
||||
case _ => child.accept(this)
|
||||
})
|
||||
}
|
||||
|
||||
def visitLinkChild(node: ExpImageNode) {
|
||||
printer.print("<img src=\"").print(fixUrl(node.url, true)).print("\" alt=\"").printEncoded(printChildrenToString(node)).print("\"/>")
|
||||
}
|
||||
}
|
||||
|
||||
object GitBucketHtmlSerializer {
|
||||
@@ -163,4 +234,14 @@ object GitBucketHtmlSerializer {
|
||||
val noSpecialChars = StringUtil.urlEncode(normalized)
|
||||
noSpecialChars.toLowerCase(Locale.ENGLISH)
|
||||
}
|
||||
|
||||
def escapeTaskList(text: String): String = {
|
||||
Pattern.compile("""^( *)- \[([x| ])\] """, Pattern.MULTILINE).matcher(text).replaceAll("$1* task:$2: ")
|
||||
}
|
||||
|
||||
def convertCheckBox(text: String, hasWritePermission: Boolean): String = {
|
||||
val disabled = if (hasWritePermission) "" else "disabled"
|
||||
text.replaceAll("task:x:", """<input type="checkbox" class="task-list-item-checkbox" checked="checked" """ + disabled + "/>")
|
||||
.replaceAll("task: :", """<input type="checkbox" class="task-list-item-checkbox" """ + disabled + "/>")
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user