mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 16:47:00 +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:
|
||||
|
||||
- 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)
|
||||
- Wiki
|
||||
- Issues
|
||||
@@ -15,9 +15,13 @@ The current version of GitBucket provides a basic features below:
|
||||
- Activity timeline
|
||||
- User management (for Administrators)
|
||||
- Group (like Organization in Github)
|
||||
- LDAP integration
|
||||
- Gravatar support
|
||||
|
||||
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
|
||||
- Statics
|
||||
- Watch / Star
|
||||
@@ -33,10 +37,22 @@ Installation
|
||||
|
||||
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
|
||||
--------
|
||||
### 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
|
||||
- Fork and pull request.
|
||||
- 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.apache.commons" % "commons-compress" % "1.5",
|
||||
"org.apache.commons" % "commons-email" % "1.3.1",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.2.5",
|
||||
"com.typesafe.slick" %% "slick" % "1.0.1",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.3.171",
|
||||
"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"))
|
||||
),
|
||||
EclipseKeys.withSource := true
|
||||
EclipseKeys.withSource := true,
|
||||
packageOptions += Package.MainClass("JettyLauncher")
|
||||
) ++ 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"?>
|
||||
<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>
|
||||
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
|
||||
|
||||
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.json._
|
||||
import org.json4s._
|
||||
@@ -32,7 +34,7 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
val path = httpRequest.getRequestURI.substring(context.length)
|
||||
|
||||
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){
|
||||
// Redirect to login form
|
||||
httpResponse.sendRedirect(context + "/signin?" + path)
|
||||
@@ -57,56 +59,47 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
*/
|
||||
implicit def context: Context = Context(servletContext.getContextPath, LoginAccount, currentURL, request)
|
||||
|
||||
private def currentURL: String = {
|
||||
val queryString = request.getQueryString
|
||||
private def currentURL: String = defining(request.getQueryString){ queryString =>
|
||||
request.getRequestURI + (if(queryString != null) "?" + queryString else "")
|
||||
}
|
||||
|
||||
private def LoginAccount: Option[Account] = {
|
||||
session.get("LOGIN_ACCOUNT") match {
|
||||
case Some(x: Account) => Some(x)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
private def LoginAccount: Option[Account] = session.getAs[Account](Keys.Session.LoginAccount)
|
||||
|
||||
def ajaxGet(path : String)(action : => Any) : Route = {
|
||||
def ajaxGet(path : String)(action : => Any) : Route =
|
||||
super.get(path){
|
||||
request.setAttribute("AJAX", "true")
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
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 =>
|
||||
request.setAttribute("AJAX", "true")
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action(form)
|
||||
}
|
||||
}
|
||||
|
||||
def ajaxPost(path : String)(action : => Any) : Route = {
|
||||
def ajaxPost(path : String)(action : => Any) : Route =
|
||||
super.post(path){
|
||||
request.setAttribute("AJAX", "true")
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
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 =>
|
||||
request.setAttribute("AJAX", "true")
|
||||
request.setAttribute(Keys.Request.Ajax, "true")
|
||||
action(form)
|
||||
}
|
||||
}
|
||||
|
||||
protected def NotFound() = {
|
||||
if(request.getAttribute("AJAX") == null){
|
||||
org.scalatra.NotFound(html.error("Not Found"))
|
||||
} else {
|
||||
protected def NotFound() =
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
org.scalatra.NotFound()
|
||||
} else {
|
||||
org.scalatra.NotFound(html.error("Not Found"))
|
||||
}
|
||||
}
|
||||
|
||||
protected def Unauthorized()(implicit context: app.Context) = {
|
||||
if(request.getAttribute("AJAX") == null){
|
||||
protected def Unauthorized()(implicit context: app.Context) =
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
org.scalatra.Unauthorized()
|
||||
} else {
|
||||
if(context.loginAccount.isDefined){
|
||||
org.scalatra.Unauthorized(redirect("/"))
|
||||
} else {
|
||||
@@ -116,13 +109,9 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
org.scalatra.Unauthorized(redirect("/signin?redirect=" + currentURL))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
org.scalatra.Unauthorized()
|
||||
}
|
||||
}
|
||||
|
||||
protected def baseUrl = {
|
||||
val url = request.getRequestURL.toString
|
||||
protected def baseUrl = defining(request.getRequestURL.toString){ url =>
|
||||
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){
|
||||
|
||||
def redirectUrl = {
|
||||
if(request.getParameter("redirect") != null){
|
||||
request.getParameter("redirect")
|
||||
} else {
|
||||
currentUrl
|
||||
}
|
||||
def redirectUrl = if(request.getParameter("redirect") != null){
|
||||
request.getParameter("redirect")
|
||||
} else {
|
||||
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.
|
||||
* Cached object are available during a request.
|
||||
*/
|
||||
def cache[A](key: String)(action: => A): A = {
|
||||
Option(request.getAttribute("cache." + key).asInstanceOf[A]).getOrElse {
|
||||
val newObject = action
|
||||
request.setAttribute("cache." + key, newObject)
|
||||
newObject
|
||||
def cache[A](key: String)(action: => A): A =
|
||||
defining(Keys.Request.Cache(key)){ cacheKey =>
|
||||
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
||||
val newObject = action
|
||||
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 {
|
||||
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){
|
||||
getAccountByUserName(userName).flatMap(_.image).map { image =>
|
||||
new java.io.File(getUserUploadDir(userName), image).delete()
|
||||
@@ -179,7 +167,6 @@ trait AccountManagementControllerBase extends ControllerBase with FileUploadCont
|
||||
updateAvatarImage(userName, Some(filename))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected def uniqueUserName: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String): Option[String] =
|
||||
@@ -215,12 +202,7 @@ trait FileUploadControllerBase {
|
||||
def removeTemporaryFiles()(implicit session: HttpSession): Unit =
|
||||
FileUtils.deleteDirectory(TemporaryDir)
|
||||
|
||||
def getUploadedFilename(fileId: String)(implicit session: HttpSession): Option[String] = {
|
||||
val filename = Option(session.getAttribute("upload_" + fileId).asInstanceOf[String])
|
||||
if(filename.isDefined){
|
||||
session.removeAttribute("upload_" + fileId)
|
||||
}
|
||||
filename
|
||||
}
|
||||
def getUploadedFilename(fileId: String)(implicit session: HttpSession): Option[String] =
|
||||
session.getAndRemove[String](Keys.Session.Upload(fileId))
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import util._
|
||||
import service._
|
||||
import java.io.File
|
||||
@@ -149,7 +150,7 @@ trait CreateRepositoryControllerBase extends ControllerBase {
|
||||
getWikiRepositoryDir(loginUserName, repository.name))
|
||||
|
||||
// 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.getCommitLog(git, branch) match {
|
||||
case Right((commits, _)) => commits.foreach { commit =>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package app
|
||||
|
||||
import service._
|
||||
import util.UsersAuthenticator
|
||||
import util.{UsersAuthenticator, Keys}
|
||||
import util.Implicits._
|
||||
|
||||
class DashboardController extends DashboardControllerBase
|
||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
||||
@@ -42,12 +43,10 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
import IssuesService._
|
||||
|
||||
// condition
|
||||
val sessionKey = "dashboard/issues"
|
||||
val condition = if(request.getQueryString == null)
|
||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
||||
else IssueSearchCondition(request)
|
||||
|
||||
session.put(sessionKey, condition)
|
||||
val condition = session.putAndGet(Keys.Session.DashboardIssues,
|
||||
if(request.hasQueryString) IssueSearchCondition(request)
|
||||
else session.getAs[IssueSearchCondition](Keys.Session.DashboardIssues).getOrElse(IssueSearchCondition())
|
||||
)
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name)
|
||||
@@ -75,15 +74,10 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
import PullRequestService._
|
||||
|
||||
// condition
|
||||
val sessionKey = "dashboard/pulls"
|
||||
val condition = {
|
||||
if(request.getQueryString == null)
|
||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
||||
else
|
||||
IssueSearchCondition(request)
|
||||
}.copy(repo = repository)
|
||||
|
||||
session.put(sessionKey, condition)
|
||||
val condition = session.putAndGet(Keys.Session.DashboardPulls, {
|
||||
if(request.hasQueryString) IssueSearchCondition(request)
|
||||
else session.getAs[IssueSearchCondition](Keys.Session.DashboardPulls).getOrElse(IssueSearchCondition())
|
||||
}.copy(repo = repository))
|
||||
|
||||
val userName = context.loginAccount.get.userName
|
||||
val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import util.{FileUtil}
|
||||
import _root_.util.{Keys, FileUtil}
|
||||
import util.ControlUtil._
|
||||
import org.scalatra._
|
||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport}
|
||||
import org.apache.commons.io.FileUtils
|
||||
@@ -18,10 +19,9 @@ class FileUploadController extends ScalatraServlet
|
||||
|
||||
post("/image"){
|
||||
fileParams.get("file") match {
|
||||
case Some(file) if(FileUtil.isImage(file.name)) => {
|
||||
val fileId = generateFileId
|
||||
case Some(file) if(FileUtil.isImage(file.name)) => defining(generateFileId){ fileId =>
|
||||
FileUtils.writeByteArrayToFile(getTemporaryFile(fileId), file.get)
|
||||
session += "upload_" + fileId -> file.name
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
Ok(fileId)
|
||||
}
|
||||
case None => BadRequest
|
||||
|
||||
@@ -4,7 +4,9 @@ import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
import service._
|
||||
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
|
||||
|
||||
class IssuesController extends IssuesControllerBase
|
||||
@@ -57,12 +59,9 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val issueId = params("id")
|
||||
|
||||
getIssue(owner, name, issueId) map {
|
||||
issues.html.issue(
|
||||
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
||||
getIssue(owner, name, issueId) map {
|
||||
issues.html.issue(
|
||||
_,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
getIssueLabels(owner, name, issueId.toInt),
|
||||
@@ -71,65 +70,64 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository)
|
||||
} getOrElse NotFound
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
issues.html.create(
|
||||
(getCollaborators(owner, name) :+ owner).sorted,
|
||||
getMilestones(owner, name),
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository)
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
issues.html.create(
|
||||
(getCollaborators(owner, name) :+ owner).sorted,
|
||||
getMilestones(owner, name),
|
||||
getLabels(owner, name),
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository)
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
||||
val userName = context.loginAccount.get.userName
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
val writable = hasWritePermission(owner, name, context.loginAccount)
|
||||
val userName = context.loginAccount.get.userName
|
||||
|
||||
// insert issue
|
||||
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
||||
if(writable) form.assignedUserName else None,
|
||||
if(writable) form.milestoneId else None)
|
||||
// insert issue
|
||||
val issueId = createIssue(owner, name, userName, form.title, form.content,
|
||||
if(writable) form.assignedUserName else None,
|
||||
if(writable) form.milestoneId else None)
|
||||
|
||||
// insert labels
|
||||
if(writable){
|
||||
form.labelNames.map { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
registerIssueLabel(owner, name, issueId, label.labelId)
|
||||
// insert labels
|
||||
if(writable){
|
||||
form.labelNames.map { value =>
|
||||
val labels = getLabels(owner, name)
|
||||
value.split(",").foreach { labelName =>
|
||||
labels.find(_.labelName == labelName).map { label =>
|
||||
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) =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
updateIssue(owner, name, issue.issueId, form.title, form.content)
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getIssue(owner, name, params("id")).map { issue =>
|
||||
if(isEditable(owner, name, issue.openedUserName)){
|
||||
updateIssue(owner, name, issue.issueId, form.title, form.content)
|
||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
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) =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
updateComment(comment.commentId, form.content)
|
||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if(isEditable(owner, name, comment.commentedUserName)){
|
||||
updateComment(comment.commentId, form.content)
|
||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
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 =>
|
||||
val issueId = params("id").toInt
|
||||
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
defining(params("id").toInt){ issueId =>
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
||||
val issueId = params("id").toInt
|
||||
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
defining(params("id").toInt){ issueId =>
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
||||
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
}
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
|
||||
@@ -223,36 +220,36 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
|
||||
val action = params.get("value")
|
||||
|
||||
executeBatch(repository) {
|
||||
handleComment(_, None, repository)( _ => action)
|
||||
defining(params.get("value")){ action =>
|
||||
executeBatch(repository) {
|
||||
handleComment(_, None, repository)( _ => action)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
|
||||
val labelId = params("value").toInt
|
||||
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
||||
defining(params("value").toInt){ labelId =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
|
||||
val value = assignedUserName("value")
|
||||
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value)
|
||||
defining(assignedUserName("value")){ value =>
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
|
||||
val value = milestoneId("value")
|
||||
|
||||
executeBatch(repository) {
|
||||
updateMilestoneId(repository.owner, repository.name, _, value)
|
||||
defining(milestoneId("value")){ value =>
|
||||
executeBatch(repository) {
|
||||
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)
|
||||
(getAction: model.Issue => Option[String] =
|
||||
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 =>
|
||||
val (action, recordActivity) =
|
||||
getAction(issue)
|
||||
.collect {
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
val userName = context.loginAccount.get.userName
|
||||
|
||||
getIssue(owner, name, issueId.toString) map { issue =>
|
||||
val (action, recordActivity) =
|
||||
getAction(issue)
|
||||
.collect {
|
||||
case "close" => true -> (Some("close") ->
|
||||
Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
||||
Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
|
||||
case "reopen" => false -> (Some("reopen") ->
|
||||
Some(recordReopenIssueActivity _))
|
||||
Some(recordReopenIssueActivity _))
|
||||
}
|
||||
.map { case (closed, t) =>
|
||||
.map { case (closed, t) =>
|
||||
updateClosed(owner, name, issueId, closed)
|
||||
t
|
||||
}
|
||||
.getOrElse(None -> None)
|
||||
.getOrElse(None -> None)
|
||||
|
||||
val commentId = content
|
||||
val commentId = content
|
||||
.map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
|
||||
.getOrElse ( action.get.capitalize -> action.get )
|
||||
match {
|
||||
case (content, action) => createComment(owner, name, userName, issueId, content, action)
|
||||
}
|
||||
match {
|
||||
case (content, action) => createComment(owner, name, userName, issueId, content, action)
|
||||
}
|
||||
|
||||
// record activity
|
||||
content foreach {
|
||||
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
|
||||
(owner, name, userName, issueId, _)
|
||||
}
|
||||
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
|
||||
// record activity
|
||||
content foreach {
|
||||
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
|
||||
(owner, name, userName, issueId, _)
|
||||
}
|
||||
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
|
||||
|
||||
// notifications
|
||||
Notifier() match {
|
||||
case f =>
|
||||
content foreach {
|
||||
f.toNotify(repository, issueId, _){
|
||||
Notifier.msgComment(s"${baseUrl}/${owner}/${name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
|
||||
// notifications
|
||||
Notifier() match {
|
||||
case f =>
|
||||
content foreach {
|
||||
f.toNotify(repository, issueId, _){
|
||||
Notifier.msgComment(s"${baseUrl}/${owner}/${name}/${
|
||||
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
action foreach {
|
||||
f.toNotify(repository, issueId, _){
|
||||
Notifier.msgStatus(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||
action foreach {
|
||||
f.toNotify(repository, issueId, _){
|
||||
Notifier.msgStatus(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issue -> commentId
|
||||
issue -> commentId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
|
||||
val owner = repository.owner
|
||||
val repoName = repository.name
|
||||
val filterUser = Map(filter -> params.getOrElse("userName", ""))
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = s"${owner}/${repoName}/issues"
|
||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||
val filterUser = Map(filter -> params.getOrElse("userName", ""))
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = Keys.Session.Issues(owner, repoName)
|
||||
|
||||
// retrieve search condition
|
||||
val condition = if(request.getQueryString == null){
|
||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
||||
} else IssueSearchCondition(request)
|
||||
// retrieve search condition
|
||||
val condition = session.putAndGet(sessionKey,
|
||||
if(request.hasQueryString) 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),
|
||||
page,
|
||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
||||
getMilestones(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
|
||||
countIssue(condition, Map.empty, false, owner -> repoName),
|
||||
context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
|
||||
context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
|
||||
countIssueGroupByLabels(owner, repoName, condition, filterUser),
|
||||
condition,
|
||||
filter,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
issues.html.list(
|
||||
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
page,
|
||||
(getCollaborators(owner, repoName) :+ owner).sorted,
|
||||
getMilestones(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
|
||||
countIssue(condition, Map.empty, false, owner -> repoName),
|
||||
context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
|
||||
context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
|
||||
countIssueGroupByLabels(owner, repoName, condition, filterUser),
|
||||
condition,
|
||||
filter,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package app
|
||||
|
||||
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator, Notifier}
|
||||
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator, Notifier, Keys}
|
||||
import util.Directory._
|
||||
import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
import service._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
@@ -62,117 +63,250 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
val issueId = params("id").toInt
|
||||
defining(repository.owner, repository.name, params("id").toInt){ case (owner, name, issueId) =>
|
||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||
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) =>
|
||||
JGitUtil.withGit(getRepositoryDir(owner, name)){ git =>
|
||||
val requestCommitId = git.getRepository.resolve(pullreq.requestBranch)
|
||||
|
||||
val (commits, diffs) =
|
||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||
|
||||
pulls.html.pullreq(
|
||||
issue, pullreq,
|
||||
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)
|
||||
pulls.html.pullreq(
|
||||
issue, pullreq,
|
||||
getComments(owner, name, issueId.toInt),
|
||||
(getCollaborators(owner, name) :+ owner).sorted,
|
||||
getMilestonesWithIssueCount(owner, name),
|
||||
commits,
|
||||
diffs,
|
||||
hasWritePermission(owner, name, context.loginAccount),
|
||||
repository)
|
||||
}
|
||||
} 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.
|
||||
* Returns true if conflict will be caused.
|
||||
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
|
||||
*/
|
||||
private def checkConflict(userName: String, repositoryName: String, branch: String,
|
||||
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.
|
||||
*
|
||||
@@ -366,9 +372,9 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
|
||||
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = {
|
||||
|
||||
withGit(
|
||||
getRepositoryDir(userName, repositoryName),
|
||||
getRepositoryDir(requestUserName, requestRepositoryName)
|
||||
using(
|
||||
Git.open(getRepositoryDir(userName, repositoryName)),
|
||||
Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
|
||||
){ (oldGit, newGit) =>
|
||||
val oldId = oldGit.getRepository.resolve(branch)
|
||||
val newId = newGit.getRepository.resolve(requestCommitId)
|
||||
@@ -385,31 +391,29 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
|
||||
val owner = repository.owner
|
||||
val repoName = repository.name
|
||||
val filterUser = userName.map { x => Map("created_by" -> x) } getOrElse Map("all" -> "")
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = s"${owner}/${repoName}/pulls"
|
||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
||||
defining(repository.owner, repository.name){ case (owner, repoName) =>
|
||||
val filterUser = userName.map { x => Map("created_by" -> x) } getOrElse Map("all" -> "")
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val sessionKey = Keys.Session.Pulls(owner, repoName)
|
||||
|
||||
// retrieve search condition
|
||||
val condition = if(request.getQueryString == null){
|
||||
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
|
||||
} else IssueSearchCondition(request)
|
||||
// retrieve search condition
|
||||
val condition = session.putAndGet(sessionKey,
|
||||
if(request.hasQueryString) 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),
|
||||
getPullRequestCountGroupByUser(condition.state == "closed", owner, Some(repoName)),
|
||||
userName,
|
||||
page,
|
||||
countIssue(condition.copy(state = "open"), filterUser, true, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, true, owner -> repoName),
|
||||
countIssue(condition, Map.empty, true, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
}
|
||||
pulls.html.list(
|
||||
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||
getPullRequestCountGroupByUser(condition.state == "closed", owner, Some(repoName)),
|
||||
userName,
|
||||
page,
|
||||
countIssue(condition.copy(state = "open" ), filterUser, true, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), filterUser, true, owner -> repoName),
|
||||
countIssue(condition, Map.empty, true, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
hasWritePermission(owner, repoName, context.loginAccount))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,13 +6,20 @@ import util.{UsersAuthenticator, OwnerAuthenticator}
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.apache.commons.io.FileUtils
|
||||
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
|
||||
with RepositoryService with AccountService with OwnerAuthenticator with UsersAuthenticator
|
||||
with RepositoryService with AccountService with WebHookService
|
||||
with OwnerAuthenticator with UsersAuthenticator
|
||||
|
||||
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)
|
||||
|
||||
val optionsForm = mapping(
|
||||
@@ -20,13 +27,21 @@ trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSuppo
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
||||
)(OptionsForm.apply)
|
||||
|
||||
|
||||
// for collaborator addition
|
||||
case class CollaboratorForm(userName: String)
|
||||
|
||||
val collaboratorForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, collaborator)))
|
||||
)(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.
|
||||
*/
|
||||
@@ -88,6 +103,53 @@ trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSuppo
|
||||
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.
|
||||
*/
|
||||
@@ -108,19 +170,25 @@ trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSuppo
|
||||
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.
|
||||
*/
|
||||
private def collaborator: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String): Option[String] = {
|
||||
val paths = request.getRequestURI.split("/")
|
||||
override def validate(name: String, value: String): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
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.")
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package app
|
||||
|
||||
import util.Directory._
|
||||
import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
import _root_.util.{ReferrerAuthenticator, JGitUtil, FileUtil, StringUtil}
|
||||
import service._
|
||||
import org.scalatra._
|
||||
@@ -56,7 +57,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val (branchName, path) = splitPath(repository, multiParams("splat").head)
|
||||
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 {
|
||||
case Right((logs, hasNext)) =>
|
||||
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 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))
|
||||
|
||||
@scala.annotation.tailrec
|
||||
@@ -84,19 +85,25 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
case true => getPathObjectId(path, walk)
|
||||
}
|
||||
|
||||
val treeWalk = new TreeWalk(git.getRepository)
|
||||
val objectId = try {
|
||||
val objectId = using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
treeWalk.setRecursive(true)
|
||||
getPathObjectId(path, treeWalk)
|
||||
} finally {
|
||||
treeWalk.release
|
||||
}
|
||||
|
||||
if(raw){
|
||||
// Download
|
||||
contentType = "application/octet-stream"
|
||||
JGitUtil.getContent(git, objectId, false).get
|
||||
val mimeType = FileUtil.getMimeType(path)
|
||||
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 {
|
||||
// Viewer
|
||||
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 =>
|
||||
val id = params("id")
|
||||
|
||||
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
|
||||
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
|
||||
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
repository, diffs, oldCommitId)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))){ revCommit =>
|
||||
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
|
||||
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
repository, diffs, oldCommitId)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -143,7 +150,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* Displays branches.
|
||||
*/
|
||||
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
|
||||
val branchInfo = repository.branchList.map { branchName =>
|
||||
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
|
||||
val cloneDir = new File(workDir, revision)
|
||||
JGitUtil.withGit(Git.cloneRepository
|
||||
using(Git.cloneRepository
|
||||
.setURI(getRepositoryDir(repository.owner, repository.name).toURI.toString)
|
||||
.setDirectory(cloneDir)
|
||||
.call){ git =>
|
||||
@@ -233,23 +240,23 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
if(repository.commitCount == 0){
|
||||
repo.html.guide(repository)
|
||||
} 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)
|
||||
// get specified commit
|
||||
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, objectId)
|
||||
|
||||
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
|
||||
defining(JGitUtil.getRevCommitFromId(git, objectId)){ revCommit =>
|
||||
// get files
|
||||
val files = JGitUtil.getFileList(git, revision, path)
|
||||
// process README.md
|
||||
val readme = files.find(_.name == "README.md").map { file =>
|
||||
StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
||||
}
|
||||
val files = JGitUtil.getFileList(git, revision, path)
|
||||
// process README.md
|
||||
val readme = files.find(_.name == "README.md").map { file =>
|
||||
StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
||||
}
|
||||
|
||||
repo.html.files(revision, repository,
|
||||
if(path == ".") Nil else path.split("/").toList, // current path
|
||||
new JGitUtil.CommitInfo(revCommit), // latest commit
|
||||
files, readme)
|
||||
repo.html.files(revision, repository,
|
||||
if(path == ".") Nil else path.split("/").toList, // current path
|
||||
new JGitUtil.CommitInfo(revCommit), // latest commit
|
||||
files, readme)
|
||||
}
|
||||
} getOrElse NotFound
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import util._
|
||||
import ControlUtil._
|
||||
import service._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
@@ -26,25 +27,25 @@ trait SearchControllerBase extends ControllerBase { self: RepositoryService
|
||||
}
|
||||
|
||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||
val query = params("q").trim
|
||||
val target = params.getOrElse("type", "code")
|
||||
val page = try {
|
||||
val i = params.getOrElse("page", "1").toInt
|
||||
if(i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
|
||||
val page = try {
|
||||
val i = params.getOrElse("page", "1").toInt
|
||||
if(i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
|
||||
target.toLowerCase match {
|
||||
case "issue" => search.html.issues(
|
||||
searchIssues(repository.owner, repository.name, query),
|
||||
countFiles(repository.owner, repository.name, query),
|
||||
query, page, repository)
|
||||
target.toLowerCase match {
|
||||
case "issue" => search.html.issues(
|
||||
searchIssues(repository.owner, repository.name, query),
|
||||
countFiles(repository.owner, repository.name, query),
|
||||
query, page, repository)
|
||||
|
||||
case _ => search.html.code(
|
||||
searchFiles(repository.owner, repository.name, query),
|
||||
countIssues(repository.owner, repository.name, query),
|
||||
query, page, repository)
|
||||
case _ => search.html.code(
|
||||
searchFiles(repository.owner, repository.name, query),
|
||||
countIssues(repository.owner, repository.name, query),
|
||||
query, page, repository)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package app
|
||||
|
||||
import service._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import util.Implicits._
|
||||
import util.Keys
|
||||
|
||||
class SignInController extends SignInControllerBase with SystemSettingsService with AccountService
|
||||
|
||||
@@ -17,16 +19,15 @@ trait SignInControllerBase extends ControllerBase { self: SystemSettingsService
|
||||
get("/signin"){
|
||||
val redirect = params.get("redirect")
|
||||
if(redirect.isDefined && redirect.get.startsWith("/")){
|
||||
session.setAttribute("REDIRECT", redirect.get)
|
||||
session.setAttribute(Keys.Session.Redirect, redirect.get)
|
||||
}
|
||||
html.signin(loadSystemSettings())
|
||||
}
|
||||
|
||||
post("/signin", form){ form =>
|
||||
val settings = loadSystemSettings()
|
||||
authenticate(loadSystemSettings(), form.userName, form.password) match {
|
||||
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.
|
||||
*/
|
||||
private def signin(account: model.Account) = {
|
||||
session.setAttribute("LOGIN_ACCOUNT", account)
|
||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||
updateLastLoginDate(account.userName)
|
||||
|
||||
session.get("REDIRECT").map { redirectUrl =>
|
||||
session.removeAttribute("REDIRECT")
|
||||
redirect(redirectUrl.asInstanceOf[String])
|
||||
session.getAndRemove[String](Keys.Session.Redirect).map { redirectUrl =>
|
||||
if(redirectUrl.replaceFirst("/$", "") == request.getContextPath){
|
||||
redirect("/")
|
||||
} else {
|
||||
redirect(redirectUrl)
|
||||
}
|
||||
}.getOrElse {
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package app
|
||||
import service._
|
||||
import util.AdminAuthenticator
|
||||
import util.StringUtil._
|
||||
import util.ControlUtil._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
|
||||
class UserManagementController extends UserManagementControllerBase
|
||||
@@ -107,29 +108,29 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||
val groupName = params("groupName")
|
||||
admin.users.html.group(getAccountByUserName(groupName), getGroupMembers(groupName))
|
||||
defining(params("groupName")){ groupName =>
|
||||
admin.users.html.group(getAccountByUserName(groupName), getGroupMembers(groupName))
|
||||
}
|
||||
})
|
||||
|
||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
||||
val groupName = params("groupName")
|
||||
getAccountByUserName(groupName).map { account =>
|
||||
updateGroup(groupName, form.url)
|
||||
defining(params("groupName"), form.memberNames.map(_.split(",").toList).getOrElse(Nil)){ case (groupName, memberNames) =>
|
||||
getAccountByUserName(groupName).map { account =>
|
||||
updateGroup(groupName, form.url)
|
||||
updateGroupMembers(form.groupName, memberNames)
|
||||
|
||||
val memberNames = form.memberNames.map(_.split(",").toList).getOrElse(Nil)
|
||||
updateGroupMembers(form.groupName, memberNames)
|
||||
|
||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
removeCollaborators(form.groupName, repositoryName)
|
||||
memberNames.foreach { userName =>
|
||||
addCollaborator(form.groupName, repositoryName, userName)
|
||||
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||
removeCollaborators(form.groupName, repositoryName)
|
||||
memberNames.foreach { userName =>
|
||||
addCollaborator(form.groupName, repositoryName, userName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
redirect("/admin/users")
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
redirect("/admin/users")
|
||||
|
||||
} getOrElse NotFound
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
post("/admin/users/_usercheck")(adminOnly {
|
||||
|
||||
@@ -3,7 +3,9 @@ package app
|
||||
import service._
|
||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, JGitUtil, StringUtil}
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
class WikiController extends WikiControllerBase
|
||||
with WikiService with RepositoryService with AccountService with ActivityService
|
||||
@@ -40,13 +42,13 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||
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 =>
|
||||
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 {
|
||||
case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository)
|
||||
case Left(_) => NotFound
|
||||
@@ -58,7 +60,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
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)
|
||||
}
|
||||
})
|
||||
@@ -66,7 +68,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
||||
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)
|
||||
}
|
||||
})
|
||||
@@ -120,7 +122,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
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 {
|
||||
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
|
||||
case Left(_) => NotFound
|
||||
@@ -130,8 +132,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
||||
getFileContent(repository.owner, repository.name, multiParams("splat").head).map { content =>
|
||||
contentType = "application/octet-stream"
|
||||
content
|
||||
contentType = "application/octet-stream"
|
||||
content
|
||||
} 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 Database.threadLocalSession
|
||||
|
||||
import model._
|
||||
import util.ControlUtil._
|
||||
|
||||
trait PullRequestService { self: IssuesService =>
|
||||
import PullRequestService._
|
||||
|
||||
def getPullRequest(owner: String, repository: String, issueId: Int): Option[(Issue, PullRequest)] = {
|
||||
val issue = getIssue(owner, repository, issueId.toString)
|
||||
if(issue.isDefined){
|
||||
Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId)).firstOption match {
|
||||
case Some(pullreq) => Some((issue.get, pullreq))
|
||||
case None => None
|
||||
}
|
||||
} else None
|
||||
}
|
||||
def getPullRequest(owner: String, repository: String, issueId: Int): Option[(Issue, PullRequest)] =
|
||||
defining(getIssue(owner, repository, issueId.toString)){ issue =>
|
||||
if(issue.isDefined){
|
||||
Query(PullRequests).filter(_.byPrimaryKey(owner, repository, issueId)).firstOption match {
|
||||
case Some(pullreq) => Some((issue.get, pullreq))
|
||||
case None => None
|
||||
}
|
||||
} else None
|
||||
}
|
||||
|
||||
def getPullRequestCountGroupByUser(closed: Boolean, owner: String, repository: Option[String]): List[PullRequestCount] =
|
||||
Query(PullRequests)
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import util.{FileUtil, StringUtil, JGitUtil}
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import model.Issue
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
@@ -9,7 +10,8 @@ import scala.collection.mutable.ListBuffer
|
||||
import org.eclipse.jgit.lib.FileMode
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
trait RepositorySearchService { self: IssuesService =>
|
||||
trait
|
||||
RepositorySearchService { self: IssuesService =>
|
||||
import RepositorySearchService._
|
||||
|
||||
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 =
|
||||
JGitUtil.withGit(getRepositoryDir(owner, repository)){ git =>
|
||||
using(Git.open(getRepositoryDir(owner, repository))){ git =>
|
||||
if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
|
||||
}
|
||||
|
||||
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)){
|
||||
Nil
|
||||
} else {
|
||||
|
||||
@@ -47,8 +47,10 @@ trait RepositoryService { self: AccountService =>
|
||||
Labels .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueComments .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Issues .filter(_.byRepository(userName, repositoryName)).delete
|
||||
PullRequests .filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueId .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Milestones .filter(_.byRepository(userName, repositoryName)).delete
|
||||
WebHooks .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Repositories .filter(_.byRepository(userName, repositoryName)).delete
|
||||
}
|
||||
|
||||
|
||||
@@ -1,73 +1,76 @@
|
||||
package service
|
||||
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import SystemSettingsService._
|
||||
|
||||
trait SystemSettingsService {
|
||||
|
||||
def saveSystemSettings(settings: SystemSettings): Unit = {
|
||||
val props = new java.util.Properties()
|
||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||
props.setProperty(Notification, settings.notification.toString)
|
||||
if(settings.notification) {
|
||||
settings.smtp.foreach { smtp =>
|
||||
props.setProperty(SmtpHost, smtp.host)
|
||||
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
||||
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
||||
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
||||
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
||||
defining(new java.util.Properties()){ props =>
|
||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||
props.setProperty(Notification, settings.notification.toString)
|
||||
if(settings.notification) {
|
||||
settings.smtp.foreach { smtp =>
|
||||
props.setProperty(SmtpHost, smtp.host)
|
||||
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
||||
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
||||
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
||||
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
||||
}
|
||||
}
|
||||
}
|
||||
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
||||
if(settings.ldapAuthentication){
|
||||
settings.ldap.map { ldap =>
|
||||
props.setProperty(LdapHost, ldap.host)
|
||||
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
||||
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
||||
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
||||
props.setProperty(LdapBaseDN, ldap.baseDN)
|
||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
|
||||
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
||||
if(settings.ldapAuthentication){
|
||||
settings.ldap.map { ldap =>
|
||||
props.setProperty(LdapHost, ldap.host)
|
||||
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
||||
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
||||
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
||||
props.setProperty(LdapBaseDN, ldap.baseDN)
|
||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||
props.setProperty(LdapMailAddressAttribute, ldap.mailAttribute)
|
||||
}
|
||||
}
|
||||
props.store(new java.io.FileOutputStream(GitBucketConf), null)
|
||||
}
|
||||
props.store(new java.io.FileOutputStream(GitBucketConf), null)
|
||||
}
|
||||
|
||||
|
||||
def loadSystemSettings(): SystemSettings = {
|
||||
val props = new java.util.Properties()
|
||||
if(GitBucketConf.exists){
|
||||
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
|
||||
defining(new java.util.Properties()){ props =>
|
||||
if(GitBucketConf.exists){
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -119,23 +122,23 @@ object SystemSettingsService {
|
||||
private val LdapUserNameAttribute = "ldap.username_attribute"
|
||||
private val LdapMailAddressAttribute = "ldap.mail_attribute"
|
||||
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||
val value = props.getProperty(key)
|
||||
if(value == null || value.isEmpty) default
|
||||
else convertType(value).asInstanceOf[A]
|
||||
}
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A =
|
||||
defining(props.getProperty(key)){ value =>
|
||||
if(value == null || value.isEmpty) default
|
||||
else convertType(value).asInstanceOf[A]
|
||||
}
|
||||
|
||||
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
|
||||
val value = props.getProperty(key)
|
||||
if(value == null || value.isEmpty) default
|
||||
else Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
}
|
||||
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] =
|
||||
defining(props.getProperty(key)){ value =>
|
||||
if(value == null || value.isEmpty) default
|
||||
else Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
}
|
||||
|
||||
private def convertType[A: ClassTag](value: String) = {
|
||||
val c = implicitly[ClassTag[A]].runtimeClass
|
||||
if(c == classOf[Boolean]) value.toBoolean
|
||||
else if(c == classOf[Int]) value.toInt
|
||||
else value
|
||||
}
|
||||
private def convertType[A: ClassTag](value: String) =
|
||||
defining(implicitly[ClassTag[A]].runtimeClass){ c =>
|
||||
if(c == classOf[Boolean]) value.toBoolean
|
||||
else if(c == classOf[Int]) value.toInt
|
||||
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.apache.commons.io.FileUtils
|
||||
import util.{Directory, JGitUtil, LockUtil}
|
||||
import util.ControlUtil._
|
||||
|
||||
object WikiService {
|
||||
|
||||
@@ -33,40 +34,40 @@ object WikiService {
|
||||
trait 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"){
|
||||
val dir = Directory.getWikiRepositoryDir(owner, repository)
|
||||
if(!dir.exists){
|
||||
try {
|
||||
JGitUtil.initRepository(dir)
|
||||
saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit")
|
||||
} finally {
|
||||
// once delete cloned repository because initial cloned repository does not have 'branch.master.merge'
|
||||
FileUtils.deleteDirectory(Directory.getWikiWorkDir(owner, repository))
|
||||
defining(Directory.getWikiRepositoryDir(owner, repository)){ dir =>
|
||||
if(!dir.exists){
|
||||
try {
|
||||
JGitUtil.initRepository(dir)
|
||||
saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit")
|
||||
} finally {
|
||||
// once delete cloned repository because initial cloned repository does not have 'branch.master.merge'
|
||||
FileUtils.deleteDirectory(Directory.getWikiWorkDir(owner, repository))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the wiki page.
|
||||
*/
|
||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
||||
if(!JGitUtil.isEmpty(git)){
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||
optionIf(!JGitUtil.isEmpty(git)){
|
||||
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)
|
||||
}
|
||||
} else None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the specified file.
|
||||
*/
|
||||
def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] = {
|
||||
JGitUtil.withGit(Directory.getWikiRepositoryDir(owner, repository)){ git =>
|
||||
if(!JGitUtil.isEmpty(git)){
|
||||
def getFileContent(owner: String, repository: String, path: String): Option[Array[Byte]] =
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||
optionIf(!JGitUtil.isEmpty(git)){
|
||||
val index = path.lastIndexOf('/')
|
||||
val parentPath = if(index < 0) "." else path.substring(0, index)
|
||||
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 =>
|
||||
git.getRepository.open(file.id).getBytes
|
||||
}
|
||||
} else None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of wiki page names.
|
||||
*/
|
||||
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", ".")
|
||||
.filter(_.name.endsWith(".md"))
|
||||
.map(_.name.replaceFirst("\\.md$", ""))
|
||||
@@ -97,36 +97,31 @@ trait WikiService {
|
||||
content: String, committer: model.Account, message: String): Option[String] = {
|
||||
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
||||
// clone working copy
|
||||
val workDir = Directory.getWikiWorkDir(owner, repository)
|
||||
cloneOrPullWorkingCopy(workDir, owner, repository)
|
||||
defining(Directory.getWikiWorkDir(owner, repository)){ workDir =>
|
||||
// clone working copy
|
||||
cloneOrPullWorkingCopy(workDir, owner, repository)
|
||||
|
||||
// write as file
|
||||
JGitUtil.withGit(workDir){ git =>
|
||||
val file = new File(workDir, newPageName + ".md")
|
||||
val added = if(!file.exists || FileUtils.readFileToString(file, "UTF-8") != content){
|
||||
FileUtils.writeStringToFile(file, content, "UTF-8")
|
||||
git.add.addFilepattern(file.getName).call
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
// write as file
|
||||
using(Git.open(workDir)){ git =>
|
||||
defining(new File(workDir, newPageName + ".md")){ file =>
|
||||
val added = executeIf(!file.exists || FileUtils.readFileToString(file, "UTF-8") != content){
|
||||
FileUtils.writeStringToFile(file, content, "UTF-8")
|
||||
git.add.addFilepattern(file.getName).call
|
||||
}
|
||||
|
||||
// delete file
|
||||
val deleted = if(currentPageName != "" && currentPageName != newPageName){
|
||||
git.rm.addFilepattern(currentPageName + ".md").call
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
// delete file
|
||||
val deleted = executeIf(currentPageName != "" && currentPageName != newPageName){
|
||||
git.rm.addFilepattern(currentPageName + ".md").call
|
||||
}
|
||||
|
||||
// commit and push
|
||||
if(added || deleted){
|
||||
val commit = git.commit.setCommitter(committer.userName, committer.mailAddress).setMessage(message).call
|
||||
git.push.call
|
||||
Some(commit.getName)
|
||||
} else {
|
||||
None
|
||||
// commit and push
|
||||
optionIf(added || deleted){
|
||||
defining(git.commit.setCommitter(committer.userName, committer.mailAddress).setMessage(message).call){ commit =>
|
||||
git.push.call
|
||||
Some(commit.getName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,35 +133,34 @@ trait WikiService {
|
||||
def deleteWikiPage(owner: String, repository: String, pageName: String,
|
||||
committer: String, mailAddress: String, message: String): Unit = {
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki"){
|
||||
// clone working copy
|
||||
val workDir = Directory.getWikiWorkDir(owner, repository)
|
||||
cloneOrPullWorkingCopy(workDir, owner, repository)
|
||||
defining(Directory.getWikiWorkDir(owner, repository)){ workDir =>
|
||||
// clone working copy
|
||||
cloneOrPullWorkingCopy(workDir, owner, repository)
|
||||
|
||||
// delete file
|
||||
new File(workDir, pageName + ".md").delete
|
||||
|
||||
JGitUtil.withGit(workDir){ git =>
|
||||
git.rm.addFilepattern(pageName + ".md").call
|
||||
|
||||
// commit and push
|
||||
git.commit.setAuthor(committer, mailAddress).setMessage(message).call
|
||||
git.push.call
|
||||
// delete file
|
||||
new File(workDir, pageName + ".md").delete
|
||||
|
||||
using(Git.open(workDir)){ git =>
|
||||
git.rm.addFilepattern(pageName + ".md").call
|
||||
|
||||
// commit and push
|
||||
git.commit.setAuthor(committer, mailAddress).setMessage(message).call
|
||||
git.push.call
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def cloneOrPullWorkingCopy(workDir: File, owner: String, repository: String): Unit = {
|
||||
if(!workDir.exists){
|
||||
val git =
|
||||
Git.cloneRepository
|
||||
.setURI(Directory.getWikiRepositoryDir(owner, repository).toURI.toString)
|
||||
.setDirectory(workDir)
|
||||
.call
|
||||
git.getRepository.close // close .git resources.
|
||||
} else {
|
||||
JGitUtil.withGit(workDir){ git =>
|
||||
git.pull.call
|
||||
}
|
||||
Git.cloneRepository
|
||||
.setURI(Directory.getWikiRepositoryDir(owner, repository).toURI.toString)
|
||||
.setDirectory(workDir)
|
||||
.call
|
||||
.getRepository
|
||||
.close
|
||||
} else using(Git.open(workDir)){ git =>
|
||||
git.pull.call
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ import org.apache.commons.io.FileUtils
|
||||
import javax.servlet.ServletContextEvent
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import util.Directory
|
||||
import util.Directory._
|
||||
import util.ControlUtil._
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
object AutoUpdate {
|
||||
|
||||
@@ -26,15 +28,14 @@ object AutoUpdate {
|
||||
*/
|
||||
def update(conn: Connection): Unit = {
|
||||
val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
|
||||
val in = Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)
|
||||
if(in != null){
|
||||
val sql = IOUtils.toString(in, "UTF-8")
|
||||
val stmt = conn.createStatement()
|
||||
try {
|
||||
logger.debug(sqlPath + "=" + sql)
|
||||
stmt.executeUpdate(sql)
|
||||
} finally {
|
||||
stmt.close()
|
||||
|
||||
using(Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath)){ in =>
|
||||
if(in != null){
|
||||
val sql = IOUtils.toString(in, "UTF-8")
|
||||
using(conn.createStatement()){ stmt =>
|
||||
logger.debug(sqlPath + "=" + sql)
|
||||
stmt.executeUpdate(sql)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,22 +50,24 @@ object AutoUpdate {
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
Version(1, 6),
|
||||
Version(1, 5),
|
||||
Version(1, 4),
|
||||
new Version(1, 3){
|
||||
override def update(conn: Connection): Unit = {
|
||||
super.update(conn)
|
||||
// Fix wiki repository configuration
|
||||
val rs = conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")
|
||||
while(rs.next){
|
||||
val wikidir = Directory.getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))
|
||||
val repository = org.eclipse.jgit.api.Git.open(wikidir).getRepository
|
||||
val config = repository.getConfig
|
||||
if(!config.getBoolean("http", "receivepack", false)){
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
using(conn.createStatement.executeQuery("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY")){ rs =>
|
||||
while(rs.next){
|
||||
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
||||
defining(git.getRepository.getConfig){ config =>
|
||||
if(!config.getBoolean("http", "receivepack", false)){
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repository.close
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -81,7 +84,7 @@ object AutoUpdate {
|
||||
/**
|
||||
* 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.
|
||||
@@ -112,27 +115,29 @@ class AutoUpdateListener extends org.h2.server.web.DbStarter {
|
||||
private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener])
|
||||
|
||||
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)
|
||||
logger.debug("H2 started")
|
||||
|
||||
logger.debug("Start schema update")
|
||||
val conn = getConnection()
|
||||
try {
|
||||
val currentVersion = getCurrentVersion()
|
||||
if(currentVersion == headVersion){
|
||||
logger.debug("No update")
|
||||
} else {
|
||||
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
|
||||
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
||||
conn.commit()
|
||||
logger.debug("Updated from " + currentVersion.versionString + " to " + headVersion.versionString)
|
||||
}
|
||||
} catch {
|
||||
case ex: Throwable => {
|
||||
logger.error("Failed to schema update", ex)
|
||||
ex.printStackTrace()
|
||||
conn.rollback()
|
||||
defining(getConnection()){ conn =>
|
||||
try {
|
||||
defining(getCurrentVersion()){ currentVersion =>
|
||||
if(currentVersion == headVersion){
|
||||
logger.debug("No update")
|
||||
} else {
|
||||
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn))
|
||||
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
||||
conn.commit()
|
||||
logger.debug("Updated from " + currentVersion.versionString + " to " + headVersion.versionString)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex: Throwable => {
|
||||
logger.error("Failed to schema update", ex)
|
||||
ex.printStackTrace()
|
||||
conn.rollback()
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.debug("End schema update")
|
||||
|
||||
@@ -4,6 +4,9 @@ import javax.servlet._
|
||||
import javax.servlet.http._
|
||||
import service.{SystemSettingsService, AccountService, RepositoryService}
|
||||
import org.slf4j.LoggerFactory
|
||||
import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
import util.Keys
|
||||
|
||||
/**
|
||||
* Provides BASIC Authentication for [[servlet.GitRepositoryServlet]].
|
||||
@@ -25,29 +28,30 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
||||
}
|
||||
|
||||
try {
|
||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
||||
val repositoryOwner = paths(2)
|
||||
val repositoryName = paths(3).replaceFirst("\\.git$", "")
|
||||
|
||||
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki", ""), "") match {
|
||||
case Some(repository) => {
|
||||
if(!request.getRequestURI.endsWith("/git-receive-pack") &&
|
||||
defining(request.paths){ case Array(_, repositoryOwner, repositoryName, _*) =>
|
||||
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
|
||||
case Some(repository) => {
|
||||
if(!request.getRequestURI.endsWith("/git-receive-pack") &&
|
||||
!"service=git-receive-pack".equals(request.getQueryString) && !repository.repository.isPrivate){
|
||||
chain.doFilter(req, wrappedResponse)
|
||||
} else {
|
||||
request.getHeader("Authorization") match {
|
||||
case null => requireAuth(response)
|
||||
case auth => decodeAuthHeader(auth).split(":") match {
|
||||
case Array(username, password) if(isWritableUser(username, password, repository)) => {
|
||||
request.setAttribute("USER_NAME", username)
|
||||
chain.doFilter(req, wrappedResponse)
|
||||
chain.doFilter(req, wrappedResponse)
|
||||
} else {
|
||||
request.getHeader("Authorization") match {
|
||||
case null => requireAuth(response)
|
||||
case auth => decodeAuthHeader(auth).split(":") match {
|
||||
case Array(username, password) if(isWritableUser(username, password, repository)) => {
|
||||
request.setAttribute(Keys.Request.UserName, username)
|
||||
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 {
|
||||
case ex: Exception => {
|
||||
|
||||
@@ -9,8 +9,12 @@ import org.slf4j.LoggerFactory
|
||||
import javax.servlet.ServletConfig
|
||||
import javax.servlet.ServletContext
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import util.{JGitUtil, Directory}
|
||||
import util.{Keys, JGitUtil, Directory}
|
||||
import util.ControlUtil._
|
||||
import util.Implicits._
|
||||
import service._
|
||||
import WebHookService._
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
/**
|
||||
* Provides Git repository via HTTP.
|
||||
@@ -51,31 +55,34 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
|
||||
override def create(request: HttpServletRequest, db: Repository): ReceivePack = {
|
||||
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("userName:" + userName)
|
||||
|
||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
||||
val owner = paths(2)
|
||||
val repository = paths(3).replaceFirst("\\.git$", "")
|
||||
|
||||
logger.debug("repository:" + owner + "/" + repository)
|
||||
defining(request.paths){ paths =>
|
||||
val owner = paths(1)
|
||||
val repository = paths(2).replaceFirst("\\.git$", "")
|
||||
val baseURL = request.getRequestURL.toString.replaceFirst("/git/.*", "")
|
||||
|
||||
receivePack.setPostReceiveHook(new CommitLogHook(owner, repository, userName))
|
||||
receivePack
|
||||
logger.debug("repository:" + owner + "/" + repository)
|
||||
logger.debug("baseURL:" + baseURL)
|
||||
|
||||
receivePack.setPostReceiveHook(new CommitLogHook(owner, repository, userName, baseURL))
|
||||
receivePack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
class CommitLogHook(owner: String, repository: String, userName: String) extends PostReceiveHook
|
||||
with RepositoryService with AccountService with IssuesService with ActivityService {
|
||||
class CommitLogHook(owner: String, repository: String, userName: String, baseURL: String) extends PostReceiveHook
|
||||
with RepositoryService with AccountService with IssuesService with ActivityService with WebHookService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
|
||||
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 =>
|
||||
val commits = JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
|
||||
val refName = command.getRefName.split("/")
|
||||
@@ -110,6 +117,16 @@ class CommitLogHook(owner: String, repository: String, userName: String) extends
|
||||
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.
|
||||
|
||||
@@ -21,9 +21,9 @@ class TransactionFilter extends Filter {
|
||||
chain.doFilter(req, res)
|
||||
} else {
|
||||
Database(req.getServletContext) withTransaction {
|
||||
logger.debug("TODO begin transaction")
|
||||
logger.debug("begin transaction")
|
||||
chain.doFilter(req, res)
|
||||
logger.debug("TODO end transaction")
|
||||
logger.debug("end transaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package util
|
||||
import app.ControllerBase
|
||||
import service._
|
||||
import RepositoryService.RepositoryInfo
|
||||
import util.Implicits._
|
||||
import util.ControlUtil._
|
||||
|
||||
/**
|
||||
* Allows only oneself and administrators.
|
||||
@@ -13,11 +15,12 @@ trait OneselfAuthenticator { self: ControllerBase =>
|
||||
|
||||
private def authenticate(action: => Any) = {
|
||||
{
|
||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action
|
||||
case Some(x) if(paths(1) == x.userName) => action
|
||||
case _ => Unauthorized()
|
||||
defining(request.paths){ paths =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action
|
||||
case Some(x) if(paths(0) == x.userName) => action
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,14 +35,15 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService =>
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
{
|
||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
||||
getRepository(paths(1), paths(2), baseUrl).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(repository.owner == x.userName) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
defining(request.paths){ paths =>
|
||||
getRepository(paths(0), paths(1), baseUrl).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(repository.owner == x.userName) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,15 +91,16 @@ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService =
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
{
|
||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
||||
getRepository(paths(1), paths(2), baseUrl).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(paths(1) == x.userName) => action(repository)
|
||||
case Some(x) if(getCollaborators(paths(1), paths(2)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
defining(request.paths){ paths =>
|
||||
getRepository(paths(0), paths(1), baseUrl).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,19 +114,20 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
{
|
||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
||||
getRepository(paths(1), paths(2), baseUrl).map { repository =>
|
||||
if(!repository.repository.isPrivate){
|
||||
action(repository)
|
||||
} else {
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(paths(1) == x.userName) => action(repository)
|
||||
case Some(x) if(getCollaborators(paths(1), paths(2)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
defining(request.paths){ paths =>
|
||||
getRepository(paths(0), paths(1), baseUrl).map { repository =>
|
||||
if(!repository.repository.isPrivate){
|
||||
action(repository)
|
||||
} else {
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,16 +141,17 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
{
|
||||
val paths = request.getRequestURI.substring(request.getContextPath.length).split("/")
|
||||
getRepository(paths(1), paths(2), baseUrl).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => 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(getCollaborators(paths(1), paths(2)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
defining(request.paths){ paths =>
|
||||
getRepository(paths(0), paths(1), baseUrl).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if(x.isAdmin) => action(repository)
|
||||
case Some(x) if(!repository.repository.isPrivate) => action(repository)
|
||||
case Some(x) if(paths(0) == x.userName) => action(repository)
|
||||
case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
} 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
|
||||
|
||||
import java.io.File
|
||||
import util.ControlUtil._
|
||||
|
||||
/**
|
||||
* Provides directories used by GitBucket.
|
||||
@@ -21,17 +22,17 @@ object Directory {
|
||||
/**
|
||||
* Repository names of the specified user.
|
||||
*/
|
||||
def getRepositories(owner: String): List[String] = {
|
||||
val dir = new File(s"${RepositoryHome}/${owner}")
|
||||
if(dir.exists){
|
||||
dir.listFiles.filter { file =>
|
||||
file.isDirectory && !file.getName.endsWith(".wiki.git")
|
||||
}.map(_.getName.replaceFirst("\\.git$", "")).toList
|
||||
} else {
|
||||
Nil
|
||||
def getRepositories(owner: String): List[String] =
|
||||
defining(new File(s"${RepositoryHome}/${owner}")){ dir =>
|
||||
if(dir.exists){
|
||||
dir.listFiles.filter { file =>
|
||||
file.isDirectory && !file.getName.endsWith(".wiki.git")
|
||||
}.map(_.getName.replaceFirst("\\.git$", "")).toList
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Substance directory of the repository.
|
||||
*/
|
||||
|
||||
@@ -4,19 +4,18 @@ import org.apache.commons.io.{IOUtils, FileUtils}
|
||||
import java.net.URLConnection
|
||||
import java.io.File
|
||||
import org.apache.commons.compress.archivers.zip.{ZipArchiveEntry, ZipArchiveOutputStream}
|
||||
import util.ControlUtil._
|
||||
|
||||
object FileUtil {
|
||||
|
||||
def getMimeType(name: String): String = {
|
||||
val fileNameMap = URLConnection.getFileNameMap()
|
||||
val mimeType = fileNameMap.getContentTypeFor(name)
|
||||
if(mimeType == null){
|
||||
"application/octeat-stream"
|
||||
} else {
|
||||
mimeType
|
||||
def getMimeType(name: String): String =
|
||||
defining(URLConnection.getFileNameMap()){ fileNameMap =>
|
||||
fileNameMap.getContentTypeFor(name) match {
|
||||
case null => "application/octet-stream"
|
||||
case mimeType => mimeType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def isImage(name: String): Boolean = getMimeType(name).startsWith("image/")
|
||||
|
||||
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
||||
@@ -36,21 +35,19 @@ object FileUtil {
|
||||
}
|
||||
}
|
||||
|
||||
val out = new ZipArchiveOutputStream(dest)
|
||||
try {
|
||||
using(new ZipArchiveOutputStream(dest)){ out =>
|
||||
addDirectoryToZip(out, dir, dir.getName)
|
||||
} finally {
|
||||
IOUtils.closeQuietly(out)
|
||||
}
|
||||
}
|
||||
|
||||
def getExtension(name: String): String = {
|
||||
val index = name.lastIndexOf('.')
|
||||
if(index >= 0){
|
||||
name.substring(index + 1)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
def getFileName(path: String): String = defining(path.lastIndexOf('/')){ i =>
|
||||
if(i >= 0) path.substring(i + 1) else path
|
||||
}
|
||||
|
||||
def getExtension(name: String): String =
|
||||
name.lastIndexOf('.') match {
|
||||
case i if(i >= 0) => name.substring(i + 1)
|
||||
case _ => ""
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package util
|
||||
|
||||
import scala.util.matching.Regex
|
||||
import javax.servlet.http.{HttpSession, HttpServletRequest}
|
||||
|
||||
/**
|
||||
* Provides some usable implicit conversions.
|
||||
@@ -12,7 +13,7 @@ object Implicits {
|
||||
def splitWith(condition: (A, A) => Boolean): Seq[Seq[A]] = split(seq)(condition)
|
||||
|
||||
@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 {
|
||||
case x :: xs => {
|
||||
xs.span(condition(x, _)) match {
|
||||
@@ -21,7 +22,6 @@ object Implicits {
|
||||
}
|
||||
case Nil => result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 util.Directory._
|
||||
import util.StringUtil._
|
||||
import util.ControlUtil._
|
||||
import scala.collection.JavaConverters._
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.revwalk._
|
||||
@@ -68,29 +69,17 @@ object JGitUtil {
|
||||
rev.getFullMessage,
|
||||
rev.getParents().map(_.name).toList)
|
||||
|
||||
val summary = {
|
||||
val i = fullMessage.trim.indexOf("\n")
|
||||
val firstLine = if(i >= 0){
|
||||
fullMessage.trim.substring(0, i).trim
|
||||
} else {
|
||||
fullMessage
|
||||
}
|
||||
if(firstLine.length > shortMessage.length){
|
||||
shortMessage
|
||||
} else {
|
||||
firstLine
|
||||
val summary = defining(fullMessage.trim.indexOf("\n")){ i =>
|
||||
defining(if(i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage){ firstLine =>
|
||||
if(firstLine.length > shortMessage.length) shortMessage else firstLine
|
||||
}
|
||||
}
|
||||
|
||||
val description = {
|
||||
val i = fullMessage.trim.indexOf("\n")
|
||||
if(i >= 0){
|
||||
val description = defining(fullMessage.trim.indexOf("\n")){ i =>
|
||||
optionIf(i >= 0){
|
||||
Some(fullMessage.trim.substring(i).trim)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -141,7 +112,7 @@ object JGitUtil {
|
||||
val revWalk = new RevWalk(git.getRepository)
|
||||
val revCommit = revWalk.parseAny(objectId) match {
|
||||
case r: RevTag => revWalk.parseCommit(r.getObject)
|
||||
case _ => revWalk.parseCommit(objectId)
|
||||
case _ => revWalk.parseCommit(objectId)
|
||||
}
|
||||
revWalk.dispose
|
||||
revCommit
|
||||
@@ -151,7 +122,7 @@ object JGitUtil {
|
||||
* Returns the repository information. It contains branch names and tag names.
|
||||
*/
|
||||
def getRepositoryInfo(owner: String, repository: String, baseUrl: String): RepositoryInfo = {
|
||||
withGit(getRepositoryDir(owner, repository)){ git =>
|
||||
using(Git.open(getRepositoryDir(owner, repository))){ git =>
|
||||
try {
|
||||
// get commit count
|
||||
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
|
||||
*/
|
||||
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)]
|
||||
|
||||
while (treeWalk.next()) {
|
||||
list.append((treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getPathString, treeWalk.getNameString))
|
||||
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
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)
|
||||
list.map { case (objectId, fileMode, path, name) =>
|
||||
@@ -270,25 +239,23 @@ object JGitUtil {
|
||||
case _ => (logs, i.hasNext)
|
||||
}
|
||||
|
||||
val revWalk = new RevWalk(git.getRepository)
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if(objectId == null){
|
||||
Left(s"${revision} can't be resolved.")
|
||||
} else {
|
||||
revWalk.markStart(revWalk.parseCommit(objectId))
|
||||
if(path.nonEmpty){
|
||||
revWalk.setRevFilter(new RevFilter(){
|
||||
def include(walk: RevWalk, commit: RevCommit): Boolean = {
|
||||
getDiffs(git, commit.getName, false)._1.find(_.newPath == path).nonEmpty
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
defining(git.getRepository.resolve(revision)){ objectId =>
|
||||
if(objectId == null){
|
||||
Left(s"${revision} can't be resolved.")
|
||||
} else {
|
||||
revWalk.markStart(revWalk.parseCommit(objectId))
|
||||
if(path.nonEmpty){
|
||||
revWalk.setRevFilter(new RevFilter(){
|
||||
def include(walk: RevWalk, commit: RevCommit): Boolean = {
|
||||
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
|
||||
}
|
||||
|
||||
val revWalk = new RevWalk(git.getRepository)
|
||||
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(begin)))
|
||||
|
||||
val commits = getCommitLog(revWalk.iterator, Nil)
|
||||
revWalk.release
|
||||
|
||||
commits.reverse
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(begin)))
|
||||
getCommitLog(revWalk.iterator, Nil).reverse
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -370,11 +334,8 @@ object JGitUtil {
|
||||
if(large == false && FileUtil.isLarge(loader.getSize)){
|
||||
None
|
||||
} else {
|
||||
val db = git.getRepository.getObjectDatabase
|
||||
try {
|
||||
using(git.getRepository.getObjectDatabase){ db =>
|
||||
Some(db.open(id).getBytes)
|
||||
} finally {
|
||||
db.close
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -392,34 +353,32 @@ object JGitUtil {
|
||||
case _ => logs
|
||||
}
|
||||
|
||||
val revWalk = new RevWalk(git.getRepository)
|
||||
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(id)))
|
||||
|
||||
val commits = getCommitLog(revWalk.iterator, Nil)
|
||||
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))
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(id)))
|
||||
val commits = getCommitLog(revWalk.iterator, Nil)
|
||||
val revCommit = commits(0)
|
||||
|
||||
} else {
|
||||
// initial commit
|
||||
val walk = new TreeWalk(git.getRepository)
|
||||
walk.addTree(revCommit.getTree)
|
||||
val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]()
|
||||
while(walk.next){
|
||||
buffer.append((if(!fetchContent){
|
||||
DiffInfo(ChangeType.ADD, null, walk.getPathString, None, None)
|
||||
} else {
|
||||
DiffInfo(ChangeType.ADD, null, walk.getPathString, None,
|
||||
JGitUtil.getContent(git, walk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray))
|
||||
}))
|
||||
if(commits.length >= 2){
|
||||
// not initial commit
|
||||
val oldCommit = commits(1)
|
||||
(getDiffs(git, oldCommit.getName, id, fetchContent), Some(oldCommit.getName))
|
||||
|
||||
} else {
|
||||
// initial commit
|
||||
using(new TreeWalk(git.getRepository)){ treeWalk =>
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
val buffer = new scala.collection.mutable.ListBuffer[DiffInfo]()
|
||||
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.
|
||||
*/
|
||||
def getBranchesOfCommit(git: Git, commitId: String): List[String] = {
|
||||
val walk = new org.eclipse.jgit.revwalk.RevWalk(git.getRepository)
|
||||
try {
|
||||
val commit = walk.parseCommit(git.getRepository.resolve(commitId + "^0"))
|
||||
|
||||
git.getRepository.getAllRefs.entrySet.asScala.filter { e =>
|
||||
(e.getKey.startsWith(Constants.R_HEADS) && walk.isMergedInto(commit, walk.parseCommit(e.getValue.getObjectId)))
|
||||
}.map { e =>
|
||||
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length)
|
||||
}.toList.sorted
|
||||
|
||||
} finally {
|
||||
walk.release
|
||||
def getBranchesOfCommit(git: Git, commitId: String): List[String] =
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))){ commit =>
|
||||
git.getRepository.getAllRefs.entrySet.asScala.filter { e =>
|
||||
(e.getKey.startsWith(Constants.R_HEADS) && revWalk.isMergedInto(commit, revWalk.parseCommit(e.getValue.getObjectId)))
|
||||
}.map { e =>
|
||||
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length)
|
||||
}.toList.sorted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of tags of the specified commit.
|
||||
*/
|
||||
def getTagsOfCommit(git: Git, commitId: String): List[String] = {
|
||||
val walk = new org.eclipse.jgit.revwalk.RevWalk(git.getRepository)
|
||||
try {
|
||||
val commit = walk.parseCommit(git.getRepository.resolve(commitId + "^0"))
|
||||
|
||||
git.getRepository.getAllRefs.entrySet.asScala.filter { e =>
|
||||
(e.getKey.startsWith(Constants.R_TAGS) && walk.isMergedInto(commit, walk.parseCommit(e.getValue.getObjectId)))
|
||||
}.map { e =>
|
||||
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length)
|
||||
}.toList.sorted.reverse
|
||||
|
||||
} finally {
|
||||
walk.release
|
||||
def getTagsOfCommit(git: Git, commitId: String): List[String] =
|
||||
using(new RevWalk(git.getRepository)){ revWalk =>
|
||||
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))){ commit =>
|
||||
git.getRepository.getAllRefs.entrySet.asScala.filter { e =>
|
||||
(e.getKey.startsWith(Constants.R_TAGS) && revWalk.isMergedInto(commit, revWalk.parseCommit(e.getValue.getObjectId)))
|
||||
}.map { e =>
|
||||
e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length)
|
||||
}.toList.sorted.reverse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def initRepository(dir: java.io.File): Unit = {
|
||||
val repository = new RepositoryBuilder().setGitDir(dir).setBare.build
|
||||
try {
|
||||
def initRepository(dir: java.io.File): Unit =
|
||||
using(new RepositoryBuilder().setGitDir(dir).setBare.build){ repository =>
|
||||
repository.create
|
||||
setReceivePack(repository)
|
||||
} finally {
|
||||
repository.close
|
||||
}
|
||||
}
|
||||
|
||||
def cloneRepository(from: java.io.File, to: java.io.File): Unit = {
|
||||
val git = Git.cloneRepository.setURI(from.toURI.toString).setDirectory(to).setBare(true).call
|
||||
try {
|
||||
def cloneRepository(from: java.io.File, to: java.io.File): Unit =
|
||||
using(Git.cloneRepository.setURI(from.toURI.toString).setDirectory(to).setBare(true).call){ git =>
|
||||
setReceivePack(git.getRepository)
|
||||
} finally {
|
||||
git.getRepository.close
|
||||
}
|
||||
}
|
||||
|
||||
def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null
|
||||
|
||||
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit = {
|
||||
val config = repository.getConfig
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
}
|
||||
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit =
|
||||
defining(repository.getConfig){ config =>
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
}
|
||||
|
||||
def getDefaultBranch(git: Git, repository: RepositoryService.RepositoryInfo,
|
||||
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
|
||||
|
||||
import service.SystemSettingsService.Ldap
|
||||
import util.ControlUtil._
|
||||
import service.SystemSettingsService
|
||||
import com.novell.ldap._
|
||||
import service.SystemSettingsService.Ldap
|
||||
import scala.Some
|
||||
import scala.annotation.tailrec
|
||||
|
||||
/**
|
||||
@@ -96,12 +95,10 @@ object LDAPUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private def findMailAddress(conn: LDAPConnection, userDN: String, mailAttribute: String): Option[String] = {
|
||||
val results = conn.search(userDN, LDAPConnection.SCOPE_BASE, null, Array[String](mailAttribute), false)
|
||||
if (results.hasMore) {
|
||||
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
||||
} else {
|
||||
None
|
||||
private def findMailAddress(conn: LDAPConnection, userDN: String, mailAttribute: String): Option[String] =
|
||||
defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, null, Array[String](mailAttribute), false)){ results =>
|
||||
optionIf (results.hasMore) {
|
||||
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package util
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.locks.{ReentrantLock, Lock}
|
||||
import util.ControlUtil._
|
||||
|
||||
object LockUtil {
|
||||
|
||||
@@ -23,8 +24,7 @@ object LockUtil {
|
||||
/**
|
||||
* Synchronizes a given function which modifies the working copy of the wiki repository.
|
||||
*/
|
||||
def lock[T](key: String)(f: => T): T = {
|
||||
val lock = getLockObject(key)
|
||||
def lock[T](key: String)(f: => T): T = defining(getLockObject(key)){ lock =>
|
||||
try {
|
||||
lock.lock()
|
||||
f
|
||||
|
||||
@@ -65,6 +65,8 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
|
||||
def toNotify(r: RepositoryService.RepositoryInfo, issueId: Int, content: String)
|
||||
(msg: String => String)(implicit context: Context) = {
|
||||
val database = Database(context.request.getServletContext)
|
||||
|
||||
val f = future {
|
||||
val email = new HtmlEmail
|
||||
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)))
|
||||
|
||||
// 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 =>
|
||||
email.setSubject(s"[${r.name}] ${issue.title} (#${issueId})")
|
||||
recipients(issue) {
|
||||
|
||||
@@ -2,14 +2,15 @@ package util
|
||||
|
||||
import java.net.{URLDecoder, URLEncoder}
|
||||
import org.mozilla.universalchardet.UniversalDetector
|
||||
import util.ControlUtil._
|
||||
|
||||
object StringUtil {
|
||||
|
||||
def sha1(value: String): String = {
|
||||
val md = java.security.MessageDigest.getInstance("SHA-1")
|
||||
md.update(value.getBytes)
|
||||
md.digest.map(b => "%02x".format(b)).mkString
|
||||
}
|
||||
def sha1(value: String): String =
|
||||
defining(java.security.MessageDigest.getInstance("SHA-1")){ md =>
|
||||
md.update(value.getBytes)
|
||||
md.digest.map(b => "%02x".format(b)).mkString
|
||||
}
|
||||
|
||||
def md5(value: String): String = {
|
||||
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 detectEncoding(content: Array[Byte]): String = {
|
||||
val detector = new UniversalDetector(null)
|
||||
detector.handleData(content, 0, content.length)
|
||||
detector.dataEnd()
|
||||
detector.getDetectedCharset match {
|
||||
case null => "UTF-8"
|
||||
case e => e
|
||||
def detectEncoding(content: Array[Byte]): String =
|
||||
defining(new UniversalDetector(null)){ detector =>
|
||||
detector.handleData(content, 0, content.length)
|
||||
detector.dataEnd()
|
||||
detector.getDetectedCharset match {
|
||||
case null => "UTF-8"
|
||||
case e => e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ trait Validations {
|
||||
*/
|
||||
def date(constraints: Constraint*): SingleValueType[java.util.Date] =
|
||||
new SingleValueType[java.util.Date]((pattern("\\d{4}-\\d{2}-\\d{2}") +: constraints): _*){
|
||||
def convert(value: String): java.util.Date = {
|
||||
val formatter = new java.text.SimpleDateFormat("yyyy-MM-dd")
|
||||
formatter.parse(value)
|
||||
}
|
||||
def convert(value: String): java.util.Date = new java.text.SimpleDateFormat("yyyy-MM-dd").parse(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class GitBucketLinkRender(context: app.Context, repository: service.RepositorySe
|
||||
}
|
||||
|
||||
class GitBucketVerbatimSerializer extends VerbatimSerializer {
|
||||
def serialize(node: VerbatimNode, printer: Printer) {
|
||||
def serialize(node: VerbatimNode, printer: Printer): Unit = {
|
||||
printer.println.print("<pre")
|
||||
if (!StringUtils.isEmpty(node.getType)) {
|
||||
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('"')
|
||||
}
|
||||
|
||||
override def visit(node: TextNode) {
|
||||
override def visit(node: TextNode): Unit = {
|
||||
// convert commit id and username to link.
|
||||
val text = if(enableRefsLink) convertRefsLinks(node.getText, repository, "issue:") else node.getText
|
||||
|
||||
|
||||
@@ -31,9 +31,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
* Converts Markdown of Wiki pages to HTML.
|
||||
*/
|
||||
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))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
def url(userName: String)(implicit context: app.Context): String =
|
||||
s"${context.path}/${userName}"
|
||||
def url(userName: String)(implicit context: app.Context): String = s"${context.path}/${userName}"
|
||||
|
||||
/**
|
||||
* Returns the url to the root of assets.
|
||||
*/
|
||||
def assets(implicit context: app.Context): String =
|
||||
s"${context.path}/assets"
|
||||
def assets(implicit context: app.Context): String = s"${context.path}/assets"
|
||||
|
||||
def isPast(date: Date): Boolean = System.currentTimeMillis > date.getTime
|
||||
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
<div class="span6">
|
||||
@if(account.isEmpty){
|
||||
<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=""/>
|
||||
<span id="error-userName" class="error"></span>
|
||||
</fieldset>
|
||||
}
|
||||
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
||||
<fieldset>
|
||||
<label for="password"><strong>Password</strong>
|
||||
<label for="password" class="strong">
|
||||
Password
|
||||
@if(account.nonEmpty){
|
||||
(Input to change password)
|
||||
}
|
||||
@@ -30,19 +31,19 @@
|
||||
</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)"/>
|
||||
<span id="error-mailAddress" class="error"></span>
|
||||
</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)"/>
|
||||
<span id="error-url" class="error"></span>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<fieldset>
|
||||
<label for="avatar"><strong>Image (Optional)</strong></label>
|
||||
<label for="avatar" class="strong">Image (Optional)</label>
|
||||
@helper.html.uploadavatar(account)
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
@@ -11,22 +11,22 @@
|
||||
<!--====================================================================-->
|
||||
<!-- Account registration -->
|
||||
<!--====================================================================-->
|
||||
<label><strong>Account registration</strong></label>
|
||||
<label class="strong">Account registration</label>
|
||||
<fieldset>
|
||||
<label>
|
||||
<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>
|
||||
<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>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- Services -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
<label><strong>Services</strong></label>
|
||||
<label class="strong">Services</label>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="checkbox" name="gravatar"@if(settings.gravatar){ checked}/>
|
||||
@@ -37,7 +37,7 @@
|
||||
<!-- Authentication -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
<label><strong>Authentication</strong></label>
|
||||
<label class="strong">Authentication</label>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="checkbox" id="ldapAuthentication" name="ldapAuthentication"@if(settings.ldap){ checked}/>
|
||||
@@ -99,7 +99,7 @@
|
||||
<!-- Notification email -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
<label><strong>Notification email</strong></label>
|
||||
<label class="strong">Notification email</label>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="checkbox" id="notification" name="notification"@if(settings.notification){ checked}/>
|
||||
|
||||
@@ -7,23 +7,23 @@
|
||||
<div class="row-fluid">
|
||||
<div class="span7">
|
||||
<fieldset>
|
||||
<label for="groupName"><strong>Group name</strong></label>
|
||||
<label for="groupName" class="strong">Group name</label>
|
||||
<span id="error-groupName" class="error"></span>
|
||||
<input type="text" name="groupName" id="groupName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label><strong>URL (Optional)</strong></label>
|
||||
<label class="strong">URL (Optional)</label>
|
||||
<span id="error-url" class="error"></span>
|
||||
<input type="text" name="url" id="url" style="width: 300px;" value="@account.map(_.url)"/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label for="avatar"><strong>Image (Optional)</strong></label>
|
||||
<label for="avatar" class="strong">Image (Optional)</label>
|
||||
@helper.html.uploadavatar(account)
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="span5">
|
||||
<fieldset>
|
||||
<label><strong>Members</strong></label>
|
||||
<label class="strong">Members</label>
|
||||
<ul id="members" class="collaborator">
|
||||
@members.map { userName =>
|
||||
<li data-name="@userName">
|
||||
@@ -32,7 +32,7 @@
|
||||
</li>
|
||||
}
|
||||
</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="hidden" id="memberNames" name="memberNames" value="@members.mkString(",")"/>
|
||||
<div>
|
||||
@@ -50,15 +50,6 @@
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#memberName').typeahead({
|
||||
source: function (query, process) {
|
||||
return $.get('@path/_user/proposals', { query: query },
|
||||
function (data) {
|
||||
return process(data.options);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#addMember').click(function(){
|
||||
$('#error-memberName').text('');
|
||||
var userName = $('#memberName').val();
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<fieldset>
|
||||
<label for="userName"><strong>Username</strong></label>
|
||||
<label for="userName" class="strong">Username</label>
|
||||
<span id="error-userName" class="error"></span>
|
||||
<input type="text" name="userName" id="userName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||
</fieldset>
|
||||
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
||||
<fieldset>
|
||||
<label for="password">
|
||||
<strong>Password</strong>
|
||||
<label for="password" class="strong">
|
||||
Password
|
||||
@if(account.isDefined){
|
||||
(Input to change password)
|
||||
}
|
||||
@@ -23,12 +23,12 @@
|
||||
</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>
|
||||
<input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label><strong>User Type</strong></label>
|
||||
<label class="strong">User Type</label>
|
||||
<label for="userType_Normal">
|
||||
<input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.isAdmin){ checked}/> Normal
|
||||
</label>
|
||||
@@ -37,14 +37,14 @@
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label><strong>URL (Optional)</strong></label>
|
||||
<label class="strong">URL (Optional)</label>
|
||||
<span id="error-url" class="error"></span>
|
||||
<input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<fieldset>
|
||||
<label for="avatar"><strong>Image (Optional)</strong></label>
|
||||
<label for="avatar" class="strong">Image (Optional)</label>
|
||||
@helper.html.uploadavatar(account)
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
@@ -48,10 +48,8 @@
|
||||
<a href="@url(repository)/settings">Settings</a>
|
||||
</th>
|
||||
}
|
||||
</tr>
|
||||
</tr>
|
||||
</table>
|
||||
<form method="POST" id="repository_form">
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
$(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){
|
||||
<div>...</div>
|
||||
} else {
|
||||
<div>
|
||||
<a href={s"${path}/${activity.userName}/${activity.repositoryName}/commit/${commit.substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a>
|
||||
<span>{commit.substring(41)}</span>
|
||||
</div>
|
||||
if(commit.nonEmpty){
|
||||
<div>
|
||||
<a href={s"${path}/${activity.userName}/${activity.repositoryName}/commit/${commit. substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a>
|
||||
<span>{commit.substring(41)}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,39 @@
|
||||
@(diffs: Seq[util.JGitUtil.DiffInfo],
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
newCommitId: Option[String],
|
||||
oldCommitId: Option[String])(implicit context: app.Context)
|
||||
oldCommitId: Option[String],
|
||||
showIndex: Boolean)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@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) =>
|
||||
<a name="diff-@i"></a>
|
||||
<table class="table table-bordered">
|
||||
@@ -103,6 +132,17 @@ function diffUsingJS(oldTextId, newTextId, outputId) {
|
||||
}
|
||||
|
||||
$(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) =>
|
||||
@if(diff.newContent != None || diff.oldContent != None){
|
||||
if($('#oldText-@i').length > 0){
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
@(buttonValue: String = "", prefix: String = "")(body: Html)
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-mini dropdown-toggle" data-toggle="dropdown">
|
||||
@if(buttonValue.isEmpty){
|
||||
@(value: String = "", prefix: String = "", mini: Boolean = true, style: String = "")(body: Html)
|
||||
<div class="btn-group"@if(style.nonEmpty){ style="@style"}>
|
||||
<button class="btn dropdown-toggle@if(mini){ btn-mini}" data-toggle="dropdown">
|
||||
@if(value.isEmpty){
|
||||
<i class="icon-cog"></i>
|
||||
} else {
|
||||
@if(prefix.nonEmpty){
|
||||
<span class="muted">@prefix:</span>
|
||||
}
|
||||
<strong>@buttonValue</strong>
|
||||
<span class="strong">@value</span>
|
||||
}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
<tr>
|
||||
<td>
|
||||
@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 {
|
||||
<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>
|
||||
</tr>
|
||||
@@ -57,7 +57,7 @@
|
||||
@recentRepositories.map { repository =>
|
||||
<tr>
|
||||
<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>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</div>
|
||||
<div class="span3">
|
||||
@if(hasWritePermission){
|
||||
<strong>Add Labels</strong>
|
||||
<span class="strong">Add Labels</span>
|
||||
<div>
|
||||
<div id="label-list">
|
||||
<ul class="label-list nav nav-pills nav-stacked">
|
||||
@@ -111,7 +111,7 @@ $(function(){
|
||||
if(milestoneId == ''){
|
||||
$('#label-milestone').text('No milestone');
|
||||
} 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');
|
||||
}
|
||||
$('input[name=milestoneId]').val(milestoneId);
|
||||
|
||||
@@ -29,19 +29,19 @@
|
||||
}
|
||||
<div class="small" style="text-align: center;">
|
||||
@defining(comments.filter( _.action.contains("comment") ).size){ count =>
|
||||
<strong>@count</strong> @plural(count, "comment")
|
||||
<span class="strong">@count</span> @plural(count, "comment")
|
||||
}
|
||||
</div>
|
||||
<hr/>
|
||||
<div style="margin-bottom: 8px;">
|
||||
<strong>Labels</strong>
|
||||
<span class="strong">Labels</span>
|
||||
@if(hasWritePermission){
|
||||
<div class="pull-right">
|
||||
@helper.html.dropdown() {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<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>
|
||||
@label.labelName
|
||||
</a>
|
||||
|
||||
@@ -28,7 +28,11 @@
|
||||
@helper.html.dropdown() {
|
||||
<li><a href="javascript:void(0);" class="assign" data-name=""><i class="icon-remove-circle"></i> Clear assignee</a></li>
|
||||
@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">
|
||||
@issue.milestoneId.map { milestoneId =>
|
||||
@milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) =>
|
||||
Milestone: <strong>@milestone.title</strong>
|
||||
Milestone: <span class="strong">@milestone.title</span>
|
||||
}
|
||||
}.getOrElse("No milestone")
|
||||
</span>
|
||||
@@ -53,7 +57,7 @@
|
||||
@milestones.map { case (milestone, _, _) =>
|
||||
<li>
|
||||
<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;">
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(isPast(dueDate)){
|
||||
@@ -79,19 +83,12 @@
|
||||
</div>
|
||||
<div class="issue-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> }
|
||||
}
|
||||
</div>
|
||||
<script>
|
||||
$(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(){
|
||||
$.get('@url(repository)/issues/_data/@issue.issueId',
|
||||
{
|
||||
@@ -138,7 +135,7 @@ $(function(){
|
||||
$('#label-milestone').text('No milestone');
|
||||
$('#milestone-progress-area').empty();
|
||||
} 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);
|
||||
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
}
|
||||
}
|
||||
<hr/>
|
||||
<strong>Labels</strong>
|
||||
<span class="strong">Labels</span>
|
||||
<div>
|
||||
<div id="label-list">
|
||||
<ul class="label-list nav nav-pills nav-stacked">
|
||||
@@ -127,7 +127,7 @@
|
||||
<hr/>
|
||||
<input type="button" class="btn btn-block" id="manageLabel" data-toggle="button" value="Manage Labels"/>
|
||||
<br/>
|
||||
<strong>New label</strong>
|
||||
<span class="strong">New label</span>
|
||||
@_root_.issues.labels.html.edit(None, repository)
|
||||
}
|
||||
</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 == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
Sort:
|
||||
<strong>
|
||||
@if(condition.sort == "created" && condition.direction == "desc"){ Newest }
|
||||
@if(condition.sort == "created" && condition.direction == "asc" ){ Oldest }
|
||||
@if(condition.sort == "comments" && condition.direction == "desc"){ Most commented }
|
||||
@if(condition.sort == "comments" && condition.direction == "asc" ){ Least commented }
|
||||
@if(condition.sort == "updated" && condition.direction == "desc"){ Recently updated }
|
||||
@if(condition.sort == "updated" && condition.direction == "asc" ){ Least recently updated }
|
||||
</strong>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@helper.html.dropdown(
|
||||
value = (condition.sort, condition.direction) match {
|
||||
case ("created" , "desc") => "Newest"
|
||||
case ("created" , "asc" ) => "Oldest"
|
||||
case ("comments", "desc") => "Most commented"
|
||||
case ("comments", "asc" ) => "Least commented"
|
||||
case ("updated" , "desc") => "Recently updated"
|
||||
case ("updated" , "asc" ) => "Least recently updated"
|
||||
},
|
||||
prefix = "Sort",
|
||||
mini = false
|
||||
){
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
@if(issues.isEmpty){
|
||||
<tr>
|
||||
@@ -94,7 +91,7 @@
|
||||
<tr>
|
||||
<td style="background-color: #eee;">
|
||||
<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>
|
||||
@helper.html.dropdown("Label") {
|
||||
@labels.map { label =>
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
<span id="error-title" class="error"></span>
|
||||
</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>
|
||||
<span id="error-description" class="error"></span>
|
||||
</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))
|
||||
<span id="error-dueDate" class="error"></span>
|
||||
</fieldset>
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<div style="width: 600px; margin: 10px auto;">
|
||||
<form id="form" method="post" action="@path/new" validate="true">
|
||||
<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">
|
||||
<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>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@@ -24,13 +24,13 @@
|
||||
<span id="error-name" class="error"></span>
|
||||
</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%;"/>
|
||||
</fieldset>
|
||||
<fieldset class="margin">
|
||||
<label>
|
||||
<input type="radio" name="isPrivate" value="false" checked>
|
||||
<strong>Public</strong><br>
|
||||
<span class="strong">Public</span><br>
|
||||
<div>
|
||||
<span class="note">All users and guests can read this repository.</span>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="radio" name="isPrivate" value="true">
|
||||
<strong>Private</strong><br>
|
||||
<span class="strong">Private</span><br>
|
||||
<div>
|
||||
<span class="note">Only collaborators can read this repository.</span>
|
||||
</div>
|
||||
@@ -48,7 +48,7 @@
|
||||
<fieldset class="margin">
|
||||
<label for="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>
|
||||
<span class="note">This will allow you to <code>git clone</code> the repository immediately.</span>
|
||||
</div>
|
||||
@@ -68,6 +68,6 @@ $('#owner-dropdown a').click(function(){
|
||||
$('#owner-dropdown i').attr('class', 'icon-white');
|
||||
$(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>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
@(issue: model.Issue,
|
||||
pullreq: model.PullRequest,
|
||||
commits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
||||
hasWritePermission: Boolean,
|
||||
@(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
forkedId: String,
|
||||
sourceId: String,
|
||||
commitId: String,
|
||||
hasConflict: Boolean,
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
originRepository: service.RepositoryService.RepositoryInfo,
|
||||
forkedRepository: service.RepositoryService.RepositoryInfo,
|
||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
@html.main("Pull Requests - " + repository.owner + "/" + repository.name){
|
||||
@html.header("pulls", repository)
|
||||
<div class="pullreq-info">
|
||||
@@ -53,14 +51,9 @@
|
||||
<div class="box-content">
|
||||
<form method="POST" action="@path/@originRepository.owner/@repository.name/pulls/new" validate="true">
|
||||
<div style="width: 260px; position: absolute; margin-left: 635px;">
|
||||
@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"/>
|
||||
<div class="check-conflict" style="display: none;">
|
||||
<img src="@assets/common/images/indicator.gif"/> Checking...
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 620px; border-right: 1px solid #d4d4d4;">
|
||||
<span class="error" id="error-title"></span>
|
||||
@@ -82,59 +75,13 @@
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
} else {
|
||||
<div class="box">
|
||||
<table class="table table-file-list" style="border: 1px solid silver;">
|
||||
@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))
|
||||
@pulls.html.commits(commits, repository)
|
||||
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true)
|
||||
}
|
||||
}
|
||||
<script>
|
||||
@@ -153,7 +100,7 @@ $(function(){
|
||||
var e = $(this);
|
||||
e.parents('ul').find('i').attr('class', 'icon-white');
|
||||
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){
|
||||
location.href = '@url(repository)/compare/' +
|
||||
@@ -173,13 +120,26 @@ $(function(){
|
||||
$('#pull-request-form').show();
|
||||
});
|
||||
|
||||
$('#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');
|
||||
@if(hasWritePermission){
|
||||
function checkConflict(from, to, noConflictHandler, hasConflictHandler){
|
||||
$('.check-conflict').show();
|
||||
$.get('@url(repository)/compare/' + from + '...' + to + '/mergecheck',
|
||||
function(data){ $('.check-conflict').html(data); });
|
||||
}
|
||||
});
|
||||
|
||||
@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>
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
comments: List[model.IssueComment],
|
||||
collaborators: List[String],
|
||||
milestones: List[(model.Milestone, Int, Int)],
|
||||
hasConflict: Boolean,
|
||||
hasWritePermission: Boolean,
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
requestRepositoryUrl: String)(implicit context: app.Context)
|
||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
<div class="row-fluid">
|
||||
@@ -17,77 +15,14 @@
|
||||
<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 id="merge-pull-request">
|
||||
<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){
|
||||
<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 class="check-conflict" style="display: none;">
|
||||
<img src="@assets/common/images/indicator.gif"/> Checking...
|
||||
</div>
|
||||
</div>
|
||||
<div id="confirm-merge-form" style="display: none;">
|
||||
<form method="POST" action="@url(repository)/pull/@issue.issueId/merge">
|
||||
<div>
|
||||
<strong>Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}</strong>
|
||||
<div class="strong">
|
||||
Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}
|
||||
</div>
|
||||
<span id="error-message" class="error"></span>
|
||||
<textarea name="message" style="width: 680px; height: 80px;">@issue.title</textarea>
|
||||
@@ -113,26 +48,22 @@
|
||||
<span class="label label-success issue-status">Open</span>
|
||||
}
|
||||
<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>
|
||||
<hr/>
|
||||
</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();
|
||||
});
|
||||
|
||||
$('#cancel-merge-pull-request').click(function(){
|
||||
$('#confirm-merge-form').hide();
|
||||
$('#merge-pull-request').show();
|
||||
});
|
||||
|
||||
@if(hasWritePermission){
|
||||
$('.check-conflict').show();
|
||||
$.get('@url(repository)/pull/@issue.issueId/mergeguide',
|
||||
function(data){ $('.check-conflict').html(data); });
|
||||
}
|
||||
});
|
||||
</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 == "closed"){ active}" href="@condition.copy(state = "closed").toURL">@closedCount Closed</a>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
Sort:
|
||||
<strong>
|
||||
@if(condition.sort == "created" && condition.direction == "desc"){ Newest }
|
||||
@if(condition.sort == "created" && condition.direction == "asc" ){ Oldest }
|
||||
@if(condition.sort == "comments" && condition.direction == "desc"){ Most commented }
|
||||
@if(condition.sort == "comments" && condition.direction == "asc" ){ Least commented }
|
||||
@if(condition.sort == "updated" && condition.direction == "desc"){ Recently updated }
|
||||
@if(condition.sort == "updated" && condition.direction == "asc" ){ Least recently updated }
|
||||
</strong>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@helper.html.dropdown(
|
||||
value = (condition.sort, condition.direction) match {
|
||||
case ("created" , "desc") => "Newest"
|
||||
case ("created" , "asc" ) => "Oldest"
|
||||
case ("comments", "desc") => "Most commented"
|
||||
case ("comments", "asc" ) => "Least commented"
|
||||
case ("updated" , "desc") => "Recently updated"
|
||||
case ("updated" , "asc" ) => "Least recently updated"
|
||||
},
|
||||
prefix = "Sort",
|
||||
mini = false
|
||||
){
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
@if(issues.isEmpty){
|
||||
<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)],
|
||||
dayByDayCommits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
||||
diffs: Seq[util.JGitUtil.DiffInfo],
|
||||
hasConflict: Boolean,
|
||||
hasWritePermission: Boolean,
|
||||
repository: service.RepositoryService.RepositoryInfo,
|
||||
requestRepositoryUrl: String)(implicit context: app.Context)
|
||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@html.main("%s - Pull Request #%d - %s/%s".format(issue.title, issue.issueId, repository.owner, repository.name)){
|
||||
@@ -39,13 +37,13 @@
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<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 class="tab-pane" id="commits">
|
||||
@pulls.html.commits(issue, pullreq, dayByDayCommits, hasWritePermission, repository)
|
||||
@pulls.html.commits(dayByDayCommits, repository)
|
||||
</div>
|
||||
<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>
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
@import util.Implicits._
|
||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
@html.main(commit.shortMessage, Some(repository)){
|
||||
@html.header("code", repository)
|
||||
@tab(commitId, repository, "commits")
|
||||
@@ -71,52 +70,10 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<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")
|
||||
@*
|
||||
@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)
|
||||
@helper.html.diff(diffs, repository, Some(commit.id), oldCommitId, true)
|
||||
}
|
||||
<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');
|
||||
}
|
||||
});
|
||||
|
||||
$('a.branch:first, a.tag:first').css({
|
||||
'font-weight': 'bold',
|
||||
'color': '#555555'
|
||||
|
||||
@@ -5,25 +5,16 @@
|
||||
<ul class="nav nav-tabs">
|
||||
@if(!hideBranchPulldown){
|
||||
<li>
|
||||
<div class="btn-group" style="margin-right: 20px;">
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
@if(id.length == 40){
|
||||
tree: <strong>@id.substring(0, 10)</strong>
|
||||
}
|
||||
@if(repository.branchList.contains(id)){
|
||||
branch: <strong>@id</strong>
|
||||
}
|
||||
@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>
|
||||
@helper.html.dropdown(
|
||||
value = if(id.length == 40) id.substring(0, 10) else id,
|
||||
prefix = if(id.length == 40) "tree" else if(repository.branchList.contains(id)) "branch" else "tree",
|
||||
mini = false,
|
||||
style = "margin-right: 20px;"
|
||||
){
|
||||
@repository.branchList.map { branch =>
|
||||
<li><a href="@url(repository)/@if(active=="commits"){commits} else {tree}/@branch">@helper.html.checkicon(id == branch) @branch</a></li>
|
||||
}
|
||||
}
|
||||
</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>
|
||||
at @datetime(issue.registeredDate)
|
||||
@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>
|
||||
|
||||
@@ -22,21 +22,9 @@
|
||||
<div>
|
||||
<span class="error" id="error-userName"></span>
|
||||
</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"/>
|
||||
</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"}>
|
||||
<a href="@url(repository)/settings/collaborators">Collaborators</a>
|
||||
</li>
|
||||
<li@if(active=="hooks"){ class="active"}>
|
||||
<a href="@url(repository)/settings/hooks">Service Hooks</a>
|
||||
</li>
|
||||
<li@if(active=="delete"){ class="active"}>
|
||||
<a href="@url(repository)/settings/delete">Delete Repository</a>
|
||||
</li>
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
<div class="box-header">Settings</div>
|
||||
<div class="box-content">
|
||||
<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"/>
|
||||
</fieldset>
|
||||
<fieldset class="margin">
|
||||
<label for="defaultBranch"><strong>Default Branch</strong></label>
|
||||
<label for="defaultBranch" class="strong">Default Branch</label>
|
||||
<select name="defaultBranch" id="defaultBranch">
|
||||
@repository.branchList.map { branch =>
|
||||
<option value="@branch"@if(branch==repository.repository.defaultBranch){ selected}>@branch</option>
|
||||
@@ -28,7 +28,7 @@
|
||||
@if(!repository.repository.isPrivate ){ checked }
|
||||
@if(repository.repository.parentUserName.isDefined){ disabled }
|
||||
>
|
||||
<strong>Public</strong><br>
|
||||
<span class="strong">Public</span><br>
|
||||
<div>
|
||||
<span class="note">All users and guests can read this repository.</span>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@
|
||||
@if(repository.repository.isPrivate ){ checked }
|
||||
@if(repository.repository.parentUserName.isDefined){ disabled }
|
||||
>
|
||||
<strong>Private</strong><br>
|
||||
<span class="strong">Private</span><br>
|
||||
<div>
|
||||
<span class="note">Only collaborators can read this repository.</span>
|
||||
</div>
|
||||
@@ -54,8 +54,8 @@
|
||||
<div class="box-content">
|
||||
<dl>
|
||||
<dt>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="wiki" id="wiki"/> <strong>Wiki</strong>
|
||||
<label class="checkbox strong">
|
||||
<input type="checkbox" name="wiki" id="wiki"/> Wiki
|
||||
</label>
|
||||
</dt>
|
||||
<dd>
|
||||
@@ -67,8 +67,8 @@
|
||||
<hr>
|
||||
<dl>
|
||||
<dt>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="issue" id="issue"/> <strong>Issue</strong>
|
||||
<label class="checkbox strong">
|
||||
<input type="checkbox" name="issue" id="issue"/> Issue
|
||||
</label>
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
@@ -22,5 +22,5 @@
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@helper.html.diff(diffs, repository, None, None)
|
||||
@helper.html.diff(diffs, repository, None, None, false)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
@import context._
|
||||
@import view.helpers._
|
||||
<ul class="nav nav-tabs">
|
||||
<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 == "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 == "history"){ class="active"}><a href="@url(repository)/wiki/_history">Wiki History</a></li>
|
||||
<li class="pull-right">
|
||||
@defining(repository.url.replaceFirst("\\.git$", ".wiki.git")){ repositoryUrl =>
|
||||
|
||||
Reference in New Issue
Block a user