mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 12:15:35 +02:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d71d39917 | ||
|
|
5430564065 | ||
|
|
54bc8c16d8 | ||
|
|
0affdb6ad0 | ||
|
|
532978522a | ||
|
|
05a9a0b45c | ||
|
|
24f8ad11ad | ||
|
|
ce943a0e6c | ||
|
|
204c0cd0f8 | ||
|
|
c213008f1c | ||
|
|
e6ad069509 | ||
|
|
38c7e3cdf8 | ||
|
|
2be79f6590 | ||
|
|
2f7125b6c0 | ||
|
|
bb03a6fc9b | ||
|
|
7b774aee1a | ||
|
|
d53619c247 | ||
|
|
d34118bdfd | ||
|
|
c57bc487a3 | ||
|
|
296fc9a3df | ||
|
|
fd8b5780f3 | ||
|
|
602b6c635a | ||
|
|
a79180699e | ||
|
|
e9901a8abf | ||
|
|
4e63d64c13 | ||
|
|
4261b7adbe | ||
|
|
f30c9f6171 | ||
|
|
c00b704843 | ||
|
|
e89b2020a3 | ||
|
|
18ca3cbd80 | ||
|
|
062d6cd066 | ||
|
|
b4dd067d61 | ||
|
|
fd22e2911a | ||
|
|
73d9e69e43 | ||
|
|
7e4c29f4cf | ||
|
|
32672262ef | ||
|
|
3c865ea20b | ||
|
|
d8698d02b7 | ||
|
|
d5b47e5adb | ||
|
|
accb1cf2ab | ||
|
|
aa8da1b046 | ||
|
|
c52ed32949 | ||
|
|
ec6f4ff734 | ||
|
|
06b0dbf2e5 | ||
|
|
98d24248c2 | ||
|
|
cec1dc98a9 | ||
|
|
36115734bb | ||
|
|
c1eccd391d | ||
|
|
7fe86fcdb2 | ||
|
|
7f81ec52c1 | ||
|
|
7c269de39b | ||
|
|
aa9e34e992 | ||
|
|
4d0ab514fb | ||
|
|
9d526b32e0 | ||
|
|
90a83c5c64 | ||
|
|
e6e5cc67d5 | ||
|
|
4a6eb95474 | ||
|
|
7bce8cf3b6 | ||
|
|
4d1605ded2 | ||
|
|
2bec2cfa93 | ||
|
|
ff07872a3d | ||
|
|
35733cd82e | ||
|
|
38df990033 | ||
|
|
940e2f4759 | ||
|
|
6fe65c76b1 |
20
README.md
20
README.md
@@ -6,7 +6,7 @@ GitBucket is the easily installable Github clone written with Scala.
|
|||||||
The current version of GitBucket provides a basic features below:
|
The current version of GitBucket provides a basic features below:
|
||||||
|
|
||||||
- Public / Private Git repository (http access only)
|
- Public / Private Git repository (http access only)
|
||||||
- Repository viewer (some advanced features are not implemented)
|
- Repository viewer (some advanced features such as online file editing are not implemented)
|
||||||
- Repository search (Code and Issues)
|
- Repository search (Code and Issues)
|
||||||
- Wiki
|
- Wiki
|
||||||
- Issues
|
- Issues
|
||||||
@@ -15,9 +15,13 @@ The current version of GitBucket provides a basic features below:
|
|||||||
- Activity timeline
|
- Activity timeline
|
||||||
- User management (for Administrators)
|
- User management (for Administrators)
|
||||||
- Group (like Organization in Github)
|
- Group (like Organization in Github)
|
||||||
|
- LDAP integration
|
||||||
|
- Gravatar support
|
||||||
|
|
||||||
Following features are not implemented, but we will make them in the future release!
|
Following features are not implemented, but we will make them in the future release!
|
||||||
|
|
||||||
|
- File editing in repository viewer
|
||||||
|
- Comment for the changeset
|
||||||
- Network graph
|
- Network graph
|
||||||
- Statics
|
- Statics
|
||||||
- Watch / Star
|
- Watch / Star
|
||||||
@@ -33,10 +37,22 @@ Installation
|
|||||||
|
|
||||||
The default administrator account is **root** and password is **root**.
|
The default administrator account is **root** and password is **root**.
|
||||||
|
|
||||||
To upgrade GitBucket, only replace gitbucket.war.
|
(Since 1.6) or you can start GitBucket by ```java -jar gitbucket.war``` without servlet container. In this case, GitBucket URL is **http://[hostname]:8080/**. You can specify following options.
|
||||||
|
|
||||||
|
- --port=[NUMBER]
|
||||||
|
- --prefix=[CONTEXTPATH]
|
||||||
|
|
||||||
|
To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
--------
|
--------
|
||||||
|
### 1.6 - COMMING SOON!
|
||||||
|
- Web hook.
|
||||||
|
- Performance improvement for pull request.
|
||||||
|
- Executable war file.
|
||||||
|
- Specify suitable Content-Type for downloaded files in the repository viewer.
|
||||||
|
- Fixed some bugs.
|
||||||
|
|
||||||
### 1.5 - 4 Sep 2013
|
### 1.5 - 4 Sep 2013
|
||||||
- Fork and pull request.
|
- Fork and pull request.
|
||||||
- LDAP authentication.
|
- LDAP authentication.
|
||||||
|
|||||||
55
build.xml
Normal file
55
build.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<project name="gitbucket" default="all" basedir=".">
|
||||||
|
|
||||||
|
<property name="target.dir" value="target"/>
|
||||||
|
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
||||||
|
<property name="jetty.dir" value="embed-jetty"/>
|
||||||
|
<property name="scala.version" value="2.10"/>
|
||||||
|
<property name="gitbucket.version" value="0.0.1"/>
|
||||||
|
<property name="jetty.version" value="8.1.8.v20121106"/>
|
||||||
|
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
||||||
|
|
||||||
|
<target name="clean">
|
||||||
|
<delete dir="${embed.classes.dir}"/>
|
||||||
|
<delete file="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="war" depends="clean">
|
||||||
|
<exec command="sbt.bat clean package"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="embed" depends="war">
|
||||||
|
<mkdir dir="${embed.classes.dir}"/>
|
||||||
|
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/javax.servlet-${servlet.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-continuation-${jetty.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-http-${jetty.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-io-${jetty.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-security-${jetty.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-server-${jetty.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-servlet-${jetty.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-util-${jetty.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-webapp-${jetty.version}.jar" />
|
||||||
|
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-xml-${jetty.version}.jar" />
|
||||||
|
|
||||||
|
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
||||||
|
basedir="${embed.classes.dir}"
|
||||||
|
update = "true"
|
||||||
|
includes="javax/**,org/**"/>
|
||||||
|
|
||||||
|
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
||||||
|
basedir="${target.dir}/scala-${scala.version}/classes"
|
||||||
|
update = "true"
|
||||||
|
includes="JettyLauncher.class"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="rename" depends="embed">
|
||||||
|
<rename src="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
||||||
|
dest="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="all" depends="rename">
|
||||||
|
</target>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
BIN
embed-jetty/javax.servlet-3.0.0.v201112011016.jar
Normal file
BIN
embed-jetty/javax.servlet-3.0.0.v201112011016.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-continuation-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-continuation-8.1.8.v20121106.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-http-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-http-8.1.8.v20121106.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-io-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-io-8.1.8.v20121106.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-security-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-security-8.1.8.v20121106.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-server-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-server-8.1.8.v20121106.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-servlet-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-servlet-8.1.8.v20121106.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-util-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-util-8.1.8.v20121106.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-webapp-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-webapp-8.1.8.v20121106.jar
Normal file
Binary file not shown.
BIN
embed-jetty/jetty-xml-8.1.8.v20121106.jar
Normal file
BIN
embed-jetty/jetty-xml-8.1.8.v20121106.jar
Normal file
Binary file not shown.
@@ -36,14 +36,16 @@ object MyBuild extends Build {
|
|||||||
"org.pegdown" % "pegdown" % "1.3.0",
|
"org.pegdown" % "pegdown" % "1.3.0",
|
||||||
"org.apache.commons" % "commons-compress" % "1.5",
|
"org.apache.commons" % "commons-compress" % "1.5",
|
||||||
"org.apache.commons" % "commons-email" % "1.3.1",
|
"org.apache.commons" % "commons-email" % "1.3.1",
|
||||||
|
"org.apache.httpcomponents" % "httpclient" % "4.2.5",
|
||||||
"com.typesafe.slick" %% "slick" % "1.0.1",
|
"com.typesafe.slick" %% "slick" % "1.0.1",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.3.171",
|
"com.h2database" % "h2" % "1.3.171",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.0.6" % "runtime",
|
"ch.qos.logback" % "logback-classic" % "1.0.6" % "runtime",
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container",
|
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
|
||||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts (Artifact("javax.servlet", "jar", "jar"))
|
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts (Artifact("javax.servlet", "jar", "jar"))
|
||||||
),
|
),
|
||||||
EclipseKeys.withSource := true
|
EclipseKeys.withSource := true,
|
||||||
|
packageOptions += Package.MainClass("JettyLauncher")
|
||||||
) ++ seq(Twirl.settings: _*)
|
) ++ seq(Twirl.settings: _*)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
39
src/main/java/JettyLauncher.java
Normal file
39
src/main/java/JettyLauncher.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.ProtectionDomain;
|
||||||
|
|
||||||
|
public class JettyLauncher {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
int port = 8080;
|
||||||
|
String contextPath = "/";
|
||||||
|
|
||||||
|
for(String arg: args){
|
||||||
|
if(arg.startsWith("--") && arg.contains("=")){
|
||||||
|
String[] dim = arg.split("=");
|
||||||
|
if(dim.length >= 2){
|
||||||
|
if(dim[0].equals("--port")){
|
||||||
|
port = Integer.parseInt(dim[1]);
|
||||||
|
} else if(dim[0].equals("--prefix")){
|
||||||
|
contextPath = dim[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Server server = new Server(port);
|
||||||
|
WebAppContext context = new WebAppContext();
|
||||||
|
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
||||||
|
URL location = domain.getCodeSource().getLocation();
|
||||||
|
|
||||||
|
context.setContextPath(contextPath);
|
||||||
|
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
|
||||||
|
context.setServer(server);
|
||||||
|
context.setWar(location.toExternalForm());
|
||||||
|
|
||||||
|
server.setHandler(context);
|
||||||
|
server.start();
|
||||||
|
server.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<logger name="scala.slick" level="INFO" />
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<logger name="service.WebHookService" level="DEBUG" />
|
||||||
|
<logger name="servlet" level="DEBUG" />
|
||||||
|
-->
|
||||||
</configuration>
|
</configuration>
|
||||||
8
src/main/resources/update/1_6.sql
Normal file
8
src/main/resources/update/1_6.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE WEB_HOOK (
|
||||||
|
USER_NAME VARCHAR(100) NOT NULL,
|
||||||
|
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
||||||
|
URL VARCHAR(200) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL);
|
||||||
|
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import _root_.util.Directory._
|
import _root_.util.Directory._
|
||||||
import _root_.util.{FileUtil, Validations}
|
import _root_.util.Implicits._
|
||||||
|
import _root_.util.ControlUtil._
|
||||||
|
import _root_.util.{FileUtil, Validations, Keys}
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.json._
|
import org.scalatra.json._
|
||||||
import org.json4s._
|
import org.json4s._
|
||||||
@@ -32,7 +34,7 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
val path = httpRequest.getRequestURI.substring(context.length)
|
val path = httpRequest.getRequestURI.substring(context.length)
|
||||||
|
|
||||||
if(path.startsWith("/console/")){
|
if(path.startsWith("/console/")){
|
||||||
val account = httpRequest.getSession.getAttribute("LOGIN_ACCOUNT").asInstanceOf[Account]
|
val account = httpRequest.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
|
||||||
if(account == null){
|
if(account == null){
|
||||||
// Redirect to login form
|
// Redirect to login form
|
||||||
httpResponse.sendRedirect(context + "/signin?" + path)
|
httpResponse.sendRedirect(context + "/signin?" + path)
|
||||||
@@ -57,56 +59,47 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
*/
|
*/
|
||||||
implicit def context: Context = Context(servletContext.getContextPath, LoginAccount, currentURL, request)
|
implicit def context: Context = Context(servletContext.getContextPath, LoginAccount, currentURL, request)
|
||||||
|
|
||||||
private def currentURL: String = {
|
private def currentURL: String = defining(request.getQueryString){ queryString =>
|
||||||
val queryString = request.getQueryString
|
|
||||||
request.getRequestURI + (if(queryString != null) "?" + queryString else "")
|
request.getRequestURI + (if(queryString != null) "?" + queryString else "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private def LoginAccount: Option[Account] = {
|
private def LoginAccount: Option[Account] = session.getAs[Account](Keys.Session.LoginAccount)
|
||||||
session.get("LOGIN_ACCOUNT") match {
|
|
||||||
case Some(x: Account) => Some(x)
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def ajaxGet(path : String)(action : => Any) : Route = {
|
def ajaxGet(path : String)(action : => Any) : Route =
|
||||||
super.get(path){
|
super.get(path){
|
||||||
request.setAttribute("AJAX", "true")
|
request.setAttribute(Keys.Request.Ajax, "true")
|
||||||
action
|
action
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override def ajaxGet[T](path : String, form : MappingValueType[T])(action : T => Any) : Route = {
|
override def ajaxGet[T](path : String, form : MappingValueType[T])(action : T => Any) : Route =
|
||||||
super.ajaxGet(path, form){ form =>
|
super.ajaxGet(path, form){ form =>
|
||||||
request.setAttribute("AJAX", "true")
|
request.setAttribute(Keys.Request.Ajax, "true")
|
||||||
action(form)
|
action(form)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def ajaxPost(path : String)(action : => Any) : Route = {
|
def ajaxPost(path : String)(action : => Any) : Route =
|
||||||
super.post(path){
|
super.post(path){
|
||||||
request.setAttribute("AJAX", "true")
|
request.setAttribute(Keys.Request.Ajax, "true")
|
||||||
action
|
action
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override def ajaxPost[T](path : String, form : MappingValueType[T])(action : T => Any) : Route = {
|
override def ajaxPost[T](path : String, form : MappingValueType[T])(action : T => Any) : Route =
|
||||||
super.ajaxPost(path, form){ form =>
|
super.ajaxPost(path, form){ form =>
|
||||||
request.setAttribute("AJAX", "true")
|
request.setAttribute(Keys.Request.Ajax, "true")
|
||||||
action(form)
|
action(form)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected def NotFound() = {
|
protected def NotFound() =
|
||||||
if(request.getAttribute("AJAX") == null){
|
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||||
org.scalatra.NotFound(html.error("Not Found"))
|
|
||||||
} else {
|
|
||||||
org.scalatra.NotFound()
|
org.scalatra.NotFound()
|
||||||
|
} else {
|
||||||
|
org.scalatra.NotFound(html.error("Not Found"))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected def Unauthorized()(implicit context: app.Context) = {
|
protected def Unauthorized()(implicit context: app.Context) =
|
||||||
if(request.getAttribute("AJAX") == null){
|
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||||
|
org.scalatra.Unauthorized()
|
||||||
|
} else {
|
||||||
if(context.loginAccount.isDefined){
|
if(context.loginAccount.isDefined){
|
||||||
org.scalatra.Unauthorized(redirect("/"))
|
org.scalatra.Unauthorized(redirect("/"))
|
||||||
} else {
|
} else {
|
||||||
@@ -116,13 +109,9 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
org.scalatra.Unauthorized(redirect("/signin?redirect=" + currentURL))
|
org.scalatra.Unauthorized(redirect("/signin?redirect=" + currentURL))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
org.scalatra.Unauthorized()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected def baseUrl = {
|
protected def baseUrl = defining(request.getRequestURL.toString){ url =>
|
||||||
val url = request.getRequestURL.toString
|
|
||||||
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
|
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,12 +122,10 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
*/
|
*/
|
||||||
case class Context(path: String, loginAccount: Option[Account], currentUrl: String, request: HttpServletRequest){
|
case class Context(path: String, loginAccount: Option[Account], currentUrl: String, request: HttpServletRequest){
|
||||||
|
|
||||||
def redirectUrl = {
|
def redirectUrl = if(request.getParameter("redirect") != null){
|
||||||
if(request.getParameter("redirect") != null){
|
request.getParameter("redirect")
|
||||||
request.getParameter("redirect")
|
} else {
|
||||||
} else {
|
currentUrl
|
||||||
currentUrl
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,13 +134,14 @@ case class Context(path: String, loginAccount: Option[Account], currentUrl: Stri
|
|||||||
* If object has not been cached with the specified key then retrieves by given action.
|
* If object has not been cached with the specified key then retrieves by given action.
|
||||||
* Cached object are available during a request.
|
* Cached object are available during a request.
|
||||||
*/
|
*/
|
||||||
def cache[A](key: String)(action: => A): A = {
|
def cache[A](key: String)(action: => A): A =
|
||||||
Option(request.getAttribute("cache." + key).asInstanceOf[A]).getOrElse {
|
defining(Keys.Request.Cache(key)){ cacheKey =>
|
||||||
val newObject = action
|
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
||||||
request.setAttribute("cache." + key, newObject)
|
val newObject = action
|
||||||
newObject
|
request.setAttribute(cacheKey, newObject)
|
||||||
|
newObject
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +151,7 @@ case class Context(path: String, loginAccount: Option[Account], currentUrl: Stri
|
|||||||
trait AccountManagementControllerBase extends ControllerBase with FileUploadControllerBase {
|
trait AccountManagementControllerBase extends ControllerBase with FileUploadControllerBase {
|
||||||
self: AccountService =>
|
self: AccountService =>
|
||||||
|
|
||||||
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit = {
|
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
|
||||||
if(clearImage){
|
if(clearImage){
|
||||||
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
||||||
new java.io.File(getUserUploadDir(userName), image).delete()
|
new java.io.File(getUserUploadDir(userName), image).delete()
|
||||||
@@ -179,7 +167,6 @@ trait AccountManagementControllerBase extends ControllerBase with FileUploadCont
|
|||||||
updateAvatarImage(userName, Some(filename))
|
updateAvatarImage(userName, Some(filename))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected def uniqueUserName: Constraint = new Constraint(){
|
protected def uniqueUserName: Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String): Option[String] =
|
override def validate(name: String, value: String): Option[String] =
|
||||||
@@ -215,12 +202,7 @@ trait FileUploadControllerBase {
|
|||||||
def removeTemporaryFiles()(implicit session: HttpSession): Unit =
|
def removeTemporaryFiles()(implicit session: HttpSession): Unit =
|
||||||
FileUtils.deleteDirectory(TemporaryDir)
|
FileUtils.deleteDirectory(TemporaryDir)
|
||||||
|
|
||||||
def getUploadedFilename(fileId: String)(implicit session: HttpSession): Option[String] = {
|
def getUploadedFilename(fileId: String)(implicit session: HttpSession): Option[String] =
|
||||||
val filename = Option(session.getAttribute("upload_" + fileId).asInstanceOf[String])
|
session.getAndRemove[String](Keys.Session.Upload(fileId))
|
||||||
if(filename.isDefined){
|
|
||||||
session.removeAttribute("upload_" + fileId)
|
|
||||||
}
|
|
||||||
filename
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
|
import util.ControlUtil._
|
||||||
import util._
|
import util._
|
||||||
import service._
|
import service._
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -149,7 +150,7 @@ trait CreateRepositoryControllerBase extends ControllerBase {
|
|||||||
getWikiRepositoryDir(loginUserName, repository.name))
|
getWikiRepositoryDir(loginUserName, repository.name))
|
||||||
|
|
||||||
// insert commit id
|
// insert commit id
|
||||||
JGitUtil.withGit(getRepositoryDir(loginUserName, repository.name)){ git =>
|
using(Git.open(getRepositoryDir(loginUserName, repository.name))){ git =>
|
||||||
JGitUtil.getRepositoryInfo(loginUserName, repository.name, baseUrl).branchList.foreach { branch =>
|
JGitUtil.getRepositoryInfo(loginUserName, repository.name, baseUrl).branchList.foreach { branch =>
|
||||||
JGitUtil.getCommitLog(git, branch) match {
|
JGitUtil.getCommitLog(git, branch) match {
|
||||||
case Right((commits, _)) => commits.foreach { commit =>
|
case Right((commits, _)) => commits.foreach { commit =>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import service._
|
import service._
|
||||||
import util.UsersAuthenticator
|
import util.{UsersAuthenticator, Keys}
|
||||||
|
import util.Implicits._
|
||||||
|
|
||||||
class DashboardController extends DashboardControllerBase
|
class DashboardController extends DashboardControllerBase
|
||||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
with IssuesService with PullRequestService with RepositoryService with AccountService
|
||||||
@@ -42,12 +43,10 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
import IssuesService._
|
import IssuesService._
|
||||||
|
|
||||||
// condition
|
// condition
|
||||||
val sessionKey = "dashboard/issues"
|
val condition = session.putAndGet(Keys.Session.DashboardIssues,
|
||||||
val condition = if(request.getQueryString == null)
|
if(request.hasQueryString) IssueSearchCondition(request)
|
||||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
else session.getAs[IssueSearchCondition](Keys.Session.DashboardIssues).getOrElse(IssueSearchCondition())
|
||||||
else IssueSearchCondition(request)
|
)
|
||||||
|
|
||||||
session.put(sessionKey, condition)
|
|
||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = context.loginAccount.get.userName
|
||||||
val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name)
|
val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name)
|
||||||
@@ -75,15 +74,10 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
import PullRequestService._
|
import PullRequestService._
|
||||||
|
|
||||||
// condition
|
// condition
|
||||||
val sessionKey = "dashboard/pulls"
|
val condition = session.putAndGet(Keys.Session.DashboardPulls, {
|
||||||
val condition = {
|
if(request.hasQueryString) IssueSearchCondition(request)
|
||||||
if(request.getQueryString == null)
|
else session.getAs[IssueSearchCondition](Keys.Session.DashboardPulls).getOrElse(IssueSearchCondition())
|
||||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
}.copy(repo = repository))
|
||||||
else
|
|
||||||
IssueSearchCondition(request)
|
|
||||||
}.copy(repo = repository)
|
|
||||||
|
|
||||||
session.put(sessionKey, condition)
|
|
||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = context.loginAccount.get.userName
|
||||||
val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name)
|
val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import util.{FileUtil}
|
import _root_.util.{Keys, FileUtil}
|
||||||
|
import util.ControlUtil._
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport}
|
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport}
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
@@ -18,10 +19,9 @@ class FileUploadController extends ScalatraServlet
|
|||||||
|
|
||||||
post("/image"){
|
post("/image"){
|
||||||
fileParams.get("file") match {
|
fileParams.get("file") match {
|
||||||
case Some(file) if(FileUtil.isImage(file.name)) => {
|
case Some(file) if(FileUtil.isImage(file.name)) => defining(generateFileId){ fileId =>
|
||||||
val fileId = generateFileId
|
|
||||||
FileUtils.writeByteArrayToFile(getTemporaryFile(fileId), file.get)
|
FileUtils.writeByteArrayToFile(getTemporaryFile(fileId), file.get)
|
||||||
session += "upload_" + fileId -> file.name
|
session += Keys.Session.Upload(fileId) -> file.name
|
||||||
Ok(fileId)
|
Ok(fileId)
|
||||||
}
|
}
|
||||||
case None => BadRequest
|
case None => BadRequest
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import jp.sf.amateras.scalatra.forms._
|
|||||||
|
|
||||||
import service._
|
import service._
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, ReadableUsersAuthenticator, Notifier}
|
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, ReadableUsersAuthenticator, Notifier, Keys}
|
||||||
|
import util.Implicits._
|
||||||
|
import util.ControlUtil._
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
|
|
||||||
class IssuesController extends IssuesControllerBase
|
class IssuesController extends IssuesControllerBase
|
||||||
@@ -57,12 +59,9 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||||
val owner = repository.owner
|
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
||||||
val name = repository.name
|
getIssue(owner, name, issueId) map {
|
||||||
val issueId = params("id")
|
issues.html.issue(
|
||||||
|
|
||||||
getIssue(owner, name, issueId) map {
|
|
||||||
issues.html.issue(
|
|
||||||
_,
|
_,
|
||||||
getComments(owner, name, issueId.toInt),
|
getComments(owner, name, issueId.toInt),
|
||||||
getIssueLabels(owner, name, issueId.toInt),
|
getIssueLabels(owner, name, issueId.toInt),
|
||||||
@@ -71,65 +70,64 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
getLabels(owner, name),
|
getLabels(owner, name),
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
hasWritePermission(owner, name, context.loginAccount),
|
||||||
repository)
|
repository)
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||||
val owner = repository.owner
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
val name = repository.name
|
issues.html.create(
|
||||||
|
(getCollaborators(owner, name) :+ owner).sorted,
|
||||||
issues.html.create(
|
getMilestones(owner, name),
|
||||||
(getCollaborators(owner, name) :+ owner).sorted,
|
getLabels(owner, name),
|
||||||
getMilestones(owner, name),
|
hasWritePermission(owner, name, context.loginAccount),
|
||||||
getLabels(owner, name),
|
repository)
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
}
|
||||||
repository)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||||
val owner = repository.owner
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
val name = repository.name
|
val writable = hasWritePermission(owner, name, context.loginAccount)
|
||||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
val userName = context.loginAccount.get.userName
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
|
|
||||||
// insert issue
|
// insert issue
|
||||||
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
||||||
if(writable) form.assignedUserName else None,
|
if(writable) form.assignedUserName else None,
|
||||||
if(writable) form.milestoneId else None)
|
if(writable) form.milestoneId else None)
|
||||||
|
|
||||||
// insert labels
|
// insert labels
|
||||||
if(writable){
|
if(writable){
|
||||||
form.labelNames.map { value =>
|
form.labelNames.map { value =>
|
||||||
val labels = getLabels(owner, name)
|
val labels = getLabels(owner, name)
|
||||||
value.split(",").foreach { labelName =>
|
value.split(",").foreach { labelName =>
|
||||||
labels.find(_.labelName == labelName).map { label =>
|
labels.find(_.labelName == labelName).map { label =>
|
||||||
registerIssueLabel(owner, name, issueId, label.labelId)
|
registerIssueLabel(owner, name, issueId, label.labelId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// record activity
|
||||||
|
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
||||||
|
Notifier.msgIssue(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect(s"/${owner}/${name}/issues/${issueId}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
|
||||||
Notifier.msgIssue(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/${issueId}")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) =>
|
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) =>
|
||||||
val owner = repository.owner
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
val name = repository.name
|
getIssue(owner, name, params("id")).map { issue =>
|
||||||
|
if(isEditable(owner, name, issue.openedUserName)){
|
||||||
getIssue(owner, name, params("id")).map { issue =>
|
updateIssue(owner, name, issue.issueId, form.title, form.content)
|
||||||
if(isEditable(owner, name, issue.openedUserName)){
|
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||||
updateIssue(owner, name, issue.issueId, form.title, form.content)
|
} else Unauthorized
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
} getOrElse NotFound
|
||||||
} else Unauthorized
|
}
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
@@ -147,15 +145,14 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
val owner = repository.owner
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
val name = repository.name
|
getComment(owner, name, params("id")).map { comment =>
|
||||||
|
if(isEditable(owner, name, comment.commentedUserName)){
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
updateComment(comment.commentId, form.content)
|
||||||
if(isEditable(owner, name, comment.commentedUserName)){
|
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||||
updateComment(comment.commentId, form.content)
|
} else Unauthorized
|
||||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
} getOrElse NotFound
|
||||||
} else Unauthorized
|
}
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
||||||
@@ -194,17 +191,17 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
||||||
val issueId = params("id").toInt
|
defining(params("id").toInt){ issueId =>
|
||||||
|
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
||||||
val issueId = params("id").toInt
|
defining(params("id").toInt){ issueId =>
|
||||||
|
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
|
||||||
@@ -223,36 +220,36 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
||||||
val action = params.get("value")
|
defining(params.get("value")){ action =>
|
||||||
|
executeBatch(repository) {
|
||||||
executeBatch(repository) {
|
handleComment(_, None, repository)( _ => action)
|
||||||
handleComment(_, None, repository)( _ => action)
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
|
||||||
val labelId = params("value").toInt
|
defining(params("value").toInt){ labelId =>
|
||||||
|
executeBatch(repository) { issueId =>
|
||||||
executeBatch(repository) { issueId =>
|
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
|
||||||
val value = assignedUserName("value")
|
defining(assignedUserName("value")){ value =>
|
||||||
|
executeBatch(repository) {
|
||||||
executeBatch(repository) {
|
updateAssignedUserName(repository.owner, repository.name, _, value)
|
||||||
updateAssignedUserName(repository.owner, repository.name, _, value)
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
|
||||||
val value = milestoneId("value")
|
defining(milestoneId("value")){ value =>
|
||||||
|
executeBatch(repository) {
|
||||||
executeBatch(repository) {
|
updateMilestoneId(repository.owner, repository.name, _, value)
|
||||||
updateMilestoneId(repository.owner, repository.name, _, value)
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -273,89 +270,89 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
|
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
|
||||||
(getAction: model.Issue => Option[String] =
|
(getAction: model.Issue => Option[String] =
|
||||||
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
|
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
|
||||||
val owner = repository.owner
|
|
||||||
val name = repository.name
|
|
||||||
val userName = context.loginAccount.get.userName
|
|
||||||
|
|
||||||
getIssue(owner, name, issueId.toString) map { issue =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
val (action, recordActivity) =
|
val userName = context.loginAccount.get.userName
|
||||||
getAction(issue)
|
|
||||||
.collect {
|
getIssue(owner, name, issueId.toString) map { issue =>
|
||||||
|
val (action, recordActivity) =
|
||||||
|
getAction(issue)
|
||||||
|
.collect {
|
||||||
case "close" => true -> (Some("close") ->
|
case "close" => true -> (Some("close") ->
|
||||||
Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
||||||
case "reopen" => false -> (Some("reopen") ->
|
case "reopen" => false -> (Some("reopen") ->
|
||||||
Some(recordReopenIssueActivity _))
|
Some(recordReopenIssueActivity _))
|
||||||
}
|
}
|
||||||
.map { case (closed, t) =>
|
.map { case (closed, t) =>
|
||||||
updateClosed(owner, name, issueId, closed)
|
updateClosed(owner, name, issueId, closed)
|
||||||
t
|
t
|
||||||
}
|
}
|
||||||
.getOrElse(None -> None)
|
.getOrElse(None -> None)
|
||||||
|
|
||||||
val commentId = content
|
val commentId = content
|
||||||
.map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
|
.map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
|
||||||
.getOrElse ( action.get.capitalize -> action.get )
|
.getOrElse ( action.get.capitalize -> action.get )
|
||||||
match {
|
match {
|
||||||
case (content, action) => createComment(owner, name, userName, issueId, content, action)
|
case (content, action) => createComment(owner, name, userName, issueId, content, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// record activity
|
// record activity
|
||||||
content foreach {
|
content foreach {
|
||||||
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
|
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
|
||||||
(owner, name, userName, issueId, _)
|
(owner, name, userName, issueId, _)
|
||||||
}
|
}
|
||||||
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
|
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
Notifier() match {
|
Notifier() match {
|
||||||
case f =>
|
case f =>
|
||||||
content foreach {
|
content foreach {
|
||||||
f.toNotify(repository, issueId, _){
|
f.toNotify(repository, issueId, _){
|
||||||
Notifier.msgComment(s"${baseUrl}/${owner}/${name}/${
|
Notifier.msgComment(s"${baseUrl}/${owner}/${name}/${
|
||||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
|
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
action foreach {
|
||||||
action foreach {
|
f.toNotify(repository, issueId, _){
|
||||||
f.toNotify(repository, issueId, _){
|
Notifier.msgStatus(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||||
Notifier.msgStatus(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
issue -> commentId
|
issue -> commentId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
|
private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
|
||||||
val owner = repository.owner
|
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||||
val repoName = repository.name
|
val filterUser = Map(filter -> params.getOrElse("userName", ""))
|
||||||
val filterUser = Map(filter -> params.getOrElse("userName", ""))
|
val page = IssueSearchCondition.page(request)
|
||||||
val page = IssueSearchCondition.page(request)
|
val sessionKey = Keys.Session.Issues(owner, repoName)
|
||||||
val sessionKey = s"${owner}/${repoName}/issues"
|
|
||||||
|
|
||||||
// retrieve search condition
|
// retrieve search condition
|
||||||
val condition = if(request.getQueryString == null){
|
val condition = session.putAndGet(sessionKey,
|
||||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
if(request.hasQueryString) IssueSearchCondition(request)
|
||||||
} else IssueSearchCondition(request)
|
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||||
|
)
|
||||||
|
|
||||||
session.put(sessionKey, condition)
|
issues.html.list(
|
||||||
|
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||||
issues.html.list(
|
page,
|
||||||
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
(getCollaborators(owner, repoName) :+ owner).sorted,
|
||||||
page,
|
getMilestones(owner, repoName),
|
||||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
getLabels(owner, repoName),
|
||||||
getMilestones(owner, repoName),
|
countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
|
||||||
getLabels(owner, repoName),
|
countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
|
||||||
countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
|
countIssue(condition, Map.empty, false, owner -> repoName),
|
||||||
countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
|
context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
|
||||||
countIssue(condition, Map.empty, false, owner -> repoName),
|
context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
|
||||||
context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
|
countIssueGroupByLabels(owner, repoName, condition, filterUser),
|
||||||
context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
|
condition,
|
||||||
countIssueGroupByLabels(owner, repoName, condition, filterUser),
|
filter,
|
||||||
condition,
|
repository,
|
||||||
filter,
|
hasWritePermission(owner, repoName, context.loginAccount))
|
||||||
repository,
|
}
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator, Notifier}
|
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator, Notifier, Keys}
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
|
import util.ControlUtil._
|
||||||
import service._
|
import service._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
@@ -62,117 +63,250 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||||
val owner = repository.owner
|
defining(repository.owner, repository.name, params("id").toInt){ case (owner, name, issueId) =>
|
||||||
val name = repository.name
|
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||||
val issueId = params("id").toInt
|
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
||||||
|
val (commits, diffs) =
|
||||||
|
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||||
|
|
||||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
pulls.html.pullreq(
|
||||||
JGitUtil.withGit(getRepositoryDir(owner, name)){ git =>
|
issue, pullreq,
|
||||||
val requestCommitId = git.getRepository.resolve(pullreq.requestBranch)
|
getComments(owner, name, issueId.toInt),
|
||||||
|
(getCollaborators(owner, name) :+ owner).sorted,
|
||||||
val (commits, diffs) =
|
getMilestonesWithIssueCount(owner, name),
|
||||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
commits,
|
||||||
|
diffs,
|
||||||
pulls.html.pullreq(
|
hasWritePermission(owner, name, context.loginAccount),
|
||||||
issue, pullreq,
|
repository)
|
||||||
getComments(owner, name, issueId.toInt),
|
|
||||||
(getCollaborators(owner, name) :+ owner).sorted,
|
|
||||||
getMilestonesWithIssueCount(owner, name),
|
|
||||||
commits,
|
|
||||||
diffs,
|
|
||||||
if(issue.closed){
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
checkConflict(owner, name, pullreq.branch, owner, name, pullreq.requestBranch)
|
|
||||||
},
|
|
||||||
hasWritePermission(owner, name, context.loginAccount),
|
|
||||||
repository,
|
|
||||||
s"${baseUrl}${context.path}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
|
|
||||||
}
|
|
||||||
|
|
||||||
} getOrElse NotFound
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
|
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}/merge"){
|
|
||||||
val issueId = params("id").toInt
|
|
||||||
|
|
||||||
getPullRequest(repository.owner, repository.name, issueId).map { case (issue, pullreq) =>
|
|
||||||
val remote = getRepositoryDir(repository.owner, repository.name)
|
|
||||||
val tmpdir = new java.io.File(getTemporaryDir(repository.owner, repository.name), s"merge-${issueId}")
|
|
||||||
val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).setBranch(pullreq.branch).call
|
|
||||||
|
|
||||||
try {
|
|
||||||
// mark issue as merged and close.
|
|
||||||
val loginAccount = context.loginAccount.get
|
|
||||||
createComment(repository.owner, repository.name, loginAccount.userName, issueId, form.message, "merge")
|
|
||||||
createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Close", "close")
|
|
||||||
updateClosed(repository.owner, repository.name, issueId, true)
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, form.message)
|
|
||||||
|
|
||||||
// TODO apply ref comment
|
|
||||||
|
|
||||||
// fetch pull request to temporary working repository
|
|
||||||
val pullRequestBranchName = s"gitbucket-pullrequest-${issueId}"
|
|
||||||
|
|
||||||
git.fetch
|
|
||||||
.setRemote(getRepositoryDir(repository.owner, repository.name).toURI.toString)
|
|
||||||
.setRefSpecs(new RefSpec(s"refs/pull/${issueId}/head:refs/heads/${pullRequestBranchName}")).call
|
|
||||||
|
|
||||||
// merge pull request
|
|
||||||
git.checkout.setName(pullreq.branch).call
|
|
||||||
|
|
||||||
val result = git.merge
|
|
||||||
.include(git.getRepository.resolve(pullRequestBranchName))
|
|
||||||
.setFastForward(FastForwardMode.NO_FF)
|
|
||||||
.setCommit(false)
|
|
||||||
.call
|
|
||||||
|
|
||||||
if(result.getConflicts != null){
|
|
||||||
throw new RuntimeException("This pull request can't merge automatically.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge commit
|
|
||||||
git.getRepository.writeMergeCommitMsg(
|
|
||||||
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestRepositoryName}\n"
|
|
||||||
+ form.message)
|
|
||||||
|
|
||||||
git.commit
|
|
||||||
.setCommitter(new PersonIdent(loginAccount.userName, loginAccount.mailAddress))
|
|
||||||
.call
|
|
||||||
|
|
||||||
// push
|
|
||||||
git.push.call
|
|
||||||
|
|
||||||
val (commits, _) = getRequestCompareInfo(repository.owner, repository.name, pullreq.commitIdFrom,
|
|
||||||
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
|
|
||||||
|
|
||||||
commits.flatten.foreach { commit =>
|
|
||||||
if(!existsCommitId(repository.owner, repository.name, commit.id)){
|
|
||||||
insertCommitId(repository.owner, repository.name, commit.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier().toNotify(repository, issueId, "merge"){
|
|
||||||
Notifier.msgStatus(s"${baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
git.getRepository.close
|
|
||||||
FileUtils.deleteDirectory(tmpdir)
|
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository =>
|
||||||
|
defining(repository.owner, repository.name, params("id").toInt){ case (owner, name, issueId) =>
|
||||||
|
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||||
|
pulls.html.mergeguide(
|
||||||
|
checkConflict(owner, name, pullreq.branch, owner, name, pullreq.requestBranch),
|
||||||
|
pullreq,
|
||||||
|
s"${baseUrl}${context.path}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
|
||||||
|
defining(repository.owner, repository.name, params("id").toInt){ case (owner, name, issueId) =>
|
||||||
|
LockUtil.lock(s"${owner}/${name}/merge"){
|
||||||
|
getPullRequest(owner, name, issueId).map { case (issue, pullreq) =>
|
||||||
|
val remote = getRepositoryDir(owner, name)
|
||||||
|
val tmpdir = new java.io.File(getTemporaryDir(owner, name), s"merge-${issueId}")
|
||||||
|
val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).setBranch(pullreq.branch).call
|
||||||
|
|
||||||
|
try {
|
||||||
|
// mark issue as merged and close.
|
||||||
|
val loginAccount = context.loginAccount.get
|
||||||
|
createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
|
||||||
|
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
|
||||||
|
updateClosed(owner, name, issueId, true)
|
||||||
|
|
||||||
|
// record activity
|
||||||
|
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message)
|
||||||
|
|
||||||
|
// fetch pull request to temporary working repository
|
||||||
|
val pullRequestBranchName = s"gitbucket-pullrequest-${issueId}"
|
||||||
|
|
||||||
|
git.fetch
|
||||||
|
.setRemote(getRepositoryDir(owner, name).toURI.toString)
|
||||||
|
.setRefSpecs(new RefSpec(s"refs/pull/${issueId}/head:refs/heads/${pullRequestBranchName}")).call
|
||||||
|
|
||||||
|
// merge pull request
|
||||||
|
git.checkout.setName(pullreq.branch).call
|
||||||
|
|
||||||
|
val result = git.merge
|
||||||
|
.include(git.getRepository.resolve(pullRequestBranchName))
|
||||||
|
.setFastForward(FastForwardMode.NO_FF)
|
||||||
|
.setCommit(false)
|
||||||
|
.call
|
||||||
|
|
||||||
|
if(result.getConflicts != null){
|
||||||
|
throw new RuntimeException("This pull request can't merge automatically.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge commit
|
||||||
|
git.getRepository.writeMergeCommitMsg(
|
||||||
|
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestRepositoryName}\n"
|
||||||
|
+ form.message)
|
||||||
|
|
||||||
|
git.commit
|
||||||
|
.setCommitter(new PersonIdent(loginAccount.userName, loginAccount.mailAddress))
|
||||||
|
.call
|
||||||
|
|
||||||
|
// push
|
||||||
|
git.push.call
|
||||||
|
|
||||||
|
val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom,
|
||||||
|
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
|
||||||
|
|
||||||
|
commits.flatten.foreach { commit =>
|
||||||
|
if(!existsCommitId(owner, name, commit.id)){
|
||||||
|
insertCommitId(owner, name, commit.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
Notifier().toNotify(repository, issueId, "merge"){
|
||||||
|
Notifier.msgStatus(s"${baseUrl}/${owner}/${name}/pull/${issueId}")
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
git.getRepository.close
|
||||||
|
FileUtils.deleteDirectory(tmpdir)
|
||||||
|
}
|
||||||
|
} getOrElse NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
|
||||||
|
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||||
|
case (Some(originUserName), Some(originRepositoryName)) => {
|
||||||
|
getRepository(originUserName, originRepositoryName, baseUrl).map { originRepository =>
|
||||||
|
using(
|
||||||
|
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
|
||||||
|
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||||
|
){ (oldGit, newGit) =>
|
||||||
|
val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2
|
||||||
|
val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2
|
||||||
|
|
||||||
|
redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}")
|
||||||
|
}
|
||||||
|
} getOrElse NotFound
|
||||||
|
}
|
||||||
|
case _ => {
|
||||||
|
using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git =>
|
||||||
|
val defaultBranch = JGitUtil.getDefaultBranch(git, forkedRepository).get._2
|
||||||
|
redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/:owner/:repository/compare/*...*")(referrersOnly { repository =>
|
||||||
|
val Seq(origin, forked) = multiParams("splat")
|
||||||
|
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, repository.owner)
|
||||||
|
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, repository.owner)
|
||||||
|
|
||||||
|
(getRepository(originOwner, repository.name, baseUrl),
|
||||||
|
getRepository(forkedOwner, repository.name, baseUrl)) match {
|
||||||
|
case (Some(originRepository), Some(forkedRepository)) => {
|
||||||
|
using(
|
||||||
|
Git.open(getRepositoryDir(originOwner, repository.name)),
|
||||||
|
Git.open(getRepositoryDir(forkedOwner, repository.name))
|
||||||
|
){ case (oldGit, newGit) =>
|
||||||
|
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
||||||
|
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
||||||
|
|
||||||
|
val forkedId = getForkedCommitId(oldGit, newGit,
|
||||||
|
originOwner, repository.name, originBranch,
|
||||||
|
forkedOwner, repository.name, forkedBranch)
|
||||||
|
|
||||||
|
val oldId = oldGit.getRepository.resolve(forkedId)
|
||||||
|
val newId = newGit.getRepository.resolve(forkedBranch)
|
||||||
|
|
||||||
|
val (commits, diffs) = getRequestCompareInfo(
|
||||||
|
originOwner, repository.name, oldId.getName,
|
||||||
|
forkedOwner, repository.name, newId.getName)
|
||||||
|
|
||||||
|
pulls.html.compare(
|
||||||
|
commits,
|
||||||
|
diffs,
|
||||||
|
repository.repository.originUserName.map { userName =>
|
||||||
|
userName :: getForkedRepositories(userName, repository.name)
|
||||||
|
} getOrElse List(repository.owner),
|
||||||
|
originBranch,
|
||||||
|
forkedBranch,
|
||||||
|
oldId.getName,
|
||||||
|
newId.getName,
|
||||||
|
repository,
|
||||||
|
originRepository,
|
||||||
|
forkedRepository,
|
||||||
|
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => NotFound
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(collaboratorsOnly { repository =>
|
||||||
|
val Seq(origin, forked) = multiParams("splat")
|
||||||
|
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, repository.owner)
|
||||||
|
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, repository.owner)
|
||||||
|
|
||||||
|
(getRepository(originOwner, repository.name, baseUrl),
|
||||||
|
getRepository(forkedOwner, repository.name, baseUrl)) match {
|
||||||
|
case (Some(originRepository), Some(forkedRepository)) => {
|
||||||
|
using(
|
||||||
|
Git.open(getRepositoryDir(originOwner, repository.name)),
|
||||||
|
Git.open(getRepositoryDir(forkedOwner, repository.name))
|
||||||
|
){ case (oldGit, newGit) =>
|
||||||
|
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
||||||
|
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
||||||
|
|
||||||
|
pulls.html.mergecheck(
|
||||||
|
checkConflict(originOwner, repository.name, originBranch, forkedOwner, repository.name, forkedBranch))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ => NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
|
||||||
|
val loginUserName = context.loginAccount.get.userName
|
||||||
|
|
||||||
|
val issueId = createIssue(
|
||||||
|
owner = repository.owner,
|
||||||
|
repository = repository.name,
|
||||||
|
loginUser = loginUserName,
|
||||||
|
title = form.title,
|
||||||
|
content = form.content,
|
||||||
|
assignedUserName = None,
|
||||||
|
milestoneId = None,
|
||||||
|
isPullRequest = true)
|
||||||
|
|
||||||
|
createPullRequest(
|
||||||
|
originUserName = repository.owner,
|
||||||
|
originRepositoryName = repository.name,
|
||||||
|
issueId = issueId,
|
||||||
|
originBranch = form.targetBranch,
|
||||||
|
requestUserName = form.requestUserName,
|
||||||
|
requestRepositoryName = repository.name,
|
||||||
|
requestBranch = form.requestBranch,
|
||||||
|
commitIdFrom = form.commitIdFrom,
|
||||||
|
commitIdTo = form.commitIdTo)
|
||||||
|
|
||||||
|
// fetch requested branch
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
|
git.fetch
|
||||||
|
.setRemote(getRepositoryDir(form.requestUserName, repository.name).toURI.toString)
|
||||||
|
.setRefSpecs(new RefSpec(s"refs/heads/${form.requestBranch}:refs/pull/${issueId}/head"))
|
||||||
|
.call
|
||||||
|
}
|
||||||
|
|
||||||
|
// record activity
|
||||||
|
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
|
||||||
|
|
||||||
|
// notifications
|
||||||
|
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
||||||
|
Notifier.msgPullRequest(s"${baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether conflict will be caused in merging.
|
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
||||||
* Returns true if conflict will be caused.
|
|
||||||
*/
|
*/
|
||||||
private def checkConflict(userName: String, repositoryName: String, branch: String,
|
private def checkConflict(userName: String, repositoryName: String, branch: String,
|
||||||
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
|
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
|
||||||
@@ -205,134 +339,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
|
|
||||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
|
||||||
case (Some(originUserName), Some(originRepositoryName)) => {
|
|
||||||
getRepository(originUserName, originRepositoryName, baseUrl).map { originRepository =>
|
|
||||||
withGit(
|
|
||||||
getRepositoryDir(originUserName, originRepositoryName),
|
|
||||||
getRepositoryDir(forkedRepository.owner, forkedRepository.name)
|
|
||||||
){ (oldGit, newGit) =>
|
|
||||||
val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2
|
|
||||||
val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2
|
|
||||||
|
|
||||||
redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}")
|
|
||||||
}
|
|
||||||
} getOrElse NotFound
|
|
||||||
}
|
|
||||||
case _ => {
|
|
||||||
JGitUtil.withGit(getRepositoryDir(forkedRepository.owner, forkedRepository.name)){ git =>
|
|
||||||
val defaultBranch = JGitUtil.getDefaultBranch(git, forkedRepository).get._2
|
|
||||||
redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/compare/*...*")(referrersOnly { repository =>
|
|
||||||
val Seq(origin, forked) = multiParams("splat")
|
|
||||||
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, repository.owner)
|
|
||||||
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, repository.owner)
|
|
||||||
|
|
||||||
(getRepository(originOwner, repository.name, baseUrl),
|
|
||||||
getRepository(forkedOwner, repository.name, baseUrl)) match {
|
|
||||||
case (Some(originRepository), Some(forkedRepository)) => {
|
|
||||||
withGit(
|
|
||||||
getRepositoryDir(originOwner, repository.name),
|
|
||||||
getRepositoryDir(forkedOwner, repository.name)
|
|
||||||
){ case (oldGit, newGit) =>
|
|
||||||
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
|
||||||
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
|
||||||
|
|
||||||
val forkedId = getForkedCommitId(oldGit, newGit,
|
|
||||||
originOwner, repository.name, originBranch,
|
|
||||||
forkedOwner, repository.name, forkedBranch)
|
|
||||||
|
|
||||||
val oldId = oldGit.getRepository.resolve(forkedId)
|
|
||||||
val newId = newGit.getRepository.resolve(forkedBranch)
|
|
||||||
|
|
||||||
val (commits, diffs) = getRequestCompareInfo(
|
|
||||||
originOwner, repository.name, oldId.getName,
|
|
||||||
forkedOwner, repository.name, newId.getName)
|
|
||||||
|
|
||||||
pulls.html.compare(
|
|
||||||
commits,
|
|
||||||
diffs,
|
|
||||||
repository.repository.originUserName.map { userName =>
|
|
||||||
userName :: getForkedRepositories(userName, repository.name)
|
|
||||||
} getOrElse List(repository.owner),
|
|
||||||
originBranch,
|
|
||||||
forkedBranch,
|
|
||||||
oldId.getName,
|
|
||||||
newId.getName,
|
|
||||||
checkConflict(originOwner, repository.name, originBranch, forkedOwner, repository.name, forkedBranch),
|
|
||||||
repository,
|
|
||||||
originRepository,
|
|
||||||
forkedRepository,
|
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case _ => NotFound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
|
|
||||||
val loginUserName = context.loginAccount.get.userName
|
|
||||||
|
|
||||||
val issueId = createIssue(
|
|
||||||
owner = repository.owner,
|
|
||||||
repository = repository.name,
|
|
||||||
loginUser = loginUserName,
|
|
||||||
title = form.title,
|
|
||||||
content = form.content,
|
|
||||||
assignedUserName = None,
|
|
||||||
milestoneId = None,
|
|
||||||
isPullRequest = true)
|
|
||||||
|
|
||||||
createPullRequest(
|
|
||||||
originUserName = repository.owner,
|
|
||||||
originRepositoryName = repository.name,
|
|
||||||
issueId = issueId,
|
|
||||||
originBranch = form.targetBranch,
|
|
||||||
requestUserName = form.requestUserName,
|
|
||||||
requestRepositoryName = repository.name,
|
|
||||||
requestBranch = form.requestBranch,
|
|
||||||
commitIdFrom = form.commitIdFrom,
|
|
||||||
commitIdTo = form.commitIdTo)
|
|
||||||
|
|
||||||
// fetch requested branch
|
|
||||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
|
||||||
git.fetch
|
|
||||||
.setRemote(getRepositoryDir(form.requestUserName, repository.name).toURI.toString)
|
|
||||||
.setRefSpecs(new RefSpec(s"refs/heads/${form.requestBranch}:refs/pull/${issueId}/head"))
|
|
||||||
.call
|
|
||||||
}
|
|
||||||
|
|
||||||
// record activity
|
|
||||||
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
|
|
||||||
|
|
||||||
// notifications
|
|
||||||
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
|
||||||
Notifier.msgPullRequest(s"${baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles w Git object simultaneously.
|
|
||||||
*/
|
|
||||||
private def withGit[T](oldDir: java.io.File, newDir: java.io.File)(action: (Git, Git) => T): T = {
|
|
||||||
val oldGit = Git.open(oldDir)
|
|
||||||
val newGit = Git.open(newDir)
|
|
||||||
try {
|
|
||||||
action(oldGit, newGit)
|
|
||||||
} finally {
|
|
||||||
oldGit.getRepository.close
|
|
||||||
newGit.getRepository.close
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses branch identifier and extracts owner and branch name as tuple.
|
* Parses branch identifier and extracts owner and branch name as tuple.
|
||||||
*
|
*
|
||||||
@@ -366,9 +372,9 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
|
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
|
||||||
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = {
|
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = {
|
||||||
|
|
||||||
withGit(
|
using(
|
||||||
getRepositoryDir(userName, repositoryName),
|
Git.open(getRepositoryDir(userName, repositoryName)),
|
||||||
getRepositoryDir(requestUserName, requestRepositoryName)
|
Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
|
||||||
){ (oldGit, newGit) =>
|
){ (oldGit, newGit) =>
|
||||||
val oldId = oldGit.getRepository.resolve(branch)
|
val oldId = oldGit.getRepository.resolve(branch)
|
||||||
val newId = newGit.getRepository.resolve(requestCommitId)
|
val newId = newGit.getRepository.resolve(requestCommitId)
|
||||||
@@ -385,31 +391,29 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
|
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
||||||
val owner = repository.owner
|
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||||
val repoName = repository.name
|
val filterUser = userName.map { x => Map("created_by" -> x) } getOrElse Map("all" -> "")
|
||||||
val filterUser = userName.map { x => Map("created_by" -> x) } getOrElse Map("all" -> "")
|
val page = IssueSearchCondition.page(request)
|
||||||
val page = IssueSearchCondition.page(request)
|
val sessionKey = Keys.Session.Pulls(owner, repoName)
|
||||||
val sessionKey = s"${owner}/${repoName}/pulls"
|
|
||||||
|
|
||||||
// retrieve search condition
|
// retrieve search condition
|
||||||
val condition = if(request.getQueryString == null){
|
val condition = session.putAndGet(sessionKey,
|
||||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
if(request.hasQueryString) IssueSearchCondition(request)
|
||||||
} else IssueSearchCondition(request)
|
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||||
|
)
|
||||||
|
|
||||||
session.put(sessionKey, condition)
|
pulls.html.list(
|
||||||
|
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||||
pulls.html.list(
|
getPullRequestCountGroupByUser(condition.state == "closed", owner, Some(repoName)),
|
||||||
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
userName,
|
||||||
getPullRequestCountGroupByUser(condition.state == "closed", owner, Some(repoName)),
|
page,
|
||||||
userName,
|
countIssue(condition.copy(state = "open" ), filterUser, true, owner -> repoName),
|
||||||
page,
|
countIssue(condition.copy(state = "closed"), filterUser, true, owner -> repoName),
|
||||||
countIssue(condition.copy(state = "open"), filterUser, true, owner -> repoName),
|
countIssue(condition, Map.empty, true, owner -> repoName),
|
||||||
countIssue(condition.copy(state = "closed"), filterUser, true, owner -> repoName),
|
condition,
|
||||||
countIssue(condition, Map.empty, true, owner -> repoName),
|
repository,
|
||||||
condition,
|
hasWritePermission(owner, repoName, context.loginAccount))
|
||||||
repository,
|
}
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,20 @@ import util.{UsersAuthenticator, OwnerAuthenticator}
|
|||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.scalatra.FlashMapSupport
|
import org.scalatra.FlashMapSupport
|
||||||
|
import service.WebHookService.WebHookPayload
|
||||||
|
import util.JGitUtil.CommitInfo
|
||||||
|
import util.ControlUtil._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||||
with RepositoryService with AccountService with OwnerAuthenticator with UsersAuthenticator
|
with RepositoryService with AccountService with WebHookService
|
||||||
|
with OwnerAuthenticator with UsersAuthenticator
|
||||||
|
|
||||||
trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSupport {
|
trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSupport {
|
||||||
self: RepositoryService with AccountService with OwnerAuthenticator with UsersAuthenticator =>
|
self: RepositoryService with AccountService with WebHookService
|
||||||
|
with OwnerAuthenticator with UsersAuthenticator =>
|
||||||
|
|
||||||
|
// for repository options
|
||||||
case class OptionsForm(description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
case class OptionsForm(description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
||||||
|
|
||||||
val optionsForm = mapping(
|
val optionsForm = mapping(
|
||||||
@@ -20,13 +27,21 @@ trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSuppo
|
|||||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
||||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
"isPrivate" -> trim(label("Repository Type", boolean()))
|
||||||
)(OptionsForm.apply)
|
)(OptionsForm.apply)
|
||||||
|
|
||||||
|
// for collaborator addition
|
||||||
case class CollaboratorForm(userName: String)
|
case class CollaboratorForm(userName: String)
|
||||||
|
|
||||||
val collaboratorForm = mapping(
|
val collaboratorForm = mapping(
|
||||||
"userName" -> trim(label("Username", text(required, collaborator)))
|
"userName" -> trim(label("Username", text(required, collaborator)))
|
||||||
)(CollaboratorForm.apply)
|
)(CollaboratorForm.apply)
|
||||||
|
|
||||||
|
// for web hook url addition
|
||||||
|
case class WebHookForm(url: String)
|
||||||
|
|
||||||
|
val webHookForm = mapping(
|
||||||
|
"url" -> trim(label("url", text(required, webHook)))
|
||||||
|
)(WebHookForm.apply)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to the Options page.
|
* Redirect to the Options page.
|
||||||
*/
|
*/
|
||||||
@@ -88,6 +103,53 @@ trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSuppo
|
|||||||
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the web hook page.
|
||||||
|
*/
|
||||||
|
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
||||||
|
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), repository, flash.get("info"))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the web hook URL.
|
||||||
|
*/
|
||||||
|
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
|
||||||
|
addWebHookURL(repository.owner, repository.name, form.url)
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the web hook URL.
|
||||||
|
*/
|
||||||
|
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
|
||||||
|
deleteWebHookURL(repository.owner, repository.name, params("url"))
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the test request to registered web hook URLs.
|
||||||
|
*/
|
||||||
|
get("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
val commits = git.log
|
||||||
|
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||||
|
.setMaxCount(3)
|
||||||
|
.call.iterator.asScala.map(new CommitInfo(_))
|
||||||
|
|
||||||
|
callWebHook(repository.owner, repository.name,
|
||||||
|
WebHookPayload(
|
||||||
|
git,
|
||||||
|
"refs/heads/" + repository.repository.defaultBranch,
|
||||||
|
repository,
|
||||||
|
commits.toList,
|
||||||
|
getAccountByUserName(repository.owner).get))
|
||||||
|
|
||||||
|
flash += "info" -> "Test payload deployed!"
|
||||||
|
}
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display the delete repository page.
|
* Display the delete repository page.
|
||||||
*/
|
*/
|
||||||
@@ -108,19 +170,25 @@ trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSuppo
|
|||||||
redirect(s"/${repository.owner}")
|
redirect(s"/${repository.owner}")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides duplication check for web hook url.
|
||||||
|
*/
|
||||||
|
private def webHook: Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String): Option[String] =
|
||||||
|
getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Constraint to validate the collaborator name.
|
* Provides Constraint to validate the collaborator name.
|
||||||
*/
|
*/
|
||||||
private def collaborator: Constraint = new Constraint(){
|
private def collaborator: Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String): Option[String] = {
|
override def validate(name: String, value: String): Option[String] =
|
||||||
val paths = request.getRequestURI.split("/")
|
|
||||||
getAccountByUserName(value) match {
|
getAccountByUserName(value) match {
|
||||||
case None => Some("User does not exist.")
|
case None => Some("User does not exist.")
|
||||||
case Some(x) if(x.userName == paths(1) || getCollaborators(paths(1), paths(2)).contains(x.userName))
|
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
|
||||||
=> Some("User can access this repository already.")
|
=> Some("User can access this repository already.")
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package app
|
|||||||
|
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
import util.Implicits._
|
import util.Implicits._
|
||||||
|
import util.ControlUtil._
|
||||||
import _root_.util.{ReferrerAuthenticator, JGitUtil, FileUtil, StringUtil}
|
import _root_.util.{ReferrerAuthenticator, JGitUtil, FileUtil, StringUtil}
|
||||||
import service._
|
import service._
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
@@ -56,7 +57,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
val (branchName, path) = splitPath(repository, multiParams("splat").head)
|
val (branchName, path) = splitPath(repository, multiParams("splat").head)
|
||||||
val page = params.getOrElse("page", "1").toInt
|
val page = params.getOrElse("page", "1").toInt
|
||||||
|
|
||||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
||||||
case Right((logs, hasNext)) =>
|
case Right((logs, hasNext)) =>
|
||||||
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||||
@@ -75,7 +76,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
val (id, path) = splitPath(repository, multiParams("splat").head)
|
||||||
val raw = params.get("raw").getOrElse("false").toBoolean
|
val raw = params.get("raw").getOrElse("false").toBoolean
|
||||||
|
|
||||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||||
|
|
||||||
@scala.annotation.tailrec
|
@scala.annotation.tailrec
|
||||||
@@ -84,19 +85,25 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
case true => getPathObjectId(path, walk)
|
case true => getPathObjectId(path, walk)
|
||||||
}
|
}
|
||||||
|
|
||||||
val treeWalk = new TreeWalk(git.getRepository)
|
val objectId = using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||||
val objectId = try {
|
|
||||||
treeWalk.addTree(revCommit.getTree)
|
treeWalk.addTree(revCommit.getTree)
|
||||||
treeWalk.setRecursive(true)
|
treeWalk.setRecursive(true)
|
||||||
getPathObjectId(path, treeWalk)
|
getPathObjectId(path, treeWalk)
|
||||||
} finally {
|
|
||||||
treeWalk.release
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(raw){
|
if(raw){
|
||||||
// Download
|
// Download
|
||||||
contentType = "application/octet-stream"
|
val mimeType = FileUtil.getMimeType(path)
|
||||||
JGitUtil.getContent(git, objectId, false).get
|
val bytes = JGitUtil.getContent(git, objectId, false).get
|
||||||
|
|
||||||
|
contentType = if(mimeType == "application/octet-stream" && FileUtil.isText(bytes)){
|
||||||
|
"text/plain"
|
||||||
|
} else {
|
||||||
|
mimeType
|
||||||
|
}
|
||||||
|
//response.setHeader("Content-Disposition", s"inline; filename=${FileUtil.getFileName(path)}")
|
||||||
|
bytes
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Viewer
|
// Viewer
|
||||||
val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize)
|
val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize)
|
||||||
@@ -127,14 +134,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/commit/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/commit/:id")(referrersOnly { repository =>
|
||||||
val id = params("id")
|
val id = params("id")
|
||||||
|
|
||||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))){ revCommit =>
|
||||||
|
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
|
||||||
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
|
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||||
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
repository, diffs, oldCommitId)
|
||||||
repository, diffs, oldCommitId)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -143,7 +150,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
* Displays branches.
|
* Displays branches.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
||||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
// retrieve latest update date of each branch
|
// retrieve latest update date of each branch
|
||||||
val branchInfo = repository.branchList.map { branchName =>
|
val branchInfo = repository.branchList.map { branchName =>
|
||||||
val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next
|
val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next
|
||||||
@@ -176,7 +183,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
// clone the repository
|
// clone the repository
|
||||||
val cloneDir = new File(workDir, revision)
|
val cloneDir = new File(workDir, revision)
|
||||||
JGitUtil.withGit(Git.cloneRepository
|
using(Git.cloneRepository
|
||||||
.setURI(getRepositoryDir(repository.owner, repository.name).toURI.toString)
|
.setURI(getRepositoryDir(repository.owner, repository.name).toURI.toString)
|
||||||
.setDirectory(cloneDir)
|
.setDirectory(cloneDir)
|
||||||
.call){ git =>
|
.call){ git =>
|
||||||
@@ -233,23 +240,23 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
if(repository.commitCount == 0){
|
if(repository.commitCount == 0){
|
||||||
repo.html.guide(repository)
|
repo.html.guide(repository)
|
||||||
} else {
|
} else {
|
||||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head)
|
val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head)
|
||||||
// get specified commit
|
// get specified commit
|
||||||
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
|
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, objectId)
|
defining(JGitUtil.getRevCommitFromId(git, objectId)){ revCommit =>
|
||||||
|
|
||||||
// get files
|
// get files
|
||||||
val files = JGitUtil.getFileList(git, revision, path)
|
val files = JGitUtil.getFileList(git, revision, path)
|
||||||
// process README.md
|
// process README.md
|
||||||
val readme = files.find(_.name == "README.md").map { file =>
|
val readme = files.find(_.name == "README.md").map { file =>
|
||||||
StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.html.files(revision, repository,
|
repo.html.files(revision, repository,
|
||||||
if(path == ".") Nil else path.split("/").toList, // current path
|
if(path == ".") Nil else path.split("/").toList, // current path
|
||||||
new JGitUtil.CommitInfo(revCommit), // latest commit
|
new JGitUtil.CommitInfo(revCommit), // latest commit
|
||||||
files, readme)
|
files, readme)
|
||||||
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import util._
|
import util._
|
||||||
|
import ControlUtil._
|
||||||
import service._
|
import service._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
|
||||||
@@ -26,25 +27,25 @@ trait SearchControllerBase extends ControllerBase { self: RepositoryService
|
|||||||
}
|
}
|
||||||
|
|
||||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||||
val query = params("q").trim
|
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
||||||
val target = params.getOrElse("type", "code")
|
val page = try {
|
||||||
val page = try {
|
val i = params.getOrElse("page", "1").toInt
|
||||||
val i = params.getOrElse("page", "1").toInt
|
if(i <= 0) 1 else i
|
||||||
if(i <= 0) 1 else i
|
} catch {
|
||||||
} catch {
|
case e: NumberFormatException => 1
|
||||||
case e: NumberFormatException => 1
|
}
|
||||||
}
|
|
||||||
|
|
||||||
target.toLowerCase match {
|
target.toLowerCase match {
|
||||||
case "issue" => search.html.issues(
|
case "issue" => search.html.issues(
|
||||||
searchIssues(repository.owner, repository.name, query),
|
searchIssues(repository.owner, repository.name, query),
|
||||||
countFiles(repository.owner, repository.name, query),
|
countFiles(repository.owner, repository.name, query),
|
||||||
query, page, repository)
|
query, page, repository)
|
||||||
|
|
||||||
case _ => search.html.code(
|
case _ => search.html.code(
|
||||||
searchFiles(repository.owner, repository.name, query),
|
searchFiles(repository.owner, repository.name, query),
|
||||||
countIssues(repository.owner, repository.name, query),
|
countIssues(repository.owner, repository.name, query),
|
||||||
query, page, repository)
|
query, page, repository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package app
|
|||||||
|
|
||||||
import service._
|
import service._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
import util.Implicits._
|
||||||
|
import util.Keys
|
||||||
|
|
||||||
class SignInController extends SignInControllerBase with SystemSettingsService with AccountService
|
class SignInController extends SignInControllerBase with SystemSettingsService with AccountService
|
||||||
|
|
||||||
@@ -17,16 +19,15 @@ trait SignInControllerBase extends ControllerBase { self: SystemSettingsService
|
|||||||
get("/signin"){
|
get("/signin"){
|
||||||
val redirect = params.get("redirect")
|
val redirect = params.get("redirect")
|
||||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||||
session.setAttribute("REDIRECT", redirect.get)
|
session.setAttribute(Keys.Session.Redirect, redirect.get)
|
||||||
}
|
}
|
||||||
html.signin(loadSystemSettings())
|
html.signin(loadSystemSettings())
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/signin", form){ form =>
|
post("/signin", form){ form =>
|
||||||
val settings = loadSystemSettings()
|
|
||||||
authenticate(loadSystemSettings(), form.userName, form.password) match {
|
authenticate(loadSystemSettings(), form.userName, form.password) match {
|
||||||
case Some(account) => signin(account)
|
case Some(account) => signin(account)
|
||||||
case None => redirect("/signin")
|
case None => redirect("/signin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,12 +40,15 @@ trait SignInControllerBase extends ControllerBase { self: SystemSettingsService
|
|||||||
* Set account information into HttpSession and redirect.
|
* Set account information into HttpSession and redirect.
|
||||||
*/
|
*/
|
||||||
private def signin(account: model.Account) = {
|
private def signin(account: model.Account) = {
|
||||||
session.setAttribute("LOGIN_ACCOUNT", account)
|
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||||
updateLastLoginDate(account.userName)
|
updateLastLoginDate(account.userName)
|
||||||
|
|
||||||
session.get("REDIRECT").map { redirectUrl =>
|
session.getAndRemove[String](Keys.Session.Redirect).map { redirectUrl =>
|
||||||
session.removeAttribute("REDIRECT")
|
if(redirectUrl.replaceFirst("/$", "") == request.getContextPath){
|
||||||
redirect(redirectUrl.asInstanceOf[String])
|
redirect("/")
|
||||||
|
} else {
|
||||||
|
redirect(redirectUrl)
|
||||||
|
}
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
redirect("/")
|
redirect("/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package app
|
|||||||
import service._
|
import service._
|
||||||
import util.AdminAuthenticator
|
import util.AdminAuthenticator
|
||||||
import util.StringUtil._
|
import util.StringUtil._
|
||||||
|
import util.ControlUtil._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
|
||||||
class UserManagementController extends UserManagementControllerBase
|
class UserManagementController extends UserManagementControllerBase
|
||||||
@@ -107,29 +108,29 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||||
val groupName = params("groupName")
|
defining(params("groupName")){ groupName =>
|
||||||
admin.users.html.group(getAccountByUserName(groupName), getGroupMembers(groupName))
|
admin.users.html.group(getAccountByUserName(groupName), getGroupMembers(groupName))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
||||||
val groupName = params("groupName")
|
defining(params("groupName"), form.memberNames.map(_.split(",").toList).getOrElse(Nil)){ case (groupName, memberNames) =>
|
||||||
getAccountByUserName(groupName).map { account =>
|
getAccountByUserName(groupName).map { account =>
|
||||||
updateGroup(groupName, form.url)
|
updateGroup(groupName, form.url)
|
||||||
|
updateGroupMembers(form.groupName, memberNames)
|
||||||
|
|
||||||
val memberNames = form.memberNames.map(_.split(",").toList).getOrElse(Nil)
|
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
updateGroupMembers(form.groupName, memberNames)
|
removeCollaborators(form.groupName, repositoryName)
|
||||||
|
memberNames.foreach { userName =>
|
||||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
addCollaborator(form.groupName, repositoryName, userName)
|
||||||
removeCollaborators(form.groupName, repositoryName)
|
}
|
||||||
memberNames.foreach { userName =>
|
|
||||||
addCollaborator(form.groupName, repositoryName, userName)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||||
redirect("/admin/users")
|
redirect("/admin/users")
|
||||||
|
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/_usercheck")(adminOnly {
|
post("/admin/users/_usercheck")(adminOnly {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package app
|
|||||||
import service._
|
import service._
|
||||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, JGitUtil, StringUtil}
|
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, JGitUtil, StringUtil}
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
|
import util.ControlUtil._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
class WikiController extends WikiControllerBase
|
class WikiController extends WikiControllerBase
|
||||||
with WikiService with RepositoryService with AccountService with ActivityService
|
with WikiService with RepositoryService with AccountService with ActivityService
|
||||||
@@ -40,13 +42,13 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||||
wiki.html.page(pageName, page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
wiki.html.page(pageName, page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${pageName}/_edit") // TODO URLEncode
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
|
||||||
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
||||||
case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository)
|
case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository)
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound
|
||||||
@@ -58,7 +60,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
val commitId = params("commitId").split("\\.\\.\\.")
|
val commitId = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
wiki.html.compare(Some(pageName), JGitUtil.getDiffs(git, commitId(0), commitId(1), true), repository)
|
wiki.html.compare(Some(pageName), JGitUtil.getDiffs(git, commitId(0), commitId(1), true), repository)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -66,7 +68,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
||||||
val commitId = params("commitId").split("\\.\\.\\.")
|
val commitId = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
wiki.html.compare(None, JGitUtil.getDiffs(git, commitId(0), commitId(1), true), repository)
|
wiki.html.compare(None, JGitUtil.getDiffs(git, commitId(0), commitId(1), true), repository)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -120,7 +122,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||||
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master") match {
|
JGitUtil.getCommitLog(git, "master") match {
|
||||||
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
|
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound
|
||||||
@@ -130,8 +132,8 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
||||||
getFileContent(repository.owner, repository.name, multiParams("splat").head).map { content =>
|
getFileContent(repository.owner, repository.name, multiParams("splat").head).map { content =>
|
||||||
contentType = "application/octet-stream"
|
contentType = "application/octet-stream"
|
||||||
content
|
content
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
16
src/main/scala/model/WebHook.scala
Normal file
16
src/main/scala/model/WebHook.scala
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import scala.slick.driver.H2Driver.simple._
|
||||||
|
|
||||||
|
object WebHooks extends Table[WebHook]("WEB_HOOK") with BasicTemplate {
|
||||||
|
def url = column[String]("URL")
|
||||||
|
def * = userName ~ repositoryName ~ url <> (WebHook, WebHook.unapply _)
|
||||||
|
|
||||||
|
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url is url.bind)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class WebHook(
|
||||||
|
userName: String,
|
||||||
|
repositoryName: String,
|
||||||
|
url: String
|
||||||
|
)
|
||||||
@@ -2,21 +2,21 @@ package service
|
|||||||
|
|
||||||
import scala.slick.driver.H2Driver.simple._
|
import scala.slick.driver.H2Driver.simple._
|
||||||
import Database.threadLocalSession
|
import Database.threadLocalSession
|
||||||
|
|
||||||
import model._
|
import model._
|
||||||
|
import util.ControlUtil._
|
||||||
|
|
||||||
trait PullRequestService { self: IssuesService =>
|
trait PullRequestService { self: IssuesService =>
|
||||||
import PullRequestService._
|
import PullRequestService._
|
||||||
|
|
||||||
def getPullRequest(owner: String, repository: String, issueId: Int): Option[(Issue, PullRequest)] = {
|
def getPullRequest(owner: String, repository: String, issueId: Int): Option[(Issue, PullRequest)] =
|
||||||
val issue = getIssue(owner, repository, issueId.toString)
|
defining(getIssue(owner, repository, issueId.toString)){ issue =>
|
||||||
if(issue.isDefined){
|
if(issue.isDefined){
|
||||||
Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId)).firstOption match {
|
Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId)).firstOption match {
|
||||||
case Some(pullreq) => Some((issue.get, pullreq))
|
case Some(pullreq) => Some((issue.get, pullreq))
|
||||||
case None => None
|
case None => None
|
||||||
}
|
}
|
||||||
} else None
|
} else None
|
||||||
}
|
}
|
||||||
|
|
||||||
def getPullRequestCountGroupByUser(closed: Boolean, owner: String, repository: Option[String]): List[PullRequestCount] =
|
def getPullRequestCountGroupByUser(closed: Boolean, owner: String, repository: Option[String]): List[PullRequestCount] =
|
||||||
Query(PullRequests)
|
Query(PullRequests)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package service
|
|||||||
|
|
||||||
import util.{FileUtil, StringUtil, JGitUtil}
|
import util.{FileUtil, StringUtil, JGitUtil}
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
|
import util.ControlUtil._
|
||||||
import model.Issue
|
import model.Issue
|
||||||
import org.eclipse.jgit.revwalk.RevWalk
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
import org.eclipse.jgit.treewalk.TreeWalk
|
import org.eclipse.jgit.treewalk.TreeWalk
|
||||||
@@ -9,7 +10,8 @@ import scala.collection.mutable.ListBuffer
|
|||||||
import org.eclipse.jgit.lib.FileMode
|
import org.eclipse.jgit.lib.FileMode
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
trait RepositorySearchService { self: IssuesService =>
|
trait
|
||||||
|
RepositorySearchService { self: IssuesService =>
|
||||||
import RepositorySearchService._
|
import RepositorySearchService._
|
||||||
|
|
||||||
def countIssues(owner: String, repository: String, query: String): Int =
|
def countIssues(owner: String, repository: String, query: String): Int =
|
||||||
@@ -27,12 +29,12 @@ trait RepositorySearchService { self: IssuesService =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
def countFiles(owner: String, repository: String, query: String): Int =
|
def countFiles(owner: String, repository: String, query: String): Int =
|
||||||
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
using(Git.open(getRepositoryDir(owner, repository))){ git =>
|
||||||
if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
|
if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
|
||||||
}
|
}
|
||||||
|
|
||||||
def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] =
|
def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] =
|
||||||
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
using(Git.open(getRepositoryDir(owner, repository))){ git =>
|
||||||
if(JGitUtil.isEmpty(git)){
|
if(JGitUtil.isEmpty(git)){
|
||||||
Nil
|
Nil
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -47,8 +47,10 @@ trait RepositoryService { self: AccountService =>
|
|||||||
Labels .filter(_.byRepository(userName, repositoryName)).delete
|
Labels .filter(_.byRepository(userName, repositoryName)).delete
|
||||||
IssueComments .filter(_.byRepository(userName, repositoryName)).delete
|
IssueComments .filter(_.byRepository(userName, repositoryName)).delete
|
||||||
Issues .filter(_.byRepository(userName, repositoryName)).delete
|
Issues .filter(_.byRepository(userName, repositoryName)).delete
|
||||||
|
PullRequests .filter(_.byRepository(userName, repositoryName)).delete
|
||||||
IssueId .filter(_.byRepository(userName, repositoryName)).delete
|
IssueId .filter(_.byRepository(userName, repositoryName)).delete
|
||||||
Milestones .filter(_.byRepository(userName, repositoryName)).delete
|
Milestones .filter(_.byRepository(userName, repositoryName)).delete
|
||||||
|
WebHooks .filter(_.byRepository(userName, repositoryName)).delete
|
||||||
Repositories .filter(_.byRepository(userName, repositoryName)).delete
|
Repositories .filter(_.byRepository(userName, repositoryName)).delete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +1,76 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
|
import util.ControlUtil._
|
||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
|
|
||||||
trait SystemSettingsService {
|
trait SystemSettingsService {
|
||||||
|
|
||||||
def saveSystemSettings(settings: SystemSettings): Unit = {
|
def saveSystemSettings(settings: SystemSettings): Unit = {
|
||||||
val props = new java.util.Properties()
|
defining(new java.util.Properties()){ props =>
|
||||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||||
props.setProperty(Notification, settings.notification.toString)
|
props.setProperty(Notification, settings.notification.toString)
|
||||||
if(settings.notification) {
|
if(settings.notification) {
|
||||||
settings.smtp.foreach { smtp =>
|
settings.smtp.foreach { smtp =>
|
||||||
props.setProperty(SmtpHost, smtp.host)
|
props.setProperty(SmtpHost, smtp.host)
|
||||||
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
||||||
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
||||||
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
||||||
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
||||||
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
if(settings.ldapAuthentication){
|
||||||
if(settings.ldapAuthentication){
|
settings.ldap.map { ldap =>
|
||||||
settings.ldap.map { ldap =>
|
props.setProperty(LdapHost, ldap.host)
|
||||||
props.setProperty(LdapHost, ldap.host)
|
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
||||||
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
||||||
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
||||||
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
props.setProperty(LdapBaseDN, ldap.baseDN)
|
||||||
props.setProperty(LdapBaseDN, ldap.baseDN)
|
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
|
||||||
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
|
}
|
||||||
}
|
}
|
||||||
|
props.store(new java.io.FileOutputStream(GitBucketConf), null)
|
||||||
}
|
}
|
||||||
props.store(new java.io.FileOutputStream(GitBucketConf), null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def loadSystemSettings(): SystemSettings = {
|
def loadSystemSettings(): SystemSettings = {
|
||||||
val props = new java.util.Properties()
|
defining(new java.util.Properties()){ props =>
|
||||||
if(GitBucketConf.exists){
|
if(GitBucketConf.exists){
|
||||||
props.load(new java.io.FileInputStream(GitBucketConf))
|
props.load(new java.io.FileInputStream(GitBucketConf))
|
||||||
}
|
|
||||||
SystemSettings(
|
|
||||||
getValue(props, AllowAccountRegistration, false),
|
|
||||||
getValue(props, Gravatar, true),
|
|
||||||
getValue(props, Notification, false),
|
|
||||||
if(getValue(props, Notification, false)){
|
|
||||||
Some(Smtp(
|
|
||||||
getValue(props, SmtpHost, ""),
|
|
||||||
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
|
|
||||||
getOptionValue(props, SmtpUser, None),
|
|
||||||
getOptionValue(props, SmtpPassword, None),
|
|
||||||
getOptionValue[Boolean](props, SmtpSsl, None)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
getValue(props, LdapAuthentication, false),
|
|
||||||
if(getValue(props, LdapAuthentication, false)){
|
|
||||||
Some(Ldap(
|
|
||||||
getValue(props, LdapHost, ""),
|
|
||||||
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
|
|
||||||
getOptionValue(props, LdapBindDN, None),
|
|
||||||
getOptionValue(props, LdapBindPassword, None),
|
|
||||||
getValue(props, LdapBaseDN, ""),
|
|
||||||
getValue(props, LdapUserNameAttribute, ""),
|
|
||||||
getValue(props, LdapMailAddressAttribute, "")))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
)
|
SystemSettings(
|
||||||
|
getValue(props, AllowAccountRegistration, false),
|
||||||
|
getValue(props, Gravatar, true),
|
||||||
|
getValue(props, Notification, false),
|
||||||
|
if(getValue(props, Notification, false)){
|
||||||
|
Some(Smtp(
|
||||||
|
getValue(props, SmtpHost, ""),
|
||||||
|
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
|
||||||
|
getOptionValue(props, SmtpUser, None),
|
||||||
|
getOptionValue(props, SmtpPassword, None),
|
||||||
|
getOptionValue[Boolean](props, SmtpSsl, None)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
getValue(props, LdapAuthentication, false),
|
||||||
|
if(getValue(props, LdapAuthentication, false)){
|
||||||
|
Some(Ldap(
|
||||||
|
getValue(props, LdapHost, ""),
|
||||||
|
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
|
||||||
|
getOptionValue(props, LdapBindDN, None),
|
||||||
|
getOptionValue(props, LdapBindPassword, None),
|
||||||
|
getValue(props, LdapBaseDN, ""),
|
||||||
|
getValue(props, LdapUserNameAttribute, ""),
|
||||||
|
getValue(props, LdapMailAddressAttribute, "")))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -119,23 +122,23 @@ object SystemSettingsService {
|
|||||||
private val LdapUserNameAttribute = "ldap.username_attribute"
|
private val LdapUserNameAttribute = "ldap.username_attribute"
|
||||||
private val LdapMailAddressAttribute = "ldap.mail_attribute"
|
private val LdapMailAddressAttribute = "ldap.mail_attribute"
|
||||||
|
|
||||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =
|
||||||
val value = props.getProperty(key)
|
defining(props.getProperty(key)){ value =>
|
||||||
if(value == null || value.isEmpty) default
|
if(value == null || value.isEmpty) default
|
||||||
else convertType(value).asInstanceOf[A]
|
else convertType(value).asInstanceOf[A]
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
|
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] =
|
||||||
val value = props.getProperty(key)
|
defining(props.getProperty(key)){ value =>
|
||||||
if(value == null || value.isEmpty) default
|
if(value == null || value.isEmpty) default
|
||||||
else Some(convertType(value)).asInstanceOf[Option[A]]
|
else Some(convertType(value)).asInstanceOf[Option[A]]
|
||||||
}
|
}
|
||||||
|
|
||||||
private def convertType[A: ClassTag](value: String) = {
|
private def convertType[A: ClassTag](value: String) =
|
||||||
val c = implicitly[ClassTag[A]].runtimeClass
|
defining(implicitly[ClassTag[A]].runtimeClass){ c =>
|
||||||
if(c == classOf[Boolean]) value.toBoolean
|
if(c == classOf[Boolean]) value.toBoolean
|
||||||
else if(c == classOf[Int]) value.toInt
|
else if(c == classOf[Int]) value.toInt
|
||||||
else value
|
else value
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
145
src/main/scala/service/WebHookService.scala
Normal file
145
src/main/scala/service/WebHookService.scala
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import scala.slick.driver.H2Driver.simple._
|
||||||
|
import Database.threadLocalSession
|
||||||
|
|
||||||
|
import model._
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import service.RepositoryService.RepositoryInfo
|
||||||
|
import util.JGitUtil
|
||||||
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
import util.JGitUtil.CommitInfo
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.apache.http.message.BasicNameValuePair
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity
|
||||||
|
import org.apache.http.protocol.HTTP
|
||||||
|
import org.apache.http.NameValuePair
|
||||||
|
|
||||||
|
trait WebHookService {
|
||||||
|
import WebHookService._
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(classOf[WebHookService])
|
||||||
|
|
||||||
|
def getWebHookURLs(owner: String, repository: String): List[WebHook] =
|
||||||
|
Query(WebHooks).filter(_.byRepository(owner, repository)).sortBy(_.url).list
|
||||||
|
|
||||||
|
def addWebHookURL(owner: String, repository: String, url :String): Unit =
|
||||||
|
WebHooks.insert(WebHook(owner, repository, url))
|
||||||
|
|
||||||
|
def deleteWebHookURL(owner: String, repository: String, url :String): Unit =
|
||||||
|
Query(WebHooks).filter(_.byPrimaryKey(owner, repository, url)).delete
|
||||||
|
|
||||||
|
def callWebHook(owner: String, repository: String, payload: WebHookPayload): Unit = {
|
||||||
|
import org.json4s._
|
||||||
|
import org.json4s.jackson.Serialization
|
||||||
|
import org.json4s.jackson.Serialization.{read, write}
|
||||||
|
import org.apache.http.client.methods.HttpPost
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient
|
||||||
|
import scala.concurrent._
|
||||||
|
import ExecutionContext.Implicits.global
|
||||||
|
|
||||||
|
logger.debug("start callWebHook")
|
||||||
|
implicit val formats = Serialization.formats(NoTypeHints)
|
||||||
|
|
||||||
|
val webHookURLs = getWebHookURLs(owner, repository)
|
||||||
|
|
||||||
|
if(webHookURLs.nonEmpty){
|
||||||
|
val json = write(payload)
|
||||||
|
val httpClient = new DefaultHttpClient()
|
||||||
|
|
||||||
|
webHookURLs.foreach { webHookUrl =>
|
||||||
|
val f = future {
|
||||||
|
logger.debug(s"start web hook invocation for ${webHookUrl}")
|
||||||
|
val httpPost = new HttpPost(webHookUrl.url)
|
||||||
|
|
||||||
|
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
||||||
|
params.add(new BasicNameValuePair("payload", json))
|
||||||
|
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"))
|
||||||
|
|
||||||
|
httpClient.execute(httpPost)
|
||||||
|
httpPost.releaseConnection()
|
||||||
|
logger.debug(s"end web hook invocation for ${webHookUrl}")
|
||||||
|
}
|
||||||
|
f.onSuccess {
|
||||||
|
case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}")
|
||||||
|
}
|
||||||
|
f.onFailure {
|
||||||
|
case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("end callWebHook")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object WebHookService {
|
||||||
|
|
||||||
|
case class WebHookPayload(
|
||||||
|
ref: String,
|
||||||
|
commits: List[WebHookCommit],
|
||||||
|
repository: WebHookRepository)
|
||||||
|
|
||||||
|
object WebHookPayload {
|
||||||
|
def apply(git: Git, refName: String, repositoryInfo: RepositoryInfo,
|
||||||
|
commits: List[CommitInfo], repositoryOwner: Account): WebHookPayload =
|
||||||
|
WebHookPayload(
|
||||||
|
refName,
|
||||||
|
commits.map { commit =>
|
||||||
|
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||||
|
val commitUrl = repositoryInfo.url.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/commit/" + commit.id
|
||||||
|
|
||||||
|
WebHookCommit(
|
||||||
|
id = commit.id,
|
||||||
|
message = commit.fullMessage,
|
||||||
|
timestamp = commit.time.toString,
|
||||||
|
url = commitUrl,
|
||||||
|
added = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.ADD) => x.newPath },
|
||||||
|
removed = diffs._1.collect { case x if(x.changeType == DiffEntry.ChangeType.DELETE) => x.oldPath },
|
||||||
|
modified = diffs._1.collect { case x if(x.changeType != DiffEntry.ChangeType.ADD &&
|
||||||
|
x.changeType != DiffEntry.ChangeType.DELETE) => x.newPath },
|
||||||
|
author = WebHookUser(
|
||||||
|
name = commit.committer,
|
||||||
|
email = commit.mailAddress
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.toList,
|
||||||
|
WebHookRepository(
|
||||||
|
name = repositoryInfo.name,
|
||||||
|
url = repositoryInfo.url,
|
||||||
|
description = repositoryInfo.repository.description.getOrElse(""),
|
||||||
|
watchers = 0,
|
||||||
|
forks = repositoryInfo.forkedCount,
|
||||||
|
`private` = repositoryInfo.repository.isPrivate,
|
||||||
|
owner = WebHookUser(
|
||||||
|
name = repositoryOwner.userName,
|
||||||
|
email = repositoryOwner.mailAddress
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class WebHookCommit(
|
||||||
|
id: String,
|
||||||
|
message: String,
|
||||||
|
timestamp: String,
|
||||||
|
url: String,
|
||||||
|
added: List[String],
|
||||||
|
removed: List[String],
|
||||||
|
modified: List[String],
|
||||||
|
author: WebHookUser)
|
||||||
|
|
||||||
|
case class WebHookRepository(
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
description: String,
|
||||||
|
watchers: Int,
|
||||||
|
forks: Int,
|
||||||
|
`private`: Boolean,
|
||||||
|
owner: WebHookUser)
|
||||||
|
|
||||||
|
case class WebHookUser(
|
||||||
|
name: String,
|
||||||
|
email: String)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import java.util.Date
|
|||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import util.{Directory, JGitUtil, LockUtil}
|
import util.{Directory, JGitUtil, LockUtil}
|
||||||
|
import util.ControlUtil._
|
||||||
|
|
||||||
object WikiService {
|
object WikiService {
|
||||||
|
|
||||||
@@ -33,40 +34,40 @@ object WikiService {
|
|||||||
trait WikiService {
|
trait WikiService {
|
||||||
import WikiService._
|
import WikiService._
|
||||||
|
|
||||||
def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit = {
|
def createWikiRepository(loginAccount: model.Account, owner: String, repository: String): Unit =
|
||||||
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
||||||
val dir = Directory.getWikiRepositoryDir(owner, repository)
|
defining(Directory.getWikiRepositoryDir(owner, repository)){ dir =>
|
||||||
if(!dir.exists){
|
if(!dir.exists){
|
||||||
try {
|
try {
|
||||||
JGitUtil.initRepository(dir)
|
JGitUtil.initRepository(dir)
|
||||||
saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit")
|
saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit")
|
||||||
} finally {
|
} finally {
|
||||||
// once delete cloned repository because initial cloned repository does not have 'branch.master.merge'
|
// once delete cloned repository because initial cloned repository does not have 'branch.master.merge'
|
||||||
FileUtils.deleteDirectory(Directory.getWikiWorkDir(owner, repository))
|
FileUtils.deleteDirectory(Directory.getWikiWorkDir(owner, repository))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the wiki page.
|
* Returns the wiki page.
|
||||||
*/
|
*/
|
||||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||||
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||||
if(!JGitUtil.isEmpty(git)){
|
optionIf(!JGitUtil.isEmpty(git)){
|
||||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
||||||
WikiPageInfo(file.name, new String(git.getRepository.open(file.id).getBytes, "UTF-8"), file.committer, file.time)
|
WikiPageInfo(file.name, new String(git.getRepository.open(file.id).getBytes, "UTF-8"), file.committer, file.time)
|
||||||
}
|
}
|
||||||
} else None
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the content of the specified file.
|
* Returns the content of the specified file.
|
||||||
*/
|
*/
|
||||||
def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] = {
|
def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] =
|
||||||
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||||
if(!JGitUtil.isEmpty(git)){
|
optionIf(!JGitUtil.isEmpty(git)){
|
||||||
val index = path.lastIndexOf('/')
|
val index = path.lastIndexOf('/')
|
||||||
val parentPath = if(index < 0) "." else path.substring(0, index)
|
val parentPath = if(index < 0) "." else path.substring(0, index)
|
||||||
val fileName = if(index < 0) path else path.substring(index + 1)
|
val fileName = if(index < 0) path else path.substring(index + 1)
|
||||||
@@ -74,15 +75,14 @@ trait WikiService {
|
|||||||
JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file =>
|
JGitUtil.getFileList(git, "master", parentPath).find(_.name == fileName).map { file =>
|
||||||
git.getRepository.open(file.id).getBytes
|
git.getRepository.open(file.id).getBytes
|
||||||
}
|
}
|
||||||
} else None
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of wiki page names.
|
* Returns the list of wiki page names.
|
||||||
*/
|
*/
|
||||||
def getWikiPageList(owner: String, repository: String): List[String] = {
|
def getWikiPageList(owner: String, repository: String): List[String] = {
|
||||||
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||||
JGitUtil.getFileList(git, "master", ".")
|
JGitUtil.getFileList(git, "master", ".")
|
||||||
.filter(_.name.endsWith(".md"))
|
.filter(_.name.endsWith(".md"))
|
||||||
.map(_.name.replaceFirst("\\.md$", ""))
|
.map(_.name.replaceFirst("\\.md$", ""))
|
||||||
@@ -97,36 +97,31 @@ trait WikiService {
|
|||||||
content: String, committer: model.Account, message: String): Option[String] = {
|
content: String, committer: model.Account, message: String): Option[String] = {
|
||||||
|
|
||||||
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
||||||
// clone working copy
|
defining(Directory.getWikiWorkDir(owner, repository)){ workDir =>
|
||||||
val workDir = Directory.getWikiWorkDir(owner, repository)
|
// clone working copy
|
||||||
cloneOrPullWorkingCopy(workDir, owner, repository)
|
cloneOrPullWorkingCopy(workDir, owner, repository)
|
||||||
|
|
||||||
// write as file
|
// write as file
|
||||||
JGitUtil.withGit(workDir){ git =>
|
using(Git.open(workDir)){ git =>
|
||||||
val file = new File(workDir, newPageName + ".md")
|
defining(new File(workDir, newPageName + ".md")){ file =>
|
||||||
val added = if(!file.exists || FileUtils.readFileToString(file, "UTF-8") != content){
|
val added = executeIf(!file.exists || FileUtils.readFileToString(file, "UTF-8") != content){
|
||||||
FileUtils.writeStringToFile(file, content, "UTF-8")
|
FileUtils.writeStringToFile(file, content, "UTF-8")
|
||||||
git.add.addFilepattern(file.getName).call
|
git.add.addFilepattern(file.getName).call
|
||||||
true
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete file
|
// delete file
|
||||||
val deleted = if(currentPageName != "" && currentPageName != newPageName){
|
val deleted = executeIf(currentPageName != "" && currentPageName != newPageName){
|
||||||
git.rm.addFilepattern(currentPageName + ".md").call
|
git.rm.addFilepattern(currentPageName + ".md").call
|
||||||
true
|
}
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit and push
|
// commit and push
|
||||||
if(added || deleted){
|
optionIf(added || deleted){
|
||||||
val commit = git.commit.setCommitter(committer.userName, committer.mailAddress).setMessage(message).call
|
defining(git.commit.setCommitter(committer.userName, committer.mailAddress).setMessage(message).call){ commit =>
|
||||||
git.push.call
|
git.push.call
|
||||||
Some(commit.getName)
|
Some(commit.getName)
|
||||||
} else {
|
}
|
||||||
None
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,35 +133,34 @@ trait WikiService {
|
|||||||
def deleteWikiPage(owner: String, repository: String, pageName: String,
|
def deleteWikiPage(owner: String, repository: String, pageName: String,
|
||||||
committer: String, mailAddress: String, message: String): Unit = {
|
committer: String, mailAddress: String, message: String): Unit = {
|
||||||
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
||||||
// clone working copy
|
defining(Directory.getWikiWorkDir(owner, repository)){ workDir =>
|
||||||
val workDir = Directory.getWikiWorkDir(owner, repository)
|
// clone working copy
|
||||||
cloneOrPullWorkingCopy(workDir, owner, repository)
|
cloneOrPullWorkingCopy(workDir, owner, repository)
|
||||||
|
|
||||||
// delete file
|
// delete file
|
||||||
new File(workDir, pageName + ".md").delete
|
new File(workDir, pageName + ".md").delete
|
||||||
|
|
||||||
JGitUtil.withGit(workDir){ git =>
|
using(Git.open(workDir)){ git =>
|
||||||
git.rm.addFilepattern(pageName + ".md").call
|
git.rm.addFilepattern(pageName + ".md").call
|
||||||
|
|
||||||
// commit and push
|
// commit and push
|
||||||
git.commit.setAuthor(committer, mailAddress).setMessage(message).call
|
git.commit.setAuthor(committer, mailAddress).setMessage(message).call
|
||||||
git.push.call
|
git.push.call
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = {
|
private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = {
|
||||||
if(!workDir.exists){
|
if(!workDir.exists){
|
||||||
val git =
|
Git.cloneRepository
|
||||||
Git.cloneRepository
|
.setURI(Directory.getWikiRepositoryDir(owner, repository).toURI.toString)
|
||||||
.setURI(Directory.getWikiRepositoryDir(owner, repository).toURI.toString)
|
.setDirectory(workDir)
|
||||||
.setDirectory(workDir)
|
.call
|
||||||
.call
|
.getRepository
|
||||||
git.getRepository.close // close .git resources.
|
.close
|
||||||
} else {
|
} else using(Git.open(workDir)){ git =>
|
||||||
JGitUtil.withGit(workDir){ git =>
|
git.pull.call
|
||||||
git.pull.call
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import org.apache.commons.io.FileUtils
|
|||||||
import javax.servlet.ServletContextEvent
|
import javax.servlet.ServletContextEvent
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import util.Directory
|
import util.Directory._
|
||||||
|
import util.ControlUtil._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
object AutoUpdate {
|
object AutoUpdate {
|
||||||
|
|
||||||
@@ -26,15 +28,14 @@ object AutoUpdate {
|
|||||||
*/
|
*/
|
||||||
def update(conn: Connection): Unit = {
|
def update(conn: Connection): Unit = {
|
||||||
val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
|
val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
|
||||||
val in = Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)
|
|
||||||
if(in != null){
|
using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in =>
|
||||||
val sql = IOUtils.toString(in, "UTF-8")
|
if(in != null){
|
||||||
val stmt = conn.createStatement()
|
val sql = IOUtils.toString(in, "UTF-8")
|
||||||
try {
|
using(conn.createStatement()){ stmt =>
|
||||||
logger.debug(sqlPath + "=" + sql)
|
logger.debug(sqlPath + "=" + sql)
|
||||||
stmt.executeUpdate(sql)
|
stmt.executeUpdate(sql)
|
||||||
} finally {
|
}
|
||||||
stmt.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,22 +50,24 @@ object AutoUpdate {
|
|||||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||||
*/
|
*/
|
||||||
val versions = Seq(
|
val versions = Seq(
|
||||||
|
Version(1, 6),
|
||||||
Version(1, 5),
|
Version(1, 5),
|
||||||
Version(1, 4),
|
Version(1, 4),
|
||||||
new Version(1, 3){
|
new Version(1, 3){
|
||||||
override def update(conn: Connection): Unit = {
|
override def update(conn: Connection): Unit = {
|
||||||
super.update(conn)
|
super.update(conn)
|
||||||
// Fix wiki repository configuration
|
// Fix wiki repository configuration
|
||||||
val rs = conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")
|
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
|
||||||
while(rs.next){
|
while(rs.next){
|
||||||
val wikidir = Directory.getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))
|
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
||||||
val repository = org.eclipse.jgit.api.Git.open(wikidir).getRepository
|
defining(git.getRepository.getConfig){ config =>
|
||||||
val config = repository.getConfig
|
if(!config.getBoolean("http", "receivepack", false)){
|
||||||
if(!config.getBoolean("http", "receivepack", false)){
|
config.setBoolean("http", null, "receivepack", true)
|
||||||
config.setBoolean("http", null, "receivepack", true)
|
config.save
|
||||||
config.save
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
repository.close
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -81,7 +84,7 @@ object AutoUpdate {
|
|||||||
/**
|
/**
|
||||||
* The version file (GITBUCKET_HOME/version).
|
* The version file (GITBUCKET_HOME/version).
|
||||||
*/
|
*/
|
||||||
val versionFile = new File(Directory.GitBucketHome, "version")
|
val versionFile = new File(GitBucketHome, "version")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current version from the version file.
|
* Returns the current version from the version file.
|
||||||
@@ -112,27 +115,29 @@ class AutoUpdateListener extends org.h2.server.web.DbStarter {
|
|||||||
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
|
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
|
||||||
|
|
||||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||||
event.getServletContext.setInitParameter("db.url", s"jdbc:h2:${Directory.DatabaseHome}")
|
event.getServletContext.setInitParameter("db.url", s"jdbc:h2:${DatabaseHome}")
|
||||||
super.contextInitialized(event)
|
super.contextInitialized(event)
|
||||||
logger.debug("H2 started")
|
logger.debug("H2 started")
|
||||||
|
|
||||||
logger.debug("Start schema update")
|
logger.debug("Start schema update")
|
||||||
val conn = getConnection()
|
defining(getConnection()){ conn =>
|
||||||
try {
|
try {
|
||||||
val currentVersion = getCurrentVersion()
|
defining(getCurrentVersion()){ currentVersion =>
|
||||||
if(currentVersion == headVersion){
|
if(currentVersion == headVersion){
|
||||||
logger.debug("No update")
|
logger.debug("No update")
|
||||||
} else {
|
} else {
|
||||||
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
|
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
|
||||||
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
logger.debug("Updated from " + currentVersion.versionString + " to " + headVersion.versionString)
|
logger.debug("Updated from " + currentVersion.versionString + " to " + headVersion.versionString)
|
||||||
}
|
}
|
||||||
} catch {
|
}
|
||||||
case ex: Throwable => {
|
} catch {
|
||||||
logger.error("Failed to schema update", ex)
|
case ex: Throwable => {
|
||||||
ex.printStackTrace()
|
logger.error("Failed to schema update", ex)
|
||||||
conn.rollback()
|
ex.printStackTrace()
|
||||||
|
conn.rollback()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.debug("End schema update")
|
logger.debug("End schema update")
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import javax.servlet._
|
|||||||
import javax.servlet.http._
|
import javax.servlet.http._
|
||||||
import service.{SystemSettingsService, AccountService, RepositoryService}
|
import service.{SystemSettingsService, AccountService, RepositoryService}
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import util.Implicits._
|
||||||
|
import util.ControlUtil._
|
||||||
|
import util.Keys
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides BASIC Authentication for [[servlet.GitRepositoryServlet]].
|
* Provides BASIC Authentication for [[servlet.GitRepositoryServlet]].
|
||||||
@@ -25,29 +28,30 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
defining(request.paths){ case Array(_, repositoryOwner, repositoryName, _*) =>
|
||||||
val repositoryOwner = paths(2)
|
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
|
||||||
val repositoryName = paths(3).replaceFirst("\\.git$", "")
|
case Some(repository) => {
|
||||||
|
if(!request.getRequestURI.endsWith("/git-receive-pack") &&
|
||||||
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki", ""), "") match {
|
|
||||||
case Some(repository) => {
|
|
||||||
if(!request.getRequestURI.endsWith("/git-receive-pack") &&
|
|
||||||
!"service=git-receive-pack".equals(request.getQueryString) && !repository.repository.isPrivate){
|
!"service=git-receive-pack".equals(request.getQueryString) && !repository.repository.isPrivate){
|
||||||
chain.doFilter(req, wrappedResponse)
|
chain.doFilter(req, wrappedResponse)
|
||||||
} else {
|
} else {
|
||||||
request.getHeader("Authorization") match {
|
request.getHeader("Authorization") match {
|
||||||
case null => requireAuth(response)
|
case null => requireAuth(response)
|
||||||
case auth => decodeAuthHeader(auth).split(":") match {
|
case auth => decodeAuthHeader(auth).split(":") match {
|
||||||
case Array(username, password) if(isWritableUser(username, password, repository)) => {
|
case Array(username, password) if(isWritableUser(username, password, repository)) => {
|
||||||
request.setAttribute("USER_NAME", username)
|
request.setAttribute(Keys.Request.UserName, username)
|
||||||
chain.doFilter(req, wrappedResponse)
|
chain.doFilter(req, wrappedResponse)
|
||||||
|
}
|
||||||
|
case _ => requireAuth(response)
|
||||||
}
|
}
|
||||||
case _ => requireAuth(response)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case None => {
|
||||||
|
logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.")
|
||||||
|
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case None => response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case ex: Exception => {
|
case ex: Exception => {
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ import org.slf4j.LoggerFactory
|
|||||||
import javax.servlet.ServletConfig
|
import javax.servlet.ServletConfig
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import util.{JGitUtil, Directory}
|
import util.{Keys, JGitUtil, Directory}
|
||||||
|
import util.ControlUtil._
|
||||||
|
import util.Implicits._
|
||||||
import service._
|
import service._
|
||||||
|
import WebHookService._
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Git repository via HTTP.
|
* Provides Git repository via HTTP.
|
||||||
@@ -51,31 +55,34 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
|
|
||||||
override def create(request: HttpServletRequest, db: Repository): ReceivePack = {
|
override def create(request: HttpServletRequest, db: Repository): ReceivePack = {
|
||||||
val receivePack = new ReceivePack(db)
|
val receivePack = new ReceivePack(db)
|
||||||
val userName = request.getAttribute("USER_NAME").asInstanceOf[String]
|
val userName = request.getAttribute(Keys.Request.UserName).asInstanceOf[String]
|
||||||
|
|
||||||
logger.debug("requestURI: " + request.getRequestURI)
|
logger.debug("requestURI: " + request.getRequestURI)
|
||||||
logger.debug("userName:" + userName)
|
logger.debug("userName:" + userName)
|
||||||
|
|
||||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
defining(request.paths){ paths =>
|
||||||
val owner = paths(2)
|
val owner = paths(1)
|
||||||
val repository = paths(3).replaceFirst("\\.git$", "")
|
val repository = paths(2).replaceFirst("\\.git$", "")
|
||||||
|
val baseURL = request.getRequestURL.toString.replaceFirst("/git/.*", "")
|
||||||
logger.debug("repository:" + owner + "/" + repository)
|
|
||||||
|
|
||||||
receivePack.setPostReceiveHook(new CommitLogHook(owner, repository, userName))
|
logger.debug("repository:" + owner + "/" + repository)
|
||||||
receivePack
|
logger.debug("baseURL:" + baseURL)
|
||||||
|
|
||||||
|
receivePack.setPostReceiveHook(new CommitLogHook(owner, repository, userName, baseURL))
|
||||||
|
receivePack
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
class CommitLogHook(owner: String, repository: String, userName: String) extends PostReceiveHook
|
class CommitLogHook(owner: String, repository: String, userName: String, baseURL: String) extends PostReceiveHook
|
||||||
with RepositoryService with AccountService with IssuesService with ActivityService {
|
with RepositoryService with AccountService with IssuesService with ActivityService with WebHookService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||||
|
|
||||||
def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||||
JGitUtil.withGit(Directory.getRepositoryDir(owner, repository)) { git =>
|
using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||||
commands.asScala.foreach { command =>
|
commands.asScala.foreach { command =>
|
||||||
val commits = JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
|
val commits = JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
|
||||||
val refName = command.getRefName.split("/")
|
val refName = command.getRefName.split("/")
|
||||||
@@ -110,6 +117,16 @@ class CommitLogHook(owner: String, repository: String, userName: String) extends
|
|||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// call web hook
|
||||||
|
val payload = WebHookPayload(
|
||||||
|
git,
|
||||||
|
command.getRefName,
|
||||||
|
getRepository(owner, repository, baseURL).get,
|
||||||
|
newCommits,
|
||||||
|
getAccountByUserName(owner).get)
|
||||||
|
|
||||||
|
callWebHook(owner, repository, payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update repository last modified time.
|
// update repository last modified time.
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ class TransactionFilter extends Filter {
|
|||||||
chain.doFilter(req, res)
|
chain.doFilter(req, res)
|
||||||
} else {
|
} else {
|
||||||
Database(req.getServletContext) withTransaction {
|
Database(req.getServletContext) withTransaction {
|
||||||
logger.debug("TODO begin transaction")
|
logger.debug("begin transaction")
|
||||||
chain.doFilter(req, res)
|
chain.doFilter(req, res)
|
||||||
logger.debug("TODO end transaction")
|
logger.debug("end transaction")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package util
|
|||||||
import app.ControllerBase
|
import app.ControllerBase
|
||||||
import service._
|
import service._
|
||||||
import RepositoryService.RepositoryInfo
|
import RepositoryService.RepositoryInfo
|
||||||
|
import util.Implicits._
|
||||||
|
import util.ControlUtil._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows only oneself and administrators.
|
* Allows only oneself and administrators.
|
||||||
@@ -13,11 +15,12 @@ trait OneselfAuthenticator { self: ControllerBase =>
|
|||||||
|
|
||||||
private def authenticate(action: => Any) = {
|
private def authenticate(action: => Any) = {
|
||||||
{
|
{
|
||||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
defining(request.paths){ paths =>
|
||||||
context.loginAccount match {
|
context.loginAccount match {
|
||||||
case Some(x) if(x.isAdmin) => action
|
case Some(x) if(x.isAdmin) => action
|
||||||
case Some(x) if(paths(1) == x.userName) => action
|
case Some(x) if(paths(0) == x.userName) => action
|
||||||
case _ => Unauthorized()
|
case _ => Unauthorized()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,14 +35,15 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService =>
|
|||||||
|
|
||||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||||
{
|
{
|
||||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
defining(request.paths){ paths =>
|
||||||
getRepository(paths(1), paths(2), baseUrl).map { repository =>
|
getRepository(paths(0), paths(1), baseUrl).map { repository =>
|
||||||
context.loginAccount match {
|
context.loginAccount match {
|
||||||
case Some(x) if(x.isAdmin) => action(repository)
|
case Some(x) if(x.isAdmin) => action(repository)
|
||||||
case Some(x) if(repository.owner == x.userName) => action(repository)
|
case Some(x) if(repository.owner == x.userName) => action(repository)
|
||||||
case _ => Unauthorized()
|
case _ => Unauthorized()
|
||||||
}
|
}
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,15 +91,16 @@ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService =
|
|||||||
|
|
||||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||||
{
|
{
|
||||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
defining(request.paths){ paths =>
|
||||||
getRepository(paths(1), paths(2), baseUrl).map { repository =>
|
getRepository(paths(0), paths(1), baseUrl).map { repository =>
|
||||||
context.loginAccount match {
|
context.loginAccount match {
|
||||||
case Some(x) if(x.isAdmin) => action(repository)
|
case Some(x) if(x.isAdmin) => action(repository)
|
||||||
case Some(x) if(paths(1) == x.userName) => action(repository)
|
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||||
case Some(x) if(getCollaborators(paths(1), paths(2)).contains(x.userName)) => action(repository)
|
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||||
case _ => Unauthorized()
|
case _ => Unauthorized()
|
||||||
}
|
}
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,19 +114,20 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
|
|||||||
|
|
||||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||||
{
|
{
|
||||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
defining(request.paths){ paths =>
|
||||||
getRepository(paths(1), paths(2), baseUrl).map { repository =>
|
getRepository(paths(0), paths(1), baseUrl).map { repository =>
|
||||||
if(!repository.repository.isPrivate){
|
if(!repository.repository.isPrivate){
|
||||||
action(repository)
|
action(repository)
|
||||||
} else {
|
} else {
|
||||||
context.loginAccount match {
|
context.loginAccount match {
|
||||||
case Some(x) if(x.isAdmin) => action(repository)
|
case Some(x) if(x.isAdmin) => action(repository)
|
||||||
case Some(x) if(paths(1) == x.userName) => action(repository)
|
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||||
case Some(x) if(getCollaborators(paths(1), paths(2)).contains(x.userName)) => action(repository)
|
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||||
case _ => Unauthorized()
|
case _ => Unauthorized()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} getOrElse NotFound()
|
||||||
} getOrElse NotFound()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,16 +141,17 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =
|
|||||||
|
|
||||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||||
{
|
{
|
||||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
defining(request.paths){ paths =>
|
||||||
getRepository(paths(1), paths(2), baseUrl).map { repository =>
|
getRepository(paths(0), paths(1), baseUrl).map { repository =>
|
||||||
context.loginAccount match {
|
context.loginAccount match {
|
||||||
case Some(x) if(x.isAdmin) => action(repository)
|
case Some(x) if(x.isAdmin) => action(repository)
|
||||||
case Some(x) if(!repository.repository.isPrivate) => action(repository)
|
case Some(x) if(!repository.repository.isPrivate) => action(repository)
|
||||||
case Some(x) if(paths(1) == x.userName) => action(repository)
|
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||||
case Some(x) if(getCollaborators(paths(1), paths(2)).contains(x.userName)) => action(repository)
|
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||||
case _ => Unauthorized()
|
case _ => Unauthorized()
|
||||||
}
|
}
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
src/main/scala/util/ControlUtil.scala
Normal file
48
src/main/scala/util/ControlUtil.scala
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides control facilities.
|
||||||
|
*/
|
||||||
|
object ControlUtil {
|
||||||
|
|
||||||
|
def defining[A, B](value: A)(f: A => B): B = f(value)
|
||||||
|
|
||||||
|
def using[A <% { def close(): Unit }, B](resource: A)(f: A => B): B =
|
||||||
|
try f(resource) finally {
|
||||||
|
if(resource != null){
|
||||||
|
try {
|
||||||
|
resource.close()
|
||||||
|
} catch {
|
||||||
|
case e: Throwable => // ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def using[T](git: Git)(f: Git => T): T =
|
||||||
|
try f(git) finally git.getRepository.close
|
||||||
|
|
||||||
|
def using[T](git1: Git, git2: Git)(f: (Git, Git) => T): T =
|
||||||
|
try f(git1, git2) finally {
|
||||||
|
git1.getRepository.close
|
||||||
|
git2.getRepository.close
|
||||||
|
}
|
||||||
|
|
||||||
|
def using[T](revWalk: RevWalk)(f: RevWalk => T): T =
|
||||||
|
try f(revWalk) finally revWalk.release()
|
||||||
|
|
||||||
|
def using[T](treeWalk: TreeWalk)(f: TreeWalk => T): T =
|
||||||
|
try f(treeWalk) finally treeWalk.release()
|
||||||
|
|
||||||
|
def executeIf(condition: => Boolean)(action: => Unit): Boolean =
|
||||||
|
if(condition){
|
||||||
|
action
|
||||||
|
true
|
||||||
|
} else false
|
||||||
|
|
||||||
|
def optionIf[T](condition: => Boolean)(action: => Option[T]): Option[T] =
|
||||||
|
if(condition) action else None
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import util.ControlUtil._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides directories used by GitBucket.
|
* Provides directories used by GitBucket.
|
||||||
@@ -21,17 +22,17 @@ object Directory {
|
|||||||
/**
|
/**
|
||||||
* Repository names of the specified user.
|
* Repository names of the specified user.
|
||||||
*/
|
*/
|
||||||
def getRepositories(owner: String): List[String] = {
|
def getRepositories(owner: String): List[String] =
|
||||||
val dir = new File(s"${RepositoryHome}/${owner}")
|
defining(new File(s"${RepositoryHome}/${owner}")){ dir =>
|
||||||
if(dir.exists){
|
if(dir.exists){
|
||||||
dir.listFiles.filter { file =>
|
dir.listFiles.filter { file =>
|
||||||
file.isDirectory && !file.getName.endsWith(".wiki.git")
|
file.isDirectory && !file.getName.endsWith(".wiki.git")
|
||||||
}.map(_.getName.replaceFirst("\\.git$", "")).toList
|
}.map(_.getName.replaceFirst("\\.git$", "")).toList
|
||||||
} else {
|
} else {
|
||||||
Nil
|
Nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Substance directory of the repository.
|
* Substance directory of the repository.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,19 +4,18 @@ import org.apache.commons.io.{IOUtils, FileUtils}
|
|||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import org.apache.commons.compress.archivers.zip.{ZipArchiveEntry, ZipArchiveOutputStream}
|
import org.apache.commons.compress.archivers.zip.{ZipArchiveEntry, ZipArchiveOutputStream}
|
||||||
|
import util.ControlUtil._
|
||||||
|
|
||||||
object FileUtil {
|
object FileUtil {
|
||||||
|
|
||||||
def getMimeType(name: String): String = {
|
def getMimeType(name: String): String =
|
||||||
val fileNameMap = URLConnection.getFileNameMap()
|
defining(URLConnection.getFileNameMap()){ fileNameMap =>
|
||||||
val mimeType = fileNameMap.getContentTypeFor(name)
|
fileNameMap.getContentTypeFor(name) match {
|
||||||
if(mimeType == null){
|
case null => "application/octet-stream"
|
||||||
"application/octeat-stream"
|
case mimeType => mimeType
|
||||||
} else {
|
}
|
||||||
mimeType
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def isImage(name: String): Boolean = getMimeType(name).startsWith("image/")
|
def isImage(name: String): Boolean = getMimeType(name).startsWith("image/")
|
||||||
|
|
||||||
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
||||||
@@ -36,21 +35,19 @@ object FileUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val out = new ZipArchiveOutputStream(dest)
|
using(new ZipArchiveOutputStream(dest)){ out =>
|
||||||
try {
|
|
||||||
addDirectoryToZip(out, dir, dir.getName)
|
addDirectoryToZip(out, dir, dir.getName)
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(out)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getExtension(name: String): String = {
|
def getFileName(path: String): String = defining(path.lastIndexOf('/')){ i =>
|
||||||
val index = name.lastIndexOf('.')
|
if(i >= 0) path.substring(i + 1) else path
|
||||||
if(index >= 0){
|
|
||||||
name.substring(index + 1)
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getExtension(name: String): String =
|
||||||
|
name.lastIndexOf('.') match {
|
||||||
|
case i if(i >= 0) => name.substring(i + 1)
|
||||||
|
case _ => ""
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import scala.util.matching.Regex
|
import scala.util.matching.Regex
|
||||||
|
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides some usable implicit conversions.
|
* Provides some usable implicit conversions.
|
||||||
@@ -12,7 +13,7 @@ object Implicits {
|
|||||||
def splitWith(condition: (A, A) => Boolean): Seq[Seq[A]] = split(seq)(condition)
|
def splitWith(condition: (A, A) => Boolean): Seq[Seq[A]] = split(seq)(condition)
|
||||||
|
|
||||||
@scala.annotation.tailrec
|
@scala.annotation.tailrec
|
||||||
private def split[A](list: Seq[A], result: Seq[Seq[A]] = Nil)(condition: (A, A) => Boolean): Seq[Seq[A]] = {
|
private def split[A](list: Seq[A], result: Seq[Seq[A]] = Nil)(condition: (A, A) => Boolean): Seq[Seq[A]] =
|
||||||
list match {
|
list match {
|
||||||
case x :: xs => {
|
case x :: xs => {
|
||||||
xs.span(condition(x, _)) match {
|
xs.span(condition(x, _)) match {
|
||||||
@@ -21,7 +22,6 @@ object Implicits {
|
|||||||
}
|
}
|
||||||
case Nil => result
|
case Nil => result
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit class RichString(value: String){
|
implicit class RichString(value: String){
|
||||||
@@ -43,4 +43,30 @@ object Implicits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implicit class RichRequest(request: HttpServletRequest){
|
||||||
|
|
||||||
|
def paths: Array[String] = request.getRequestURI.substring(request.getContextPath.length + 1).split("/")
|
||||||
|
|
||||||
|
def hasQueryString: Boolean = request.getQueryString != null
|
||||||
|
|
||||||
|
def hasAttribute(name: String): Boolean = request.getAttribute(name) != null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit class RichSession(session: HttpSession){
|
||||||
|
|
||||||
|
def putAndGet[T](key: String, value: T): T = {
|
||||||
|
session.setAttribute(key, value)
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAndRemove[T](key: String): Option[T] = {
|
||||||
|
val value = session.getAttribute(key).asInstanceOf[T]
|
||||||
|
if(value == null){
|
||||||
|
session.removeAttribute(key)
|
||||||
|
}
|
||||||
|
Option(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package util
|
|||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import util.Directory._
|
import util.Directory._
|
||||||
import util.StringUtil._
|
import util.StringUtil._
|
||||||
|
import util.ControlUtil._
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
import org.eclipse.jgit.lib._
|
import org.eclipse.jgit.lib._
|
||||||
import org.eclipse.jgit.revwalk._
|
import org.eclipse.jgit.revwalk._
|
||||||
@@ -68,29 +69,17 @@ object JGitUtil {
|
|||||||
rev.getFullMessage,
|
rev.getFullMessage,
|
||||||
rev.getParents().map(_.name).toList)
|
rev.getParents().map(_.name).toList)
|
||||||
|
|
||||||
val summary = {
|
val summary = defining(fullMessage.trim.indexOf("\n")){ i =>
|
||||||
val i = fullMessage.trim.indexOf("\n")
|
defining(if(i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage){ firstLine =>
|
||||||
val firstLine = if(i >= 0){
|
if(firstLine.length > shortMessage.length) shortMessage else firstLine
|
||||||
fullMessage.trim.substring(0, i).trim
|
|
||||||
} else {
|
|
||||||
fullMessage
|
|
||||||
}
|
|
||||||
if(firstLine.length > shortMessage.length){
|
|
||||||
shortMessage
|
|
||||||
} else {
|
|
||||||
firstLine
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val description = {
|
val description = defining(fullMessage.trim.indexOf("\n")){ i =>
|
||||||
val i = fullMessage.trim.indexOf("\n")
|
optionIf(i >= 0){
|
||||||
if(i >= 0){
|
|
||||||
Some(fullMessage.trim.substring(i).trim)
|
Some(fullMessage.trim.substring(i).trim)
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String])
|
case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String])
|
||||||
@@ -112,24 +101,6 @@ object JGitUtil {
|
|||||||
*/
|
*/
|
||||||
case class TagInfo(name: String, time: Date, id: String)
|
case class TagInfo(name: String, time: Date, id: String)
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this method to use the Git object.
|
|
||||||
* Repository resources are released certainly after processing.
|
|
||||||
*/
|
|
||||||
def withGit[T](dir: java.io.File)(f: Git => T): T = withGit(Git.open(dir))(f)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this method to use the Git object.
|
|
||||||
* Repository resources are released certainly after processing.
|
|
||||||
*/
|
|
||||||
def withGit[T](git: Git)(f: Git => T): T = {
|
|
||||||
try {
|
|
||||||
f(git)
|
|
||||||
} finally {
|
|
||||||
git.getRepository.close
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns RevCommit from the commit or tag id.
|
* Returns RevCommit from the commit or tag id.
|
||||||
*
|
*
|
||||||
@@ -141,7 +112,7 @@ object JGitUtil {
|
|||||||
val revWalk = new RevWalk(git.getRepository)
|
val revWalk = new RevWalk(git.getRepository)
|
||||||
val revCommit = revWalk.parseAny(objectId) match {
|
val revCommit = revWalk.parseAny(objectId) match {
|
||||||
case r: RevTag => revWalk.parseCommit(r.getObject)
|
case r: RevTag => revWalk.parseCommit(r.getObject)
|
||||||
case _ => revWalk.parseCommit(objectId)
|
case _ => revWalk.parseCommit(objectId)
|
||||||
}
|
}
|
||||||
revWalk.dispose
|
revWalk.dispose
|
||||||
revCommit
|
revCommit
|
||||||
@@ -151,7 +122,7 @@ object JGitUtil {
|
|||||||
* Returns the repository information. It contains branch names and tag names.
|
* Returns the repository information. It contains branch names and tag names.
|
||||||
*/
|
*/
|
||||||
def getRepositoryInfo(owner: String, repository: String, baseUrl: String): RepositoryInfo = {
|
def getRepositoryInfo(owner: String, repository: String, baseUrl: String): RepositoryInfo = {
|
||||||
withGit(getRepositoryDir(owner, repository)){ git =>
|
using(Git.open(getRepositoryDir(owner, repository))){ git =>
|
||||||
try {
|
try {
|
||||||
// get commit count
|
// get commit count
|
||||||
val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(1000).sum
|
val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(1000).sum
|
||||||
@@ -188,45 +159,43 @@ object JGitUtil {
|
|||||||
* @return HTML of the file list
|
* @return HTML of the file list
|
||||||
*/
|
*/
|
||||||
def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = {
|
def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = {
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
|
||||||
val objectId = git.getRepository.resolve(revision)
|
|
||||||
val revCommit = revWalk.parseCommit(objectId)
|
|
||||||
|
|
||||||
val treeWalk = new TreeWalk(git.getRepository)
|
|
||||||
treeWalk.addTree(revCommit.getTree)
|
|
||||||
if(path != "."){
|
|
||||||
treeWalk.setRecursive(true)
|
|
||||||
treeWalk.setFilter(new TreeFilter(){
|
|
||||||
|
|
||||||
var stopRecursive = false
|
|
||||||
|
|
||||||
def include(walker: TreeWalk): Boolean = {
|
|
||||||
val targetPath = walker.getPathString
|
|
||||||
if((path + "/").startsWith(targetPath)){
|
|
||||||
true
|
|
||||||
} else if(targetPath.startsWith(path + "/") && targetPath.substring(path.length + 1).indexOf("/") < 0){
|
|
||||||
stopRecursive = true
|
|
||||||
treeWalk.setRecursive(false)
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def shouldBeRecursive(): Boolean = !stopRecursive
|
|
||||||
|
|
||||||
override def clone: TreeFilter = return this
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
val list = new scala.collection.mutable.ListBuffer[(ObjectId, FileMode, String, String)]
|
val list = new scala.collection.mutable.ListBuffer[(ObjectId, FileMode, String, String)]
|
||||||
|
|
||||||
while (treeWalk.next()) {
|
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||||
list.append((treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getPathString, treeWalk.getNameString))
|
val objectId = git.getRepository.resolve(revision)
|
||||||
|
val revCommit = revWalk.parseCommit(objectId)
|
||||||
|
|
||||||
|
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||||
|
treeWalk.addTree(revCommit.getTree)
|
||||||
|
if(path != "."){
|
||||||
|
treeWalk.setRecursive(true)
|
||||||
|
treeWalk.setFilter(new TreeFilter(){
|
||||||
|
|
||||||
|
var stopRecursive = false
|
||||||
|
|
||||||
|
def include(walker: TreeWalk): Boolean = {
|
||||||
|
val targetPath = walker.getPathString
|
||||||
|
if((path + "/").startsWith(targetPath)){
|
||||||
|
true
|
||||||
|
} else if(targetPath.startsWith(path + "/") && targetPath.substring(path.length + 1).indexOf("/") < 0){
|
||||||
|
stopRecursive = true
|
||||||
|
treeWalk.setRecursive(false)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def shouldBeRecursive(): Boolean = !stopRecursive
|
||||||
|
|
||||||
|
override def clone: TreeFilter = return this
|
||||||
|
})
|
||||||
|
}
|
||||||
|
while (treeWalk.next()) {
|
||||||
|
list.append((treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getPathString, treeWalk.getNameString))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
treeWalk.release
|
|
||||||
revWalk.dispose
|
|
||||||
|
|
||||||
val commits = getLatestCommitFromPaths(git, list.toList.map(_._3), revision)
|
val commits = getLatestCommitFromPaths(git, list.toList.map(_._3), revision)
|
||||||
list.map { case (objectId, fileMode, path, name) =>
|
list.map { case (objectId, fileMode, path, name) =>
|
||||||
@@ -270,25 +239,23 @@ object JGitUtil {
|
|||||||
case _ => (logs, i.hasNext)
|
case _ => (logs, i.hasNext)
|
||||||
}
|
}
|
||||||
|
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||||
val objectId = git.getRepository.resolve(revision)
|
defining(git.getRepository.resolve(revision)){ objectId =>
|
||||||
if(objectId == null){
|
if(objectId == null){
|
||||||
Left(s"${revision} can't be resolved.")
|
Left(s"${revision} can't be resolved.")
|
||||||
} else {
|
} else {
|
||||||
revWalk.markStart(revWalk.parseCommit(objectId))
|
revWalk.markStart(revWalk.parseCommit(objectId))
|
||||||
if(path.nonEmpty){
|
if(path.nonEmpty){
|
||||||
revWalk.setRevFilter(new RevFilter(){
|
revWalk.setRevFilter(new RevFilter(){
|
||||||
def include(walk: RevWalk, commit: RevCommit): Boolean = {
|
def include(walk: RevWalk, commit: RevCommit): Boolean = {
|
||||||
getDiffs(git, commit.getName, false)._1.find(_.newPath == path).nonEmpty
|
getDiffs(git, commit.getName, false)._1.find(_.newPath == path).nonEmpty
|
||||||
|
}
|
||||||
|
override def clone(): RevFilter = this
|
||||||
|
})
|
||||||
}
|
}
|
||||||
override def clone(): RevFilter = this
|
Right(getCommitLog(revWalk.iterator, 0, Nil))
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val commits = getCommitLog(revWalk.iterator, 0, Nil)
|
|
||||||
revWalk.release
|
|
||||||
|
|
||||||
Right(commits)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,13 +275,10 @@ object JGitUtil {
|
|||||||
case false => logs
|
case false => logs
|
||||||
}
|
}
|
||||||
|
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||||
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(begin)))
|
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(begin)))
|
||||||
|
getCommitLog(revWalk.iterator, Nil).reverse
|
||||||
val commits = getCommitLog(revWalk.iterator, Nil)
|
}
|
||||||
revWalk.release
|
|
||||||
|
|
||||||
commits.reverse
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -370,11 +334,8 @@ object JGitUtil {
|
|||||||
if(large == false && FileUtil.isLarge(loader.getSize)){
|
if(large == false && FileUtil.isLarge(loader.getSize)){
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
val db = git.getRepository.getObjectDatabase
|
using(git.getRepository.getObjectDatabase){ db =>
|
||||||
try {
|
|
||||||
Some(db.open(id).getBytes)
|
Some(db.open(id).getBytes)
|
||||||
} finally {
|
|
||||||
db.close
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -392,34 +353,32 @@ object JGitUtil {
|
|||||||
case _ => logs
|
case _ => logs
|
||||||
}
|
}
|
||||||
|
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||||
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(id)))
|
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(id)))
|
||||||
|
val commits = getCommitLog(revWalk.iterator, Nil)
|
||||||
val commits = getCommitLog(revWalk.iterator, Nil)
|
val revCommit = commits(0)
|
||||||
revWalk.release
|
|
||||||
|
|
||||||
val revCommit = commits(0)
|
|
||||||
|
|
||||||
if(commits.length >= 2){
|
|
||||||
// not initial commit
|
|
||||||
val oldCommit = commits(1)
|
|
||||||
(getDiffs(git, oldCommit.getName, id, fetchContent), Some(oldCommit.getName))
|
|
||||||
|
|
||||||
} else {
|
if(commits.length >= 2){
|
||||||
// initial commit
|
// not initial commit
|
||||||
val walk = new TreeWalk(git.getRepository)
|
val oldCommit = commits(1)
|
||||||
walk.addTree(revCommit.getTree)
|
(getDiffs(git, oldCommit.getName, id, fetchContent), Some(oldCommit.getName))
|
||||||
val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]()
|
|
||||||
while(walk.next){
|
} else {
|
||||||
buffer.append((if(!fetchContent){
|
// initial commit
|
||||||
DiffInfo(ChangeType.ADD, null, walk.getPathString, None, None)
|
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||||
} else {
|
treeWalk.addTree(revCommit.getTree)
|
||||||
DiffInfo(ChangeType.ADD, null, walk.getPathString, None,
|
val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]()
|
||||||
JGitUtil.getContent(git, walk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray))
|
while(treeWalk.next){
|
||||||
}))
|
buffer.append((if(!fetchContent){
|
||||||
|
DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None, None)
|
||||||
|
} else {
|
||||||
|
DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None,
|
||||||
|
JGitUtil.getContent(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
(buffer.toList, None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
walk.release
|
|
||||||
(buffer.toList, None)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,67 +406,49 @@ object JGitUtil {
|
|||||||
/**
|
/**
|
||||||
* Returns the list of branch names of the specified commit.
|
* Returns the list of branch names of the specified commit.
|
||||||
*/
|
*/
|
||||||
def getBranchesOfCommit(git: Git, commitId: String): List[String] = {
|
def getBranchesOfCommit(git: Git, commitId: String): List[String] =
|
||||||
val walk = new org.eclipse.jgit.revwalk.RevWalk(git.getRepository)
|
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||||
try {
|
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))){ commit =>
|
||||||
val commit = walk.parseCommit(git.getRepository.resolve(commitId + "^0"))
|
git.getRepository.getAllRefs.entrySet.asScala.filter { e =>
|
||||||
|
(e.getKey.startsWith(Constants.R_HEADS) && revWalk.isMergedInto(commit, revWalk.parseCommit(e.getValue.getObjectId)))
|
||||||
git.getRepository.getAllRefs.entrySet.asScala.filter { e =>
|
}.map { e =>
|
||||||
(e.getKey.startsWith(Constants.R_HEADS) && walk.isMergedInto(commit, walk.parseCommit(e.getValue.getObjectId)))
|
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length)
|
||||||
}.map { e =>
|
}.toList.sorted
|
||||||
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length)
|
}
|
||||||
}.toList.sorted
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
walk.release
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of tags of the specified commit.
|
* Returns the list of tags of the specified commit.
|
||||||
*/
|
*/
|
||||||
def getTagsOfCommit(git: Git, commitId: String): List[String] = {
|
def getTagsOfCommit(git: Git, commitId: String): List[String] =
|
||||||
val walk = new org.eclipse.jgit.revwalk.RevWalk(git.getRepository)
|
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||||
try {
|
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))){ commit =>
|
||||||
val commit = walk.parseCommit(git.getRepository.resolve(commitId + "^0"))
|
git.getRepository.getAllRefs.entrySet.asScala.filter { e =>
|
||||||
|
(e.getKey.startsWith(Constants.R_TAGS) && revWalk.isMergedInto(commit, revWalk.parseCommit(e.getValue.getObjectId)))
|
||||||
git.getRepository.getAllRefs.entrySet.asScala.filter { e =>
|
}.map { e =>
|
||||||
(e.getKey.startsWith(Constants.R_TAGS) && walk.isMergedInto(commit, walk.parseCommit(e.getValue.getObjectId)))
|
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length)
|
||||||
}.map { e =>
|
}.toList.sorted.reverse
|
||||||
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length)
|
}
|
||||||
}.toList.sorted.reverse
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
walk.release
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def initRepository(dir: java.io.File): Unit = {
|
def initRepository(dir: java.io.File): Unit =
|
||||||
val repository = new RepositoryBuilder().setGitDir(dir).setBare.build
|
using(new RepositoryBuilder().setGitDir(dir).setBare.build){ repository =>
|
||||||
try {
|
|
||||||
repository.create
|
repository.create
|
||||||
setReceivePack(repository)
|
setReceivePack(repository)
|
||||||
} finally {
|
|
||||||
repository.close
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def cloneRepository(from: java.io.File, to: java.io.File): Unit = {
|
def cloneRepository(from: java.io.File, to: java.io.File): Unit =
|
||||||
val git = Git.cloneRepository.setURI(from.toURI.toString).setDirectory(to).setBare(true).call
|
using(Git.cloneRepository.setURI(from.toURI.toString).setDirectory(to).setBare(true).call){ git =>
|
||||||
try {
|
|
||||||
setReceivePack(git.getRepository)
|
setReceivePack(git.getRepository)
|
||||||
} finally {
|
|
||||||
git.getRepository.close
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null
|
def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null
|
||||||
|
|
||||||
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit = {
|
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit =
|
||||||
val config = repository.getConfig
|
defining(repository.getConfig){ config =>
|
||||||
config.setBoolean("http", null, "receivepack", true)
|
config.setBoolean("http", null, "receivepack", true)
|
||||||
config.save
|
config.save
|
||||||
}
|
}
|
||||||
|
|
||||||
def getDefaultBranch(git: Git, repository: RepositoryService.RepositoryInfo,
|
def getDefaultBranch(git: Git, repository: RepositoryService.RepositoryInfo,
|
||||||
revstr: String = ""): Option[(ObjectId, String)] = {
|
revstr: String = ""): Option[(ObjectId, String)] = {
|
||||||
|
|||||||
72
src/main/scala/util/Keys.scala
Normal file
72
src/main/scala/util/Keys.scala
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define key strings for request attributes, session attributes or flash attributes.
|
||||||
|
*/
|
||||||
|
object Keys {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define session keys.
|
||||||
|
*/
|
||||||
|
object Session {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session key for the logged in account information.
|
||||||
|
*/
|
||||||
|
val LoginAccount = "LOGIN_ACCOUNT"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session key for the redirect URL.
|
||||||
|
*/
|
||||||
|
val Redirect = "REDIRECT"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session key for the issue search condition in dashboard.
|
||||||
|
*/
|
||||||
|
val DashboardIssues = "dashboard/issues"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session key for the pull request search condition in dashboard.
|
||||||
|
*/
|
||||||
|
val DashboardPulls = "dashboard/pulls"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate session key for the issue search condition.
|
||||||
|
*/
|
||||||
|
def Issues(owner: String, name: String) = s"${owner}/${name}/issues"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate session key for the pull request search condition.
|
||||||
|
*/
|
||||||
|
def Pulls(owner: String, name: String) = s"${owner}/${name}/pulls"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate session key for the upload filename.
|
||||||
|
*/
|
||||||
|
def Upload(fileId: String) = s"upload_${fileId}"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define request keys.
|
||||||
|
*/
|
||||||
|
object Request {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request key for the Ajax request flag.
|
||||||
|
*/
|
||||||
|
val Ajax = "AJAX"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request key for the username which is used during Git repository access.
|
||||||
|
*/
|
||||||
|
val UserName = "USER_NAME"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate request key for the request cache.
|
||||||
|
*/
|
||||||
|
def Cache(key: String) = s"cache.${key}"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import service.SystemSettingsService.Ldap
|
import util.ControlUtil._
|
||||||
import service.SystemSettingsService
|
import service.SystemSettingsService
|
||||||
import com.novell.ldap._
|
import com.novell.ldap._
|
||||||
import service.SystemSettingsService.Ldap
|
import service.SystemSettingsService.Ldap
|
||||||
import scala.Some
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,12 +95,10 @@ object LDAPUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def findMailAddress(conn: LDAPConnection, userDN: String, mailAttribute: String): Option[String] = {
|
private def findMailAddress(conn: LDAPConnection, userDN: String, mailAttribute: String): Option[String] =
|
||||||
val results = conn.search(userDN, LDAPConnection.SCOPE_BASE, null, Array[String](mailAttribute), false)
|
defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, null, Array[String](mailAttribute), false)){ results =>
|
||||||
if (results.hasMore) {
|
optionIf (results.hasMore) {
|
||||||
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
||||||
} else {
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package util
|
|||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.locks.{ReentrantLock, Lock}
|
import java.util.concurrent.locks.{ReentrantLock, Lock}
|
||||||
|
import util.ControlUtil._
|
||||||
|
|
||||||
object LockUtil {
|
object LockUtil {
|
||||||
|
|
||||||
@@ -23,8 +24,7 @@ object LockUtil {
|
|||||||
/**
|
/**
|
||||||
* Synchronizes a given function which modifies the working copy of the wiki repository.
|
* Synchronizes a given function which modifies the working copy of the wiki repository.
|
||||||
*/
|
*/
|
||||||
def lock[T](key: String)(f: => T): T = {
|
def lock[T](key: String)(f: => T): T = defining(getLockObject(key)){ lock =>
|
||||||
val lock = getLockObject(key)
|
|
||||||
try {
|
try {
|
||||||
lock.lock()
|
lock.lock()
|
||||||
f
|
f
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
|
|
||||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||||
(msg: String => String)(implicit context: Context) = {
|
(msg: String => String)(implicit context: Context) = {
|
||||||
|
val database = Database(context.request.getServletContext)
|
||||||
|
|
||||||
val f = future {
|
val f = future {
|
||||||
val email = new HtmlEmail
|
val email = new HtmlEmail
|
||||||
email.setHostName(smtp.host)
|
email.setHostName(smtp.host)
|
||||||
@@ -79,7 +81,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
email.setHtmlMsg(msg(view.Markdown.toHtml(content, r, false, true)))
|
email.setHtmlMsg(msg(view.Markdown.toHtml(content, r, false, true)))
|
||||||
|
|
||||||
// TODO Can we use the Database Session in other than Transaction Filter?
|
// TODO Can we use the Database Session in other than Transaction Filter?
|
||||||
Database(context.request.getServletContext) withSession {
|
database withSession {
|
||||||
getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
|
getIssue(r.owner, r.name, issueId.toString) foreach { issue =>
|
||||||
email.setSubject(s"[${r.name}] ${issue.title} (#${issueId})")
|
email.setSubject(s"[${r.name}] ${issue.title} (#${issueId})")
|
||||||
recipients(issue) {
|
recipients(issue) {
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ package util
|
|||||||
|
|
||||||
import java.net.{URLDecoder, URLEncoder}
|
import java.net.{URLDecoder, URLEncoder}
|
||||||
import org.mozilla.universalchardet.UniversalDetector
|
import org.mozilla.universalchardet.UniversalDetector
|
||||||
|
import util.ControlUtil._
|
||||||
|
|
||||||
object StringUtil {
|
object StringUtil {
|
||||||
|
|
||||||
def sha1(value: String): String = {
|
def sha1(value: String): String =
|
||||||
val md = java.security.MessageDigest.getInstance("SHA-1")
|
defining(java.security.MessageDigest.getInstance("SHA-1")){ md =>
|
||||||
md.update(value.getBytes)
|
md.update(value.getBytes)
|
||||||
md.digest.map(b => "%02x".format(b)).mkString
|
md.digest.map(b => "%02x".format(b)).mkString
|
||||||
}
|
}
|
||||||
|
|
||||||
def md5(value: String): String = {
|
def md5(value: String): String = {
|
||||||
val md = java.security.MessageDigest.getInstance("MD5")
|
val md = java.security.MessageDigest.getInstance("MD5")
|
||||||
@@ -28,13 +29,13 @@ object StringUtil {
|
|||||||
|
|
||||||
def convertFromByteArray(content: Array[Byte]): String = new String(content, detectEncoding(content))
|
def convertFromByteArray(content: Array[Byte]): String = new String(content, detectEncoding(content))
|
||||||
|
|
||||||
def detectEncoding(content: Array[Byte]): String = {
|
def detectEncoding(content: Array[Byte]): String =
|
||||||
val detector = new UniversalDetector(null)
|
defining(new UniversalDetector(null)){ detector =>
|
||||||
detector.handleData(content, 0, content.length)
|
detector.handleData(content, 0, content.length)
|
||||||
detector.dataEnd()
|
detector.dataEnd()
|
||||||
detector.getDetectedCharset match {
|
detector.getDetectedCharset match {
|
||||||
case null => "UTF-8"
|
case null => "UTF-8"
|
||||||
case e => e
|
case e => e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ trait Validations {
|
|||||||
*/
|
*/
|
||||||
def date(constraints: Constraint*): SingleValueType[java.util.Date] =
|
def date(constraints: Constraint*): SingleValueType[java.util.Date] =
|
||||||
new SingleValueType[java.util.Date]((pattern("\\d{4}-\\d{2}-\\d{2}") +: constraints): _*){
|
new SingleValueType[java.util.Date]((pattern("\\d{4}-\\d{2}-\\d{2}") +: constraints): _*){
|
||||||
def convert(value: String): java.util.Date = {
|
def convert(value: String): java.util.Date = new java.text.SimpleDateFormat("yyyy-MM-dd").parse(value)
|
||||||
val formatter = new java.text.SimpleDateFormat("yyyy-MM-dd")
|
|
||||||
formatter.parse(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class GitBucketLinkRender(context: app.Context, repository: service.RepositorySe
|
|||||||
}
|
}
|
||||||
|
|
||||||
class GitBucketVerbatimSerializer extends VerbatimSerializer {
|
class GitBucketVerbatimSerializer extends VerbatimSerializer {
|
||||||
def serialize(node: VerbatimNode, printer: Printer) {
|
def serialize(node: VerbatimNode, printer: Printer): Unit = {
|
||||||
printer.println.print("<pre")
|
printer.println.print("<pre")
|
||||||
if (!StringUtils.isEmpty(node.getType)) {
|
if (!StringUtils.isEmpty(node.getType)) {
|
||||||
printer.print(" class=").print('"').print("prettyprint ").print(node.getType).print('"')
|
printer.print(" class=").print('"').print("prettyprint ").print(node.getType).print('"')
|
||||||
@@ -98,11 +98,11 @@ class GitBucketHtmlSerializer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def printAttribute(name: String, value: String) {
|
private def printAttribute(name: String, value: String): Unit = {
|
||||||
printer.print(' ').print(name).print('=').print('"').print(value).print('"')
|
printer.print(' ').print(name).print('=').print('"').print(value).print('"')
|
||||||
}
|
}
|
||||||
|
|
||||||
override def visit(node: TextNode) {
|
override def visit(node: TextNode): Unit = {
|
||||||
// convert commit id and username to link.
|
// convert commit id and username to link.
|
||||||
val text = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
val text = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
* Converts Markdown of Wiki pages to HTML.
|
* Converts Markdown of Wiki pages to HTML.
|
||||||
*/
|
*/
|
||||||
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo,
|
def markdown(value: String, repository: service.RepositoryService.RepositoryInfo,
|
||||||
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html = {
|
enableWikiLink: Boolean, enableRefsLink: Boolean)(implicit context: app.Context): Html =
|
||||||
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink))
|
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink))
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns <img> which displays the avatar icon.
|
* Returns <img> which displays the avatar icon.
|
||||||
@@ -81,14 +80,12 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
/**
|
/**
|
||||||
* Generates the url to the account page.
|
* Generates the url to the account page.
|
||||||
*/
|
*/
|
||||||
def url(userName: String)(implicit context: app.Context): String =
|
def url(userName: String)(implicit context: app.Context): String = s"${context.path}/${userName}"
|
||||||
s"${context.path}/${userName}"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the url to the root of assets.
|
* Returns the url to the root of assets.
|
||||||
*/
|
*/
|
||||||
def assets(implicit context: app.Context): String =
|
def assets(implicit context: app.Context): String = s"${context.path}/assets"
|
||||||
s"${context.path}/assets"
|
|
||||||
|
|
||||||
def isPast(date: Date): Boolean = System.currentTimeMillis > date.getTime
|
def isPast(date: Date): Boolean = System.currentTimeMillis > date.getTime
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,15 @@
|
|||||||
<div class="span6">
|
<div class="span6">
|
||||||
@if(account.isEmpty){
|
@if(account.isEmpty){
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="userName"><strong>User name</strong></label>
|
<label for="userName" class="strong">User name</label>
|
||||||
<input type="text" name="userName" id="userName" value=""/>
|
<input type="text" name="userName" id="userName" value=""/>
|
||||||
<span id="error-userName" class="error"></span>
|
<span id="error-userName" class="error"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="password"><strong>Password</strong>
|
<label for="password" class="strong">
|
||||||
|
Password
|
||||||
@if(account.nonEmpty){
|
@if(account.nonEmpty){
|
||||||
(Input to change password)
|
(Input to change password)
|
||||||
}
|
}
|
||||||
@@ -30,19 +31,19 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="mailAddress"><strong>Mail Address</strong></label>
|
<label for="mailAddress" class="strong">Mail Address</label>
|
||||||
<input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/>
|
<input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/>
|
||||||
<span id="error-mailAddress" class="error"></span>
|
<span id="error-mailAddress" class="error"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="url"><strong>URL (Optional)</strong></label>
|
<label for="url" class="strong">URL (Optional)</label>
|
||||||
<input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/>
|
<input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/>
|
||||||
<span id="error-url" class="error"></span>
|
<span id="error-url" class="error"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="span6">
|
<div class="span6">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="avatar"><strong>Image (Optional)</strong></label>
|
<label for="avatar" class="strong">Image (Optional)</label>
|
||||||
@helper.html.uploadavatar(account)
|
@helper.html.uploadavatar(account)
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,22 +11,22 @@
|
|||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- Account registration -->
|
<!-- Account registration -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<label><strong>Account registration</strong></label>
|
<label class="strong">Account registration</label>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="allowAccountRegistration" value="true"@if(settings.allowAccountRegistration){ checked}>
|
<input type="radio" name="allowAccountRegistration" value="true"@if(settings.allowAccountRegistration){ checked}>
|
||||||
<strong>Allow</strong> - Users can create account by themselves.
|
<span class="strong">Allow</span> - Users can create account by themselves.
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="allowAccountRegistration" value="false"@if(!settings.allowAccountRegistration){ checked}>
|
<input type="radio" name="allowAccountRegistration" value="false"@if(!settings.allowAccountRegistration){ checked}>
|
||||||
<strong>Deny</strong> - Only administrators can create account.
|
<span class="strong">Deny</span> - Only administrators can create account.
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
<label><strong>Services</strong></label>
|
<label class="strong">Services</label>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" name="gravatar"@if(settings.gravatar){ checked}/>
|
<input type="checkbox" name="gravatar"@if(settings.gravatar){ checked}/>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<!-- Authentication -->
|
<!-- Authentication -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
<label><strong>Authentication</strong></label>
|
<label class="strong">Authentication</label>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="ldapAuthentication" name="ldapAuthentication"@if(settings.ldap){ checked}/>
|
<input type="checkbox" id="ldapAuthentication" name="ldapAuthentication"@if(settings.ldap){ checked}/>
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
<!-- Notification email -->
|
<!-- Notification email -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
<label><strong>Notification email</strong></label>
|
<label class="strong">Notification email</label>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="notification" name="notification"@if(settings.notification){ checked}/>
|
<input type="checkbox" id="notification" name="notification"@if(settings.notification){ checked}/>
|
||||||
|
|||||||
@@ -7,23 +7,23 @@
|
|||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="span7">
|
<div class="span7">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="groupName"><strong>Group name</strong></label>
|
<label for="groupName" class="strong">Group name</label>
|
||||||
<span id="error-groupName" class="error"></span>
|
<span id="error-groupName" class="error"></span>
|
||||||
<input type="text" name="groupName" id="groupName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
<input type="text" name="groupName" id="groupName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label><strong>URL (Optional)</strong></label>
|
<label class="strong">URL (Optional)</label>
|
||||||
<span id="error-url" class="error"></span>
|
<span id="error-url" class="error"></span>
|
||||||
<input type="text" name="url" id="url" style="width: 300px;" value="@account.map(_.url)"/>
|
<input type="text" name="url" id="url" style="width: 300px;" value="@account.map(_.url)"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="avatar"><strong>Image (Optional)</strong></label>
|
<label for="avatar" class="strong">Image (Optional)</label>
|
||||||
@helper.html.uploadavatar(account)
|
@helper.html.uploadavatar(account)
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="span5">
|
<div class="span5">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label><strong>Members</strong></label>
|
<label class="strong">Members</label>
|
||||||
<ul id="members" class="collaborator">
|
<ul id="members" class="collaborator">
|
||||||
@members.map { userName =>
|
@members.map { userName =>
|
||||||
<li data-name="@userName">
|
<li data-name="@userName">
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<input type="text" id="memberName" style="width: 200px; margin-bottom: 0px;"/>
|
@helper.html.account("memberName", 200)
|
||||||
<input type="button" class="btn" value="Add" id="addMember"/>
|
<input type="button" class="btn" value="Add" id="addMember"/>
|
||||||
<input type="hidden" id="memberNames" name="memberNames" value="@members.mkString(",")"/>
|
<input type="hidden" id="memberNames" name="memberNames" value="@members.mkString(",")"/>
|
||||||
<div>
|
<div>
|
||||||
@@ -50,15 +50,6 @@
|
|||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('#memberName').typeahead({
|
|
||||||
source: function (query, process) {
|
|
||||||
return $.get('@path/_user/proposals', { query: query },
|
|
||||||
function (data) {
|
|
||||||
return process(data.options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#addMember').click(function(){
|
$('#addMember').click(function(){
|
||||||
$('#error-memberName').text('');
|
$('#error-memberName').text('');
|
||||||
var userName = $('#memberName').val();
|
var userName = $('#memberName').val();
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
<div class="span6">
|
<div class="span6">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="userName"><strong>Username</strong></label>
|
<label for="userName" class="strong">Username</label>
|
||||||
<span id="error-userName" class="error"></span>
|
<span id="error-userName" class="error"></span>
|
||||||
<input type="text" name="userName" id="userName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
<input type="text" name="userName" id="userName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="password">
|
<label for="password" class="strong">
|
||||||
<strong>Password</strong>
|
Password
|
||||||
@if(account.isDefined){
|
@if(account.isDefined){
|
||||||
(Input to change password)
|
(Input to change password)
|
||||||
}
|
}
|
||||||
@@ -23,12 +23,12 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="mailAddress"><strong>Mail Address</strong></label>
|
<label for="mailAddress" class="strong">Mail Address</label>
|
||||||
<span id="error-mailAddress" class="error"></span>
|
<span id="error-mailAddress" class="error"></span>
|
||||||
<input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/>
|
<input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label><strong>User Type</strong></label>
|
<label class="strong">User Type</label>
|
||||||
<label for="userType_Normal">
|
<label for="userType_Normal">
|
||||||
<input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.isAdmin){ checked}/> Normal
|
<input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.isAdmin){ checked}/> Normal
|
||||||
</label>
|
</label>
|
||||||
@@ -37,14 +37,14 @@
|
|||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label><strong>URL (Optional)</strong></label>
|
<label class="strong">URL (Optional)</label>
|
||||||
<span id="error-url" class="error"></span>
|
<span id="error-url" class="error"></span>
|
||||||
<input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/>
|
<input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="span6">
|
<div class="span6">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="avatar"><strong>Image (Optional)</strong></label>
|
<label for="avatar" class="strong">Image (Optional)</label>
|
||||||
@helper.html.uploadavatar(account)
|
@helper.html.uploadavatar(account)
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,10 +48,8 @@
|
|||||||
<a href="@url(repository)/settings">Settings</a>
|
<a href="@url(repository)/settings">Settings</a>
|
||||||
</th>
|
</th>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<form method="POST" id="repository_form">
|
|
||||||
</form>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(function(){
|
$(function(){
|
||||||
$('table.global-nav th.box-header').click(function(){
|
$('table.global-nav th.box-header').click(function(){
|
||||||
|
|||||||
15
src/main/twirl/helper/account.scala.html
Normal file
15
src/main/twirl/helper/account.scala.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@(id: String, width: Int)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
<input type="text" name="@id" id="@id" style="width: @{width}px; margin-bottom: 0px;"/>
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#@id').typeahead({
|
||||||
|
source: function (query, process) {
|
||||||
|
return $.get('@path/_user/proposals', { query: query },
|
||||||
|
function (data) {
|
||||||
|
return process(data.options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -24,10 +24,12 @@
|
|||||||
if(i == 3){
|
if(i == 3){
|
||||||
<div>...</div>
|
<div>...</div>
|
||||||
} else {
|
} else {
|
||||||
<div>
|
if(commit.nonEmpty){
|
||||||
<a href={s"${path}/${activity.userName}/${activity.repositoryName}/commit/${commit.substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a>
|
<div>
|
||||||
<span>{commit.substring(41)}</span>
|
<a href={s"${path}/${activity.userName}/${activity.repositoryName}/commit/${commit. substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a>
|
||||||
</div>
|
<span>{commit.substring(41)}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,39 @@
|
|||||||
@(diffs: Seq[util.JGitUtil.DiffInfo],
|
@(diffs: Seq[util.JGitUtil.DiffInfo],
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
newCommitId: Option[String],
|
newCommitId: Option[String],
|
||||||
oldCommitId: Option[String])(implicit context: app.Context)
|
oldCommitId: Option[String],
|
||||||
|
showIndex: Boolean)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||||
|
@if(showIndex){
|
||||||
|
<div>
|
||||||
|
<div class="pull-right" style="margin-bottom: 10px;">
|
||||||
|
<input id="toggle-file-list" type="button" class="btn" value="Show file list"/>
|
||||||
|
</div>
|
||||||
|
Showing @diffs.size changed @plural(diffs.size, "file")
|
||||||
|
</div>
|
||||||
|
<ul id="commit-file-list" style="display: none;">
|
||||||
|
@diffs.zipWithIndex.map { case (diff, i) =>
|
||||||
|
<li@if(i > 0){ class="border"}>
|
||||||
|
<a href="#diff-@i">
|
||||||
|
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
|
||||||
|
<img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath
|
||||||
|
}
|
||||||
|
@if(diff.changeType == ChangeType.ADD){
|
||||||
|
<img src="@assets/common/images/diff_add.png"/> @diff.newPath
|
||||||
|
}
|
||||||
|
@if(diff.changeType == ChangeType.MODIFY){
|
||||||
|
<img src="@assets/common/images/diff_edit.png"/> @diff.newPath
|
||||||
|
}
|
||||||
|
@if(diff.changeType == ChangeType.DELETE){
|
||||||
|
<img src="@assets/common/images/diff_delete.png"/> @diff.oldPath
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
@diffs.zipWithIndex.map { case (diff, i) =>
|
@diffs.zipWithIndex.map { case (diff, i) =>
|
||||||
<a name="diff-@i"></a>
|
<a name="diff-@i"></a>
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
@@ -103,6 +132,17 @@ function diffUsingJS(oldTextId, newTextId, outputId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(function(){
|
$(function(){
|
||||||
|
@if(showIndex){
|
||||||
|
$('#toggle-file-list').click(function(){
|
||||||
|
$('#commit-file-list').toggle();
|
||||||
|
if($(this).val() == 'Show file list'){
|
||||||
|
$(this).val('Hide file list');
|
||||||
|
} else {
|
||||||
|
$(this).val('Show file list');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@diffs.zipWithIndex.map { case (diff, i) =>
|
@diffs.zipWithIndex.map { case (diff, i) =>
|
||||||
@if(diff.newContent != None || diff.oldContent != None){
|
@if(diff.newContent != None || diff.oldContent != None){
|
||||||
if($('#oldText-@i').length > 0){
|
if($('#oldText-@i').length > 0){
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
@(buttonValue: String = "", prefix: String = "")(body: Html)
|
@(value: String = "", prefix: String = "", mini: Boolean = true, style: String = "")(body: Html)
|
||||||
<div class="btn-group">
|
<div class="btn-group"@if(style.nonEmpty){ style="@style"}>
|
||||||
<button class="btn btn-mini dropdown-toggle" data-toggle="dropdown">
|
<button class="btn dropdown-toggle@if(mini){ btn-mini}" data-toggle="dropdown">
|
||||||
@if(buttonValue.isEmpty){
|
@if(value.isEmpty){
|
||||||
<i class="icon-cog"></i>
|
<i class="icon-cog"></i>
|
||||||
} else {
|
} else {
|
||||||
@if(prefix.nonEmpty){
|
@if(prefix.nonEmpty){
|
||||||
<span class="muted">@prefix:</span>
|
<span class="muted">@prefix:</span>
|
||||||
}
|
}
|
||||||
<strong>@buttonValue</strong>
|
<span class="strong">@value</span>
|
||||||
}
|
}
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@if(repository.owner == loginAccount.get.userName){
|
@if(repository.owner == loginAccount.get.userName){
|
||||||
<a href="@url(repository)"><strong>@repository.name</strong></a>
|
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
|
||||||
} else {
|
} else {
|
||||||
<a href="@url(repository)">@repository.owner/<strong>@repository.name</strong></a>
|
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
@recentRepositories.map { repository =>
|
@recentRepositories.map { repository =>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="@url(repository)">@repository.owner/<strong>@repository.name</strong></a>
|
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="span3">
|
<div class="span3">
|
||||||
@if(hasWritePermission){
|
@if(hasWritePermission){
|
||||||
<strong>Add Labels</strong>
|
<span class="strong">Add Labels</span>
|
||||||
<div>
|
<div>
|
||||||
<div id="label-list">
|
<div id="label-list">
|
||||||
<ul class="label-list nav nav-pills nav-stacked">
|
<ul class="label-list nav nav-pills nav-stacked">
|
||||||
@@ -111,7 +111,7 @@ $(function(){
|
|||||||
if(milestoneId == ''){
|
if(milestoneId == ''){
|
||||||
$('#label-milestone').text('No milestone');
|
$('#label-milestone').text('No milestone');
|
||||||
} else {
|
} else {
|
||||||
$('#label-milestone').html($('<span>').append('Milestone: ').append($('<strong>').text(title)));
|
$('#label-milestone').html($('<span>').append('Milestone: ').append($('<span class="strong">').text(title)));
|
||||||
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
||||||
}
|
}
|
||||||
$('input[name=milestoneId]').val(milestoneId);
|
$('input[name=milestoneId]').val(milestoneId);
|
||||||
|
|||||||
@@ -29,19 +29,19 @@
|
|||||||
}
|
}
|
||||||
<div class="small" style="text-align: center;">
|
<div class="small" style="text-align: center;">
|
||||||
@defining(comments.filter( _.action.contains("comment") ).size){ count =>
|
@defining(comments.filter( _.action.contains("comment") ).size){ count =>
|
||||||
<strong>@count</strong> @plural(count, "comment")
|
<span class="strong">@count</span> @plural(count, "comment")
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
<div style="margin-bottom: 8px;">
|
<div style="margin-bottom: 8px;">
|
||||||
<strong>Labels</strong>
|
<span class="strong">Labels</span>
|
||||||
@if(hasWritePermission){
|
@if(hasWritePermission){
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@helper.html.dropdown() {
|
@helper.html.dropdown() {
|
||||||
@labels.map { label =>
|
@labels.map { label =>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" class="toggle-label" data-label-id="@label.labelId">
|
<a href="#" class="toggle-label" data-label-id="@label.labelId">
|
||||||
<i class="@{if(issueLabels.exists(_.labelId == label.labelId)) "icon-ok" else "icon-white"}"></i>
|
@helper.html.checkicon(issueLabels.exists(_.labelId == label.labelId))
|
||||||
<span class="label" style="background-color: #@label.color;"> </span>
|
<span class="label" style="background-color: #@label.color;"> </span>
|
||||||
@label.labelName
|
@label.labelName
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -28,7 +28,11 @@
|
|||||||
@helper.html.dropdown() {
|
@helper.html.dropdown() {
|
||||||
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
|
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
|
||||||
@collaborators.map { collaborator =>
|
@collaborators.map { collaborator =>
|
||||||
<li><a href="javascript:void(0);" class="assign" data-name="@collaborator"><i class="icon-white"></i>@avatar(collaborator, 20) @collaborator</a></li>
|
<li>
|
||||||
|
<a href="javascript:void(0);" class="assign" data-name="@collaborator">
|
||||||
|
@helper.html.checkicon(Some(collaborator) == issue.assignedUserName)@avatar(collaborator, 20) @collaborator
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +40,7 @@
|
|||||||
<span id="label-milestone">
|
<span id="label-milestone">
|
||||||
@issue.milestoneId.map { milestoneId =>
|
@issue.milestoneId.map { milestoneId =>
|
||||||
@milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) =>
|
@milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) =>
|
||||||
Milestone: <strong>@milestone.title</strong>
|
Milestone: <span class="strong">@milestone.title</span>
|
||||||
}
|
}
|
||||||
}.getOrElse("No milestone")
|
}.getOrElse("No milestone")
|
||||||
</span>
|
</span>
|
||||||
@@ -53,7 +57,7 @@
|
|||||||
@milestones.map { case (milestone, _, _) =>
|
@milestones.map { case (milestone, _, _) =>
|
||||||
<li>
|
<li>
|
||||||
<a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title">
|
<a href="javascript:void(0);" class="milestone" data-id="@milestone.milestoneId" data-title="@milestone.title">
|
||||||
<i class="icon-white"></i> @milestone.title
|
@helper.html.checkicon(Some(milestone.milestoneId) == issue.milestoneId) @milestone.title
|
||||||
<div class="small" style="padding-left: 20px;">
|
<div class="small" style="padding-left: 20px;">
|
||||||
@milestone.dueDate.map { dueDate =>
|
@milestone.dueDate.map { dueDate =>
|
||||||
@if(isPast(dueDate)){
|
@if(isPast(dueDate)){
|
||||||
@@ -79,19 +83,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="issue-participants">
|
<div class="issue-participants">
|
||||||
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
|
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
|
||||||
<strong>@participants.size</strong> @plural(participants.size, "participant")
|
<span class="strong">@participants.size</span> @plural(participants.size, "participant")
|
||||||
@participants.map { participant => <a href="@url(participant)">@avatar(participant, 20, tooltip = true)</a> }
|
@participants.map { participant => <a href="@url(participant)">@avatar(participant, 20, tooltip = true)</a> }
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
@if(issue.assignedUserName.isDefined){
|
|
||||||
$('a.assign[data-name=@issue.assignedUserName] i').attr('class', 'icon-ok');
|
|
||||||
}
|
|
||||||
@if(issue.milestoneId.isDefined){
|
|
||||||
$('a.milestone[data-id=@issue.milestoneId] i').attr('class', 'icon-ok');
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#edit').click(function(){
|
$('#edit').click(function(){
|
||||||
$.get('@url(repository)/issues/_data/@issue.issueId',
|
$.get('@url(repository)/issues/_data/@issue.issueId',
|
||||||
{
|
{
|
||||||
@@ -138,7 +135,7 @@ $(function(){
|
|||||||
$('#label-milestone').text('No milestone');
|
$('#label-milestone').text('No milestone');
|
||||||
$('#milestone-progress-area').empty();
|
$('#milestone-progress-area').empty();
|
||||||
} else {
|
} else {
|
||||||
$('#label-milestone').html($('<span>').append('Milestone: ').append($('<strong>').text(title)));
|
$('#label-milestone').html($('<span>').append('Milestone: ').append($('<span class="strong">').text(title)));
|
||||||
$('#milestone-progress-area').html(data);
|
$('#milestone-progress-area').html(data);
|
||||||
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
<hr/>
|
<hr/>
|
||||||
<strong>Labels</strong>
|
<span class="strong">Labels</span>
|
||||||
<div>
|
<div>
|
||||||
<div id="label-list">
|
<div id="label-list">
|
||||||
<ul class="label-list nav nav-pills nav-stacked">
|
<ul class="label-list nav nav-pills nav-stacked">
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
<hr/>
|
<hr/>
|
||||||
<input type="button" class="btn btn-block" id="manageLabel" data-toggle="button" value="Manage Labels"/>
|
<input type="button" class="btn btn-block" id="manageLabel" data-toggle="button" value="Manage Labels"/>
|
||||||
<br/>
|
<br/>
|
||||||
<strong>New label</strong>
|
<span class="strong">New label</span>
|
||||||
@_root_.issues.labels.html.edit(None, repository)
|
@_root_.issues.labels.html.edit(None, repository)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,52 +29,49 @@
|
|||||||
<a class="btn@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
|
<a class="btn@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
|
||||||
<a class="btn@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
<a class="btn@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
@helper.html.dropdown(
|
||||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
value = (condition.sort, condition.direction) match {
|
||||||
Sort:
|
case ("created" , "desc") => "Newest"
|
||||||
<strong>
|
case ("created" , "asc" ) => "Oldest"
|
||||||
@if(condition.sort == "created" && condition.direction == "desc"){ Newest }
|
case ("comments", "desc") => "Most commented"
|
||||||
@if(condition.sort == "created" && condition.direction == "asc" ){ Oldest }
|
case ("comments", "asc" ) => "Least commented"
|
||||||
@if(condition.sort == "comments" && condition.direction == "desc"){ Most commented }
|
case ("updated" , "desc") => "Recently updated"
|
||||||
@if(condition.sort == "comments" && condition.direction == "asc" ){ Least commented }
|
case ("updated" , "asc" ) => "Least recently updated"
|
||||||
@if(condition.sort == "updated" && condition.direction == "desc"){ Recently updated }
|
},
|
||||||
@if(condition.sort == "updated" && condition.direction == "asc" ){ Least recently updated }
|
prefix = "Sort",
|
||||||
</strong>
|
mini = false
|
||||||
<span class="caret"></span>
|
){
|
||||||
</button>
|
<li>
|
||||||
<ul class="dropdown-menu">
|
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
</li>
|
||||||
</a>
|
}
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<table class="table table-bordered table-hover table-issues">
|
<table class="table table-bordered table-hover table-issues">
|
||||||
@if(issues.isEmpty){
|
@if(issues.isEmpty){
|
||||||
<tr>
|
<tr>
|
||||||
@@ -94,7 +91,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="background-color: #eee;">
|
<td style="background-color: #eee;">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-mini" id="state"><strong>@{if(condition.state == "open") "Close" else "Reopen"}</strong></button>
|
<button class="btn btn-mini strong" id="state">@{if(condition.state == "open") "Close" else "Reopen"}</button>
|
||||||
</div>
|
</div>
|
||||||
@helper.html.dropdown("Label") {
|
@helper.html.dropdown("Label") {
|
||||||
@labels.map { label =>
|
@labels.map { label =>
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
<span id="error-title" class="error"></span>
|
<span id="error-title" class="error"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="description"><strong>Description</strong></label>
|
<label for="description" class="strong">Description</label>
|
||||||
<textarea id="description" name="description" style="width: 500px; height: 150px;">@milestone.map(_.description)</textarea>
|
<textarea id="description" name="description" style="width: 500px; height: 150px;">@milestone.map(_.description)</textarea>
|
||||||
<span id="error-description" class="error"></span>
|
<span id="error-description" class="error"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="dueDate"><strong>Due Date</strong></label>
|
<label for="dueDate" class="strong">Due Date</label>
|
||||||
@helper.html.datepicker("dueDate", milestone.flatMap(_.dueDate))
|
@helper.html.datepicker("dueDate", milestone.flatMap(_.dueDate))
|
||||||
<span id="error-dueDate" class="error"></span>
|
<span id="error-dueDate" class="error"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
<div style="width: 600px; margin: 10px auto;">
|
<div style="width: 600px; margin: 10px auto;">
|
||||||
<form id="form" method="post" action="@path/new" validate="true">
|
<form id="form" method="post" action="@path/new" validate="true">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="name"><strong>Repository name</strong></label>
|
<label for="name" class="strong">Repository name</label>
|
||||||
<div class="btn-group" style="margin-bottom: 10px;" id="owner-dropdown">
|
<div class="btn-group" style="margin-bottom: 10px;" id="owner-dropdown">
|
||||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
||||||
<strong>@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</strong>
|
<span class="strong">@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span>
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
@@ -24,13 +24,13 @@
|
|||||||
<span id="error-name" class="error"></span>
|
<span id="error-name" class="error"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="description"><strong>Description</strong> (optional)</label>
|
<label for="description" class="strong">Description (optional)</label>
|
||||||
<input type="text" name="description" id="description" style="width: 95%;"/>
|
<input type="text" name="description" id="description" style="width: 95%;"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="margin">
|
<fieldset class="margin">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="isPrivate" value="false" checked>
|
<input type="radio" name="isPrivate" value="false" checked>
|
||||||
<strong>Public</strong><br>
|
<span class="strong">Public</span><br>
|
||||||
<div>
|
<div>
|
||||||
<span class="note">All users and guests can read this repository.</span>
|
<span class="note">All users and guests can read this repository.</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="isPrivate" value="true">
|
<input type="radio" name="isPrivate" value="true">
|
||||||
<strong>Private</strong><br>
|
<span class="strong">Private</span><br>
|
||||||
<div>
|
<div>
|
||||||
<span class="note">Only collaborators can read this repository.</span>
|
<span class="note">Only collaborators can read this repository.</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<fieldset class="margin">
|
<fieldset class="margin">
|
||||||
<label for="createReadme">
|
<label for="createReadme">
|
||||||
<input type="checkbox" name="createReadme" id="createReadme"/>
|
<input type="checkbox" name="createReadme" id="createReadme"/>
|
||||||
<strong>Initialize this repository with a README</strong>
|
<span class="strong">Initialize this repository with a README</span>
|
||||||
<div>
|
<div>
|
||||||
<span class="note">This will allow you to <code>git clone</code> the repository immediately.</span>
|
<span class="note">This will allow you to <code>git clone</code> the repository immediately.</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,6 +68,6 @@ $('#owner-dropdown a').click(function(){
|
|||||||
$('#owner-dropdown i').attr('class', 'icon-white');
|
$('#owner-dropdown i').attr('class', 'icon-white');
|
||||||
$(this).find('i').attr('class', 'icon-ok');
|
$(this).find('i').attr('class', 'icon-ok');
|
||||||
|
|
||||||
$('#owner-dropdown strong').html($(this).find('span').html());
|
$('#owner-dropdown span.strong').html($(this).find('span').html());
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
@(issue: model.Issue,
|
@(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
||||||
pullreq: model.PullRequest,
|
|
||||||
commits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
|
||||||
hasWritePermission: Boolean,
|
|
||||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
|
|||||||
@@ -5,14 +5,12 @@
|
|||||||
forkedId: String,
|
forkedId: String,
|
||||||
sourceId: String,
|
sourceId: String,
|
||||||
commitId: String,
|
commitId: String,
|
||||||
hasConflict: Boolean,
|
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
originRepository: service.RepositoryService.RepositoryInfo,
|
originRepository: service.RepositoryService.RepositoryInfo,
|
||||||
forkedRepository: service.RepositoryService.RepositoryInfo,
|
forkedRepository: service.RepositoryService.RepositoryInfo,
|
||||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
hasWritePermission: Boolean)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
|
||||||
@html.main("Pull Requests - " + repository.owner + "/" + repository.name){
|
@html.main("Pull Requests - " + repository.owner + "/" + repository.name){
|
||||||
@html.header("pulls", repository)
|
@html.header("pulls", repository)
|
||||||
<div class="pullreq-info">
|
<div class="pullreq-info">
|
||||||
@@ -53,14 +51,9 @@
|
|||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<form method="POST" action="@path/@originRepository.owner/@repository.name/pulls/new" validate="true">
|
<form method="POST" action="@path/@originRepository.owner/@repository.name/pulls/new" validate="true">
|
||||||
<div style="width: 260px; position: absolute; margin-left: 635px;">
|
<div style="width: 260px; position: absolute; margin-left: 635px;">
|
||||||
@if(hasConflict){
|
<div class="check-conflict" style="display: none;">
|
||||||
<h4>We can’t automatically merge these branches</h4>
|
<img src="@assets/common/images/indicator.gif"/> Checking...
|
||||||
<p>Don't worry, you can still submit the pull request.</p>
|
</div>
|
||||||
} else {
|
|
||||||
<h4 style="color: #468847;">Able to merge</h4>
|
|
||||||
<p>These branches can be automatically merged.</p>
|
|
||||||
}
|
|
||||||
<input type="submit" class="btn btn-success btn-block" value="Send pull request"/>
|
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 620px; border-right: 1px solid #d4d4d4;">
|
<div style="width: 620px; border-right: 1px solid #d4d4d4;">
|
||||||
<span class="error" id="error-title"></span>
|
<span class="error" id="error-title"></span>
|
||||||
@@ -82,59 +75,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||||
<h4>There isn't anything to compare.</h4>
|
<h4>There isn't anything to compare.</h4>
|
||||||
<strong>@originRepository.owner:@originId</strong> and <strong>@forkedRepository.owner:@forkedId</strong> are identical.
|
<span class="strong">@originRepository.owner:@originId</span> and <span class="strong">@forkedRepository.owner:@forkedId</span> are identical.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
} else {
|
} else {
|
||||||
<div class="box">
|
@pulls.html.commits(commits, repository)
|
||||||
<table class="table table-file-list" style="border: 1px solid silver;">
|
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true)
|
||||||
@commits.map { day =>
|
|
||||||
<tr>
|
|
||||||
<th colspan="3" class="box-header" style="font-weight: normal;">@date(day.head.time)</th>
|
|
||||||
</tr>
|
|
||||||
@day.map { commit =>
|
|
||||||
<tr>
|
|
||||||
<td style="width: 20%;">
|
|
||||||
@avatar(commit.committer, 20)
|
|
||||||
<a href="@url(commit.committer)" class="username">@commit.committer</a>
|
|
||||||
</td>
|
|
||||||
<td>@commit.shortMessage</td>
|
|
||||||
<td style="width: 10%; text-align: right;">
|
|
||||||
<a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="pull-right" style="margin-bottom: 10px;">
|
|
||||||
<input id="toggle-file-list" type="button" class="btn" value="Show file list"/>
|
|
||||||
</div>
|
|
||||||
Showing @diffs.size changed @plural(diffs.size, "file")
|
|
||||||
</div>
|
|
||||||
<ul id="commit-file-list" style="display: none;">
|
|
||||||
@diffs.zipWithIndex.map { case (diff, i) =>
|
|
||||||
<li@if(i > 0){ class="border"}>
|
|
||||||
<a href="#diff-@i">
|
|
||||||
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
|
|
||||||
<img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.ADD){
|
|
||||||
<img src="@assets/common/images/diff_add.png"/> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.MODIFY){
|
|
||||||
<img src="@assets/common/images/diff_edit.png"/> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.DELETE){
|
|
||||||
<img src="@assets/common/images/diff_delete.png"/> @diff.oldPath
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
@@ -153,7 +100,7 @@ $(function(){
|
|||||||
var e = $(this);
|
var e = $(this);
|
||||||
e.parents('ul').find('i').attr('class', 'icon-white');
|
e.parents('ul').find('i').attr('class', 'icon-white');
|
||||||
e.find('i').attr('class', 'icon-ok');
|
e.find('i').attr('class', 'icon-ok');
|
||||||
e.parents('div.btn-group').find('button strong').text(e.text());
|
e.parents('div.btn-group').find('button span.strong').text(e.text());
|
||||||
|
|
||||||
@if(members.isEmpty){
|
@if(members.isEmpty){
|
||||||
location.href = '@url(repository)/compare/' +
|
location.href = '@url(repository)/compare/' +
|
||||||
@@ -173,13 +120,26 @@ $(function(){
|
|||||||
$('#pull-request-form').show();
|
$('#pull-request-form').show();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#toggle-file-list').click(function(){
|
@if(hasWritePermission){
|
||||||
$('#commit-file-list').toggle();
|
function checkConflict(from, to, noConflictHandler, hasConflictHandler){
|
||||||
if($(this).val() == 'Show file list'){
|
$('.check-conflict').show();
|
||||||
$(this).val('Hide file list');
|
$.get('@url(repository)/compare/' + from + '...' + to + '/mergecheck',
|
||||||
} else {
|
function(data){ $('.check-conflict').html(data); });
|
||||||
$(this).val('Show file list');
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
@if(members.isEmpty){
|
||||||
|
checkConflict(
|
||||||
|
$.trim($('i.icon-ok').parents('a.origin-branch').data('name')),
|
||||||
|
$.trim($('i.icon-ok').parents('a.forked-branch').data('name'))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
checkConflict(
|
||||||
|
$.trim($('i.icon-ok').parents('a.origin-owner' ).data('name')) + ":" +
|
||||||
|
$.trim($('i.icon-ok').parents('a.origin-branch').data('name')),
|
||||||
|
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('name')) + ":" +
|
||||||
|
$.trim($('i.icon-ok').parents('a.forked-branch').data('name'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
comments: List[model.IssueComment],
|
comments: List[model.IssueComment],
|
||||||
collaborators: List[String],
|
collaborators: List[String],
|
||||||
milestones: List[(model.Milestone, Int, Int)],
|
milestones: List[(model.Milestone, Int, Int)],
|
||||||
hasConflict: Boolean,
|
|
||||||
hasWritePermission: Boolean,
|
hasWritePermission: Boolean,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||||
requestRepositoryUrl: String)(implicit context: app.Context)
|
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
@@ -17,77 +15,14 @@
|
|||||||
<div class="box issue-comment-box" style="background-color: #d8f5cd;">
|
<div class="box issue-comment-box" style="background-color: #d8f5cd;">
|
||||||
<div class="box-content"class="issue-content" style="border: 1px solid #95c97e; padding: 10px;">
|
<div class="box-content"class="issue-content" style="border: 1px solid #95c97e; padding: 10px;">
|
||||||
<div id="merge-pull-request">
|
<div id="merge-pull-request">
|
||||||
<div class="pull-right">
|
<div class="check-conflict" style="display: none;">
|
||||||
<input type="button" class="btn btn-success" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict){ disabled="true"}/>
|
<img src="@assets/common/images/indicator.gif"/> Checking...
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
@if(hasConflict){
|
|
||||||
<strong>We can’t automatically merge this pull request.</strong>
|
|
||||||
} else {
|
|
||||||
<strong>This pull request can be automatically merged.</strong>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="small">
|
|
||||||
@if(hasConflict){
|
|
||||||
<a href="#" id="show-command-line">Use the command line</a> to resolve conflicts before continuing.
|
|
||||||
} else {
|
|
||||||
You can also merge branches on the <a href="#" id="show-command-line">command line</a>.
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div id="command-line" style="display: none;">
|
|
||||||
<hr>
|
|
||||||
@if(hasConflict){
|
|
||||||
<strong>Checkout via command line</strong>
|
|
||||||
<p>
|
|
||||||
If you cannot merge a pull request automatically here, you have the option of checking
|
|
||||||
it out via command line to resolve conflicts and perform a manual merge.
|
|
||||||
</p>
|
|
||||||
} else {
|
|
||||||
<strong>Merging via command line</strong>
|
|
||||||
<p>
|
|
||||||
If you do not want to use the merge button or an automatic merge cannot be performed,
|
|
||||||
you can perform a manual merge on the command line.
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
@helper.html.copy("repository-url-copy", requestRepositoryUrl){
|
|
||||||
<input type="text" value="@requestRepositoryUrl" id="repository-url" readonly>
|
|
||||||
}
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
<strong>Step 1:</strong> Check out a new branch to test the changes — run this from your project directory
|
|
||||||
</p>
|
|
||||||
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.requestBranch}"){ command =>
|
|
||||||
@helper.html.copy("merge-command-copy-1", command){
|
|
||||||
<pre style="width: 500px; float: left;">@command</pre>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
<strong>Step 2:</strong> Bring in @{pullreq.requestUserName}'s changes and test
|
|
||||||
</p>
|
|
||||||
@defining(s"git pull ${requestRepositoryUrl} ${pullreq.requestBranch}"){ command =>
|
|
||||||
@helper.html.copy("merge-command-copy-2", command){
|
|
||||||
<pre style="width: 500px; float: left;">@command</pre>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
<strong>Step 3:</strong> Merge the changes and update the server
|
|
||||||
</p>
|
|
||||||
@defining(s"git checkout master\ngit merge ${pullreq.requestUserName}-${pullreq.branch}\ngit push origin ${pullreq.branch}"){ command =>
|
|
||||||
@helper.html.copy("merge-command-copy-3", command){
|
|
||||||
<pre style="width: 500px; float: left;">@command</pre>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="confirm-merge-form" style="display: none;">
|
<div id="confirm-merge-form" style="display: none;">
|
||||||
<form method="POST" action="@url(repository)/pull/@issue.issueId/merge">
|
<form method="POST" action="@url(repository)/pull/@issue.issueId/merge">
|
||||||
<div>
|
<div class="strong">
|
||||||
<strong>Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}</strong>
|
Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}
|
||||||
</div>
|
</div>
|
||||||
<span id="error-message" class="error"></span>
|
<span id="error-message" class="error"></span>
|
||||||
<textarea name="message" style="width: 680px; height: 80px;">@issue.title</textarea>
|
<textarea name="message" style="width: 680px; height: 80px;">@issue.title</textarea>
|
||||||
@@ -113,26 +48,22 @@
|
|||||||
<span class="label label-success issue-status">Open</span>
|
<span class="label label-success issue-status">Open</span>
|
||||||
}
|
}
|
||||||
<div class="small" style="text-align: center;">
|
<div class="small" style="text-align: center;">
|
||||||
<strong>@comments.size</strong> @plural(comments.size, "comment")
|
<span class="strong">@comments.size</span> @plural(comments.size, "comment")
|
||||||
</div>
|
</div>
|
||||||
<hr/>
|
<hr/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('#show-command-line').click(function(){
|
|
||||||
$('#command-line').show();
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#merge-pull-request-button').click(function(){
|
|
||||||
$('#merge-pull-request').hide();
|
|
||||||
$('#confirm-merge-form').show();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#cancel-merge-pull-request').click(function(){
|
$('#cancel-merge-pull-request').click(function(){
|
||||||
$('#confirm-merge-form').hide();
|
$('#confirm-merge-form').hide();
|
||||||
$('#merge-pull-request').show();
|
$('#merge-pull-request').show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@if(hasWritePermission){
|
||||||
|
$('.check-conflict').show();
|
||||||
|
$.get('@url(repository)/pull/@issue.issueId/mergeguide',
|
||||||
|
function(data){ $('.check-conflict').html(data); });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
@(issue: model.Issue,
|
|
||||||
pullreq: model.PullRequest,
|
|
||||||
diffs: Seq[util.JGitUtil.DiffInfo],
|
|
||||||
newCommitId: String,
|
|
||||||
oldCommitId: String,
|
|
||||||
hasWritePermission: Boolean,
|
|
||||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
|
||||||
@import context._
|
|
||||||
@import view.helpers._
|
|
||||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
|
||||||
<div>
|
|
||||||
<div class="pull-right" style="margin-bottom: 10px;">
|
|
||||||
<input id="toggle-file-list" type="button" class="btn" value="Show file list"/>
|
|
||||||
</div>
|
|
||||||
Showing @diffs.size changed @plural(diffs.size, "file")
|
|
||||||
</div>
|
|
||||||
<ul id="commit-file-list" style="display: none;">
|
|
||||||
@diffs.zipWithIndex.map { case (diff, i) =>
|
|
||||||
<li@if(i > 0){ class="border"}>
|
|
||||||
<a href="#diff-@i">
|
|
||||||
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
|
|
||||||
<img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.ADD){
|
|
||||||
<img src="@assets/common/images/diff_add.png"/> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.MODIFY){
|
|
||||||
<img src="@assets/common/images/diff_edit.png"/> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.DELETE){
|
|
||||||
<img src="@assets/common/images/diff_delete.png"/> @diff.oldPath
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
@helper.html.diff(diffs, repository, Some(newCommitId), Some(oldCommitId))
|
|
||||||
<script>
|
|
||||||
$(function(){
|
|
||||||
$('#toggle-file-list').click(function(){
|
|
||||||
$('#commit-file-list').toggle();
|
|
||||||
if($(this).val() == 'Show file list'){
|
|
||||||
$(this).val('Hide file list');
|
|
||||||
} else {
|
|
||||||
$(this).val('Show file list');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -20,52 +20,49 @@
|
|||||||
<a class="btn@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
|
<a class="btn@if(condition.state == "open"){ active}" href="@condition.copy(state = "open").toURL">@openCount Open</a>
|
||||||
<a class="btn@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
<a class="btn@if(condition.state == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
@helper.html.dropdown(
|
||||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
value = (condition.sort, condition.direction) match {
|
||||||
Sort:
|
case ("created" , "desc") => "Newest"
|
||||||
<strong>
|
case ("created" , "asc" ) => "Oldest"
|
||||||
@if(condition.sort == "created" && condition.direction == "desc"){ Newest }
|
case ("comments", "desc") => "Most commented"
|
||||||
@if(condition.sort == "created" && condition.direction == "asc" ){ Oldest }
|
case ("comments", "asc" ) => "Least commented"
|
||||||
@if(condition.sort == "comments" && condition.direction == "desc"){ Most commented }
|
case ("updated" , "desc") => "Recently updated"
|
||||||
@if(condition.sort == "comments" && condition.direction == "asc" ){ Least commented }
|
case ("updated" , "asc" ) => "Least recently updated"
|
||||||
@if(condition.sort == "updated" && condition.direction == "desc"){ Recently updated }
|
},
|
||||||
@if(condition.sort == "updated" && condition.direction == "asc" ){ Least recently updated }
|
prefix = "Sort",
|
||||||
</strong>
|
mini = false
|
||||||
<span class="caret"></span>
|
){
|
||||||
</button>
|
<li>
|
||||||
<ul class="dropdown-menu">
|
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
</li>
|
||||||
</a>
|
<li>
|
||||||
</li>
|
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||||
<li>
|
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
</a>
|
||||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
</li>
|
||||||
</a>
|
}
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<table class="table table-bordered table-hover table-issues">
|
<table class="table table-bordered table-hover table-issues">
|
||||||
@if(issues.isEmpty){
|
@if(issues.isEmpty){
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
9
src/main/twirl/pulls/mergecheck.scala.html
Normal file
9
src/main/twirl/pulls/mergecheck.scala.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@(hasConflict: Boolean)
|
||||||
|
@if(hasConflict){
|
||||||
|
<h4>We can’t automatically merge these branches</h4>
|
||||||
|
<p>Don't worry, you can still submit the pull request.</p>
|
||||||
|
} else {
|
||||||
|
<h4 style="color: #468847;">Able to merge</h4>
|
||||||
|
<p>These branches can be automatically merged.</p>
|
||||||
|
}
|
||||||
|
<input type="submit" class="btn btn-success btn-block" value="Send pull request"/>
|
||||||
84
src/main/twirl/pulls/mergeguide.scala.html
Normal file
84
src/main/twirl/pulls/mergeguide.scala.html
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
@(hasConflict: Boolean,
|
||||||
|
pullreq: model.PullRequest,
|
||||||
|
requestRepositoryUrl: String)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
<div class="pull-right">
|
||||||
|
<input type="button" class="btn btn-success" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict){ disabled="true"}/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
@if(hasConflict){
|
||||||
|
<span class="strong">We can’t automatically merge this pull request.</span>
|
||||||
|
} else {
|
||||||
|
<span class="strong">This pull request can be automatically merged.</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="small">
|
||||||
|
@if(hasConflict){
|
||||||
|
<a href="#" id="show-command-line">Use the command line</a> to resolve conflicts before continuing.
|
||||||
|
} else {
|
||||||
|
You can also merge branches on the <a href="#" id="show-command-line">command line</a>.
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div id="command-line" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
@if(hasConflict){
|
||||||
|
<span class="strong">Checkout via command line</span>
|
||||||
|
<p>
|
||||||
|
If you cannot merge a pull request automatically here, you have the option of checking
|
||||||
|
it out via command line to resolve conflicts and perform a manual merge.
|
||||||
|
</p>
|
||||||
|
} else {
|
||||||
|
<span class="strong">Merging via command line</span>
|
||||||
|
<p>
|
||||||
|
If you do not want to use the merge button or an automatic merge cannot be performed,
|
||||||
|
you can perform a manual merge on the command line.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
@helper.html.copy("repository-url-copy", requestRepositoryUrl){
|
||||||
|
<input type="text" value="@requestRepositoryUrl" id="repository-url" readonly>
|
||||||
|
}
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span class="strong">Step 1:</span> Check out a new branch to test the changes — run this from your project directory
|
||||||
|
</p>
|
||||||
|
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.requestBranch}"){ command =>
|
||||||
|
@helper.html.copy("merge-command-copy-1", command){
|
||||||
|
<pre style="width: 500px; float: left;">@command</pre>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span class="strong">Step 2:</span> Bring in @{pullreq.requestUserName}'s changes and test
|
||||||
|
</p>
|
||||||
|
@defining(s"git pull ${requestRepositoryUrl} ${pullreq.requestBranch}"){ command =>
|
||||||
|
@helper.html.copy("merge-command-copy-2", command){
|
||||||
|
<pre style="width: 500px; float: left;">@command</pre>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
<span class="strong">Step 3:</span> Merge the changes and update the server
|
||||||
|
</p>
|
||||||
|
@defining(s"git checkout master\ngit merge ${pullreq.requestUserName}-${pullreq.branch}\ngit push origin ${pullreq.branch}"){ command =>
|
||||||
|
@helper.html.copy("merge-command-copy-3", command){
|
||||||
|
<pre style="width: 500px; float: left;">@command</pre>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#show-command-line').click(function(){
|
||||||
|
$('#command-line').show();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#merge-pull-request-button').click(function(){
|
||||||
|
$('#merge-pull-request').hide();
|
||||||
|
$('#confirm-merge-form').show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -5,10 +5,8 @@
|
|||||||
milestones: List[(model.Milestone, Int, Int)],
|
milestones: List[(model.Milestone, Int, Int)],
|
||||||
dayByDayCommits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
dayByDayCommits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
||||||
diffs: Seq[util.JGitUtil.DiffInfo],
|
diffs: Seq[util.JGitUtil.DiffInfo],
|
||||||
hasConflict: Boolean,
|
|
||||||
hasWritePermission: Boolean,
|
hasWritePermission: Boolean,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||||
requestRepositoryUrl: String)(implicit context: app.Context)
|
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main("%s - Pull Request #%d - %s/%s".format(issue.title, issue.issueId, repository.owner, repository.name)){
|
@html.main("%s - Pull Request #%d - %s/%s".format(issue.title, issue.issueId, repository.owner, repository.name)){
|
||||||
@@ -39,13 +37,13 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active" id="discussion">
|
<div class="tab-pane active" id="discussion">
|
||||||
@pulls.html.discussion(issue, pullreq, comments, collaborators, milestones, hasConflict, hasWritePermission, repository, requestRepositoryUrl)
|
@pulls.html.discussion(issue, pullreq, comments, collaborators, milestones, hasWritePermission, repository)
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="commits">
|
<div class="tab-pane" id="commits">
|
||||||
@pulls.html.commits(issue, pullreq, dayByDayCommits, hasWritePermission, repository)
|
@pulls.html.commits(dayByDayCommits, repository)
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="files">
|
<div class="tab-pane" id="files">
|
||||||
@pulls.html.files(issue, pullreq, diffs, commits.head.id, commits.last.id, hasWritePermission, repository)
|
@helper.html.diff(diffs, repository, Some(commits.head.id), Some(commits.last.id), true)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@import util.Implicits._
|
@import util.Implicits._
|
||||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
|
||||||
@html.main(commit.shortMessage, Some(repository)){
|
@html.main(commit.shortMessage, Some(repository)){
|
||||||
@html.header("code", repository)
|
@html.header("code", repository)
|
||||||
@tab(commitId, repository, "commits")
|
@tab(commitId, repository, "commits")
|
||||||
@@ -71,52 +70,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<div>
|
@helper.html.diff(diffs, repository, Some(commit.id), oldCommitId, true)
|
||||||
<div class="pull-right" style="margin-bottom: 10px;">
|
|
||||||
<input id="toggle-file-list" type="button" class="btn" value="Show file list"/>
|
|
||||||
</div>
|
|
||||||
Showing @diffs.size changed @plural(diffs.size, "file")
|
|
||||||
@*
|
|
||||||
@if(diffs.size == 1){
|
|
||||||
Showing 1 changed file
|
|
||||||
} else {
|
|
||||||
Showing @diffs.size changed files
|
|
||||||
}
|
|
||||||
*@
|
|
||||||
</div>
|
|
||||||
<ul id="commit-file-list" style="display: none;">
|
|
||||||
@diffs.zipWithIndex.map { case (diff, i) =>
|
|
||||||
<li@if(i > 0){ class="border"}>
|
|
||||||
<a href="#diff-@i">
|
|
||||||
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
|
|
||||||
<img src="@assets/common/images/diff_move.png"/> @diff.oldPath -> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.ADD){
|
|
||||||
<img src="@assets/common/images/diff_add.png"/> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.MODIFY){
|
|
||||||
<img src="@assets/common/images/diff_edit.png"/> @diff.newPath
|
|
||||||
}
|
|
||||||
@if(diff.changeType == ChangeType.DELETE){
|
|
||||||
<img src="@assets/common/images/diff_delete.png"/> @diff.oldPath
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
@helper.html.diff(diffs, repository, Some(commit.id), oldCommitId)
|
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('#toggle-file-list').click(function(){
|
|
||||||
$('#commit-file-list').toggle();
|
|
||||||
if($(this).val() == 'Show file list'){
|
|
||||||
$(this).val('Hide file list');
|
|
||||||
} else {
|
|
||||||
$(this).val('Show file list');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('a.branch:first, a.tag:first').css({
|
$('a.branch:first, a.tag:first').css({
|
||||||
'font-weight': 'bold',
|
'font-weight': 'bold',
|
||||||
'color': '#555555'
|
'color': '#555555'
|
||||||
|
|||||||
@@ -5,25 +5,16 @@
|
|||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
@if(!hideBranchPulldown){
|
@if(!hideBranchPulldown){
|
||||||
<li>
|
<li>
|
||||||
<div class="btn-group" style="margin-right: 20px;">
|
@helper.html.dropdown(
|
||||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
value = if(id.length == 40) id.substring(0, 10) else id,
|
||||||
@if(id.length == 40){
|
prefix = if(id.length == 40) "tree" else if(repository.branchList.contains(id)) "branch" else "tree",
|
||||||
tree: <strong>@id.substring(0, 10)</strong>
|
mini = false,
|
||||||
}
|
style = "margin-right: 20px;"
|
||||||
@if(repository.branchList.contains(id)){
|
){
|
||||||
branch: <strong>@id</strong>
|
@repository.branchList.map { branch =>
|
||||||
}
|
<li><a href="@url(repository)/@if(active=="commits"){commits} else {tree}/@branch">@helper.html.checkicon(id == branch) @branch</a></li>
|
||||||
@if(repository.tags.exists(_.name == id)){
|
}
|
||||||
tag: <strong>@id</strong>
|
}
|
||||||
}
|
|
||||||
<span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
@repository.branchList.map { branch =>
|
|
||||||
<li><a href="@url(repository)/@if(active=="commits"){commits} else {tree}/@branch">@helper.html.checkicon(id == branch) @branch</a></li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li@if(active=="files" ){ class="active"}><a href="@url(repository)/tree/@id">Files</a></li>
|
<li@if(active=="files" ){ class="active"}><a href="@url(repository)/tree/@id">Files</a></li>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a>
|
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a>
|
||||||
at @datetime(issue.registeredDate)
|
at @datetime(issue.registeredDate)
|
||||||
@if(issue.commentCount > 0){
|
@if(issue.commentCount > 0){
|
||||||
<i class="icon-comment"></i><strong>@issue.commentCount</strong> @plural(issue.commentCount, "comment")
|
<i class="icon-comment"></i><span class="strong">@issue.commentCount</span> @plural(issue.commentCount, "comment")
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,21 +22,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<span class="error" id="error-userName"></span>
|
<span class="error" id="error-userName"></span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" name="userName" id="userName" style="width: 300px; margin-bottom: 0px;"/>
|
@helper.html.account("userName", 300)
|
||||||
<input type="submit" class="btn" value="Add"/>
|
<input type="submit" class="btn" value="Add"/>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<script>
|
|
||||||
$(function(){
|
|
||||||
$('#userName').typeahead({
|
|
||||||
source: function (query, process) {
|
|
||||||
return $.get('@path/_user/proposals', { query: query },
|
|
||||||
function (data) {
|
|
||||||
return process(data.options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
23
src/main/twirl/settings/hooks.scala.html
Normal file
23
src/main/twirl/settings/hooks.scala.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
@(webHooks: List[model.WebHook], repository: service.RepositoryService.RepositoryInfo, info: Option[Any])(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
@html.main("Settings", Some(repository)){
|
||||||
|
@html.header("settings", repository)
|
||||||
|
@menu("hooks", repository){
|
||||||
|
@helper.html.information(info)
|
||||||
|
<h3>WebHook URLs</h3>
|
||||||
|
<ul>
|
||||||
|
@webHooks.map { webHook =>
|
||||||
|
<li>@webHook.url <a href="@url(repository)/settings/hooks/delete?url=@urlEncode(webHook.url)" class="remove">(remove)</a></li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<form method="POST" action="@url(repository)/settings/hooks/add" validate="true">
|
||||||
|
<div>
|
||||||
|
<span class="error" id="error-url"></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" name="url" id="url" style="width: 300px; margin-bottom: 0px;"/>
|
||||||
|
<input type="submit" class="btn" value="Add"/>
|
||||||
|
<a href="@url(repository)/settings/hooks/test" class="btn">Test Hook</a>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@
|
|||||||
<li@if(active=="collaborators"){ class="active"}>
|
<li@if(active=="collaborators"){ class="active"}>
|
||||||
<a href="@url(repository)/settings/collaborators">Collaborators</a>
|
<a href="@url(repository)/settings/collaborators">Collaborators</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li@if(active=="hooks"){ class="active"}>
|
||||||
|
<a href="@url(repository)/settings/hooks">Service Hooks</a>
|
||||||
|
</li>
|
||||||
<li@if(active=="delete"){ class="active"}>
|
<li@if(active=="delete"){ class="active"}>
|
||||||
<a href="@url(repository)/settings/delete">Delete Repository</a>
|
<a href="@url(repository)/settings/delete">Delete Repository</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
<div class="box-header">Settings</div>
|
<div class="box-header">Settings</div>
|
||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="description"><strong>Description</strong></label>
|
<label for="description" class="strong">Description</label>
|
||||||
<input type="text" name="description" id="description" style="width: 600px;" value="@repository.repository.description"/>
|
<input type="text" name="description" id="description" style="width: 600px;" value="@repository.repository.description"/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="margin">
|
<fieldset class="margin">
|
||||||
<label for="defaultBranch"><strong>Default Branch</strong></label>
|
<label for="defaultBranch" class="strong">Default Branch</label>
|
||||||
<select name="defaultBranch" id="defaultBranch">
|
<select name="defaultBranch" id="defaultBranch">
|
||||||
@repository.branchList.map { branch =>
|
@repository.branchList.map { branch =>
|
||||||
<option value="@branch"@if(branch==repository.repository.defaultBranch){ selected}>@branch</option>
|
<option value="@branch"@if(branch==repository.repository.defaultBranch){ selected}>@branch</option>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
@if(!repository.repository.isPrivate ){ checked }
|
@if(!repository.repository.isPrivate ){ checked }
|
||||||
@if(repository.repository.parentUserName.isDefined){ disabled }
|
@if(repository.repository.parentUserName.isDefined){ disabled }
|
||||||
>
|
>
|
||||||
<strong>Public</strong><br>
|
<span class="strong">Public</span><br>
|
||||||
<div>
|
<div>
|
||||||
<span class="note">All users and guests can read this repository.</span>
|
<span class="note">All users and guests can read this repository.</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
@if(repository.repository.isPrivate ){ checked }
|
@if(repository.repository.isPrivate ){ checked }
|
||||||
@if(repository.repository.parentUserName.isDefined){ disabled }
|
@if(repository.repository.parentUserName.isDefined){ disabled }
|
||||||
>
|
>
|
||||||
<strong>Private</strong><br>
|
<span class="strong">Private</span><br>
|
||||||
<div>
|
<div>
|
||||||
<span class="note">Only collaborators can read this repository.</span>
|
<span class="note">Only collaborators can read this repository.</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
<div class="box-content">
|
<div class="box-content">
|
||||||
<dl>
|
<dl>
|
||||||
<dt>
|
<dt>
|
||||||
<label class="checkbox">
|
<label class="checkbox strong">
|
||||||
<input type="checkbox" name="wiki" id="wiki"/> <strong>Wiki</strong>
|
<input type="checkbox" name="wiki" id="wiki"/> Wiki
|
||||||
</label>
|
</label>
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
@@ -67,8 +67,8 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>
|
<dt>
|
||||||
<label class="checkbox">
|
<label class="checkbox strong">
|
||||||
<input type="checkbox" name="issue" id="issue"/> <strong>Issue</strong>
|
<input type="checkbox" name="issue" id="issue"/> Issue
|
||||||
</label>
|
</label>
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
|
|||||||
@@ -22,5 +22,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@helper.html.diff(diffs, repository, None, None)
|
@helper.html.diff(diffs, repository, None, None, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li@if(active == "home"){ class="active"}><a href="@url(repository)/wiki">Home</a></li>
|
<li@if(active == "home" ){ class="active"}><a href="@url(repository)/wiki">Home</a></li>
|
||||||
<li@if(active == "pages"){ class="active"}><a href="@url(repository)/wiki/_pages">Pages</a></li>
|
<li@if(active == "pages" ){ class="active"}><a href="@url(repository)/wiki/_pages">Pages</a></li>
|
||||||
<li@if(active == "history"){ class="active"}><a href="@url(repository)/wiki/_history">Wiki History</a></li>
|
<li@if(active == "history"){ class="active"}><a href="@url(repository)/wiki/_history">Wiki History</a></li>
|
||||||
<li class="pull-right">
|
<li class="pull-right">
|
||||||
@defining(repository.url.replaceFirst("\\.git$", ".wiki.git")){ repositoryUrl =>
|
@defining(repository.url.replaceFirst("\\.git$", ".wiki.git")){ repositoryUrl =>
|
||||||
|
|||||||
Reference in New Issue
Block a user