Merge branch 'master' into feature/plugin-hotdeploy

This commit is contained in:
Naoki Takezoe
2017-05-02 14:21:53 +09:00
42 changed files with 296 additions and 163 deletions

View File

@@ -1,6 +1,7 @@
# Guideline for Issues # Guideline for Issues
- At first, See [FAQ](https://github.com/gitbucket/gitbucket/wiki/FAQ) and check issues whether there is a same question or request in the past. - At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue. - If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja). - We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- Write an issue in English. At least, write subject in English. - Write an issue in English. At least, write subject in English.
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.

View File

@@ -1,31 +1,35 @@
GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.png)](https://gitter.im/gitbucket/gitbucket) [![Build Status](https://travis-ci.org/gitbucket/gitbucket.svg?branch=master)](https://travis-ci.org/gitbucket/gitbucket) GitBucket [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.png)](https://gitter.im/gitbucket/gitbucket) [![Build Status](https://travis-ci.org/gitbucket/gitbucket.svg?branch=master)](https://travis-ci.org/gitbucket/gitbucket)
========= =========
GitBucket is a Git platform powered by Scala offering: GitBucket is a Git web platform powered by Scala offering:
- Easy installation - Easy installation
- Intuitive UI
- High extensibility by plugins - High extensibility by plugins
- API compatibility with GitHub - API compatibility with GitHub
You can try an [online demo](https://gitbucket.herokuapp.com/) *(ID: root / Pass: root)* of GitBucket, and also get the latest information at [GitBucket News](https://gitbucket.github.io/gitbucket-news/).
Features Features
-------- --------
The current version of GitBucket provides a basic features below: The current version of GitBucket provides many features such as:
- Public / Private Git repository (http and ssh access) - Public / Private Git repositories (with http/https and ssh access)
- GitLFS support - GitLFS support
- Repository viewer includes online file editor - Repository viewer including an online file editor
- Issues, Pull request and Wiki for repositories - Issues, Pull Requests and Wiki for repositories
- Activity timeline and email notification - Activity timeline and email notifications
- Account and group management with LDAP integration - Account and group management with LDAP integration
- Plug-in system - a Plug-in system
If you want to try the development version of GitBucket, see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/how_to_run.md). If you want to try the development version of GitBucket, see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/how_to_run.md).
Installation Installation
-------- --------
GitBucket requires **Java8**. You have to install it if it is not already installed. GitBucket requires **Java8**. You have to install it, if it is not already installed.
1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`. 1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
2. Go to `http://[hostname]:8080/` and log in with **root** / **root**. 2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
You can specify following options: You can specify following options:
@@ -45,24 +49,37 @@ To upgrade GitBucket, replace `gitbucket.war` with the new version, after stoppi
Plugins Plugins
-------- --------
GitBucket has a plug-in system to allow extensions to GitBucket. We provide some official plug-ins: GitBucket has a plug-in system that allows extra functionality. Officially the following plug-ins are provided:
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin) - [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin) - [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
You can find more plugins made by the community at [GitBucket community plugins](http://gitbucket-plugins.github.io/). You can find more plugins made by the community at [GitBucket community plugins](http://gitbucket-plugins.github.io/).
Support Support
-------- --------
- If you have any questions about GitBucket, send it to the [gitter room](https://gitter.im/gitbucket/gitbucket) before opening an issue. - If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
- Make sure check whether there is the same question or request in the past. - If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- When raise a new issue, write at least the subject in **English**. - We can also provide support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- We can also provide support in Japanese at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja). - Write an issue in English. At least, write subject in English.
- The first priority of GitBucket is easy installation and API compatibility with GitHub, so we might reject if your request is against it. - The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
Release Notes Release Notes
------------- -------------
### 4.12 - 30 Apr 2017
- [Gist plug-in](https://github.com/gitbucket/gitbucket-gist-plugin) provides JavaScript to embed snippet
- Dropdown menu filter in the branch comparing page
- Caution for the embedded H2 database
### 4.11 - 1 Apr 2017
- Deploy keys support
- Auto generate avatar images
- Collaborators of the private forked repository are copied from the original repository
- Cache avatar images in the browser
- New extension point to receive events about repository
### 4.10 - 25 Feb 2017 ### 4.10 - 25 Feb 2017
- Update to Scala 2.12, Scalatra 2.5 and Slick 3.2 - Update to Scala 2.12, Scalatra 2.5 and Slick 3.2
- Display file size in the file viewer - Display file size in the file viewer

View File

@@ -10,7 +10,7 @@ sourcesInBase := false
organization := Organization organization := Organization
name := Name name := Name
version := GitBucketVersion version := GitBucketVersion
scalaVersion := "2.12.1" scalaVersion := "2.12.2"
// dependency settings // dependency settings
resolvers ++= Seq( resolvers ++= Seq(
@@ -21,8 +21,8 @@ resolvers ++= Seq(
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/" "amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
) )
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.6.1.201703071140-r", "org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.7.0.201704051617-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.6.1.201703071140-r", "org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.7.0.201704051617-r",
"org.scalatra" %% "scalatra" % ScalatraVersion, "org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion, "org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.5.0", "org.json4s" %% "json4s-jackson" % "3.5.0",
@@ -60,7 +60,7 @@ libraryDependencies ++= Seq(
) )
// Compiler settings // Compiler settings
scalacOptions := Seq("-deprecation", "-language:postfixOps") scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method")
javacOptions in compile ++= Seq("-target", "8", "-source", "8") javacOptions in compile ++= Seq("-target", "8", "-source", "8")
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml" javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"

View File

@@ -1,18 +1,24 @@
How to run from the source tree How to run from the source tree
======== ========
Install [sbt](http://www.scala-sbt.org/index.html) at first.
```
$ brew install sbt
```
Run for Development Run for Development
-------- --------
If you want to test GitBucket, input following command at the root directory of the source tree. If you want to test GitBucket, type the following command in the root directory of the source tree.
``` ```
$ sbt ~jetty:start $ sbt ~jetty:start
``` ```
Then access to `http://localhost:8080/` by your browser. The default administrator account is `root` and password is `root`. Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
Source code modification is detected and reloaded automatically. You can modify logging configuration by editing `src/main/resources/logback-dev.xml`. Source code modifications are detected and a reloaded happens automatically. You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
Build war file Build war file
-------- --------
@@ -23,9 +29,9 @@ To build war file, run the following command:
$ sbt package $ sbt package
``` ```
`gitbucket_2.11-x.x.x.war` is generated into `target/scala-2.11`. `gitbucket_2.12-x.x.x.war` is generated into `target/scala-2.12`.
To build executable war file, run To build an executable war file, run
``` ```
$ sbt executable $ sbt executable
@@ -35,8 +41,8 @@ at the top of the source tree. It generates executable `gitbucket.war` into `tar
Run tests spec Run tests spec
--------- ---------
To run the full serie of tests, run the following command: To run the full series of tests, run the following command:
``` ```
sbt test $ sbt test
``` ```

View File

@@ -1,7 +1,7 @@
Notification Email Notification Email
======== ========
GitBucket sends email to target users by enabling the notification email by an administrator. GitBucket can send email notification to users if this feature is enabled by an administrator.
The timing of the notification are as follows: The timing of the notification are as follows:
@@ -20,4 +20,4 @@ Notified users are as follows:
* collaborators * collaborators
* participants * participants
However, the operation in person is excluded from the target. However, the person performing the operation is excluded from the notification.

View File

@@ -34,8 +34,6 @@ object GitBucketCoreModule extends Module("gitbucket-core",
Generate release files Generate release files
-------- --------
Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https://maven.apache.org/).
### Make release war file ### Make release war file
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`. Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
@@ -52,4 +50,12 @@ For plug-in development, we have to publish the GitBucket jar file to the Maven
$ sbt publish-signed $ sbt publish-signed
``` ```
Then operate release sequence at https://oss.sonatype.org/. Then logged-in https://oss.sonatype.org/ and delete following files from the staging repository:
- gitbucket_2.12-x.x.x.war
- gitbucket_2.12-x.x.x.war.asc
- gitbucket_2.12-x.x.x.war.asc.md5
- gitbucket_2.12-x.x.x.war.asc.sha1
- gitbucket_2.12-x.x.x.war.md5
At last, close and release the repository.

Binary file not shown.

View File

@@ -1,2 +0,0 @@
set SCRIPT_DIR=%~dp0
java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.12.jar" %*

2
sbt.sh
View File

@@ -1,2 +0,0 @@
#!/bin/sh
java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.12.jar "$@"

View File

@@ -1,4 +1,9 @@
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File; import java.io.File;
@@ -8,6 +13,8 @@ import java.security.ProtectionDomain;
public class JettyLauncher { public class JettyLauncher {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
System.setProperty("java.awt.headless", "true");
String host = null; String host = null;
int port = 8080; int port = 8080;
InetSocketAddress address = null; InetSocketAddress address = null;
@@ -19,19 +26,25 @@ public class JettyLauncher {
if(arg.startsWith("--") && arg.contains("=")) { if(arg.startsWith("--") && arg.contains("=")) {
String[] dim = arg.split("="); String[] dim = arg.split("=");
if(dim.length >= 2) { if(dim.length >= 2) {
if(dim[0].equals("--host")) { switch (dim[0]) {
case "--host":
host = dim[1]; host = dim[1];
} else if(dim[0].equals("--port")) { break;
case "--port":
port = Integer.parseInt(dim[1]); port = Integer.parseInt(dim[1]);
} else if(dim[0].equals("--prefix")) { break;
case "--prefix":
contextPath = dim[1]; contextPath = dim[1];
if(!contextPath.startsWith("/")){ if (!contextPath.startsWith("/")) {
contextPath = "/" + contextPath; contextPath = "/" + contextPath;
} }
} else if(dim[0].equals("--gitbucket.home")){ break;
case "--gitbucket.home":
System.setProperty("gitbucket.home", dim[1]); System.setProperty("gitbucket.home", dim[1]);
} else if(dim[0].equals("--temp_dir")){ break;
case "--temp_dir":
tmpDirPath = dim[1]; tmpDirPath = dim[1];
break;
} }
} }
} }
@@ -54,6 +67,15 @@ public class JettyLauncher {
// connector.setPort(port); // connector.setPort(port);
// server.addConnector(connector); // server.addConnector(connector);
// Disabling Server header
for (Connector connector : server.getConnectors()) {
for (ConnectionFactory factory : connector.getConnectionFactories()) {
if (factory instanceof HttpConnectionFactory) {
((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false);
}
}
}
WebAppContext context = new WebAppContext(); WebAppContext context = new WebAppContext();
File tmpDir; File tmpDir;
@@ -85,7 +107,9 @@ public class JettyLauncher {
context.setInitParameter("org.scalatra.ForceHttps", "true"); context.setInitParameter("org.scalatra.ForceHttps", "true");
} }
server.setHandler(context); Handler handler = addStatisticsHandler(context);
server.setHandler(handler);
server.setStopAtShutdown(true); server.setStopAtShutdown(true);
server.setStopTimeout(7_000); server.setStopTimeout(7_000);
server.start(); server.start();
@@ -114,4 +138,12 @@ public class JettyLauncher {
} }
dir.delete(); dir.delete();
} }
private static Handler addStatisticsHandler(Handler handler) {
// The graceful shutdown is implemented via the statistics handler.
// See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142
final StatisticsHandler statisticsHandler = new StatisticsHandler();
statisticsHandler.setHandler(handler);
return statisticsHandler;
}
} }

View File

@@ -31,5 +31,6 @@ object GitBucketCoreModule extends Module("gitbucket-core",
new Version("4.10.0"), new Version("4.10.0"),
new Version("4.11.0", new Version("4.11.0",
new LiquibaseMigration("update/gitbucket-core_4.11.xml") new LiquibaseMigration("update/gitbucket-core_4.11.xml")
) ),
new Version("4.12.0")
) )

View File

@@ -37,7 +37,7 @@ object ApiRepository{
name = repository.repositoryName, name = repository.repositoryName,
full_name = s"${repository.userName}/${repository.repositoryName}", full_name = s"${repository.userName}/${repository.repositoryName}",
description = repository.description.getOrElse(""), description = repository.description.getOrElse(""),
watchers = 0, watchers = watchers,
forks = forkedCount, forks = forkedCount,
`private` = repository.isPrivate, `private` = repository.isPrivate,
default_branch = repository.defaultBranch, default_branch = repository.defaultBranch,

View File

@@ -19,8 +19,8 @@ case class CreateAStatus(
def isValid: Boolean = { def isValid: Boolean = {
CommitState.valueOf(state).isDefined && CommitState.valueOf(state).isDefined &&
// only http // only http
target_url.filterNot(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length<255).isEmpty && target_url.forall(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length < 255) &&
context.filterNot(f => f.length<255).isEmpty && context.forall(f => f.length < 255) &&
description.filterNot(f => f.length<1000).isEmpty description.forall(f => f.length < 1000)
} }
} }

View File

@@ -15,7 +15,6 @@ import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import org.scalatra.BadRequest import org.scalatra.BadRequest
import java.util.Date
class AccountController extends AccountControllerBase class AccountController extends AccountControllerBase
@@ -61,7 +60,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val sshKeyForm = mapping( val sshKeyForm = mapping(
"title" -> trim(label("Title", text(required, maxlength(100)))), "title" -> trim(label("Title", text(required, maxlength(100)))),
"publicKey" -> trim(label("Key" , text(required, validPublicKey))) "publicKey" -> trim2(label("Key" , text(required, validPublicKey)))
)(SshKeyForm.apply) )(SshKeyForm.apply)
val personalTokenForm = mapping( val personalTokenForm = mapping(
@@ -150,20 +149,21 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_avatar"){ get("/:userName/_avatar"){
val userName = params("userName") val userName = params("userName")
getAccountByUserName(userName).map{ account => contentType = "image/png"
getAccountByUserName(userName).flatMap{ account =>
response.setDateHeader("Last-Modified", account.updatedDate.getTime) response.setDateHeader("Last-Modified", account.updatedDate.getTime)
account.image.map{ image => account.image.map{ image =>
RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image)) Some(RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image)))
}.getOrElse{ }.getOrElse{
contentType = "image/png" if (account.isGroupAccount) {
(if (account.isGroupAccount) {
TextAvatarUtil.textGroupAvatar(account.fullName) TextAvatarUtil.textGroupAvatar(account.fullName)
} else { } else {
TextAvatarUtil.textAvatar(account.fullName) TextAvatarUtil.textAvatar(account.fullName)
}).getOrElse(Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")) }
} }
}.getOrElse{ }.getOrElse{
NotFound() response.setHeader("Cache-Control", "max-age=3600")
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
} }
} }

View File

@@ -125,7 +125,7 @@ trait ApiControllerBase extends ControllerBase {
get ("/api/v3/repos/:owner/:repo/branches/:branch")(referrersOnly { repository => get ("/api/v3/repos/:owner/:repo/branches/:branch")(referrersOnly { repository =>
//import gitbucket.core.api._ //import gitbucket.core.api._
(for{ (for{
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined branch <- params.get("branch") if repository.branchList.contains(branch)
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch) br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
} yield { } yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch) val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
@@ -287,7 +287,7 @@ trait ApiControllerBase extends ControllerBase {
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository => patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
import gitbucket.core.api._ import gitbucket.core.api._
(for{ (for{
branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined branch <- params.get("branch") if repository.branchList.contains(branch)
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection) protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch) br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
} yield { } yield {

View File

@@ -40,10 +40,6 @@ abstract class ControllerBase extends ScalatraFilter
contentType = formats("json") contentType = formats("json")
} }
// TODO Scala 2.11
// // Don't set content type via Accept header.
// override def format(implicit request: HttpServletRequest) = ""
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try { override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
val httpRequest = request.asInstanceOf[HttpServletRequest] val httpRequest = request.asInstanceOf[HttpServletRequest]
val httpResponse = response.asInstanceOf[HttpServletResponse] val httpResponse = response.asInstanceOf[HttpServletResponse]
@@ -151,7 +147,6 @@ abstract class ControllerBase extends ScalatraFilter
} }
} }
// TODO Scala 2.11
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty, override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
includeContextPath: Boolean = true, includeServletPath: Boolean = true, includeContextPath: Boolean = true, includeServletPath: Boolean = true,
absolutize: Boolean = true, withSessionId: Boolean = true) absolutize: Boolean = true, withSessionId: Boolean = true)
@@ -159,6 +154,18 @@ abstract class ControllerBase extends ScalatraFilter
if (path.startsWith("http")) path if (path.startsWith("http")) path
else baseUrl + super.url(path, params, false, false, false) else baseUrl + super.url(path, params, false, false, false)
/**
* Extends scalatra-form's trim rule to eliminate CR and LF.
*/
protected def trim2[T](valueType: SingleValueType[T]): SingleValueType[T] = new SingleValueType[T](){
def convert(value: String, messages: Messages): T = valueType.convert(trim(value), messages)
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] =
valueType.validate(name, trim(value), params, messages)
private def trim(value: String): String = if(value == null) null else value.replaceAll("\r\n", "").trim
}
/** /**
* Use this method to response the raw data against XSS. * Use this method to response the raw data against XSS.
*/ */

View File

@@ -26,13 +26,13 @@ trait IndexControllerBase extends ControllerBase {
"password" -> trim(label("Password", text(required))) "password" -> trim(label("Password", text(required)))
)(SignInForm.apply) )(SignInForm.apply)
val searchForm = mapping( // val searchForm = mapping(
"query" -> trim(text(required)), // "query" -> trim(text(required)),
"owner" -> trim(text(required)), // "owner" -> trim(text(required)),
"repository" -> trim(text(required)) // "repository" -> trim(text(required))
)(SearchForm.apply) // )(SearchForm.apply)
//
case class SearchForm(query: String, owner: String, repository: String) // case class SearchForm(query: String, owner: String, repository: String)
get("/"){ get("/"){
@@ -163,7 +163,7 @@ trait IndexControllerBase extends ControllerBase {
get("/search"){ get("/search"){
val query = params.getOrElse("query", "").trim.toLowerCase val query = params.getOrElse("query", "").trim.toLowerCase
val visibleRepositories = getVisibleRepositories(context.loginAccount, None) val visibleRepositories = getVisibleRepositories(context.loginAccount, repositoryUserName = None, withoutPhysicalInfo = true)
val repositories = visibleRepositories.filter { repository => val repositories = visibleRepositories.filter { repository =>
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0 repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0
} }

View File

@@ -40,7 +40,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
) )
val optionsForm = mapping( val optionsForm = mapping(
"repositoryName" -> trim(label("Repository Name" , text(required, maxlength(100), identifier, renameRepositoryName))), "repositoryName" -> trim(label("Repository Name" , text(required, maxlength(100), repository, renameRepositoryName))),
"description" -> trim(label("Description" , optional(text()))), "description" -> trim(label("Description" , optional(text()))),
"isPrivate" -> trim(label("Repository Type" , boolean())), "isPrivate" -> trim(label("Repository Type" , boolean())),
"issuesOption" -> trim(label("Issues Option" , text(required, featureOption))), "issuesOption" -> trim(label("Issues Option" , text(required, featureOption))),
@@ -63,7 +63,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
val deployKeyForm = mapping( val deployKeyForm = mapping(
"title" -> trim(label("Title", text(required, maxlength(100)))), "title" -> trim(label("Title", text(required, maxlength(100)))),
"publicKey" -> trim(label("Key" , text(required))), // TODO duplication check in the repository? "publicKey" -> trim2(label("Key" , text(required))), // TODO duplication check in the repository?
"allowWrite" -> trim(label("Key" , boolean())) "allowWrite" -> trim(label("Key" , boolean()))
)(DeployKeyForm.apply) )(DeployKeyForm.apply)
@@ -139,6 +139,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
FileUtils.moveDirectory(dir, getLfsDir(repository.owner, form.repositoryName)) FileUtils.moveDirectory(dir, getLfsDir(repository.owner, form.repositoryName))
} }
} }
// Move attached directory
defining(getAttachedDir(repository.owner, repository.name)){ dir =>
if(dir.isDirectory) {
FileUtils.moveDirectory(dir, getAttachedDir(repository.owner, form.repositoryName))
}
}
// Delete parent directory // Delete parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name)) FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
@@ -157,7 +163,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/** Update default branch */ /** Update default branch */
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
if(repository.branchList.find(_ == form.defaultBranch).isEmpty){ if(!repository.branchList.contains(form.defaultBranch)){
redirect(s"/${repository.owner}/${repository.name}/settings/options") redirect(s"/${repository.owner}/${repository.name}/settings/options")
} else { } else {
saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch) saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch)
@@ -174,7 +180,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
get("/:owner/:repository/settings/branches/:branch")(ownerOnly { repository => get("/:owner/:repository/settings/branches/:branch")(ownerOnly { repository =>
import gitbucket.core.api._ import gitbucket.core.api._
val branch = params("branch") val branch = params("branch")
if(repository.branchList.find(_ == branch).isEmpty){ if(!repository.branchList.contains(branch)){
redirect(s"/${repository.owner}/${repository.name}/settings/branches") redirect(s"/${repository.owner}/${repository.name}/settings/branches")
} else { } else {
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch)) val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
@@ -352,6 +358,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
FileUtils.moveDirectory(dir, getLfsDir(form.newOwner, repository.name)) FileUtils.moveDirectory(dir, getLfsDir(form.newOwner, repository.name))
} }
} }
// Move attached directory
defining(getAttachedDir(repository.owner, repository.name)){ dir =>
if(dir.isDirectory) {
FileUtils.moveDirectory(dir, getAttachedDir(form.newOwner, repository.name))
}
}
// Delere parent directory // Delere parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name)) FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))

View File

@@ -232,7 +232,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
oldFileName = form.oldFileName, oldFileName = form.oldFileName,
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
charset = form.charset, charset = form.charset,
message = if(form.oldFileName.exists(_ == form.newFileName)){ message = if(form.oldFileName.contains(form.newFileName)){
form.message.getOrElse(s"Update ${form.newFileName}") form.message.getOrElse(s"Update ${form.newFileName}")
} else { } else {
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}") form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
@@ -614,7 +614,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val permission = JGitUtil.processTree(git, headTip){ (path, tree) => val permission = JGitUtil.processTree(git, headTip){ (path, tree) =>
// Add all entries except the editing file // Add all entries except the editing file
if(!newPath.exists(_ == path) && !oldPath.exists(_ == path)){ if(!newPath.contains(path) && !oldPath.contains(path)){
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
} }
// Retrieve permission if file exists to keep it // Retrieve permission if file exists to keep it
@@ -671,11 +671,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): Unit = { private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): Unit = {
val revision = name.stripSuffix(suffix) val revision = name.stripSuffix(suffix)
val filename = repository.name + "-" +
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + suffix
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision)) val oid = git.getRepository.resolve(revision)
val revCommit = JGitUtil.getRevCommitFromId(git, oid)
val sha1 = oid.getName()
val repositorySuffix = (if(sha1.startsWith(revision)) sha1 else revision).replace('/','-')
val filename = repository.name + "-" + repositorySuffix + suffix
contentType = "application/octet-stream" contentType = "application/octet-stream"
response.setHeader("Content-Disposition", s"attachment; filename=${filename}") response.setHeader("Content-Disposition", s"attachment; filename=${filename}")
@@ -683,6 +684,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
git.archive git.archive
.setFormat(suffix.tail) .setFormat(suffix.tail)
.setPrefix(repository.name + "-" + repositorySuffix + "/")
.setTree(revCommit) .setTree(revCommit)
.setOutputStream(response.getOutputStream) .setOutputStream(response.getOutputStream)
.call() .call()

View File

@@ -286,7 +286,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
} }
}.toList){ case (groupName, members) => }.toList){ case (groupName, members) =>
getAccountByUserName(groupName, true).map { account => getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.url, form.description, form.isRemoved) updateGroup(groupName, form.description, form.url, form.isRemoved)
if(form.isRemoved){ if(form.isRemoved){
// Remove from GROUP_MEMBER // Remove from GROUP_MEMBER

View File

@@ -9,10 +9,10 @@ trait MilestoneComponent extends TemplateComponent { self: Profile =>
class Milestones(tag: Tag) extends Table[Milestone](tag, "MILESTONE") with MilestoneTemplate { class Milestones(tag: Tag) extends Table[Milestone](tag, "MILESTONE") with MilestoneTemplate {
override val milestoneId = column[Int]("MILESTONE_ID", O AutoInc) override val milestoneId = column[Int]("MILESTONE_ID", O AutoInc)
val title = column[String]("TITLE") val title = column[String]("TITLE")
val description = column[String]("DESCRIPTION") val description = column[Option[String]]("DESCRIPTION")
val dueDate = column[java.util.Date]("DUE_DATE") val dueDate = column[Option[java.util.Date]]("DUE_DATE")
val closedDate = column[java.util.Date]("CLOSED_DATE") val closedDate = column[Option[java.util.Date]]("CLOSED_DATE")
def * = (userName, repositoryName, milestoneId, title, description.?, dueDate.?, closedDate.?) <> (Milestone.tupled, Milestone.unapply) def * = (userName, repositoryName, milestoneId, title, description, dueDate, closedDate) <> (Milestone.tupled, Milestone.unapply)
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId) def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) = byMilestone(userName, repositoryName, milestoneId) def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) = byMilestone(userName, repositoryName, milestoneId)

View File

@@ -86,7 +86,7 @@ class PluginRegistry {
def addRenderer(extension: String, renderer: Renderer): Unit = renderers += ((extension, renderer)) def addRenderer(extension: String, renderer: Renderer): Unit = renderers += ((extension, renderer))
def getRenderer(extension: String): Renderer = renderers.get(extension).getOrElse(DefaultRenderer) def getRenderer(extension: String): Renderer = renderers.getOrElse(extension, DefaultRenderer)
def renderableExtensions: Seq[String] = renderers.keys.toSeq def renderableExtensions: Seq[String] = renderers.keys.toSeq
@@ -220,7 +220,7 @@ object PluginRegistry {
val classLoader = new URLClassLoader(Array(installedJar.toURI.toURL), Thread.currentThread.getContextClassLoader) val classLoader = new URLClassLoader(Array(installedJar.toURI.toURL), Thread.currentThread.getContextClassLoader)
try { try {
val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin] val plugin = classLoader.loadClass("Plugin").getDeclaredConstructor().newInstance().asInstanceOf[Plugin]
val pluginId = plugin.pluginId val pluginId = plugin.pluginId
// Check duplication // Check duplication

View File

@@ -166,8 +166,8 @@ trait AccountService {
def updateGroup(groupName: String, description: Option[String], url: Option[String], removed: Boolean)(implicit s: Session): Unit = def updateGroup(groupName: String, description: Option[String], url: Option[String], removed: Boolean)(implicit s: Session): Unit =
Accounts.filter(_.userName === groupName.bind) Accounts.filter(_.userName === groupName.bind)
.map(t => (t.url.?, t.description.?, t.removed)) .map(t => (t.url.?, t.description.?, t.updatedDate, t.removed))
.update(url, description, removed) .update(url, description, currentDate, removed)
def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = { def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = {
GroupMembers.filter(_.groupName === groupName.bind).delete GroupMembers.filter(_.groupName === groupName.bind).delete

View File

@@ -111,7 +111,7 @@ trait IssuesService {
val (_, cs) = status.head val (_, cs) = status.head
Some(CommitStatusInfo( Some(CommitStatusInfo(
count = status.length, count = status.length,
successCount = status.filter(_._2.state == CommitState.SUCCESS).length, successCount = status.count(_._2.state == CommitState.SUCCESS),
context = (if(status.length == 1) Some(cs.context) else None), context = (if(status.length == 1) Some(cs.context) else None),
state = (if(status.length == 1) Some(cs.state) else None), state = (if(status.length == 1) Some(cs.state) else None),
targetUrl = (if(status.length == 1) cs.targetUrl else None), targetUrl = (if(status.length == 1) cs.targetUrl else None),

View File

@@ -21,7 +21,7 @@ trait MilestonesService {
def updateMilestone(milestone: Milestone)(implicit s: Session): Unit = def updateMilestone(milestone: Milestone)(implicit s: Session): Unit =
Milestones Milestones
.filter (t => t.byPrimaryKey(milestone.userName, milestone.repositoryName, milestone.milestoneId)) .filter (t => t.byPrimaryKey(milestone.userName, milestone.repositoryName, milestone.milestoneId))
.map (t => (t.title, t.description.?, t.dueDate.?, t.closedDate.?)) .map (t => (t.title, t.description, t.dueDate, t.closedDate))
.update (milestone.title, milestone.description, milestone.dueDate, milestone.closedDate) .update (milestone.title, milestone.description, milestone.dueDate, milestone.closedDate)
def openMilestone(milestone: Milestone)(implicit s: Session): Unit = def openMilestone(milestone: Milestone)(implicit s: Session): Unit =

View File

@@ -76,7 +76,7 @@ object ProtectedBranchService {
includeAdministrators: Boolean) extends AccountService with CommitStatusService { includeAdministrators: Boolean) extends AccountService with CommitStatusService {
def isAdministrator(pusher: String)(implicit session: Session): Boolean = def isAdministrator(pusher: String)(implicit session: Session): Boolean =
pusher == owner || getGroupMembers(owner).filter(gm => gm.userName == pusher && gm.isManager).nonEmpty pusher == owner || getGroupMembers(owner).exists(gm => gm.userName == pusher && gm.isManager)
/** /**
* Can't be force pushed * Can't be force pushed

View File

@@ -265,7 +265,7 @@ object PullRequestService {
val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ") val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")
state -> summary state -> summary
} }
lazy val statusesAndRequired:List[(CommitStatus, Boolean)] = statuses.map{ s => s -> branchProtection.contexts.exists(_==s.context) } lazy val statusesAndRequired:List[(CommitStatus, Boolean)] = statuses.map{ s => s -> branchProtection.contexts.contains(s.context) }
lazy val isAllSuccess = commitStateSummary._1==CommitState.SUCCESS lazy val isAllSuccess = commitStateSummary._1==CommitState.SUCCESS
} }
} }

View File

@@ -262,11 +262,19 @@ trait RepositoryService { self: AccountService =>
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName) JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName)
}, },
repository, repository,
if(withoutPhysicalInfo){
-1
} else {
getForkedCount( getForkedCount(
repository.originUserName.getOrElse(repository.userName), repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName) repository.originRepositoryName.getOrElse(repository.repositoryName)
), )
getRepositoryManagers(repository.userName)) },
if(withoutPhysicalInfo){
Nil
} else {
getRepositoryManagers(repository.userName)
})
} }
} }
@@ -300,7 +308,7 @@ trait RepositoryService { self: AccountService =>
case None => Repositories filter(_.isPrivate === false.bind) case None => Repositories filter(_.isPrivate === false.bind)
}).filter { t => }).filter { t =>
repositoryUserName.map { userName => t.userName === userName.bind } getOrElse LiteralColumn(true) repositoryUserName.map { userName => t.userName === userName.bind } getOrElse LiteralColumn(true)
}.sortBy(_.lastActivityDate desc).list.map{ repository => }.sortBy(_.lastActivityDate desc).list.map { repository =>
new RepositoryInfo( new RepositoryInfo(
if(withoutPhysicalInfo){ if(withoutPhysicalInfo){
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName) new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName)
@@ -308,11 +316,19 @@ trait RepositoryService { self: AccountService =>
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName) JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName)
}, },
repository, repository,
if(withoutPhysicalInfo){
-1
} else {
getForkedCount( getForkedCount(
repository.originUserName.getOrElse(repository.userName), repository.originUserName.getOrElse(repository.userName),
repository.originRepositoryName.getOrElse(repository.repositoryName) repository.originRepositoryName.getOrElse(repository.repositoryName)
), )
getRepositoryManagers(repository.userName)) },
if(withoutPhysicalInfo) {
Nil
} else {
getRepositoryManagers(repository.userName)
})
} }
} }

View File

@@ -177,7 +177,7 @@ trait WikiService {
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
JGitUtil.processTree(git, headId){ (path, tree) => JGitUtil.processTree(git, headId){ (path, tree) =>
if(revertInfo.find(x => x.filePath == path).isEmpty){ if(!revertInfo.exists(x => x.filePath == path)){
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
} }
} }

View File

@@ -51,8 +51,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
private def pluginRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, private def pluginRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain,
settings: SystemSettings, isUpdating: Boolean, filter: GitRepositoryFilter): Unit = { settings: SystemSettings, isUpdating: Boolean, filter: GitRepositoryFilter): Unit = {
implicit val r = request Database() withSession { implicit session =>
val account = for { val account = for {
auth <- Option(request.getHeader("Authorization")) auth <- Option(request.getHeader("Authorization"))
Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
@@ -62,12 +61,13 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
account account
} }
if(filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)){ if (filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)) {
chain.doFilter(request, response) chain.doFilter(request, response)
} else { } else {
AuthUtil.requireAuth(response) AuthUtil.requireAuth(response)
} }
} }
}
private def defaultRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, private def defaultRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain,
settings: SystemSettings, isUpdating: Boolean): Unit = { settings: SystemSettings, isUpdating: Boolean): Unit = {

View File

@@ -105,7 +105,7 @@ abstract class DefaultGitCommand(val owner: String, val repoName: String) extend
} }
} }
case AuthType.DeployKeyType(key) => { case AuthType.DeployKeyType(key) => {
getDeployKeys(owner, repoName).filter(sshKey => SshUtil.str2PublicKey(sshKey.publicKey).exists(_ == key)) match { getDeployKeys(owner, repoName).filter(sshKey => SshUtil.str2PublicKey(sshKey.publicKey).contains(key)) match {
case List(_) => true case List(_) => true
case _ => false case _ => false
} }
@@ -123,7 +123,7 @@ abstract class DefaultGitCommand(val owner: String, val repoName: String) extend
} }
} }
case AuthType.DeployKeyType(key) => { case AuthType.DeployKeyType(key) => {
getDeployKeys(owner, repoName).filter(sshKey => SshUtil.str2PublicKey(sshKey.publicKey).exists(_ == key)) match { getDeployKeys(owner, repoName).filter(sshKey => SshUtil.str2PublicKey(sshKey.publicKey).contains(key)) match {
case List(x) if x.allowWrite => true case List(x) if x.allowWrite => true
case _ => false case _ => false
} }

View File

@@ -68,7 +68,7 @@ class PublicKeyAuthenticator(genericUser: String) extends PublickeyAuthenticator
private def authenticateGenericUser(userName: String, key: PublicKey, session: ServerSession, genericUser: String)(implicit s: Session): Boolean = { private def authenticateGenericUser(userName: String, key: PublicKey, session: ServerSession, genericUser: String)(implicit s: Session): Boolean = {
// find all users having the key we got from ssh // find all users having the key we got from ssh
val possibleUserNames = getAllKeys().filter { sshKey => val possibleUserNames = getAllKeys().filter { sshKey =>
SshUtil.str2PublicKey(sshKey.publicKey).exists(_ == key) SshUtil.str2PublicKey(sshKey.publicKey).contains(key)
}.map(_.userName).distinct }.map(_.userName).distinct
// determine the user - if different accounts share the same key, tough luck // determine the user - if different accounts share the same key, tough luck
@@ -85,7 +85,7 @@ class PublicKeyAuthenticator(genericUser: String) extends PublickeyAuthenticator
}.getOrElse { }.getOrElse {
// search deploy keys // search deploy keys
val existsDeployKey = getAllDeployKeys().exists { sshKey => val existsDeployKey = getAllDeployKeys().exists { sshKey =>
SshUtil.str2PublicKey(sshKey.publicKey).exists(_ == key) SshUtil.str2PublicKey(sshKey.publicKey).contains(key)
} }
if(existsDeployKey){ if(existsDeployKey){
// found deploy key for repository // found deploy key for repository

View File

@@ -18,8 +18,8 @@ object SshUtil {
val parts = key.split(" ") val parts = key.split(" ")
if (parts.size < 2) { if (parts.size < 2) {
logger.debug(s"Invalid PublicKey Format: ${key}") logger.debug(s"Invalid PublicKey Format: ${key}")
return None None
} } else {
try { try {
val encodedKey = parts(1) val encodedKey = parts(1)
val decode = Base64.getDecoder.decode(Constants.encodeASCII(encodedKey)) val decode = Base64.getDecoder.decode(Constants.encodeASCII(encodedKey))
@@ -30,6 +30,7 @@ object SshUtil {
None None
} }
} }
}
def fingerPrint(key: String): Option[String] = def fingerPrint(key: String): Option[String] =
str2PublicKey(key) map KeyUtils.getFingerPrint str2PublicKey(key) map KeyUtils.getFingerPrint

View File

@@ -952,7 +952,7 @@ object JGitUtil {
* @return the last modified commit of specified path * @return the last modified commit of specified path
*/ */
def getLastModifiedCommit(git: Git, startCommit: RevCommit, path: String): RevCommit = { def getLastModifiedCommit(git: Git, startCommit: RevCommit, path: String): RevCommit = {
return git.log.add(startCommit).addPath(path).setMaxCount(1).call.iterator.next git.log.add(startCommit).addPath(path).setMaxCount(1).call.iterator.next
} }
def getBranches(owner: String, name: String, defaultBranch: String, origin: Boolean): Seq[BranchInfo] = { def getBranches(owner: String, name: String, defaultBranch: String, origin: Boolean): Seq[BranchInfo] = {

View File

@@ -1,4 +1,5 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context) @(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.util.DatabaseConfig
@gitbucket.core.html.main("System settings"){ @gitbucket.core.html.main("System settings"){
@gitbucket.core.admin.html.menu("system"){ @gitbucket.core.admin.html.menu("system"){
@gitbucket.core.helper.html.information(info) @gitbucket.core.helper.html.information(info)
@@ -20,7 +21,17 @@
</tr> </tr>
<tr> <tr>
<td>DATABASE_URL</td> <td>DATABASE_URL</td>
@if(DatabaseConfig.url.startsWith("jdbc:h2:")) {
<td class="danger">
<p>@gitbucket.core.util.DatabaseConfig.url</p>
<p>
Your GitBucket is running on embedded H2 database.
Recommend to <a href="https://github.com/gitbucket/gitbucket/wiki/External-database-configuration">configure to use external database</a> if you would like to use GitBucket for important purpose.
</p>
</td>
}else{
<td>@gitbucket.core.util.DatabaseConfig.url</td> <td>@gitbucket.core.util.DatabaseConfig.url</td>
}
</tr> </tr>
</table> </table>
<!--====================================================================--> <!--====================================================================-->

View File

@@ -49,10 +49,7 @@ $(function(){
$('#filter-box').keyup(function(){ $('#filter-box').keyup(function(){
var inputVal = $('#filter-box').val(); var inputVal = $('#filter-box').val();
$.each($('li.repo-link a'), function(index, elem) { $.each($('li.repo-link a'), function(index, elem) {
console.log(inputVal); if ( !inputVal || !elem.text.trim() || elem.text.trim().toLowerCase().indexOf(inputVal.toLowerCase()) >= 0 ) {
console.log(elem.text.trim());
console.log(elem.text.trim().lastIndexOf(inputVal, 0));
if (!inputVal || !elem.text.trim() || elem.text.trim().indexOf(inputVal) >= 0) {
$(elem).parent().show(); $(elem).parent().show();
} else { } else {
$(elem).parent().hide(); $(elem).parent().hide();

View File

@@ -31,7 +31,7 @@
$('#branch-control-input').keyup(function() { $('#branch-control-input').keyup(function() {
var inputVal = $('#branch-control-input').val(); var inputVal = $('#branch-control-input').val();
$.each($('#branch-control-input').parent().parent().find('a'), function(index, elem) { $.each($('#branch-control-input').parent().parent().find('a'), function(index, elem) {
if (!inputVal || !elem.text.trim() || elem.text.trim().lastIndexOf(inputVal, 0) >= 0) { if (!inputVal || !elem.text.trim() || elem.text.trim().toLowerCase().indexOf(inputVal.toLowerCase()) >= 0) {
$(elem).parent().show(); $(elem).parent().show();
} else { } else {
$(elem).parent().hide(); $(elem).parent().hide();

View File

@@ -16,7 +16,7 @@
</div> </div>
</div> </div>
Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @helpers.plural(diffs.size, "file")</a> Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @helpers.plural(diffs.size, "file")</a>
<ul id="commit-file-list" style="display: none;clear:right"> <ul id="commit-file-list" style="display: none;">
@diffs.zipWithIndex.map { case (diff, i) => @diffs.zipWithIndex.map { case (diff, i) =>
<li class="border"> <li class="border">
<span class="pull-right diffstat" data-diff-id="@i"></span> <span class="pull-right diffstat" data-diff-id="@i"></span>

View File

@@ -32,7 +32,7 @@ $(function(){
$('#@{filter}-input').keyup(function() { $('#@{filter}-input').keyup(function() {
var inputVal = $('#@{filter}-input').val(); var inputVal = $('#@{filter}-input').val();
$.each($('#@{filter}-input').parent().parent().find('a'), function(index, elem) { $.each($('#@{filter}-input').parent().parent().find('a'), function(index, elem) {
if (!inputVal || !elem.text.trim() || elem.text.trim().lastIndexOf(inputVal, 0) >= 0) { if ( !inputVal || !elem.text.trim() || elem.text.trim().toLowerCase().indexOf(inputVal.toLowerCase()) >=0 ) {
$(elem).parent().show(); $(elem).parent().show();
} else { } else {
$(elem).parent().hide(); $(elem).parent().hide();

View File

@@ -221,3 +221,27 @@ $(function(){
} }
}); });
</script> </script>
<script>
function dropdownFilter(dropdownItem,placeHolder,id) {
$('<li><input id="' + id + '" type="text" class="form-control input-sm dropdown-filter-input" placeholder="' + placeHolder + '"/></li>')
.insertBefore($("li:has(a" + dropdownItem + "):first"));
$('#'+id).keyup(function() {
var inputVal = $('#'+id).val();
$.each($('#'+id).parent().parent().find('a'), function(index, elem) {
if (!inputVal || !elem.text.trim() || elem.text.trim().toLowerCase().indexOf(inputVal.toLowerCase()) >= 0) {
$(elem).parent().show();
} else {
$(elem).parent().hide();
}
});
});
}
$(function(){
dropdownFilter('.origin-owner', 'Find Repository...', 'origin-owner-control-input');
dropdownFilter('.origin-branch','Find Branch...', 'origin-branch-control-input');
dropdownFilter('.forked-owner', 'Find Repository...', 'forked-owner-control-input');
dropdownFilter('.forked-branch','Find Branch...', 'forked-branch-control-input');
});
</script>

View File

@@ -342,6 +342,8 @@ div.account-image {
ul.dropdown-menu { ul.dropdown-menu {
padding: 2px 0; padding: 2px 0;
overflow: auto;
max-height: 100vh;
} }
ul.dropdown-menu li { ul.dropdown-menu li {
@@ -556,6 +558,7 @@ pre.commit-description {
background-color: transparent; background-color: transparent;
padding: 2px; padding: 2px;
margin: 0px; margin: 0px;
white-space: pre-wrap;
} }
#repository-url { #repository-url {
@@ -586,6 +589,7 @@ ul#commit-file-list {
ul#commit-file-list li.border { ul#commit-file-list li.border {
padding-bottom: 2px; padding-bottom: 2px;
border-top: 1px solid #eee; border-top: 1px solid #eee;
clear: right;
} }
li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9 { li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9 {

View File

@@ -29,7 +29,7 @@ class GitBucketCoreModuleSpec extends FunSuite {
} }
test("Migration MySQL", ExternalDBTest){ test("Migration MySQL", ExternalDBTest){
val config = aMysqldConfig(v5_7_10) val config = aMysqldConfig(v5_7_latest)
.withPort(3306) .withPort(3306)
.withUser("sa", "sa") .withUser("sa", "sa")
.withCharset(Charset.UTF8) .withCharset(Charset.UTF8)